@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 +21 -4
- package/README.md +5 -1
- package/dist/chromium.d.ts +4 -3
- package/dist/chromium.d.ts.map +1 -1
- package/dist/events.d.ts +2 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/gcsPlanV2Publisher.d.ts +25 -0
- package/dist/gcsPlanV2Publisher.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +187 -91
- package/dist/index.js.map +4 -4
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +178 -83
- package/dist/server.js.map +4 -4
- package/package.json +2 -2
- package/terraform/smoke-safety.test.ts +3 -0
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
|
|
59
|
-
#
|
|
60
|
-
#
|
|
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
|
-
|
|
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
|
package/dist/chromium.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
package/dist/chromium.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chromium.d.ts","sourceRoot":"","sources":["../src/chromium.ts"],"names":[],"mappings":"AAAA
|
|
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. */
|
package/dist/events.d.ts.map
CHANGED
|
@@ -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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
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
|
-
|
|
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 =
|
|
378
|
-
const projectArchive =
|
|
379
|
-
const projectDir =
|
|
380
|
-
const planDir =
|
|
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 =
|
|
494
|
+
const planTar = join2(work, "plan.tar.gz");
|
|
389
495
|
await tarDirectory(planDir, planTar);
|
|
390
|
-
const planTarUri = `${
|
|
391
|
-
const audioPath =
|
|
392
|
-
const hasAudio = existsSync3(audioPath) &&
|
|
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?.
|
|
523
|
+
const primitive = deps?.primitives?.planV2WithPublisher ?? planV2WithPublisher;
|
|
418
524
|
primeChrome(deps);
|
|
419
|
-
const work =
|
|
420
|
-
const projectArchive =
|
|
421
|
-
const projectDir =
|
|
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
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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:
|
|
458
|
-
ChunkCount:
|
|
459
|
-
TotalFrames:
|
|
460
|
-
Fps:
|
|
461
|
-
Width:
|
|
462
|
-
Height:
|
|
463
|
-
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:
|
|
467
|
-
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 =
|
|
483
|
-
const planTar =
|
|
484
|
-
const planDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
570
|
-
const planTar =
|
|
571
|
-
const planDir =
|
|
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 =
|
|
578
|
-
if (existsSync3(planAudio) &&
|
|
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" ?
|
|
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 =
|
|
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(
|
|
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" ?
|
|
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 =
|
|
639
|
-
|
|
727
|
+
const transportDir = join2(work, "plan-v2");
|
|
728
|
+
mkdirSync3(transportDir, { recursive: true });
|
|
640
729
|
await downloadGcsObjectToFile(
|
|
641
730
|
storage,
|
|
642
731
|
event.PlanV2ManifestGcsUri,
|
|
643
|
-
|
|
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 =
|
|
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
|
|
758
|
+
return join2(planV2Dir, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
670
759
|
}
|
|
671
760
|
function planV2BlobUri(prefix, digest) {
|
|
672
|
-
return `${
|
|
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
|
-
|
|
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 =
|
|
693
|
-
|
|
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 =
|
|
796
|
+
const localPath = join2(chunksDir, basename(key));
|
|
702
797
|
await downloadGcsObjectToFile(storage, uri, localPath);
|
|
703
798
|
if (format === "png-sequence") {
|
|
704
|
-
const dirPath =
|
|
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
|
|
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
|
-
|
|
858
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
764
859
|
} catch {
|
|
765
860
|
}
|
|
766
861
|
}
|
|
767
862
|
function verifyPlanHash(planDir, expected) {
|
|
768
|
-
const planJsonPath =
|
|
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
|
|
845
|
-
import { tmpdir as
|
|
846
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
964
|
+
const workdir = mkdtempSync3(join3(tmpdir3(), "hf-deploy-site-"));
|
|
870
965
|
try {
|
|
871
|
-
const tarball =
|
|
966
|
+
const tarball = join3(workdir, "project.tar.gz");
|
|
872
967
|
await tarDirectory(opts.projectDir, tarball);
|
|
873
|
-
const 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
|
-
|
|
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,
|