Skip to content

Commit e90ad2d

Browse files
authored
feat(cli): add hyperframes lambda deploy/render/progress/destroy (#910)
* feat(cli): add hyperframes lambda deploy/render/progress/destroy Wraps the @hyperframes/aws-lambda SDK + the Phase 6a SAM template behind a single CLI surface so an end-to-end render is three commands instead of the ~8 manual bun+sam+aws steps the smoke script does today: hyperframes lambda deploy hyperframes lambda render ./my-project --width 1920 --height 1080 --wait hyperframes lambda destroy Subcommands: - deploy: build handler.zip + sam-deploy + persist stack outputs to <cwd>/.hyperframes/lambda-stack-<name>.json - sites create: pre-upload a project to S3 with a stable content hash so re-renders skip the tar+PUT pass - render: start a Step Functions execution; --wait blocks and streams per-chunk progress + accrued cost - progress: one-shot snapshot — status, frames, cost breakdown, errors. Accepts renderId or executionArn - destroy: sam-delete + drop the local state file (S3 bucket is Retain'd by the template; documented in --help and in docs/packages/cli.mdx) To keep @sparticuz/chromium out of the CLI's transitive deps, this also adds a dedicated ./sdk subpath export to @hyperframes/aws-lambda; the CLI imports from @hyperframes/aws-lambda/sdk exclusively. The existing . barrel still re-exports both handler + SDK for adopters who want one entry point. Defaults are deliberately cost-conservative for first-time users: --concurrency=8 (low enough to never surprise) and --memory=10240 (the common case; documented for adopters who want to tune down). Tests: 5 unit tests on the state-file round-trip. CLI integration against sam local invoke is part of the upcoming PR 6.6 (lambda-local regression harness). * refactor(cli): /simplify pass on the lambda command group Two small cleanups on top of the lambda CLI: - Replace parseFormat / parseCodec / parseQuality / parseChromeSource (four near-identical helpers) with a single generic parseEnum() + typed const-tuple lookups. The four callers now read as one-line arrow functions that lift the allowed values out of the function body so they're easy to extend. - DEFAULT_STACK_NAME was const-declared then re-exported at the bottom of state.ts; just mark the const export inline. No behavior changes. All CLI tests still pass. * fix(cli): keep @hyperframes/aws-lambda external in the tsup bundle esbuild can't bundle @hyperframes/aws-lambda's transitive AWS SDK deps (@aws-sdk/* + @smithy/*) cleanly into a node binary — the SDK's .browser.js conditional re-exports break the resolver: ESM Build failed No matching export in "splitStream.browser.js" for import "splitStream" (and ~10 similar errors) Mark aws-lambda as `external` so esbuild doesn't follow it, and move it from devDependencies to dependencies so the published CLI can resolve it from node_modules at runtime. The lambda subverb files dynamic-import only on `hyperframes lambda *` invocation, so the CLI cold-start cost is unchanged. The install-size hit (AWS SDK + @sparticuz/chromium ≈ 200 MiB) is documented as a v1 tradeoff; a future split into a lambda-sdk-only subpackage can pare this back. * fix(cli): address PR review on lambda CLI Two blockers + four important items from Vai's review: - `--memory` was parsed and recorded in the local state file but never forwarded to `sam deploy` as a parameter override. Worse, `progress.ts` then read the *recorded* value for cost math, so `--memory 5120` produced wrong cost numbers downstream. Thread `LambdaMemoryMb` through samDeploy's --parameter-overrides. - `--profile` was only consumed by deploy / destroy. render and progress fell back to the default credentials chain — a user with `--profile prod` would silently render against their default account (wrong-account billing footgun). Set `process.env.AWS_PROFILE` (and `AWS_REGION`) in the dispatcher before any subverb runs; the AWS SDK reads them natively, so render / progress / sites all benefit without each subverb threading the flag through the SDK call. - `--profile` + destroy now also reads `process.env.AWS_PROFILE` as a fallback (matching deploy's existing env fallback). - `--wait --json` printed both the start handle AND the final progress snapshot, producing two concatenated JSON blobs that `jq` rejected. Now emits a single document: handle (without --wait) OR final progress (with --wait). - Negative integers on `--width` / `--height` / `--chunk-size` / `--max-parallel-chunks` / `--memory` / `--concurrency` now fail loudly via a new `parsePositiveInt` wrapper instead of flowing into the SDK and producing opaque AWS validation errors mid- render. - `DEFAULT_STACK_NAME` is now centralized to the literal `"hyperframes-default"` and consumed from one place. Previously the value was assembled as `hyperframes-${"default"}` in three sites and hardcoded as `"hyperframes-default"` in a fourth. `requireStack`'s hint now matches the dispatcher's default. The faked `SiteHandle` for `--site-id` keeps the documented placeholder fields but also surfaces `bucketName` (from PR 909's extended SiteHandle interface), matching the SDK contract. All CLI unit tests + the full bundler build still pass. * fix(cli): keep aws-lambda out of CLI runtime deps The "Smoke: global install" CI step packs the CLI via `npm pack` and installs it globally via `npm install -g <tgz>`. npm doesn't understand the workspace: protocol, so a runtime `dependencies` entry of `@hyperframes/aws-lambda: workspace:*` blows up with: npm error code EUNSUPPORTEDPROTOCOL npm error Unsupported URL Type "workspace:": workspace:* (pnpm rewrites workspace:* on publish; npm pack doesn't.) Three changes to unblock the smoke + keep the published CLI install small for users who don't deploy to Lambda: - Move `@hyperframes/aws-lambda` from CLI's `dependencies` back to `devDependencies`. It's already external in tsup.config.ts; the bundle references it via runtime resolution only. - Convert the static `import { … } from "@hyperframes/aws-lambda/sdk"` in sites.ts / render.ts / progress.ts to `await import()` inside each function. tsup with `splitting: false` was inlining those static imports at the top of the bundle, which made Node eagerly resolve them at CLI startup (MODULE_NOT_FOUND before any lambda subcommand even runs). Dynamic imports stay dynamic in the bundle. - Add a friendly missing-module check in the lambda dispatcher. When a user runs `hyperframes lambda deploy / render / sites / progress / destroy` without aws-lambda installed, they now see: @hyperframes/aws-lambda is not installed. The `hyperframes lambda deploy` command needs it at runtime. Install it alongside the CLI: npm install -g @hyperframes/aws-lambda Verified locally: pack + global install + `hyperframes init --example blank` now succeeds end-to-end (was the same scenario the CI smoke job runs).
1 parent 78fce8b commit e90ad2d

19 files changed

Lines changed: 1299 additions & 8 deletions

File tree

bun.lock

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/packages/cli.mdx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,80 @@ This is suppressed in CI environments, non-TTY shells, and when `HYPERFRAMES_NO_
860860
</Tab>
861861
</Tabs>
862862

863+
## hyperframes lambda
864+
865+
Deploy HyperFrames distributed rendering to AWS Lambda and drive renders from your laptop or CI.
866+
867+
The `hyperframes lambda` command group wraps the `@hyperframes/aws-lambda` SDK plus AWS SAM so an end-to-end render is three commands:
868+
869+
```bash
870+
hyperframes lambda deploy
871+
hyperframes lambda render ./my-project --width 1920 --height 1080 --wait
872+
hyperframes lambda destroy # when you're done
873+
```
874+
875+
### Prerequisites
876+
877+
- AWS credentials configured (env vars, `~/.aws/credentials`, SSO, or IMDS).
878+
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) on `PATH`.
879+
- `bun` on `PATH` (used to build the Lambda handler ZIP).
880+
881+
### Subcommands
882+
883+
#### `lambda deploy`
884+
885+
Builds `packages/aws-lambda/dist/handler.zip` and SAM-deploys the stack at `examples/aws-lambda/template.yaml`. On success, writes `<cwd>/.hyperframes/lambda-stack-<stackName>.json` so the other subcommands don't need to re-derive the bucket / state-machine ARN.
886+
887+
```bash
888+
hyperframes lambda deploy \
889+
--stack-name=hyperframes-prod \
890+
--region=us-east-1 \
891+
--concurrency=8 \
892+
--memory=10240
893+
```
894+
895+
Idempotent — re-running on the same `--stack-name` resolves to a no-op when nothing changed.
896+
897+
#### `lambda sites create <projectDir>`
898+
899+
Tars + uploads `<projectDir>` to S3 with a content-addressed key. Returns a `siteId` you can reuse across multiple renders so a re-render of the same tree skips the upload.
900+
901+
```bash
902+
hyperframes lambda sites create ./my-project
903+
# → siteId: abc1234deadbeef0 (stable across re-runs of the same tree)
904+
905+
hyperframes lambda render ./my-project --site-id=abc1234deadbeef0 --width 1920 --height 1080
906+
```
907+
908+
#### `lambda render <projectDir>`
909+
910+
Starts a Step Functions execution. Returns immediately with a `renderId` (use `lambda progress` to poll) unless `--wait` is set, in which case the CLI blocks until the render finishes and streams per-chunk progress lines.
911+
912+
```bash
913+
hyperframes lambda render ./my-project \
914+
--width=1920 --height=1080 --fps=30 --format=mp4 \
915+
--chunk-size=240 --max-parallel-chunks=16 \
916+
--wait
917+
```
918+
919+
`--json` swaps the human-readable output for a machine-parseable JSON snapshot.
920+
921+
#### `lambda progress <renderId | executionArn>`
922+
923+
Prints one progress snapshot — overall percent, frames rendered, Lambda invocations, accrued cost, and any errors. Accepts either a bare `renderId` (resolved against the stack's state-machine ARN) or a full SFN execution ARN.
924+
925+
```bash
926+
hyperframes lambda progress hf-render-abcd1234
927+
```
928+
929+
#### `lambda destroy`
930+
931+
Calls `sam delete --no-prompts` and drops the local state file. The render S3 bucket is configured with CloudFormation `Retain` so it survives destruction — empty and delete it via the AWS console / CLI if you want the storage back.
932+
933+
### State files
934+
935+
`hyperframes lambda` keeps per-stack metadata under `<cwd>/.hyperframes/lambda-stack-<name>.json` so the verbs don't need to call `describe-stacks` every time. Commit the file to a repo or `.gitignore` it depending on your workflow — it contains the bucket name, state-machine ARN, and region, none of which are secrets but all of which are AWS-account-identifying.
936+
863937
## hyperframes.json
864938

865939
`hyperframes init` writes a `hyperframes.json` file at the root of every new project. `hyperframes add` reads it to know which registry to pull items from and where to drop them. Edit the file (or delete it to fall back to defaults) to reshape your project layout or point at a custom registry.

packages/aws-lambda/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"exports": {
1919
".": "./src/index.ts",
2020
"./handler": "./src/handler.ts",
21+
"./sdk": "./src/sdk/index.ts",
2122
"./cdk": "./src/cdk/index.ts"
2223
},
2324
"publishConfig": {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* SDK subpath export — `@hyperframes/aws-lambda/sdk`.
3+
*
4+
* Pulled into its own subpath so consumers that only drive Lambda renders
5+
* (CLI, CI scripts, adopter tooling) don't pay the cost of importing
6+
* `./handler.js`, which transitively pulls `@sparticuz/chromium` +
7+
* `puppeteer-core` into the module graph. The SDK files here are
8+
* AWS-SDK only — safe to load in any Node environment.
9+
*/
10+
11+
export { deploySite, type DeploySiteOptions, type SiteHandle } from "./deploySite.js";
12+
export { renderToLambda, type RenderHandle, type RenderToLambdaOptions } from "./renderToLambda.js";
13+
export {
14+
getRenderProgress,
15+
type GetRenderProgressOptions,
16+
type RenderError,
17+
type RenderProgress,
18+
type RenderStatus,
19+
} from "./getRenderProgress.js";
20+
export {
21+
type BilledLambdaInvocation,
22+
computeRenderCost,
23+
type RenderCost,
24+
} from "./costAccounting.js";
25+
export { InvalidConfigError, validateDistributedRenderConfig } from "./validateConfig.js";
26+
export type { SerializableDistributedRenderConfig } from "../events.js";

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
},
4343
"devDependencies": {
4444
"@clack/prompts": "^1.1.0",
45+
"@hyperframes/aws-lambda": "workspace:*",
4546
"@hyperframes/core": "workspace:*",
4647
"@hyperframes/engine": "workspace:*",
4748
"@hyperframes/producer": "workspace:*",

packages/cli/src/cli.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const subCommands = {
8686
validate: () => import("./commands/validate.js").then((m) => m.default),
8787
snapshot: () => import("./commands/snapshot.js").then((m) => m.default),
8888
capture: () => import("./commands/capture.js").then((m) => m.default),
89+
lambda: () => import("./commands/lambda.js").then((m) => m.default),
8990
};
9091

9192
const main = defineCommand({

0 commit comments

Comments
 (0)