mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
* 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).
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
/**
|
|
* `hyperframes lambda sites create <projectDir>` — tar + upload a project
|
|
* to the deployed render bucket at a content-addressed S3 prefix. Used
|
|
* by adopters who want to pre-stage a project so multiple subsequent
|
|
* renders share the upload.
|
|
*
|
|
* Without this verb, every `render` call re-tars the same tree on every
|
|
* invocation. With it, the same `siteId` resolves to a `HeadObject`-204
|
|
* short-circuit inside `deploySite`.
|
|
*/
|
|
|
|
import { resolve as resolvePath } from "node:path";
|
|
import { c } from "../../ui/colors.js";
|
|
import { DEFAULT_STACK_NAME, requireStack } from "./state.js";
|
|
|
|
// `@hyperframes/aws-lambda` is a workspace devDependency in `packages/cli`
|
|
// so the published CLI install stays small for users who don't deploy to
|
|
// Lambda. The lambda subverbs dynamic-import it on call. The dispatcher in
|
|
// `commands/lambda.ts` checks the import resolves before any subverb runs
|
|
// and prints a friendly install hint on `ERR_MODULE_NOT_FOUND`.
|
|
async function loadSDK(): Promise<typeof import("@hyperframes/aws-lambda/sdk")> {
|
|
return import("@hyperframes/aws-lambda/sdk");
|
|
}
|
|
|
|
export interface SitesCreateArgs {
|
|
projectDir: string;
|
|
stackName: string;
|
|
siteId?: string;
|
|
/** Print machine-readable JSON instead of the human-friendly summary. */
|
|
json: boolean;
|
|
}
|
|
|
|
export async function runSitesCreate(args: SitesCreateArgs): Promise<void> {
|
|
const stack = requireStack(args.stackName);
|
|
const projectDir = resolvePath(args.projectDir);
|
|
|
|
const { deploySite } = await loadSDK();
|
|
const handle = await deploySite({
|
|
projectDir,
|
|
bucketName: stack.bucketName,
|
|
region: stack.region,
|
|
siteId: args.siteId,
|
|
});
|
|
|
|
if (args.json) {
|
|
console.log(JSON.stringify(handle, null, 2));
|
|
return;
|
|
}
|
|
|
|
console.log(
|
|
c.success(handle.uploaded ? "Site uploaded." : "Site already up to date (skipped upload)."),
|
|
);
|
|
console.log(` ${c.dim("Site ID:")} ${handle.siteId}`);
|
|
console.log(` ${c.dim("S3 URI:")} ${handle.projectS3Uri}`);
|
|
console.log(` ${c.dim("Bytes:")} ${handle.bytes}`);
|
|
console.log(` ${c.dim("Uploaded at:")} ${handle.uploadedAt}`);
|
|
console.log();
|
|
console.log(
|
|
c.dim(
|
|
`Render with: hyperframes lambda render ${args.projectDir} --site-id=${handle.siteId}` +
|
|
(args.stackName === DEFAULT_STACK_NAME ? "" : ` --stack-name=${args.stackName}`),
|
|
),
|
|
);
|
|
}
|