@hyperframes/gcp-cloud-run 0.6.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +118 -0
- package/README.md +128 -0
- package/dist/chromium.d.ts +39 -0
- package/dist/chromium.d.ts.map +1 -0
- package/dist/events.d.ts +130 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/formatExtension.d.ts +11 -0
- package/dist/formatExtension.d.ts.map +1 -0
- package/dist/gcsTransport.d.ts +53 -0
- package/dist/gcsTransport.d.ts.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +855 -0
- package/dist/index.js.map +7 -0
- package/dist/sdk/costAccounting.d.ts +67 -0
- package/dist/sdk/costAccounting.d.ts.map +1 -0
- package/dist/sdk/deploySite.d.ts +54 -0
- package/dist/sdk/deploySite.d.ts.map +1 -0
- package/dist/sdk/getRenderProgress.d.ts +91 -0
- package/dist/sdk/getRenderProgress.d.ts.map +1 -0
- package/dist/sdk/index.d.ts +17 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +390 -0
- package/dist/sdk/index.js.map +7 -0
- package/dist/sdk/renderToCloudRun.d.ts +97 -0
- package/dist/sdk/renderToCloudRun.d.ts.map +1 -0
- package/dist/sdk/validateConfig.d.ts +36 -0
- package/dist/sdk/validateConfig.d.ts.map +1 -0
- package/dist/server.d.ts +58 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +517 -0
- package/dist/server.js.map +7 -0
- package/package.json +62 -0
- package/terraform/main.tf +197 -0
- package/terraform/outputs.tf +34 -0
- package/terraform/providers.tf +12 -0
- package/terraform/variables.tf +75 -0
- package/terraform/versions.tf +9 -0
- package/terraform/workflow.yaml +179 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# HyperFrames distributed render — Cloud Run image.
|
|
2
|
+
#
|
|
3
|
+
# One container image, three roles (plan / renderChunk / assemble). Cloud
|
|
4
|
+
# Workflows POSTs a JSON body with an `Action` field; `dist/server.js`
|
|
5
|
+
# dispatches to the matching `@hyperframes/producer/distributed` primitive.
|
|
6
|
+
#
|
|
7
|
+
# Build context is the REPOSITORY ROOT (not this package dir) because the
|
|
8
|
+
# package depends on the `@hyperframes/producer` workspace and renders with
|
|
9
|
+
# the same chrome-headless-shell + fonts + ffmpeg the production renderer
|
|
10
|
+
# uses. The example smoke script and `hyperframes cloudrun deploy` both
|
|
11
|
+
# build with the repo root as context:
|
|
12
|
+
#
|
|
13
|
+
# docker build -f packages/gcp-cloud-run/Dockerfile -t <image> .
|
|
14
|
+
#
|
|
15
|
+
# NOTE: this Dockerfile only builds from a full hyperframes monorepo checkout
|
|
16
|
+
# — it COPYs sibling workspace packages (core/engine/producer). It is NOT
|
|
17
|
+
# buildable from the published npm tarball alone; install the repo (or use
|
|
18
|
+
# `hyperframes cloudrun deploy`, which builds from your checkout via Cloud
|
|
19
|
+
# Build) to produce the image.
|
|
20
|
+
#
|
|
21
|
+
# Unlike the AWS Lambda adapter there is no ZIP-size ceiling and no runtime
|
|
22
|
+
# Chrome decompression step — the binary lives in the image at a fixed path.
|
|
23
|
+
|
|
24
|
+
FROM node:22-bookworm-slim
|
|
25
|
+
|
|
26
|
+
# ── System dependencies (identical set to the producer's render image) ───────
|
|
27
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
28
|
+
ca-certificates \
|
|
29
|
+
curl \
|
|
30
|
+
unzip \
|
|
31
|
+
ffmpeg \
|
|
32
|
+
libgbm1 \
|
|
33
|
+
libnss3 \
|
|
34
|
+
libatk-bridge2.0-0 \
|
|
35
|
+
libdrm2 \
|
|
36
|
+
libxcomposite1 \
|
|
37
|
+
libxdamage1 \
|
|
38
|
+
libxrandr2 \
|
|
39
|
+
libcups2 \
|
|
40
|
+
libasound2 \
|
|
41
|
+
libpangocairo-1.0-0 \
|
|
42
|
+
libxshmfence1 \
|
|
43
|
+
libgtk-3-0 \
|
|
44
|
+
fonts-liberation \
|
|
45
|
+
fonts-noto-color-emoji \
|
|
46
|
+
fonts-noto-cjk \
|
|
47
|
+
fonts-noto-core \
|
|
48
|
+
fonts-noto-extra \
|
|
49
|
+
fonts-noto-ui-core \
|
|
50
|
+
fonts-freefont-ttf \
|
|
51
|
+
fonts-dejavu-core \
|
|
52
|
+
fontconfig \
|
|
53
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
54
|
+
&& apt-get clean \
|
|
55
|
+
&& fc-cache -fv
|
|
56
|
+
|
|
57
|
+
# ── chrome-headless-shell (deterministic BeginFrame capture) ─────────────────
|
|
58
|
+
# Pinned to the SAME build the producer regression baselines were generated
|
|
59
|
+
# against so distributed renders are pixel-comparable. Bump together with the
|
|
60
|
+
# producer's Dockerfile.test pin and a baseline regen.
|
|
61
|
+
RUN npx --yes @puppeteer/browsers install chrome-headless-shell@148.0.7778.167 \
|
|
62
|
+
--path /opt/puppeteer \
|
|
63
|
+
&& CHS="$(find /opt/puppeteer/chrome-headless-shell -name chrome-headless-shell -type f | head -n1)" \
|
|
64
|
+
&& mkdir -p /opt/chrome \
|
|
65
|
+
&& ln -s "$CHS" /opt/chrome/chrome-headless-shell \
|
|
66
|
+
&& /opt/chrome/chrome-headless-shell --version
|
|
67
|
+
|
|
68
|
+
# The chromium.ts resolver reads this first; pointing the engine straight at
|
|
69
|
+
# the symlinked binary avoids the runtime cache scan.
|
|
70
|
+
ENV HYPERFRAMES_CHROME_PATH=/opt/chrome/chrome-headless-shell
|
|
71
|
+
ENV PRODUCER_HEADLESS_SHELL_PATH=/opt/chrome/chrome-headless-shell
|
|
72
|
+
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
|
73
|
+
ENV CONTAINER=true
|
|
74
|
+
|
|
75
|
+
WORKDIR /app
|
|
76
|
+
|
|
77
|
+
# Install bun (the repo's package manager / build driver). Pin the version:
|
|
78
|
+
# the container runs `bun dist/server.js` and relies on bun's ESM `require`
|
|
79
|
+
# interop to load the producer bundle, so a future bun release changing that
|
|
80
|
+
# behaviour shouldn't silently break the next image rebuild. Bump deliberately.
|
|
81
|
+
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.9"
|
|
82
|
+
ENV PATH="/root/.bun/bin:$PATH"
|
|
83
|
+
|
|
84
|
+
# Install workspace dependencies. Copy manifests first for layer caching.
|
|
85
|
+
COPY package.json bun.lock ./
|
|
86
|
+
COPY packages/core/package.json packages/core/package.json
|
|
87
|
+
COPY packages/engine/package.json packages/engine/package.json
|
|
88
|
+
COPY packages/player/package.json packages/player/package.json
|
|
89
|
+
COPY packages/producer/package.json packages/producer/package.json
|
|
90
|
+
COPY packages/cli/package.json packages/cli/package.json
|
|
91
|
+
COPY packages/studio/package.json packages/studio/package.json
|
|
92
|
+
COPY packages/shader-transitions/package.json packages/shader-transitions/package.json
|
|
93
|
+
COPY packages/aws-lambda/package.json packages/aws-lambda/package.json
|
|
94
|
+
COPY packages/gcp-cloud-run/package.json packages/gcp-cloud-run/package.json
|
|
95
|
+
RUN bun install --frozen-lockfile
|
|
96
|
+
|
|
97
|
+
# Copy source for the packages the render path needs.
|
|
98
|
+
COPY packages/core/ packages/core/
|
|
99
|
+
COPY packages/engine/ packages/engine/
|
|
100
|
+
COPY packages/producer/ packages/producer/
|
|
101
|
+
COPY packages/gcp-cloud-run/ packages/gcp-cloud-run/
|
|
102
|
+
|
|
103
|
+
# Build core runtime artifacts (needed by the renderer) + producer, then the
|
|
104
|
+
# adapter. Generate embedded font data so glyph layout matches production.
|
|
105
|
+
RUN bun run --filter @hyperframes/core build:hyperframes-runtime:modular \
|
|
106
|
+
&& (cd packages/producer && bunx tsx scripts/generate-font-data.ts) \
|
|
107
|
+
&& bun run --cwd packages/producer build \
|
|
108
|
+
&& bun run --cwd packages/gcp-cloud-run build
|
|
109
|
+
|
|
110
|
+
# Cloud Run injects PORT (default 8080). The server reads it.
|
|
111
|
+
ENV PORT=8080
|
|
112
|
+
EXPOSE 8080
|
|
113
|
+
|
|
114
|
+
WORKDIR /app/packages/gcp-cloud-run
|
|
115
|
+
# Run under bun, not node. The repo is bun-native and `@hyperframes/producer`'s
|
|
116
|
+
# bundled `distributed.js` relies on a `require` being available at runtime
|
|
117
|
+
# (its esbuild interop shim); bun provides one in ESM, bare `node` does not.
|
|
118
|
+
CMD ["bun", "dist/server.js"]
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @hyperframes/gcp-cloud-run
|
|
2
|
+
|
|
3
|
+
Google Cloud Run + Cloud Workflows adapter for HyperFrames distributed
|
|
4
|
+
rendering. The OSS render primitives (`plan` → `renderChunk` × N →
|
|
5
|
+
`assemble`) are pure functions over local file paths; this package is the
|
|
6
|
+
deployment, orchestration, and storage glue that runs them on Google Cloud —
|
|
7
|
+
the GCP counterpart to [`@hyperframes/aws-lambda`](../aws-lambda).
|
|
8
|
+
|
|
9
|
+
Two surfaces, one package:
|
|
10
|
+
|
|
11
|
+
- **Server-side handler** (`./server`) — a Cloud Run HTTP service that
|
|
12
|
+
dispatches `plan` / `renderChunk` / `assemble` on the request body's
|
|
13
|
+
`Action` field, bridging GCS ↔ the container's filesystem around each OSS
|
|
14
|
+
primitive. This is what the bundled `Dockerfile` runs.
|
|
15
|
+
- **Client-side SDK** (`./sdk`) — `renderToCloudRun`, `getRenderProgress`,
|
|
16
|
+
`deploySite`, `validateDistributedRenderConfig`, and `computeRenderCost`.
|
|
17
|
+
Call these from a Node process (CI, CLI, app backend) to drive a deployed
|
|
18
|
+
stack without writing GCS / Workflows boilerplate.
|
|
19
|
+
|
|
20
|
+
The package is **not** a dependency of `@hyperframes/producer`; install it
|
|
21
|
+
separately.
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
GCS bucket ←→ Cloud Run service (plan / renderChunk / assemble)
|
|
27
|
+
▲
|
|
28
|
+
│ OIDC-authenticated http.post, one per step
|
|
29
|
+
│
|
|
30
|
+
Cloud Workflows (Plan → parallel RenderChunk → Assemble)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **Plan** downloads the project tarball, runs `plan()`, uploads the planDir
|
|
34
|
+
tarball (+ audio) to GCS, and returns the chunk count.
|
|
35
|
+
- **RenderChunk** runs in a parallel `for` loop in the workflow, fanned out
|
|
36
|
+
up to the plan's chunk count. Each invocation renders one chunk and uploads
|
|
37
|
+
it.
|
|
38
|
+
- **Assemble** downloads every chunk + audio, stitches the final
|
|
39
|
+
deliverable, and uploads it.
|
|
40
|
+
|
|
41
|
+
Every step is a `POST` to the same Cloud Run URL with a different `Action`.
|
|
42
|
+
The workflow accumulates each step's small result body and returns
|
|
43
|
+
`{ Plan, Chunks, Assemble }` so `getRenderProgress` can read frame totals and
|
|
44
|
+
per-step durations on success.
|
|
45
|
+
|
|
46
|
+
## Chrome runtime
|
|
47
|
+
|
|
48
|
+
Unlike the Lambda adapter — which fights a 250 MB ZIP ceiling and
|
|
49
|
+
decompresses `@sparticuz/chromium` into `/tmp` at runtime — Cloud Run runs a
|
|
50
|
+
container image. The `Dockerfile` installs the same pinned
|
|
51
|
+
`chrome-headless-shell` build and font set the production renderer uses, at a
|
|
52
|
+
fixed path, and exports `HYPERFRAMES_CHROME_PATH`. CDP-level `BeginFrame`
|
|
53
|
+
works because the command lives in the protocol, not the binary. There is no
|
|
54
|
+
runtime decompression step and no packaging ceiling.
|
|
55
|
+
|
|
56
|
+
## Deploying
|
|
57
|
+
|
|
58
|
+
The `terraform/` module provisions everything: the GCS render bucket, the
|
|
59
|
+
Cloud Run service, the Cloud Workflows definition, two least-privilege
|
|
60
|
+
service accounts (the service reads/writes the bucket; the workflow invokes
|
|
61
|
+
the service), and a runaway-request alert.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 1. Build + push the image (Cloud Build or local docker).
|
|
65
|
+
gcloud builds submit . \
|
|
66
|
+
--tag REGION-docker.pkg.dev/PROJECT/REPO/hyperframes-render:TAG
|
|
67
|
+
|
|
68
|
+
# 2. Apply the module.
|
|
69
|
+
terraform -chdir=node_modules/@hyperframes/gcp-cloud-run/terraform init
|
|
70
|
+
terraform -chdir=node_modules/@hyperframes/gcp-cloud-run/terraform apply \
|
|
71
|
+
-var project_id=PROJECT \
|
|
72
|
+
-var region=us-central1 \
|
|
73
|
+
-var image=REGION-docker.pkg.dev/PROJECT/REPO/hyperframes-render:TAG
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Terraform outputs `render_bucket_name`, `service_url`, `workflow_name`, and
|
|
77
|
+
`region` — pass them straight into the SDK.
|
|
78
|
+
|
|
79
|
+
## Using the SDK
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { renderToCloudRun, getRenderProgress } from "@hyperframes/gcp-cloud-run/sdk";
|
|
83
|
+
|
|
84
|
+
const handle = await renderToCloudRun({
|
|
85
|
+
projectDir: "./my-composition",
|
|
86
|
+
config: { fps: 30, width: 1920, height: 1080, format: "mp4" },
|
|
87
|
+
bucketName: "hyperframes-render-my-project", // from terraform output
|
|
88
|
+
projectId: "my-project",
|
|
89
|
+
location: "us-central1",
|
|
90
|
+
workflowId: "hyperframes-render",
|
|
91
|
+
serviceUrl: "https://hyperframes-render-abc.us-central1.run.app",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Poll until done.
|
|
95
|
+
let progress = await getRenderProgress({ executionName: handle.executionName });
|
|
96
|
+
while (progress.status === "running") {
|
|
97
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
98
|
+
progress = await getRenderProgress({ executionName: handle.executionName });
|
|
99
|
+
}
|
|
100
|
+
console.log(progress.status, progress.outputFile, progress.costs.displayCost);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`deploySite` is called implicitly when you pass `projectDir`; call it
|
|
104
|
+
yourself to pre-upload once and reuse the `siteHandle` across many renders
|
|
105
|
+
(e.g. personalised template batches).
|
|
106
|
+
|
|
107
|
+
## Running tests
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bun test # unit tests over an in-memory GCS double — no network
|
|
111
|
+
bun run typecheck
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The live end-to-end smoke (build image → terraform apply → render a fixture
|
|
115
|
+
through the workflow → PSNR-compare → destroy) lives at
|
|
116
|
+
`examples/gcp-cloud-run/scripts/smoke.sh` and needs a GCP project with
|
|
117
|
+
billing enabled.
|
|
118
|
+
|
|
119
|
+
## What's still ahead
|
|
120
|
+
|
|
121
|
+
- **Mid-flight per-chunk progress.** `getRenderProgress` reports coarse
|
|
122
|
+
`running` progress and exact numbers on success. Reading the Cloud
|
|
123
|
+
Workflows step-entries API would give per-chunk progress while the render
|
|
124
|
+
is in flight; tracked as a follow-up.
|
|
125
|
+
- **Cloud Run Jobs / Firebase Functions variants.** This first version
|
|
126
|
+
targets Cloud Run services + Workflows (the closest analog to Lambda +
|
|
127
|
+
Step Functions). The same handler runs unchanged under Cloud Run Jobs;
|
|
128
|
+
only the orchestration trigger differs.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Run Chrome resolver.
|
|
3
|
+
*
|
|
4
|
+
* `renderChunk()` (the only primitive that needs a browser) launches Chrome
|
|
5
|
+
* via the engine's `BrowserManager`. Because Cloud Run runs a container
|
|
6
|
+
* image rather than a size-capped ZIP, the Chrome story is far simpler than
|
|
7
|
+
* the Lambda adapter's: the `Dockerfile` installs `chrome-headless-shell`
|
|
8
|
+
* (the same BeginFrame-capable build the K8s deploy uses) into the image at
|
|
9
|
+
* a known path and exports `HYPERFRAMES_CHROME_PATH`. There is no runtime
|
|
10
|
+
* decompression-into-/tmp step and no 250 MB packaging ceiling to fight.
|
|
11
|
+
*
|
|
12
|
+
* Resolution order:
|
|
13
|
+
* 1. `PRODUCER_HEADLESS_SHELL_PATH` — the engine's own override. If a
|
|
14
|
+
* caller (or the Docker image) already set it, honour it untouched.
|
|
15
|
+
* 2. `HYPERFRAMES_CHROME_PATH` — set by the Dockerfile to the installed
|
|
16
|
+
* `chrome-headless-shell` binary.
|
|
17
|
+
* 3. A small list of conventional install paths, as a last resort for
|
|
18
|
+
* images built outside our Dockerfile.
|
|
19
|
+
*
|
|
20
|
+
* Throws {@link ChromeBinaryUnavailableError} when nothing resolves, so a
|
|
21
|
+
* misconfigured image fails loudly at the first chunk rather than emitting
|
|
22
|
+
* a confusing puppeteer-core "executablePath must be specified" assertion.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when the Chrome binary resolver can't produce a usable path. The
|
|
26
|
+
* class name is the workflow's non-retryable error discriminator.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ChromeBinaryUnavailableError extends Error {
|
|
29
|
+
readonly name = "ChromeBinaryUnavailableError";
|
|
30
|
+
readonly resolvedPath: string | null;
|
|
31
|
+
constructor(resolvedPath: string | null, hint: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the absolute path to a Chrome binary suitable for BeginFrame.
|
|
35
|
+
* Pure (no env mutation) so callers decide whether to export the result
|
|
36
|
+
* into `PRODUCER_HEADLESS_SHELL_PATH`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveChromeExecutablePath(): string;
|
|
39
|
+
//# sourceMappingURL=chromium.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromium.d.ts","sourceRoot":"","sources":["../src/chromium.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IAGrD,SAAkB,IAAI,kCAAkC;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;gBACzB,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM;CAItD;AAgBD;;;;GAIG;AAEH,wBAAgB,2BAA2B,IAAI,MAAM,CAiCpD"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request + result types for the HyperFrames distributed render handler
|
|
3
|
+
* running on Cloud Run.
|
|
4
|
+
*
|
|
5
|
+
* The Cloud Workflows definition in `packages/gcp-cloud-run/terraform/workflow.yaml`
|
|
6
|
+
* dispatches on the `Action` field of the JSON request body. Each action
|
|
7
|
+
* maps 1:1 onto one of the three OSS distributed primitives:
|
|
8
|
+
*
|
|
9
|
+
* "plan" → `plan(projectDir, config, planDir)` (Activity A)
|
|
10
|
+
* "renderChunk" → `renderChunk(planDir, chunkIndex, output)` (Activity B)
|
|
11
|
+
* "assemble" → `assemble(planDir, chunkPaths, audio, out)` (Activity C)
|
|
12
|
+
*
|
|
13
|
+
* All file I/O is mediated by GCS — the handler downloads inputs into a
|
|
14
|
+
* per-request workdir under the container's writable `/tmp`, invokes the
|
|
15
|
+
* primitive, uploads outputs back to GCS, and returns a small JSON payload
|
|
16
|
+
* that fits inside a Cloud Workflows step variable (Workflows caps a single
|
|
17
|
+
* step's memory; chunk results stay well under 1 KB so the orchestration
|
|
18
|
+
* can hold one per Map iteration).
|
|
19
|
+
*
|
|
20
|
+
* These shapes are intentionally identical to `@hyperframes/aws-lambda`'s
|
|
21
|
+
* `events.ts` apart from the URI scheme (`gs://` vs `s3://`): the wire
|
|
22
|
+
* contract is the adapter's, the primitives underneath are shared.
|
|
23
|
+
*/
|
|
24
|
+
import type { DistributedFormat, SerializableDistributedRenderConfig } from "@hyperframes/producer/distributed";
|
|
25
|
+
export type { SerializableDistributedRenderConfig } from "@hyperframes/producer/distributed";
|
|
26
|
+
/** Discriminator for the three roles the one Cloud Run image fulfills. */
|
|
27
|
+
export type CloudRunAction = "plan" | "renderChunk" | "assemble";
|
|
28
|
+
/**
|
|
29
|
+
* Top-level shape of any request body the handler may receive.
|
|
30
|
+
*
|
|
31
|
+
* Cloud Workflows passes the step's `body` through verbatim, but a caller
|
|
32
|
+
* driving the service directly (or a Workflows definition that wraps the
|
|
33
|
+
* payload) may nest it under `Payload` / `Input`; the handler unwraps both
|
|
34
|
+
* before dispatching, matching the Lambda adapter's envelope tolerance.
|
|
35
|
+
*/
|
|
36
|
+
export type CloudRunEvent = PlanEvent | RenderChunkEvent | AssembleEvent | {
|
|
37
|
+
Payload: CloudRunEvent;
|
|
38
|
+
} | {
|
|
39
|
+
Input: CloudRunEvent;
|
|
40
|
+
};
|
|
41
|
+
/** Activity A: produce a planDir, upload to GCS. */
|
|
42
|
+
export interface PlanEvent {
|
|
43
|
+
Action: "plan";
|
|
44
|
+
/** GCS URI pointing at a `tar -czf`-archived project directory (`gs://bucket/key.tar.gz`). */
|
|
45
|
+
ProjectGcsUri: string;
|
|
46
|
+
/** GCS URI prefix where the planDir tar should be uploaded (`gs://bucket/{prefix}/`). */
|
|
47
|
+
PlanOutputGcsPrefix: string;
|
|
48
|
+
/** `DistributedRenderConfig` minus runtime-only fields (logger, abortSignal). */
|
|
49
|
+
Config: SerializableDistributedRenderConfig;
|
|
50
|
+
}
|
|
51
|
+
/** Activity B: fetch planDir, render one chunk, upload result. */
|
|
52
|
+
export interface RenderChunkEvent {
|
|
53
|
+
Action: "renderChunk";
|
|
54
|
+
/** GCS URI of the plan tar produced by a PlanEvent invocation. */
|
|
55
|
+
PlanGcsUri: string;
|
|
56
|
+
/**
|
|
57
|
+
* `PlanResult.planHash` from the Plan invocation. The handler verifies
|
|
58
|
+
* this against the untarred planDir's `plan.json` before invoking the
|
|
59
|
+
* producer, throwing a typed `PLAN_HASH_MISMATCH` on divergence so the
|
|
60
|
+
* workflow routes it as non-retryable. Defense-in-depth — the producer
|
|
61
|
+
* also re-checks internally.
|
|
62
|
+
*/
|
|
63
|
+
PlanHash: string;
|
|
64
|
+
/** 0-based chunk index this invocation should render. */
|
|
65
|
+
ChunkIndex: number;
|
|
66
|
+
/** GCS URI prefix where the chunk output should be uploaded (`gs://bucket/{prefix}/`). */
|
|
67
|
+
ChunkOutputGcsPrefix: string;
|
|
68
|
+
/** Output container format from the plan's encoder.json; drives file vs frame-dir handling. */
|
|
69
|
+
Format: DistributedFormat;
|
|
70
|
+
}
|
|
71
|
+
/** Activity C: fetch planDir + all chunks + audio, assemble, upload final. */
|
|
72
|
+
export interface AssembleEvent {
|
|
73
|
+
Action: "assemble";
|
|
74
|
+
/** GCS URI of the plan tar produced by a PlanEvent invocation. */
|
|
75
|
+
PlanGcsUri: string;
|
|
76
|
+
/** GCS URIs of every chunk, ordered by chunk index. Length must equal `chunkCount`. */
|
|
77
|
+
ChunkGcsUris: string[];
|
|
78
|
+
/** GCS URI of the planDir's `audio.aac` if the composition has audio; `null` otherwise. */
|
|
79
|
+
AudioGcsUri: string | null;
|
|
80
|
+
/** Final output GCS URI (`gs://bucket/key.mp4`). */
|
|
81
|
+
OutputGcsUri: string;
|
|
82
|
+
/** Output container format; drives file vs frame-dir handling. */
|
|
83
|
+
Format: DistributedFormat;
|
|
84
|
+
/**
|
|
85
|
+
* Optional exact-CFR re-encode at assemble time. When `true`, the final
|
|
86
|
+
* assembled video is re-encoded with `-fps_mode cfr -r <fps>` so the
|
|
87
|
+
* stream's `avg_frame_rate` matches the container's `r_frame_rate`
|
|
88
|
+
* exactly (and the file's duration is exact, not PTS-derived). Trade-off
|
|
89
|
+
* is ~2-5x the assemble wall-clock. mp4 only — webm / mov stream-copy
|
|
90
|
+
* paths already produce exact avg_frame_rate. Default `false` /
|
|
91
|
+
* unset preserves current `-c copy` behavior.
|
|
92
|
+
*/
|
|
93
|
+
Cfr?: boolean;
|
|
94
|
+
}
|
|
95
|
+
/** Result of a `plan` invocation. Carries enough to size the Map(N) state. */
|
|
96
|
+
export interface PlanResultBody {
|
|
97
|
+
Action: "plan";
|
|
98
|
+
PlanGcsUri: string;
|
|
99
|
+
PlanHash: string;
|
|
100
|
+
ChunkCount: number;
|
|
101
|
+
TotalFrames: number;
|
|
102
|
+
Fps: 24 | 30 | 60;
|
|
103
|
+
Width: number;
|
|
104
|
+
Height: number;
|
|
105
|
+
Format: DistributedFormat;
|
|
106
|
+
HasAudio: boolean;
|
|
107
|
+
AudioGcsUri: string | null;
|
|
108
|
+
FfmpegVersion: string;
|
|
109
|
+
ProducerVersion: string;
|
|
110
|
+
DurationMs: number;
|
|
111
|
+
}
|
|
112
|
+
/** Result of a `renderChunk` invocation. Sized ≤200 bytes. */
|
|
113
|
+
export interface RenderChunkResultBody {
|
|
114
|
+
Action: "renderChunk";
|
|
115
|
+
ChunkGcsUri: string;
|
|
116
|
+
ChunkIndex: number;
|
|
117
|
+
Sha256: string;
|
|
118
|
+
FramesEncoded: number;
|
|
119
|
+
DurationMs: number;
|
|
120
|
+
}
|
|
121
|
+
/** Result of an `assemble` invocation. */
|
|
122
|
+
export interface AssembleResultBody {
|
|
123
|
+
Action: "assemble";
|
|
124
|
+
OutputGcsUri: string;
|
|
125
|
+
FramesEncoded: number;
|
|
126
|
+
FileSize: number;
|
|
127
|
+
DurationMs: number;
|
|
128
|
+
}
|
|
129
|
+
export type CloudRunResult = PlanResultBody | RenderChunkResultBody | AssembleResultBody;
|
|
130
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,mCAAmC,EACpC,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EAAE,mCAAmC,EAAE,MAAM,mCAAmC,CAAC;AAE7F,0EAA0E;AAC1E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,gBAAgB,GAChB,aAAa,GACb;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,GAC1B;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAE7B,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,8FAA8F;IAC9F,aAAa,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,MAAM,EAAE,mCAAmC,CAAC;CAC7C;AAED,kEAAkE;AAClE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,oBAAoB,EAAE,MAAM,CAAC;IAC7B,+FAA+F;IAC/F,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,2FAA2F;IAC3F,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAID,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,8DAA8D;AAC9D,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,qBAAqB,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a distributed `format` to the file extension the assembled output
|
|
3
|
+
* should carry on disk + in GCS. Shared by `src/server.ts` (chunk +
|
|
4
|
+
* assemble output paths) and `src/sdk/renderToCloudRun.ts` (final
|
|
5
|
+
* output key construction) so the two sides agree on what an mp4
|
|
6
|
+
* looks like vs a png-sequence.
|
|
7
|
+
*/
|
|
8
|
+
import type { DistributedFormat } from "@hyperframes/producer/distributed";
|
|
9
|
+
export type { DistributedFormat } from "@hyperframes/producer/distributed";
|
|
10
|
+
export declare function formatExtension(format: DistributedFormat): string;
|
|
11
|
+
//# sourceMappingURL=formatExtension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatExtension.d.ts","sourceRoot":"","sources":["../src/formatExtension.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAE3E,YAAY,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAc3E,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAEjE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin GCS transport for the Cloud Run handler.
|
|
3
|
+
*
|
|
4
|
+
* The OSS distributed primitives are pure functions over local file paths;
|
|
5
|
+
* the handler bridges GCS ↔ the container's writable `/tmp` filesystem on
|
|
6
|
+
* each request. Functions here are intentionally narrow: parse a URI,
|
|
7
|
+
* download an object to a local path, upload a path, tar-pack a planDir,
|
|
8
|
+
* tar-extract a planDir back out.
|
|
9
|
+
*
|
|
10
|
+
* Tar (not zip) for planDir transit:
|
|
11
|
+
* - planDirs contain symlinks (the extract stage materializes them but
|
|
12
|
+
* the compiled/ subtree may include linked assets); tar preserves them,
|
|
13
|
+
* zip does not.
|
|
14
|
+
* - We use the `tar` npm package (pure JS over `node:zlib`) so the
|
|
15
|
+
* archive format doesn't depend on a system `tar`/`unzip` being present
|
|
16
|
+
* in the container image.
|
|
17
|
+
*
|
|
18
|
+
* Apart from the `gs://` scheme and the `@google-cloud/storage` client this
|
|
19
|
+
* is the same shape as `@hyperframes/aws-lambda`'s `s3Transport.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import type { Storage } from "@google-cloud/storage";
|
|
22
|
+
/** Parsed `gs://bucket/key` URI. */
|
|
23
|
+
export interface GcsLocation {
|
|
24
|
+
bucket: string;
|
|
25
|
+
key: string;
|
|
26
|
+
}
|
|
27
|
+
/** Parse `gs://bucket/key/path` → `{ bucket, key }`. Throws on malformed input. */
|
|
28
|
+
export declare function parseGcsUri(uri: string): GcsLocation;
|
|
29
|
+
/** Build `gs://bucket/key` from a location. */
|
|
30
|
+
export declare function formatGcsUri(loc: GcsLocation): string;
|
|
31
|
+
/** Stream a GCS object to a local file path. */
|
|
32
|
+
export declare function downloadGcsObjectToFile(storage: Storage, uri: string, destPath: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Upload a local file's contents to a GCS URI using a resumable upload.
|
|
35
|
+
* GCS objects have no practical size ceiling for the artifacts this adapter
|
|
36
|
+
* handles (plan tarballs ≤ 2 GB, chunks ≤ a few hundred MB), so a single
|
|
37
|
+
* upload call works for every case.
|
|
38
|
+
*/
|
|
39
|
+
export declare function uploadFileToGcs(storage: Storage, localPath: string, uri: string, contentType?: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Pack a directory into a `.tar.gz` at `destTarball`. Uses the `tar` npm
|
|
42
|
+
* package (pure JS over `node:zlib`) rather than spawning a system tar
|
|
43
|
+
* binary so the archive format is independent of the container's userland.
|
|
44
|
+
*/
|
|
45
|
+
export declare function tarDirectory(sourceDir: string, destTarball: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Extract a `.tar.gz` produced by {@link tarDirectory} into `destDir`.
|
|
48
|
+
* The directory is created (or cleared) before extraction so a retried
|
|
49
|
+
* request doesn't observe stale files from a prior run on the same warm
|
|
50
|
+
* container instance.
|
|
51
|
+
*/
|
|
52
|
+
export declare function untarDirectory(tarballPath: string, destDir: string): Promise<void>;
|
|
53
|
+
//# sourceMappingURL=gcsTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcsTransport.d.ts","sourceRoot":"","sources":["../src/gcsTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGrD,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,mFAAmF;AAEnF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAepD;AAED,+CAA+C;AAC/C,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAErD;AAED,gDAAgD;AAChD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYxF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hyperframes/gcp-cloud-run` — Google Cloud Run + Workflows adapter for
|
|
3
|
+
* the HyperFrames distributed render pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Two surfaces, one package:
|
|
6
|
+
*
|
|
7
|
+
* - **Server-side handler.** `dispatch`, `createApp`, the request/result
|
|
8
|
+
* types, Chrome resolution, and GCS transport. These power the Cloud Run
|
|
9
|
+
* service built from the package `Dockerfile`.
|
|
10
|
+
* - **Client-side SDK.** `renderToCloudRun`, `getRenderProgress`,
|
|
11
|
+
* `deploySite`, `validateDistributedRenderConfig`, and `computeRenderCost`.
|
|
12
|
+
* Adopters call these from their Node process (CI scripts, CLIs) to drive
|
|
13
|
+
* a deployed stack without writing GCS / Workflows boilerplate.
|
|
14
|
+
*
|
|
15
|
+
* The Terraform module that provisions the bucket + service + workflow lives
|
|
16
|
+
* under `terraform/` in the published package; see the README. The package
|
|
17
|
+
* is NOT a dependency of `@hyperframes/producer`; consumers install it
|
|
18
|
+
* separately.
|
|
19
|
+
*/
|
|
20
|
+
export { createApp, dispatch, type HandlerDeps, startServer, unwrapEvent } from "./server.js";
|
|
21
|
+
export { type AssembleEvent, type AssembleResultBody, type CloudRunAction, type CloudRunEvent, type CloudRunResult, type PlanEvent, type PlanResultBody, type RenderChunkEvent, type RenderChunkResultBody, type SerializableDistributedRenderConfig, } from "./events.js";
|
|
22
|
+
export { ChromeBinaryUnavailableError, resolveChromeExecutablePath } from "./chromium.js";
|
|
23
|
+
export { downloadGcsObjectToFile, formatGcsUri, type GcsLocation, parseGcsUri, tarDirectory, untarDirectory, uploadFileToGcs, } from "./gcsTransport.js";
|
|
24
|
+
export { deploySite, type DeploySiteOptions, type SiteHandle } from "./sdk/deploySite.js";
|
|
25
|
+
export { renderToCloudRun, type RenderHandle, type RenderToCloudRunOptions, } from "./sdk/renderToCloudRun.js";
|
|
26
|
+
export { getRenderProgress, type GetRenderProgressOptions, type RenderError, type RenderProgress, type RenderStatus, } from "./sdk/getRenderProgress.js";
|
|
27
|
+
export { type BilledCloudRunInvocation, computeRenderCost, type RenderCost, } from "./sdk/costAccounting.js";
|
|
28
|
+
export { InvalidConfigError, validateDistributedRenderConfig } from "./sdk/validateConfig.js";
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,mCAAmC,GACzC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,YAAY,EACZ,KAAK,WAAW,EAChB,WAAW,EACX,YAAY,EACZ,cAAc,EACd,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EACL,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,uBAAuB,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,KAAK,UAAU,GAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC"}
|