@hyperframes/aws-lambda 0.8.181 → 0.8.183

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/dist/events.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  import type { DistributedFormat, SerializableDistributedRenderConfig } from "@hyperframes/producer/distributed";
19
19
  export type { SerializableDistributedRenderConfig } from "@hyperframes/producer/distributed";
20
20
  /** Discriminator for the three roles the one Lambda image fulfills. */
21
- export type LambdaAction = "plan" | "renderChunk" | "assemble";
21
+ export type LambdaAction = "plan" | "audio" | "renderChunk" | "assemble";
22
22
  /** Transport protocol selected for one complete distributed render. */
23
23
  export type LambdaPlanProtocol = "v1" | "v2";
24
24
  /**
@@ -29,7 +29,7 @@ export type LambdaPlanProtocol = "v1" | "v2";
29
29
  * handler unwraps both `event.Payload` and `event.Input` before
30
30
  * dispatching.
31
31
  */
32
- export type LambdaEvent = PlanEvent | RenderChunkEvent | AssembleEvent | {
32
+ export type LambdaEvent = PlanEvent | AudioEvent | RenderChunkEvent | AssembleEvent | {
33
33
  Payload: LambdaEvent;
34
34
  } | {
35
35
  Input: LambdaEvent;
@@ -57,6 +57,10 @@ export interface PlanV2Event extends PlanEventBase {
57
57
  PlanProtocol?: "v2";
58
58
  }
59
59
  export type PlanEvent = PlanV1Event | PlanV2Event;
60
+ /** Makes a v2 plan's audio beside its chunks, from the same project and config as the plan. */
61
+ export interface AudioEvent extends Omit<PlanEventBase, "Action"> {
62
+ Action: "audio";
63
+ }
60
64
  /** Activity B: fetch planDir, render one chunk, upload result. */
61
65
  interface RenderChunkEventBase {
62
66
  Action: "renderChunk";
@@ -165,8 +169,17 @@ export interface PlanV2LambdaResult extends PlanLambdaResultBase {
165
169
  PlanProtocol: "v2";
166
170
  PlanV2ManifestS3Uri: string;
167
171
  PlanV2ArtifactS3Prefix: string;
172
+ /** The plan left its audio to the audio task, which has a worker kept free for it. */
173
+ SeparateAudio: boolean;
168
174
  }
169
175
  export type PlanLambdaResult = PlanV1LambdaResult | PlanV2LambdaResult;
176
+ export interface AudioLambdaResult {
177
+ Action: "audio";
178
+ HasAudio: boolean;
179
+ /** The track for the assembler to stitch in; `null` without audio or when the plan mixed it. */
180
+ AudioS3Uri: string | null;
181
+ DurationMs: number;
182
+ }
170
183
  /** Result of a `renderChunk` invocation. Sized ≤200 bytes per §2.4. */
171
184
  export interface RenderChunkLambdaResult {
172
185
  Action: "renderChunk";
@@ -186,4 +199,4 @@ export interface AssembleLambdaResult {
186
199
  FileSize: number;
187
200
  DurationMs: number;
188
201
  }
189
- export type LambdaResult = PlanLambdaResult | RenderChunkLambdaResult | AssembleLambdaResult;
202
+ export type LambdaResult = PlanLambdaResult | AudioLambdaResult | RenderChunkLambdaResult | AssembleLambdaResult;
package/dist/handler.d.ts CHANGED
@@ -12,8 +12,8 @@
12
12
  */
13
13
  import { LambdaClient } from "@aws-sdk/client-lambda";
14
14
  import { S3Client } from "@aws-sdk/client-s3";
15
- import { assemble, type ChunkRenderer, plan, planV2WithPublisher } from "@hyperframes/producer/distributed";
16
- import type { AssembleEvent, LambdaEvent, LambdaResult, PlanEvent, RenderChunkEvent } from "./events.js";
15
+ import { assemble, buildPlanAudio, type ChunkRenderer, plan, planV2WithPublisher } from "@hyperframes/producer/distributed";
16
+ import type { AssembleEvent, AudioEvent, LambdaEvent, LambdaResult, PlanEvent, RenderChunkEvent } from "./events.js";
17
17
  /**
18
18
  * Optional injection points used by the handler's unit tests. Production
19
19
  * callers leave these unset; the real OSS primitives are used. Tests
@@ -28,6 +28,7 @@ export interface HandlerDeps {
28
28
  primitives?: {
29
29
  plan: typeof plan;
30
30
  planV2WithPublisher?: typeof planV2WithPublisher;
31
+ buildPlanAudio?: typeof buildPlanAudio;
31
32
  renderChunk: ChunkRenderer;
32
33
  assemble: typeof assemble;
33
34
  };
@@ -47,4 +48,4 @@ export interface HandlerDeps {
47
48
  * shape; unwrap until we hit a discriminated event.
48
49
  */
49
50
  export declare function handler(event: LambdaEvent, deps?: HandlerDeps): Promise<LambdaResult>;
50
- export declare function unwrapEvent(event: LambdaEvent): PlanEvent | RenderChunkEvent | AssembleEvent;
51
+ export declare function unwrapEvent(event: LambdaEvent): PlanEvent | AudioEvent | RenderChunkEvent | AssembleEvent;
package/dist/handler.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handler,
3
3
  unwrapEvent
4
- } from "./_chunks/chunk-AK7J3QQN.js";
5
- import "./_chunks/chunk-ZY5YIH5W.js";
4
+ } from "./_chunks/chunk-MPNOI2CS.js";
5
+ import "./_chunks/chunk-SGNVVPIS.js";
6
6
  import "./_chunks/chunk-YS3KY3C5.js";
7
7
  export {
8
8
  handler,
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolveChromeExecutablePath,
7
7
  resolveChromeSource,
8
8
  unwrapEvent
9
- } from "./_chunks/chunk-AK7J3QQN.js";
9
+ } from "./_chunks/chunk-MPNOI2CS.js";
10
10
  import {
11
11
  InvalidConfigError,
12
12
  computeRenderCost,
@@ -14,7 +14,7 @@ import {
14
14
  getRenderProgress,
15
15
  renderToLambda,
16
16
  validateDistributedRenderConfig
17
- } from "./_chunks/chunk-PK7PO3MH.js";
17
+ } from "./_chunks/chunk-5FLGGUWE.js";
18
18
  import {
19
19
  downloadS3ObjectToFile,
20
20
  downloadS3ObjectToFileVerified,
@@ -24,7 +24,7 @@ import {
24
24
  untarDirectory,
25
25
  uploadContentAddressedFileToS3,
26
26
  uploadFileToS3
27
- } from "./_chunks/chunk-ZY5YIH5W.js";
27
+ } from "./_chunks/chunk-SGNVVPIS.js";
28
28
  import "./_chunks/chunk-YS3KY3C5.js";
29
29
  export {
30
30
  ChromeBinaryUnavailableError,
@@ -21,6 +21,9 @@ export interface StitchResult {
21
21
  parts: number;
22
22
  downloadedBytes: number;
23
23
  }
24
- /** One faststart mp4 at `destUri` from the stitched mp4 chunks at `chunkUris`, in order. */
25
- export declare function stitchMp4InS3(s3: S3Client, chunkUris: readonly string[], destUri: string, minPartBytes?: number): Promise<StitchResult>;
24
+ /** One faststart mp4 at `destUri` from the stitched mp4 chunks at `chunkUris`, plus `audioUri`'s track when given. */
25
+ export declare function stitchMp4InS3(s3: S3Client, chunkUris: readonly string[], destUri: string, { audioUri, minPartBytes }?: {
26
+ audioUri?: string;
27
+ minPartBytes?: number;
28
+ }): Promise<StitchResult>;
26
29
  export {};
@@ -16,7 +16,9 @@
16
16
  * `/usr/bin`, so a system-binary tar would ENOENT in the actual
17
17
  * deployment.
18
18
  */
19
- import { type S3Client } from "@aws-sdk/client-s3";
19
+ import { type CompletedPart, type CreateMultipartUploadCommandInput, type S3Client } from "@aws-sdk/client-s3";
20
+ /** S3's largest PutObject or CopyObject; bigger objects go multipart. */
21
+ export declare const MAX_SINGLE_PUT_BYTES: number;
20
22
  /** Parsed `s3://bucket/key` URI. */
21
23
  export interface S3Location {
22
24
  bucket: string;
@@ -35,13 +37,7 @@ export declare function downloadS3ObjectToFileVerified(client: S3Client, uri: st
35
37
  * SHA-256 and size are the ones the manifest pins.
36
38
  */
37
39
  export declare function presignVerifiedS3GetUrl(client: S3Client, uri: string, expectedSha256: string, expectedSize: number, expiresInSeconds: number): Promise<string>;
38
- /**
39
- * Upload a local file's contents to an S3 URI using a streaming
40
- * `PutObjectCommand`. PutObject's 5 GB cap comfortably exceeds the
41
- * distributed pipeline's 2 GB planDir limit and the typical
42
- * chunk size (≤ 200 MB), so a single PUT works for every artifact this
43
- * adapter handles.
44
- */
40
+ /** Upload a local file to an S3 URI, streamed: one PutObject up to S3's 5 GB cap, multipart above. */
45
41
  export declare function uploadFileToS3(client: S3Client, localPath: string, uri: string, contentType?: string): Promise<void>;
46
42
  /**
47
43
  * Upload one content-addressed plan-v2 artifact exactly once; S3 verifies the bytes against
@@ -49,6 +45,22 @@ export declare function uploadFileToS3(client: S3Client, localPath: string, uri:
49
45
  * and a conflicting one is never overwritten, since a chunk may already be reading that plan.
50
46
  */
51
47
  export declare function uploadContentAddressedFileToS3(client: S3Client, localPath: string, uri: string, expectedSha256: string, contentType?: string): Promise<"uploaded" | "reused">;
48
+ export type MultipartTarget = {
49
+ Bucket: string;
50
+ Key: string;
51
+ UploadId: string;
52
+ };
53
+ /**
54
+ * Creates a multipart upload, lets `sendParts` fill it, then completes it. A failure aborts the
55
+ * upload so no parts linger; `sendParts` must settle every part before it rejects.
56
+ */
57
+ export declare function multipartUpload(client: S3Client, create: CreateMultipartUploadCommandInput & {
58
+ Bucket: string;
59
+ Key: string;
60
+ }, sendParts: (target: MultipartTarget) => Promise<CompletedPart[]>, complete?: {
61
+ ifNoneMatch?: "*";
62
+ objectSize?: number;
63
+ }): Promise<void>;
52
64
  export declare function mapConcurrent<T>(values: readonly T[], concurrency: number, fn: (value: T) => Promise<void>): Promise<void>;
53
65
  export declare function sha256File(path: string): Promise<string>;
54
66
  /** Put a file into the shared content store once; reusing an aging object restarts its expiry. */
package/dist/sdk/index.js CHANGED
@@ -10,8 +10,8 @@ import {
10
10
  validateDistributedRenderConfig,
11
11
  validateStepFunctionsInputSize,
12
12
  validateVariablesPayload
13
- } from "../_chunks/chunk-PK7PO3MH.js";
14
- import "../_chunks/chunk-ZY5YIH5W.js";
13
+ } from "../_chunks/chunk-5FLGGUWE.js";
14
+ import "../_chunks/chunk-SGNVVPIS.js";
15
15
  import "../_chunks/chunk-YS3KY3C5.js";
16
16
 
17
17
  // src/sdk/prebuiltHandler.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/aws-lambda",
3
- "version": "0.8.181",
3
+ "version": "0.8.183",
4
4
  "description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,6 +11,7 @@
11
11
  "dist/",
12
12
  "prebuilt/",
13
13
  "scripts/",
14
+ "!scripts/*.test.ts",
14
15
  "README.md"
15
16
  ],
16
17
  "type": "module",
@@ -48,7 +49,7 @@
48
49
  "ffprobe-static": "^3.1.0",
49
50
  "puppeteer-core": "^25.8.0",
50
51
  "tar": "^7.4.3",
51
- "@hyperframes/producer": "^0.8.181"
52
+ "@hyperframes/producer": "^0.8.183"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/aws-lambda": "^8.10.146",
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.8.181",
3
- "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.181/hyperframes-lambda-handler.zip",
4
- "sha256": "547401886f6fd729e0e12ad07bdebfdaf990972c22efadcd6129ea1af6b8b525",
5
- "bytes": 129250221
2
+ "version": "0.8.183",
3
+ "url": "https://github.com/heygen-com/hyperframes/releases/download/v0.8.183/hyperframes-lambda-handler.zip",
4
+ "sha256": "1194f61df575fddc48692ca4807ed5d3765e357518a87e20b2086371a25d203f",
5
+ "bytes": 129235569
6
6
  }
@@ -324,6 +324,7 @@ Resources:
324
324
  MaxChunkConcurrency.$: "$.Payload.MaxChunkConcurrency"
325
325
  Format.$: "$.Payload.Format"
326
326
  HasAudio.$: "$.Payload.HasAudio"
327
+ SeparateAudio.$: "$.Payload.SeparateAudio"
327
328
  ResultPath: $.Plan
328
329
  Retry:
329
330
  - ErrorEquals:
@@ -395,7 +396,7 @@ Resources:
395
396
  Choices:
396
397
  - Variable: $.Plan.PlanProtocol
397
398
  StringEquals: v2
398
- Next: RenderChunksV2
399
+ Next: RenderV2
399
400
  Default: RenderChunks
400
401
 
401
402
  RenderChunks:
@@ -511,74 +512,151 @@ Resources:
511
512
  MaxDelaySeconds: 60
512
513
  End: true
513
514
 
514
- RenderChunksV2:
515
- Type: Map
516
- Label: RenderChunksV2
517
- ItemsPath: $.Iterator.ChunkIndexes
518
- ItemSelector:
519
- ChunkIndex.$: "$$.Map.Item.Value"
520
- PlanV2ManifestS3Uri.$: "$.Plan.PlanV2ManifestS3Uri"
521
- PlanV2ArtifactS3Prefix.$: "$.Plan.PlanV2ArtifactS3Prefix"
522
- PlanHash.$: "$.Plan.PlanHash"
523
- ChunkOutputS3Prefix.$: "$.PlanOutputS3Prefix"
524
- Format.$: "$.Plan.Format"
525
- # Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
526
- MaxConcurrencyPath: $.Plan.MaxChunkConcurrency
527
- ResultPath: $.Chunks
528
- ItemProcessor:
529
- ProcessorConfig:
530
- Mode: DISTRIBUTED
531
- ExecutionType: STANDARD
532
- StartAt: RenderChunkV2
533
- States:
534
- RenderChunkV2:
535
- Type: Task
536
- Resource: arn:aws:states:::lambda:invoke
537
- Parameters:
538
- FunctionName: !GetAtt RenderFunction.Arn
539
- Payload:
540
- Action: renderChunk
541
- PlanProtocol: v2
542
- ChunkIndex.$: "$.ChunkIndex"
543
- PlanV2ManifestS3Uri.$: "$.PlanV2ManifestS3Uri"
544
- PlanV2ArtifactS3Prefix.$: "$.PlanV2ArtifactS3Prefix"
545
- PlanHash.$: "$.PlanHash"
546
- ChunkOutputS3Prefix.$: "$.ChunkOutputS3Prefix"
547
- Format.$: "$.Format"
548
- ResultSelector:
549
- ChunkS3Uri.$: "$.Payload.ChunkS3Uri"
550
- ChunkIndex.$: "$.Payload.ChunkIndex"
551
- Sha256.$: "$.Payload.Sha256"
552
- FramesEncoded.$: "$.Payload.FramesEncoded"
553
- DurationMs.$: "$.Payload.DurationMs"
554
- Retry:
555
- - ErrorEquals:
556
- - FFMPEG_VERSION_MISMATCH
557
- - PLAN_HASH_MISMATCH
558
- - S3_URI_NOT_ALLOWED
559
- - BROWSER_GPU_NOT_SOFTWARE
560
- - PLAN_TOO_LARGE
561
- - PlanTooLargeError
562
- - PLAN_PROTOCOL_UNSUPPORTED
563
- - PlanProtocolUnsupportedError
564
- - PLAN_V2_INTEGRITY_UNRECOVERABLE
565
- - INVALID_VIDEO_METADATA
566
- - PlanV2IntegrityError
567
- - PLAN_ARTIFACT_DIGEST_MISMATCH
568
- - ChromeBinaryUnavailableError
569
- MaxAttempts: 0
570
- - ErrorEquals: [Lambda.TooManyRequestsException]
571
- IntervalSeconds: 5
572
- MaxAttempts: 10
573
- BackoffRate: 2
574
- MaxDelaySeconds: 60
575
- JitterStrategy: FULL
576
- - ErrorEquals: [States.ALL]
577
- IntervalSeconds: 2
578
- MaxAttempts: 4
579
- BackoffRate: 2
580
- MaxDelaySeconds: 60
581
- End: true
515
+ RenderV2:
516
+ # Chunks render video only while the audio task mixes the track AssembleV2 stitches in.
517
+ Type: Parallel
518
+ Branches:
519
+ - StartAt: RenderChunksV2
520
+ States:
521
+ RenderChunksV2:
522
+ Type: Map
523
+ Label: RenderChunksV2
524
+ ItemsPath: $.Iterator.ChunkIndexes
525
+ ItemSelector:
526
+ ChunkIndex.$: "$$.Map.Item.Value"
527
+ PlanV2ManifestS3Uri.$: "$.Plan.PlanV2ManifestS3Uri"
528
+ PlanV2ArtifactS3Prefix.$: "$.Plan.PlanV2ArtifactS3Prefix"
529
+ PlanHash.$: "$.Plan.PlanHash"
530
+ ChunkOutputS3Prefix.$: "$.PlanOutputS3Prefix"
531
+ Format.$: "$.Plan.Format"
532
+ # Chunks past the plan's concurrency queue here instead of being throttled by Lambda.
533
+ MaxConcurrencyPath: $.Plan.MaxChunkConcurrency
534
+ ResultPath: $.Chunks
535
+ ItemProcessor:
536
+ ProcessorConfig:
537
+ Mode: DISTRIBUTED
538
+ ExecutionType: STANDARD
539
+ StartAt: RenderChunkV2
540
+ States:
541
+ RenderChunkV2:
542
+ Type: Task
543
+ Resource: arn:aws:states:::lambda:invoke
544
+ Parameters:
545
+ FunctionName: !GetAtt RenderFunction.Arn
546
+ Payload:
547
+ Action: renderChunk
548
+ PlanProtocol: v2
549
+ ChunkIndex.$: "$.ChunkIndex"
550
+ PlanV2ManifestS3Uri.$: "$.PlanV2ManifestS3Uri"
551
+ PlanV2ArtifactS3Prefix.$: "$.PlanV2ArtifactS3Prefix"
552
+ PlanHash.$: "$.PlanHash"
553
+ ChunkOutputS3Prefix.$: "$.ChunkOutputS3Prefix"
554
+ Format.$: "$.Format"
555
+ ResultSelector:
556
+ ChunkS3Uri.$: "$.Payload.ChunkS3Uri"
557
+ ChunkIndex.$: "$.Payload.ChunkIndex"
558
+ Sha256.$: "$.Payload.Sha256"
559
+ FramesEncoded.$: "$.Payload.FramesEncoded"
560
+ DurationMs.$: "$.Payload.DurationMs"
561
+ Retry:
562
+ - ErrorEquals:
563
+ - FFMPEG_VERSION_MISMATCH
564
+ - PLAN_HASH_MISMATCH
565
+ - S3_URI_NOT_ALLOWED
566
+ - BROWSER_GPU_NOT_SOFTWARE
567
+ - PLAN_TOO_LARGE
568
+ - PlanTooLargeError
569
+ - PLAN_PROTOCOL_UNSUPPORTED
570
+ - PlanProtocolUnsupportedError
571
+ - PLAN_V2_INTEGRITY_UNRECOVERABLE
572
+ - INVALID_VIDEO_METADATA
573
+ - PlanV2IntegrityError
574
+ - PLAN_ARTIFACT_DIGEST_MISMATCH
575
+ - ChromeBinaryUnavailableError
576
+ MaxAttempts: 0
577
+ - ErrorEquals: [Lambda.TooManyRequestsException]
578
+ IntervalSeconds: 5
579
+ MaxAttempts: 10
580
+ BackoffRate: 2
581
+ MaxDelaySeconds: 60
582
+ JitterStrategy: FULL
583
+ - ErrorEquals: [States.ALL]
584
+ IntervalSeconds: 2
585
+ MaxAttempts: 4
586
+ BackoffRate: 2
587
+ MaxDelaySeconds: 60
588
+ End: true
589
+ End: true
590
+ - StartAt: AudioNeeded
591
+ States:
592
+ AudioNeeded:
593
+ Type: Choice
594
+ Choices:
595
+ - Variable: $.Plan.SeparateAudio
596
+ BooleanEquals: true
597
+ Next: Audio
598
+ Default: NoAudio
599
+ NoAudio:
600
+ Type: Pass
601
+ Result:
602
+ HasAudio: false
603
+ AudioS3Uri: null
604
+ ResultPath: $.Audio
605
+ End: true
606
+ Audio:
607
+ # The plan left its audio out: mix it here while the chunks render.
608
+ Type: Task
609
+ Resource: arn:aws:states:::lambda:invoke
610
+ Parameters:
611
+ FunctionName: !GetAtt RenderFunction.Arn
612
+ Payload:
613
+ Action: audio
614
+ ProjectS3Uri.$: "$.ProjectS3Uri"
615
+ PlanOutputS3Prefix.$: "$.PlanOutputS3Prefix"
616
+ Config.$: "$.Config"
617
+ ResultSelector:
618
+ HasAudio.$: "$.Payload.HasAudio"
619
+ AudioS3Uri.$: "$.Payload.AudioS3Uri"
620
+ ResultPath: $.Audio
621
+ Retry:
622
+ - ErrorEquals:
623
+ - FFMPEG_VERSION_MISMATCH
624
+ - PLAN_HASH_MISMATCH
625
+ - S3_URI_NOT_ALLOWED
626
+ - BROWSER_GPU_NOT_SOFTWARE
627
+ - FONT_FETCH_FAILED
628
+ - PLAN_TOO_LARGE
629
+ - PlanTooLargeError
630
+ - PLAN_PROTOCOL_UNSUPPORTED
631
+ - PlanProtocolUnsupportedError
632
+ - PLAN_V2_INTEGRITY_UNRECOVERABLE
633
+ - VIDEO_SOURCE_UNRENDERABLE
634
+ - INVALID_VIDEO_METADATA
635
+ - NOT_MEDIA_PAYLOAD
636
+ - NotMediaPayloadError
637
+ - PlanV2IntegrityError
638
+ - PLAN_ARTIFACT_DIGEST_MISMATCH
639
+ - FORMAT_NOT_SUPPORTED_IN_DISTRIBUTED
640
+ - CHUNK_EXCEEDS_WORKER_TIMEOUT
641
+ - ChromeBinaryUnavailableError
642
+ - SITE_MANIFEST_INVALID
643
+ MaxAttempts: 0
644
+ - ErrorEquals: [Lambda.TooManyRequestsException]
645
+ IntervalSeconds: 5
646
+ MaxAttempts: 10
647
+ BackoffRate: 2
648
+ MaxDelaySeconds: 60
649
+ JitterStrategy: FULL
650
+ - ErrorEquals: [States.ALL]
651
+ IntervalSeconds: 2
652
+ MaxAttempts: 4
653
+ BackoffRate: 2
654
+ MaxDelaySeconds: 60
655
+ End: true
656
+ ResultSelector:
657
+ Chunks.$: "$[0].Chunks"
658
+ Audio.$: "$[1].Audio"
659
+ ResultPath: $.Render
582
660
  Next: AssembleV2
583
661
 
584
662
  AssembleV2:
@@ -592,8 +670,8 @@ Resources:
592
670
  PlanV2ManifestS3Uri.$: "$.Plan.PlanV2ManifestS3Uri"
593
671
  PlanV2ArtifactS3Prefix.$: "$.Plan.PlanV2ArtifactS3Prefix"
594
672
  PlanHash.$: "$.Plan.PlanHash"
595
- ChunkS3Uris.$: "$.Chunks[*].ChunkS3Uri"
596
- AudioS3Uri: null
673
+ ChunkS3Uris.$: "$.Render.Chunks[*].ChunkS3Uri"
674
+ AudioS3Uri.$: "$.Render.Audio.AudioS3Uri"
597
675
  OutputS3Uri.$: "$.OutputS3Uri"
598
676
  Format.$: "$.Plan.Format"
599
677
  ResultSelector:
@@ -11,8 +11,7 @@ import {
11
11
  type HandlerZipManifest,
12
12
  PREBUILT_DIR,
13
13
  } from "../src/sdk/prebuiltHandler.js";
14
-
15
- const CHECKOUT_CODE_URI = "CodeUri: ../../packages/aws-lambda/dist/handler.zip";
14
+ import { writePrebuiltTemplate } from "./prebuilt-template.mjs";
16
15
 
17
16
  export function pinHandlerZip(repoRoot: string): HandlerZipManifest {
18
17
  const pkgDir = join(repoRoot, "packages", "aws-lambda");
@@ -31,18 +30,8 @@ export function pinHandlerZip(repoRoot: string): HandlerZipManifest {
31
30
  const sha256 = createHash("sha256").update(bytes).digest("hex");
32
31
  writeFileSync(asset.replace(/\.zip$/, ".sha256"), `${sha256} ${HANDLER_ZIP_ASSET}\n`);
33
32
 
34
- const template = readFileSync(join(repoRoot, "examples", "aws-lambda", "template.yaml"), "utf-8");
35
- if (template.split(CHECKOUT_CODE_URI).length !== 2) {
36
- throw new Error(
37
- `[pin-handler-zip] expected exactly one "${CHECKOUT_CODE_URI}" in the template`,
38
- );
39
- }
40
33
  const outDir = join(pkgDir, PREBUILT_DIR);
41
- mkdirSync(outDir, { recursive: true });
42
- writeFileSync(
43
- join(outDir, "template.yaml"),
44
- template.replace(CHECKOUT_CODE_URI, "CodeUri: handler.zip"),
45
- );
34
+ writePrebuiltTemplate(repoRoot, outDir);
46
35
  const manifest: HandlerZipManifest = {
47
36
  version: pkg.version,
48
37
  url: `${pkg.repository.url}/releases/download/v${pkg.version}/${HANDLER_ZIP_ASSET}`,
@@ -0,0 +1,19 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const CHECKOUT_CODE_URI = "CodeUri: ../../packages/aws-lambda/dist/handler.zip";
5
+
6
+ /** Writes `<outDir>/template.yaml`: the example SAM template, pointed at a `handler.zip` beside it. */
7
+ export function writePrebuiltTemplate(repoRoot, outDir) {
8
+ const template = readFileSync(join(repoRoot, "examples", "aws-lambda", "template.yaml"), "utf-8");
9
+ if (template.split(CHECKOUT_CODE_URI).length !== 2) {
10
+ throw new Error(
11
+ `[prebuilt-template] expected exactly one "${CHECKOUT_CODE_URI}" in the template`,
12
+ );
13
+ }
14
+ mkdirSync(outDir, { recursive: true });
15
+ writeFileSync(
16
+ join(outDir, "template.yaml"),
17
+ template.replace(CHECKOUT_CODE_URI, "CodeUri: handler.zip"),
18
+ );
19
+ }
@@ -1,77 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { spawnSync } from "node:child_process";
3
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { pathToFileURL } from "node:url";
7
- import * as esbuild from "esbuild";
8
- import { HANDLER_BANNER } from "./_handlerBanner.js";
9
-
10
- // The handler ships as ESM (.mjs) but inlines CJS deps that assume Node's CJS
11
- // globals exist at module scope: postcss et al. call top-level `require(...)`,
12
- // and wawoff2's emscripten build reads `__dirname`. A freshly deployed stack
13
- // crashed on every render (#1932) with "__dirname is not defined in ES module
14
- // scope"; the fix is the require/__filename/__dirname shim in HANDLER_BANNER.
15
- //
16
- // This bundles a fixture touching all three globals with the REAL banner and
17
- // imports the output, so it catches a dropped/renamed shim behaviourally
18
- // rather than by grepping for literals (which survives a broken refactor).
19
- //
20
- // The import MUST run under Node, not the `bun test` runtime: Bun defines
21
- // `__dirname`/`__filename` even in ESM, which would mask a missing shim and
22
- // green-light a broken bundle. Lambda runs Node, so we spawn `node` (guaranteed
23
- // present in CI alongside bun) to reproduce the deploy target faithfully.
24
- describe("build-zip handler banner", () => {
25
- it("shims require/__filename/__dirname so inlined CJS deps import under Node", () => {
26
- const dir = mkdtempSync(join(tmpdir(), "hf-banner-test-"));
27
- try {
28
- const entry = join(dir, "fixture.ts");
29
- const outfile = join(dir, "out.mjs");
30
- // Reference each CJS global at module top level, the way inlined deps do.
31
- // If any shim is missing, importing `out.mjs` throws at eval time.
32
- writeFileSync(
33
- entry,
34
- [
35
- "const cjsDir = __dirname;",
36
- "const cjsFile = __filename;",
37
- "const path = require('node:path');",
38
- "if (typeof cjsDir !== 'string') throw new Error('__dirname missing');",
39
- "if (typeof cjsFile !== 'string') throw new Error('__filename missing');",
40
- "if (typeof path.join !== 'function') throw new Error('require missing');",
41
- "console.log('BANNER_OK');",
42
- ].join("\n"),
43
- );
44
-
45
- esbuild.buildSync({
46
- bundle: true,
47
- platform: "node",
48
- target: "node22",
49
- format: "esm",
50
- entryPoints: [entry],
51
- outfile,
52
- banner: { js: HANDLER_BANNER },
53
- });
54
-
55
- // Import under real Node — Lambda's runtime — not the bun test runtime.
56
- const res = spawnSync(
57
- "node",
58
- [
59
- "--input-type=module",
60
- "-e",
61
- `await import(${JSON.stringify(pathToFileURL(outfile).href)});`,
62
- ],
63
- { encoding: "utf8" },
64
- );
65
-
66
- // A missing shim surfaces as a non-zero exit + ReferenceError on stderr.
67
- // Guard against a silent skip if `node` isn't on PATH (it is in CI).
68
- expect(res.error).toBeUndefined();
69
- expect(res.stderr).not.toContain("is not defined in ES module scope");
70
- expect(res.stderr).not.toContain("Dynamic require");
71
- expect(res.status).toBe(0);
72
- expect(res.stdout).toContain("BANNER_OK");
73
- } finally {
74
- rmSync(dir, { recursive: true, force: true });
75
- }
76
- });
77
- });