@hyperframes/gcp-cloud-run 0.7.72 → 0.7.74

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 CHANGED
@@ -21,7 +21,7 @@
21
21
  # Unlike the AWS Lambda adapter there is no ZIP-size ceiling and no runtime
22
22
  # Chrome decompression step — the binary lives in the image at a fixed path.
23
23
 
24
- FROM node:22-bookworm-slim
24
+ FROM node:22-bookworm-slim AS beginframe-contract
25
25
 
26
26
  # ── System dependencies (identical set to the producer's render image) ───────
27
27
  RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -55,9 +55,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
55
55
  && fc-cache -fv
56
56
 
57
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.
58
+ # Pinned deliberately. The build-stage contract probe below launches this
59
+ # exact executable and requires a renderer-ready warm-up plus a PNG-returning
60
+ # HeadlessExperimental.beginFrame before the image can build.
61
61
  RUN npx --yes @puppeteer/browsers install chrome-headless-shell@148.0.7778.167 \
62
62
  --path /opt/puppeteer \
63
63
  && CHS="$(find /opt/puppeteer/chrome-headless-shell -name chrome-headless-shell -type f | head -n1)" \
@@ -100,6 +100,23 @@ COPY packages/studio-server/package.json packages/studio-server/package.json
100
100
  COPY scripts/package-subpaths.mjs scripts/package-subpaths.mjs
101
101
  RUN bun install --frozen-lockfile
102
102
 
103
+ # Fail the image build closed if the packaged executable cannot perform the
104
+ # same BeginFrame operation the distributed renderer relies on. Generate the
105
+ # argument list with the production engine helper so a launcher-flag regression
106
+ # invalidates this layer and fails the contract instead of testing a duplicate
107
+ # hard-coded profile.
108
+ COPY packages/engine/ packages/engine/
109
+ COPY packages/aws-lambda/scripts/probe-beginframe.ts packages/aws-lambda/scripts/probe-beginframe.ts
110
+ RUN bun -e 'import { buildChromeArgs } from "./packages/engine/src/services/browserManager.ts"; await Bun.write("/tmp/hf-gcp-chrome-args.json", JSON.stringify(buildChromeArgs({ width: 800, height: 600, captureMode: "beginframe", platform: "linux" }, { browserGpuMode: "software" })))' \
111
+ && bun packages/aws-lambda/scripts/probe-beginframe.ts \
112
+ --executable-path /opt/chrome/chrome-headless-shell \
113
+ --launch-args-json /tmp/hf-gcp-chrome-args.json
114
+
115
+ # CI targets `beginframe-contract` directly, so pin/probe changes do not need
116
+ # the full monorepo build merely to verify browser capability. The deployable
117
+ # image continues from the already-proven binary and installed workspaces.
118
+ FROM beginframe-contract AS runtime
119
+
103
120
  # Copy source for the packages the render path needs.
104
121
  COPY packages/core/ packages/core/
105
122
  COPY packages/engine/ packages/engine/
package/README.md CHANGED
@@ -50,7 +50,11 @@ decompresses `@sparticuz/chromium` into `/tmp` at runtime — Cloud Run runs a
50
50
  container image. The `Dockerfile` installs the same pinned
51
51
  `chrome-headless-shell` build and font set the production renderer uses, at a
52
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
53
+ support is a binary/runtime capability, so the image build launches that
54
+ exact executable and requires an enable + warm-up + PNG-returning
55
+ `HeadlessExperimental.beginFrame` probe to pass. The end-to-end smoke also
56
+ requires every chunk to report effective `CaptureMode: "beginframe"`, which
57
+ catches runtime fallback separately from build-time packaging. There is no
54
58
  runtime decompression step and no packaging ceiling.
55
59
 
56
60
  ## Deploying
@@ -5,9 +5,10 @@
5
5
  * via the engine's `BrowserManager`. Because Cloud Run runs a container
6
6
  * image rather than a size-capped ZIP, the Chrome story is far simpler than
7
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.
8
+ * into the image at a known path and exports `HYPERFRAMES_CHROME_PATH`.
9
+ * The Dockerfile proves that exact executable's full BeginFrame screenshot
10
+ * contract during the image build. There is no runtime decompression-into-
11
+ * /tmp step and no 250 MB packaging ceiling to fight.
11
12
  *
12
13
  * Resolution order:
13
14
  * 1. `PRODUCER_HEADLESS_SHELL_PATH` — the engine's own override. If a
@@ -1 +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"}
1
+ {"version":3,"file":"chromium.d.ts","sourceRoot":"","sources":["../src/chromium.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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 CHANGED
@@ -176,6 +176,8 @@ export interface RenderChunkResultBody {
176
176
  ChunkIndex: number;
177
177
  Sha256: string;
178
178
  FramesEncoded: number;
179
+ /** Effective engine mode after browser probing. Emitted by current handlers. */
180
+ CaptureMode?: "beginframe" | "screenshot" | "drawelement";
179
181
  DurationMs: number;
180
182
  }
181
183
  /** Result of an `assemble` invocation. */
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { Storage } from "@google-cloud/storage";
2
+ import { type PlanV2ArtifactPublisher, type PlanV2PublishBlob } from "@hyperframes/producer/distributed";
3
+ export interface GcsPlanV2ArtifactPublisherOptions {
4
+ readonly storage: Storage;
5
+ /** Validated render output prefix from which all v2 object keys are derived. */
6
+ readonly planOutputGcsPrefix: string;
7
+ /** Planner-local scratch parent for the small manifest upload file. */
8
+ readonly temporaryRoot?: string;
9
+ }
10
+ /**
11
+ * Manifest-last GCS implementation of the producer's plan-v2 publication seam.
12
+ *
13
+ * Every path remains private to the planner container. Remote workers receive
14
+ * only the manifest URI and artifact prefix and materialize their own target.
15
+ */
16
+ export declare class GcsPlanV2ArtifactPublisher implements PlanV2ArtifactPublisher {
17
+ #private;
18
+ readonly artifactPrefix: string;
19
+ readonly manifestUri: string;
20
+ constructor(options: Readonly<GcsPlanV2ArtifactPublisherOptions>);
21
+ putBlob(blob: Readonly<PlanV2PublishBlob>): Promise<void>;
22
+ commitManifest(manifestBytes: string): Promise<void>;
23
+ abort(): Promise<void>;
24
+ }
25
+ //# sourceMappingURL=gcsPlanV2Publisher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gcsPlanV2Publisher.d.ts","sourceRoot":"","sources":["../src/gcsPlanV2Publisher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACvB,MAAM,mCAAmC,CAAC;AAG3C,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,uEAAuE;IACvE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAuCD;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,uBAAuB;;IACxE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAMjB,OAAO,EAAE,QAAQ,CAAC,iCAAiC,CAAC;IAU1D,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAczD,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAW7B"}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { createApp, dispatch, type HandlerDeps, startServer, unwrapEvent } from
21
21
  export { type AssembleEvent, type AssembleV1Event, type AssembleV2Event, type AssembleResultBody, type CloudRunAction, type CloudRunEvent, type CloudRunPlanProtocol, type CloudRunResult, type PlanEvent, type PlanResultBody, type PlanV1Event, type PlanV1ResultBody, type PlanV2Event, type PlanV2ResultBody, type RenderChunkEvent, type RenderChunkResultBody, type RenderChunkV1Event, type RenderChunkV2Event, type SerializableDistributedRenderConfig, } from "./events.js";
22
22
  export { ChromeBinaryUnavailableError, resolveChromeExecutablePath } from "./chromium.js";
23
23
  export { downloadGcsObjectToFile, downloadGcsObjectToFileVerified, formatGcsUri, type GcsLocation, parseGcsUri, sha256File, tarDirectory, untarDirectory, uploadContentAddressedFileToGcs, uploadFileToGcs, } from "./gcsTransport.js";
24
+ export { GcsPlanV2ArtifactPublisher, type GcsPlanV2ArtifactPublisherOptions, } from "./gcsPlanV2Publisher.js";
24
25
  export { deploySite, type DeploySiteOptions, type SiteHandle } from "./sdk/deploySite.js";
25
26
  export { renderToCloudRun, type RenderHandle, type RenderToCloudRunOptions, } from "./sdk/renderToCloudRun.js";
26
27
  export { getRenderProgress, type GetRenderProgressOptions, type RenderError, type RenderProgress, type RenderStatus, } from "./sdk/getRenderProgress.js";
@@ -1 +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,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mCAAmC,GACzC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,YAAY,EACZ,KAAK,WAAW,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,+BAA+B,EAC/B,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;AAC9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC"}
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,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mCAAmC,GACzC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,YAAY,EACZ,KAAK,WAAW,EAChB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,+BAA+B,EAC/B,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,0BAA0B,EAC1B,KAAK,iCAAiC,GACvC,MAAM,yBAAyB,CAAC;AAGjC,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;AAC9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/server.ts
2
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { basename, extname, join } from "node:path";
2
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3
+ import { tmpdir as tmpdir2 } from "node:os";
4
+ import { basename, extname, join as join2 } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { serve } from "@hono/node-server";
7
7
  import { Storage } from "@google-cloud/storage";
@@ -11,7 +11,7 @@ import {
11
11
  listPlanV2ArtifactsForTarget,
12
12
  materializePlanV2Target,
13
13
  plan,
14
- planV2,
14
+ planV2WithPublisher,
15
15
  readPlanV2Manifest,
16
16
  renderChunk
17
17
  } from "@hyperframes/producer/distributed";
@@ -227,6 +227,112 @@ async function untarDirectory(tarballPath, destDir) {
227
227
  await tar.extract({ file: tarballPath, cwd: destDir });
228
228
  }
229
229
 
230
+ // src/gcsPlanV2Publisher.ts
231
+ import { createHash as createHash2 } from "node:crypto";
232
+ import { mkdirSync as mkdirSync2, mkdtempSync, rmSync as rmSync2, statSync as statSync2, writeFileSync } from "node:fs";
233
+ import { tmpdir } from "node:os";
234
+ import { join } from "node:path";
235
+ import {
236
+ PlanV2IntegrityError
237
+ } from "@hyperframes/producer/distributed";
238
+ function isRecord(value) {
239
+ return value !== null && typeof value === "object" && !Array.isArray(value);
240
+ }
241
+ function assertSha2562(value, label) {
242
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) {
243
+ throw new PlanV2IntegrityError(`${label} must be a lowercase SHA-256 digest`);
244
+ }
245
+ return value;
246
+ }
247
+ function manifestDigests(manifestBytes) {
248
+ let value;
249
+ try {
250
+ value = JSON.parse(manifestBytes);
251
+ } catch {
252
+ throw new PlanV2IntegrityError("GCS publisher received invalid manifest JSON");
253
+ }
254
+ if (!isRecord(value) || !Array.isArray(value.artifacts)) {
255
+ throw new PlanV2IntegrityError("GCS publisher manifest requires an artifacts array");
256
+ }
257
+ return new Set(
258
+ value.artifacts.map((artifact, index) => {
259
+ if (!isRecord(artifact)) {
260
+ throw new PlanV2IntegrityError(`GCS publisher artifacts[${index}] must be an object`);
261
+ }
262
+ return assertSha2562(artifact.sha256, `GCS publisher artifacts[${index}].sha256`);
263
+ })
264
+ );
265
+ }
266
+ function trimTrailingSlash(value) {
267
+ let end = value.length;
268
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
269
+ return value.slice(0, end);
270
+ }
271
+ var GcsPlanV2ArtifactPublisher = class {
272
+ artifactPrefix;
273
+ manifestUri;
274
+ #storage;
275
+ #temporaryRoot;
276
+ #publishedDigests = /* @__PURE__ */ new Set();
277
+ #state = "open";
278
+ constructor(options) {
279
+ const outputPrefix = `${trimTrailingSlash(options.planOutputGcsPrefix)}/v2`;
280
+ parseGcsUri(outputPrefix);
281
+ this.#storage = options.storage;
282
+ this.artifactPrefix = `${outputPrefix}/artifacts/sha256`;
283
+ this.manifestUri = `${outputPrefix}/manifest.json`;
284
+ this.#temporaryRoot = options.temporaryRoot ?? tmpdir();
285
+ mkdirSync2(this.#temporaryRoot, { recursive: true });
286
+ }
287
+ async putBlob(blob) {
288
+ this.#assertOpen("publish a blob");
289
+ const digest = assertSha2562(blob.sha256, "GCS published blob sha256");
290
+ const sourceSize = statSync2(blob.sourcePath).size;
291
+ if (sourceSize !== blob.sizeBytes) {
292
+ throw new PlanV2IntegrityError(
293
+ `GCS published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
294
+ );
295
+ }
296
+ const uri = `${this.artifactPrefix}/${digest.slice(0, 2)}/${digest}`;
297
+ await uploadContentAddressedFileToGcs(this.#storage, blob.sourcePath, uri, digest);
298
+ this.#publishedDigests.add(digest);
299
+ }
300
+ async commitManifest(manifestBytes) {
301
+ this.#assertOpen("commit a manifest");
302
+ for (const digest of manifestDigests(manifestBytes)) {
303
+ if (!this.#publishedDigests.has(digest)) {
304
+ throw new PlanV2IntegrityError(
305
+ `cannot commit GCS manifest before referenced blob is durable: ${digest}`
306
+ );
307
+ }
308
+ }
309
+ const manifestDigest = createHash2("sha256").update(manifestBytes, "utf8").digest("hex");
310
+ const stagingDir = mkdtempSync(join(this.#temporaryRoot, "hf-plan-v2-manifest-"));
311
+ const manifestPath = join(stagingDir, "manifest.json");
312
+ try {
313
+ writeFileSync(manifestPath, manifestBytes, "utf8");
314
+ await uploadContentAddressedFileToGcs(
315
+ this.#storage,
316
+ manifestPath,
317
+ this.manifestUri,
318
+ manifestDigest,
319
+ "application/json"
320
+ );
321
+ this.#state = "committed";
322
+ } finally {
323
+ rmSync2(stagingDir, { recursive: true, force: true });
324
+ }
325
+ }
326
+ async abort() {
327
+ if (this.#state === "open") this.#state = "aborted";
328
+ }
329
+ #assertOpen(operation) {
330
+ if (this.#state !== "open") {
331
+ throw new PlanV2IntegrityError(`cannot ${operation} after publisher is ${this.#state}`);
332
+ }
333
+ }
334
+ };
335
+
230
336
  // src/server.ts
231
337
  var cachedStorage = null;
232
338
  function getStorage() {
@@ -374,10 +480,10 @@ async function handlePlan(event, deps) {
374
480
  const storage = deps?.storage ?? getStorage();
375
481
  const primitive = deps?.primitives?.plan ?? plan;
376
482
  primeChrome(deps);
377
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-plan-"));
378
- const projectArchive = join(work, "project.tar.gz");
379
- const projectDir = join(work, "project");
380
- const planDir = join(work, "plan");
483
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-plan-"));
484
+ const projectArchive = join2(work, "project.tar.gz");
485
+ const projectDir = join2(work, "project");
486
+ const planDir = join2(work, "plan");
381
487
  try {
382
488
  await downloadGcsObjectToFile(storage, event.ProjectGcsUri, projectArchive);
383
489
  await untarDirectory(projectArchive, projectDir);
@@ -385,11 +491,11 @@ async function handlePlan(event, deps) {
385
491
  ...event.Config
386
492
  };
387
493
  const result = await primitive(projectDir, config, planDir);
388
- const planTar = join(work, "plan.tar.gz");
494
+ const planTar = join2(work, "plan.tar.gz");
389
495
  await tarDirectory(planDir, planTar);
390
- const planTarUri = `${trimTrailingSlash(event.PlanOutputGcsPrefix)}/plan.tar.gz`;
391
- const audioPath = join(planDir, "audio.aac");
392
- const hasAudio = existsSync3(audioPath) && statSync2(audioPath).size > 0;
496
+ const planTarUri = `${trimTrailingSlash2(event.PlanOutputGcsPrefix)}/plan.tar.gz`;
497
+ const audioPath = join2(planDir, "audio.aac");
498
+ const hasAudio = existsSync3(audioPath) && statSync3(audioPath).size > 0;
393
499
  await uploadFileToGcs(storage, planTar, planTarUri, "application/gzip");
394
500
  return {
395
501
  Action: "plan",
@@ -414,57 +520,38 @@ async function handlePlan(event, deps) {
414
520
  async function handlePlanV2(event, deps) {
415
521
  const started = Date.now();
416
522
  const storage = deps?.storage ?? getStorage();
417
- const primitive = deps?.primitives?.planV2 ?? planV2;
523
+ const primitive = deps?.primitives?.planV2WithPublisher ?? planV2WithPublisher;
418
524
  primeChrome(deps);
419
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-plan-v2-"));
420
- const projectArchive = join(work, "project.tar.gz");
421
- const projectDir = join(work, "project");
422
- const planV2Dir = join(work, "plan-v2");
525
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-plan-v2-"));
526
+ const projectArchive = join2(work, "project.tar.gz");
527
+ const projectDir = join2(work, "project");
423
528
  try {
424
529
  await downloadGcsObjectToFile(storage, event.ProjectGcsUri, projectArchive);
425
530
  await untarDirectory(projectArchive, projectDir);
426
- const result = await primitive(projectDir, { ...event.Config }, planV2Dir);
427
- const manifest = readPlanV2Manifest(planV2Dir);
428
- if (manifest.planHash !== result.planHash) {
429
- throwPlanHashMismatch(result.planHash, manifest.planHash);
430
- }
431
- const outputPrefix = `${trimTrailingSlash(event.PlanOutputGcsPrefix)}/v2`;
432
- const artifactPrefix = `${outputPrefix}/artifacts/sha256`;
433
- const uniqueArtifacts = [
434
- ...new Map(manifest.artifacts.map((artifact) => [artifact.sha256, artifact])).values()
435
- ];
436
- await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
437
- await uploadContentAddressedFileToGcs(
438
- storage,
439
- planV2BlobPath(planV2Dir, artifact.sha256),
440
- planV2BlobUri(artifactPrefix, artifact.sha256),
441
- artifact.sha256
442
- );
443
- });
444
- const manifestUri = `${outputPrefix}/manifest.json`;
445
- await uploadContentAddressedFileToGcs(
531
+ const publisher = new GcsPlanV2ArtifactPublisher({
446
532
  storage,
447
- result.manifestPath,
448
- manifestUri,
449
- await sha256File(result.manifestPath),
450
- "application/json"
451
- );
533
+ planOutputGcsPrefix: event.PlanOutputGcsPrefix,
534
+ temporaryRoot: work
535
+ });
536
+ const manifest = await primitive(projectDir, { ...event.Config }, publisher, {
537
+ stagingParentDir: work
538
+ });
452
539
  return {
453
540
  Action: "plan",
454
541
  PlanProtocol: "v2",
455
- PlanV2ManifestGcsUri: manifestUri,
456
- PlanV2ArtifactGcsPrefix: artifactPrefix,
457
- PlanHash: result.planHash,
458
- ChunkCount: result.chunkCount,
459
- TotalFrames: result.totalFrames,
460
- Fps: result.fps,
461
- Width: result.width,
462
- Height: result.height,
463
- Format: result.format,
542
+ PlanV2ManifestGcsUri: publisher.manifestUri,
543
+ PlanV2ArtifactGcsPrefix: publisher.artifactPrefix,
544
+ PlanHash: manifest.planHash,
545
+ ChunkCount: manifest.chunkCount,
546
+ TotalFrames: manifest.totalFrames,
547
+ Fps: manifest.fps,
548
+ Width: manifest.width,
549
+ Height: manifest.height,
550
+ Format: manifest.format,
464
551
  HasAudio: manifest.artifacts.some((artifact) => artifact.path === "audio.aac"),
465
552
  AudioGcsUri: null,
466
- FfmpegVersion: result.ffmpegVersion,
467
- ProducerVersion: result.producerVersion,
553
+ FfmpegVersion: manifest.ffmpegVersion,
554
+ ProducerVersion: manifest.producerVersion,
468
555
  DurationMs: Date.now() - started
469
556
  };
470
557
  } finally {
@@ -479,14 +566,14 @@ async function handleRenderChunk(event, deps) {
479
566
  const storage = deps?.storage ?? getStorage();
480
567
  const primitive = deps?.primitives?.renderChunk ?? renderChunk;
481
568
  primeChrome(deps);
482
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-chunk-"));
483
- const planTar = join(work, "plan.tar.gz");
484
- const planDir = join(work, "plan");
569
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-chunk-"));
570
+ const planTar = join2(work, "plan.tar.gz");
571
+ const planDir = join2(work, "plan");
485
572
  try {
486
573
  await downloadGcsObjectToFile(storage, event.PlanGcsUri, planTar);
487
574
  await untarDirectory(planTar, planDir);
488
575
  verifyPlanHash(planDir, event.PlanHash);
489
- const chunkOutputBase = join(
576
+ const chunkOutputBase = join2(
490
577
  work,
491
578
  event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
492
579
  );
@@ -503,6 +590,7 @@ async function handleRenderChunk(event, deps) {
503
590
  ChunkIndex: event.ChunkIndex,
504
591
  Sha256: result.sha256,
505
592
  FramesEncoded: result.framesEncoded,
593
+ CaptureMode: result.captureMode,
506
594
  DurationMs: Date.now() - started
507
595
  };
508
596
  } finally {
@@ -514,7 +602,7 @@ async function handleRenderChunkV2(event, deps) {
514
602
  const storage = deps?.storage ?? getStorage();
515
603
  const primitive = deps?.primitives?.renderChunk ?? renderChunk;
516
604
  primeChrome(deps);
517
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-chunk-v2-"));
605
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-chunk-v2-"));
518
606
  try {
519
607
  const planDir = await downloadAndMaterializePlanV2(
520
608
  storage,
@@ -522,7 +610,7 @@ async function handleRenderChunkV2(event, deps) {
522
610
  { role: "chunk", chunkIndex: event.ChunkIndex },
523
611
  work
524
612
  );
525
- const chunkOutputBase = join(
613
+ const chunkOutputBase = join2(
526
614
  work,
527
615
  event.Format === "png-sequence" ? `chunk-${pad(event.ChunkIndex)}` : `chunk-${pad(event.ChunkIndex)}${formatExtension(event.Format)}`
528
616
  );
@@ -539,6 +627,7 @@ async function handleRenderChunkV2(event, deps) {
539
627
  ChunkIndex: event.ChunkIndex,
540
628
  Sha256: result.sha256,
541
629
  FramesEncoded: result.framesEncoded,
630
+ CaptureMode: result.captureMode,
542
631
  DurationMs: Date.now() - started
543
632
  };
544
633
  } finally {
@@ -546,7 +635,7 @@ async function handleRenderChunkV2(event, deps) {
546
635
  }
547
636
  }
548
637
  async function uploadChunkOutput(storage, result, prefix, chunkIndex) {
549
- const trimmed = trimTrailingSlash(prefix);
638
+ const trimmed = trimTrailingSlash2(prefix);
550
639
  if (result.outputKind === "file") {
551
640
  const ext = extname(result.outputPath);
552
641
  const uri2 = `${trimmed}/chunks/${pad(chunkIndex)}${ext}`;
@@ -566,22 +655,22 @@ async function handleAssemble(event, deps) {
566
655
  const started = Date.now();
567
656
  const storage = deps?.storage ?? getStorage();
568
657
  const primitive = deps?.primitives?.assemble ?? assemble;
569
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-assemble-"));
570
- const planTar = join(work, "plan.tar.gz");
571
- const planDir = join(work, "plan");
658
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-assemble-"));
659
+ const planTar = join2(work, "plan.tar.gz");
660
+ const planDir = join2(work, "plan");
572
661
  try {
573
662
  await downloadGcsObjectToFile(storage, event.PlanGcsUri, planTar);
574
663
  await untarDirectory(planTar, planDir);
575
664
  const chunkPaths = await downloadChunkObjects(storage, event.ChunkGcsUris, work, event.Format);
576
665
  let audioPath = null;
577
- const planAudio = join(planDir, "audio.aac");
578
- if (existsSync3(planAudio) && statSync2(planAudio).size > 0) {
666
+ const planAudio = join2(planDir, "audio.aac");
667
+ if (existsSync3(planAudio) && statSync3(planAudio).size > 0) {
579
668
  audioPath = planAudio;
580
669
  } else if (event.AudioGcsUri) {
581
670
  audioPath = planAudio;
582
671
  await downloadGcsObjectToFile(storage, event.AudioGcsUri, audioPath);
583
672
  }
584
- const finalOutput = event.Format === "png-sequence" ? join(work, "output-frames") : join(work, `output${formatExtension(event.Format)}`);
673
+ const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
585
674
  const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
586
675
  cfr: event.Cfr === true
587
676
  });
@@ -607,12 +696,12 @@ async function handleAssembleV2(event, deps) {
607
696
  const started = Date.now();
608
697
  const storage = deps?.storage ?? getStorage();
609
698
  const primitive = deps?.primitives?.assemble ?? assemble;
610
- const work = mkdtempSync(join(deps?.tmpRoot ?? tmpdir(), "hf-cr-assemble-v2-"));
699
+ const work = mkdtempSync2(join2(deps?.tmpRoot ?? tmpdir2(), "hf-cr-assemble-v2-"));
611
700
  try {
612
701
  const planDir = await downloadAndMaterializePlanV2(storage, event, { role: "assembler" }, work);
613
- const audioPath = existsSync3(join(planDir, "audio.aac")) ? join(planDir, "audio.aac") : null;
702
+ const audioPath = existsSync3(join2(planDir, "audio.aac")) ? join2(planDir, "audio.aac") : null;
614
703
  const chunkPaths = await downloadChunkObjects(storage, event.ChunkGcsUris, work, event.Format);
615
- const finalOutput = event.Format === "png-sequence" ? join(work, "output-frames") : join(work, `output${formatExtension(event.Format)}`);
704
+ const finalOutput = event.Format === "png-sequence" ? join2(work, "output-frames") : join2(work, `output${formatExtension(event.Format)}`);
616
705
  const result = await primitive(planDir, chunkPaths, audioPath, finalOutput, {
617
706
  cfr: event.Cfr === true
618
707
  });
@@ -635,12 +724,12 @@ async function handleAssembleV2(event, deps) {
635
724
  }
636
725
  }
637
726
  async function downloadAndMaterializePlanV2(storage, event, target, work) {
638
- const transportDir = join(work, "plan-v2");
639
- mkdirSync2(transportDir, { recursive: true });
727
+ const transportDir = join2(work, "plan-v2");
728
+ mkdirSync3(transportDir, { recursive: true });
640
729
  await downloadGcsObjectToFile(
641
730
  storage,
642
731
  event.PlanV2ManifestGcsUri,
643
- join(transportDir, "plan.json")
732
+ join2(transportDir, "plan.json")
644
733
  );
645
734
  const manifest = readPlanV2Manifest(transportDir);
646
735
  if (manifest.planHash !== event.PlanHash) {
@@ -653,7 +742,7 @@ async function downloadAndMaterializePlanV2(storage, event, target, work) {
653
742
  await mapConcurrent(uniqueArtifacts, 16, async (artifact) => {
654
743
  await downloadPlanV2Artifact(storage, event.PlanV2ArtifactGcsPrefix, transportDir, artifact);
655
744
  });
656
- const planDir = join(work, "plan");
745
+ const planDir = join2(work, "plan");
657
746
  materializePlanV2Target(transportDir, target, planDir);
658
747
  return planDir;
659
748
  }
@@ -666,10 +755,10 @@ async function downloadPlanV2Artifact(storage, artifactPrefix, planV2Dir, artifa
666
755
  );
667
756
  }
668
757
  function planV2BlobPath(planV2Dir, digest) {
669
- return join(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
758
+ return join2(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
670
759
  }
671
760
  function planV2BlobUri(prefix, digest) {
672
- return `${trimTrailingSlash(prefix)}/${digest.slice(0, 2)}/${digest}`;
761
+ return `${trimTrailingSlash2(prefix)}/${digest.slice(0, 2)}/${digest}`;
673
762
  }
674
763
  function throwPlanHashMismatch(expected, actual) {
675
764
  const error = new Error(
@@ -686,11 +775,17 @@ async function mapConcurrent(values, concurrency, fn) {
686
775
  await fn(values[index]);
687
776
  }
688
777
  }
689
- await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker()));
778
+ const results = await Promise.allSettled(
779
+ Array.from({ length: Math.min(concurrency, values.length) }, () => worker())
780
+ );
781
+ const failure = results.find(
782
+ (result) => result.status === "rejected"
783
+ );
784
+ if (failure) throw failure.reason;
690
785
  }
691
786
  async function downloadChunkObjects(storage, uris, workDir, format) {
692
- const chunksDir = join(workDir, "chunks");
693
- mkdirSync2(chunksDir, { recursive: true });
787
+ const chunksDir = join2(workDir, "chunks");
788
+ mkdirSync3(chunksDir, { recursive: true });
694
789
  const local = new Array(uris.length);
695
790
  await Promise.all(
696
791
  uris.map(async (uri, i) => {
@@ -698,10 +793,10 @@ async function downloadChunkObjects(storage, uris, workDir, format) {
698
793
  throw new Error(`[handler] chunk URI at index ${i} is empty`);
699
794
  }
700
795
  const { key } = parseGcsUri(uri);
701
- const localPath = join(chunksDir, basename(key));
796
+ const localPath = join2(chunksDir, basename(key));
702
797
  await downloadGcsObjectToFile(storage, uri, localPath);
703
798
  if (format === "png-sequence") {
704
- const dirPath = join(chunksDir, `frames-${pad(i)}`);
799
+ const dirPath = join2(chunksDir, `frames-${pad(i)}`);
705
800
  await untarDirectory(localPath, dirPath);
706
801
  local[i] = dirPath;
707
802
  } else {
@@ -755,17 +850,17 @@ function validateEventGcsUris(event) {
755
850
  function pad(n) {
756
851
  return n.toString().padStart(4, "0");
757
852
  }
758
- function trimTrailingSlash(prefix) {
853
+ function trimTrailingSlash2(prefix) {
759
854
  return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
760
855
  }
761
856
  function cleanupDir(dir) {
762
857
  try {
763
- rmSync2(dir, { recursive: true, force: true });
858
+ rmSync3(dir, { recursive: true, force: true });
764
859
  } catch {
765
860
  }
766
861
  }
767
862
  function verifyPlanHash(planDir, expected) {
768
- const planJsonPath = join(planDir, "plan.json");
863
+ const planJsonPath = join2(planDir, "plan.json");
769
864
  let parsed;
770
865
  try {
771
866
  parsed = JSON.parse(readFileSync(planJsonPath, "utf-8"));
@@ -841,13 +936,13 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
841
936
  }
842
937
 
843
938
  // src/sdk/deploySite.ts
844
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
845
- import { tmpdir as tmpdir2 } from "node:os";
846
- import { join as join2 } from "node:path";
939
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync4, statSync as statSync4 } from "node:fs";
940
+ import { tmpdir as tmpdir3 } from "node:os";
941
+ import { join as join3 } from "node:path";
847
942
  import { Storage as Storage2 } from "@google-cloud/storage";
848
943
  import { hashProjectDir } from "@hyperframes/producer/distributed";
849
944
  async function deploySite(opts) {
850
- if (!statSync3(opts.projectDir).isDirectory()) {
945
+ if (!statSync4(opts.projectDir).isDirectory()) {
851
946
  throw new Error(`[deploySite] projectDir is not a directory: ${opts.projectDir}`);
852
947
  }
853
948
  const siteId = opts.siteId ?? hashProjectDir(opts.projectDir);
@@ -866,11 +961,11 @@ async function deploySite(opts) {
866
961
  uploaded: false
867
962
  };
868
963
  }
869
- const workdir = mkdtempSync2(join2(tmpdir2(), "hf-deploy-site-"));
964
+ const workdir = mkdtempSync3(join3(tmpdir3(), "hf-deploy-site-"));
870
965
  try {
871
- const tarball = join2(workdir, "project.tar.gz");
966
+ const tarball = join3(workdir, "project.tar.gz");
872
967
  await tarDirectory(opts.projectDir, tarball);
873
- const size = statSync3(tarball).size;
968
+ const size = statSync4(tarball).size;
874
969
  await uploadFileToGcs(storage, tarball, projectGcsUri, "application/gzip");
875
970
  return {
876
971
  siteId,
@@ -881,7 +976,7 @@ async function deploySite(opts) {
881
976
  uploaded: true
882
977
  };
883
978
  } finally {
884
- rmSync3(workdir, { recursive: true, force: true });
979
+ rmSync4(workdir, { recursive: true, force: true });
885
980
  }
886
981
  }
887
982
  async function headObject(file) {
@@ -1170,6 +1265,7 @@ function getTerraformModuleDir() {
1170
1265
  }
1171
1266
  export {
1172
1267
  ChromeBinaryUnavailableError,
1268
+ GcsPlanV2ArtifactPublisher,
1173
1269
  InvalidConfigError2 as InvalidConfigError,
1174
1270
  computeRenderCost,
1175
1271
  createApp,