@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 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"}
@@ -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"}
@@ -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"}