hyperframes 0.4.24 → 0.4.26

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.
Files changed (2) hide show
  1. package/dist/cli.js +183 -38
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -54,7 +54,7 @@ var VERSION;
54
54
  var init_version = __esm({
55
55
  "src/version.ts"() {
56
56
  "use strict";
57
- VERSION = true ? "0.4.24" : "0.0.0-dev";
57
+ VERSION = true ? "0.4.26" : "0.0.0-dev";
58
58
  }
59
59
  });
60
60
 
@@ -10790,14 +10790,14 @@ function lintProject(project) {
10790
10790
  totalErrors += rootResult.errorCount;
10791
10791
  totalWarnings += rootResult.warningCount;
10792
10792
  totalInfos += rootResult.infoCount;
10793
- const allHtmlSources = [rootHtml];
10793
+ const allHtmlSources = [{ html: rootHtml }];
10794
10794
  const compositionsDir = resolve5(project.dir, "compositions");
10795
10795
  if (existsSync7(compositionsDir)) {
10796
10796
  const files = readdirSync2(compositionsDir).filter((f3) => f3.endsWith(".html"));
10797
10797
  for (const file of files) {
10798
10798
  const filePath = join9(compositionsDir, file);
10799
10799
  const html = readFileSync7(filePath, "utf-8");
10800
- allHtmlSources.push(html);
10800
+ allHtmlSources.push({ html, compSrcPath: `compositions/${file}` });
10801
10801
  const result = lintHyperframeHtml(html, { filePath, isSubComposition: true });
10802
10802
  results.push({ file: `compositions/${file}`, result });
10803
10803
  totalErrors += result.errorCount;
@@ -10840,7 +10840,7 @@ function lintProjectAudioFiles(projectDir, htmlSources) {
10840
10840
  return findings;
10841
10841
  }
10842
10842
  if (audioFiles.length === 0) return findings;
10843
- const hasAudioElement = htmlSources.some((html) => /<audio\b/i.test(html));
10843
+ const hasAudioElement = htmlSources.some(({ html }) => /<audio\b/i.test(html));
10844
10844
  if (!hasAudioElement) {
10845
10845
  findings.push({
10846
10846
  code: "audio_file_without_element",
@@ -10855,13 +10855,14 @@ function lintAudioSrcNotFound(projectDir, htmlSources) {
10855
10855
  const findings = [];
10856
10856
  const audioSrcRe = /<audio\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi;
10857
10857
  const missingSrcs = [];
10858
- for (const html of htmlSources) {
10858
+ for (const { html, compSrcPath } of htmlSources) {
10859
10859
  let match;
10860
10860
  while ((match = audioSrcRe.exec(html)) !== null) {
10861
10861
  const src = match[1];
10862
10862
  if (/^(https?:|data:|blob:)/i.test(src)) continue;
10863
10863
  if (/^__[A-Z_]+__$/.test(src)) continue;
10864
- const resolved = resolve5(projectDir, src);
10864
+ const rootRelative = compSrcPath ? rewriteAssetPath(compSrcPath, src) : src;
10865
+ const resolved = resolve5(projectDir, rootRelative);
10865
10866
  if (!existsSync7(resolved)) {
10866
10867
  missingSrcs.push(src);
10867
10868
  }
@@ -10910,7 +10911,7 @@ function lintDuplicateAudioTracks(htmlSources) {
10910
10911
  }
10911
10912
  const tracks = [];
10912
10913
  const seen = /* @__PURE__ */ new Set();
10913
- for (const html of htmlSources) {
10914
+ for (const { html } of htmlSources) {
10914
10915
  const audioTagRe = /<audio\b[^>]*>/gi;
10915
10916
  let match;
10916
10917
  while ((match = audioTagRe.exec(html)) !== null) {
@@ -10954,6 +10955,7 @@ var init_lintProject = __esm({
10954
10955
  "src/utils/lintProject.ts"() {
10955
10956
  "use strict";
10956
10957
  init_lint();
10958
+ init_src();
10957
10959
  AUDIO_EXTENSIONS2 = /* @__PURE__ */ new Set([".mp3", ".wav", ".aac", ".ogg", ".m4a", ".flac", ".opus"]);
10958
10960
  }
10959
10961
  });
@@ -37570,6 +37572,89 @@ var init_play = __esm({
37570
37572
  import { basename as basename7, join as join39, relative as relative4 } from "path";
37571
37573
  import { readdirSync as readdirSync15, readFileSync as readFileSync27, statSync as statSync14 } from "fs";
37572
37574
  import AdmZip from "adm-zip";
37575
+ function isRecord(value) {
37576
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37577
+ }
37578
+ function dataRecord(payload) {
37579
+ if (!isRecord(payload) || !isRecord(payload["data"])) return null;
37580
+ return payload["data"];
37581
+ }
37582
+ function stringField(record, key2) {
37583
+ const value = record[key2];
37584
+ return typeof value === "string" ? value : null;
37585
+ }
37586
+ function parsePublishedProjectResponse(payload) {
37587
+ const data = dataRecord(payload);
37588
+ if (!data) return null;
37589
+ const projectId = stringField(data, "project_id");
37590
+ const title = stringField(data, "title");
37591
+ const url = stringField(data, "url");
37592
+ const claimToken = stringField(data, "claim_token");
37593
+ const fileCount = data["file_count"];
37594
+ if (!projectId || !title || !url || !claimToken || typeof fileCount !== "number") {
37595
+ return null;
37596
+ }
37597
+ return {
37598
+ projectId,
37599
+ title,
37600
+ fileCount,
37601
+ url,
37602
+ claimToken
37603
+ };
37604
+ }
37605
+ function parseStagedUploadResponse(payload, archiveByteLength) {
37606
+ const data = dataRecord(payload);
37607
+ if (!data) return null;
37608
+ const uploadUrl = stringField(data, "upload_url");
37609
+ const uploadKey = stringField(data, "upload_key");
37610
+ const contentType = stringField(data, "content_type") || PUBLISH_CONTENT_TYPE;
37611
+ if (!uploadUrl || !uploadKey) return null;
37612
+ return {
37613
+ uploadUrl,
37614
+ uploadKey,
37615
+ contentType,
37616
+ uploadHeaders: getUploadHeaders(data, uploadUrl, contentType, archiveByteLength)
37617
+ };
37618
+ }
37619
+ function getUploadHeaders(data, uploadUrl, contentType, archiveByteLength) {
37620
+ const headers = {};
37621
+ const uploadHeaders = data["upload_headers"];
37622
+ if (isRecord(uploadHeaders)) {
37623
+ for (const [key2, value] of Object.entries(uploadHeaders)) {
37624
+ if (typeof value === "string" && key2.trim()) {
37625
+ headers[key2] = value;
37626
+ }
37627
+ }
37628
+ }
37629
+ if (!Object.keys(headers).some((key2) => key2.toLowerCase() === "content-type")) {
37630
+ headers["content-type"] = contentType;
37631
+ }
37632
+ const signedHeaders = new URL(uploadUrl).searchParams.get("X-Amz-SignedHeaders");
37633
+ if (signedHeaders?.split(";").includes("x-amz-server-side-encryption") && !Object.keys(headers).some((key2) => key2.toLowerCase() === "x-amz-server-side-encryption")) {
37634
+ headers["x-amz-server-side-encryption"] = "AES256";
37635
+ }
37636
+ if (signedHeaders?.split(";").includes("content-length") && !Object.keys(headers).some((key2) => key2.toLowerCase() === "content-length")) {
37637
+ headers["content-length"] = String(archiveByteLength);
37638
+ }
37639
+ return headers;
37640
+ }
37641
+ async function readJson(response) {
37642
+ return response.clone().json().catch(() => null);
37643
+ }
37644
+ async function readErrorMessage(response, fallback) {
37645
+ const contentType = response.headers.get("content-type") || "";
37646
+ if (contentType.includes("application/json")) {
37647
+ const payload = await readJson(response);
37648
+ if (isRecord(payload) && typeof payload["message"] === "string") {
37649
+ return payload["message"];
37650
+ }
37651
+ }
37652
+ if (response.status === 403 && response.headers.get("cf-mitigated") === "challenge") {
37653
+ return "Publish upload was blocked before reaching HyperFrames. Please retry after staged uploads are available.";
37654
+ }
37655
+ const text = await response.text().catch(() => "");
37656
+ return text.trim() ? `${fallback}: ${text.trim().slice(0, 180)}` : fallback;
37657
+ }
37573
37658
  function shouldIgnoreSegment(segment) {
37574
37659
  return segment.startsWith(".") || IGNORED_DIRS.has(segment) || IGNORED_FILES.has(segment);
37575
37660
  }
@@ -37605,42 +37690,102 @@ function createPublishArchive(projectDir) {
37605
37690
  function getPublishApiBaseUrl() {
37606
37691
  return (process.env["HYPERFRAMES_PUBLISHED_PROJECTS_API_URL"] || process.env["HEYGEN_API_URL"] || "https://api2.heygen.com").replace(/\/$/, "");
37607
37692
  }
37608
- async function publishProjectArchive(projectDir) {
37609
- const title = basename7(projectDir);
37610
- const archive = createPublishArchive(projectDir);
37611
- const archiveBytes = new Uint8Array(archive.buffer.byteLength);
37612
- archiveBytes.set(archive.buffer);
37693
+ function archiveArrayBuffer(archive) {
37694
+ const arrayBuffer = new ArrayBuffer(archive.buffer.byteLength);
37695
+ new Uint8Array(arrayBuffer).set(archive.buffer);
37696
+ return arrayBuffer;
37697
+ }
37698
+ async function publishProjectArchiveDirect(apiBaseUrl, title, archive) {
37613
37699
  const body = new FormData();
37614
37700
  body.set("title", title);
37615
- body.set("file", new File([archiveBytes], `${title}.zip`, { type: "application/zip" }));
37701
+ body.set(
37702
+ "file",
37703
+ new File([archiveArrayBuffer(archive)], `${title}.zip`, { type: PUBLISH_CONTENT_TYPE })
37704
+ );
37616
37705
  const headers = {
37617
37706
  heygen_route: "canary"
37618
37707
  };
37619
- const response = await fetch(`${getPublishApiBaseUrl()}/v1/hyperframes/projects/publish`, {
37708
+ const response = await fetch(`${apiBaseUrl}/v1/hyperframes/projects/publish`, {
37620
37709
  method: "POST",
37621
37710
  body,
37622
37711
  headers,
37623
- signal: AbortSignal.timeout(3e4)
37712
+ signal: AbortSignal.timeout(PUBLISH_REQUEST_TIMEOUT_MS)
37624
37713
  });
37625
- const payload = await response.json().catch(() => null);
37626
- const message = typeof payload?.message === "string" ? payload.message : "Failed to publish project";
37627
- if (!response.ok || !payload?.data) {
37628
- throw new Error(message);
37714
+ const payload = await readJson(response);
37715
+ const publishedProject = parsePublishedProjectResponse(payload);
37716
+ if (!response.ok || !publishedProject) {
37717
+ throw new Error(await readErrorMessage(response, "Failed to publish project"));
37629
37718
  }
37630
- return {
37631
- projectId: String(payload.data.project_id),
37632
- title: String(payload.data.title),
37633
- fileCount: Number(payload.data.file_count),
37634
- url: String(payload.data.url),
37635
- claimToken: String(payload.data.claim_token)
37636
- };
37719
+ return publishedProject;
37720
+ }
37721
+ async function publishProjectArchiveStaged(apiBaseUrl, title, archive) {
37722
+ const fileName = `${title}.zip`;
37723
+ const uploadResponse = await fetch(`${apiBaseUrl}/v1/hyperframes/projects/publish/upload`, {
37724
+ method: "POST",
37725
+ body: JSON.stringify({
37726
+ file_name: fileName,
37727
+ content_type: PUBLISH_CONTENT_TYPE,
37728
+ content_length: archive.buffer.byteLength
37729
+ }),
37730
+ headers: {
37731
+ "content-type": "application/json",
37732
+ heygen_route: "canary"
37733
+ },
37734
+ signal: AbortSignal.timeout(PUBLISH_REQUEST_TIMEOUT_MS)
37735
+ });
37736
+ if (uploadResponse.status === 404 || uploadResponse.status === 405) {
37737
+ return null;
37738
+ }
37739
+ const uploadPayload = await readJson(uploadResponse);
37740
+ const stagedUpload = parseStagedUploadResponse(uploadPayload, archive.buffer.byteLength);
37741
+ if (!uploadResponse.ok || !stagedUpload) {
37742
+ throw new Error(await readErrorMessage(uploadResponse, "Failed to prepare project upload"));
37743
+ }
37744
+ const s3Response = await fetch(stagedUpload.uploadUrl, {
37745
+ method: "PUT",
37746
+ body: new Blob([archiveArrayBuffer(archive)], { type: stagedUpload.contentType }),
37747
+ headers: stagedUpload.uploadHeaders,
37748
+ signal: AbortSignal.timeout(PUBLISH_REQUEST_TIMEOUT_MS)
37749
+ });
37750
+ if (!s3Response.ok) {
37751
+ throw new Error(await readErrorMessage(s3Response, "Failed to upload project archive"));
37752
+ }
37753
+ const completeResponse = await fetch(`${apiBaseUrl}/v1/hyperframes/projects/publish/complete`, {
37754
+ method: "POST",
37755
+ body: JSON.stringify({
37756
+ upload_key: stagedUpload.uploadKey,
37757
+ file_name: fileName,
37758
+ title
37759
+ }),
37760
+ headers: {
37761
+ "content-type": "application/json",
37762
+ heygen_route: "canary"
37763
+ },
37764
+ signal: AbortSignal.timeout(PUBLISH_REQUEST_TIMEOUT_MS)
37765
+ });
37766
+ const completePayload = await readJson(completeResponse);
37767
+ const publishedProject = parsePublishedProjectResponse(completePayload);
37768
+ if (!completeResponse.ok || !publishedProject) {
37769
+ throw new Error(await readErrorMessage(completeResponse, "Failed to publish project"));
37770
+ }
37771
+ return publishedProject;
37772
+ }
37773
+ async function publishProjectArchive(projectDir) {
37774
+ const title = basename7(projectDir);
37775
+ const archive = createPublishArchive(projectDir);
37776
+ const apiBaseUrl = getPublishApiBaseUrl();
37777
+ const stagedResult = await publishProjectArchiveStaged(apiBaseUrl, title, archive);
37778
+ if (stagedResult) return stagedResult;
37779
+ return publishProjectArchiveDirect(apiBaseUrl, title, archive);
37637
37780
  }
37638
- var IGNORED_DIRS, IGNORED_FILES;
37781
+ var IGNORED_DIRS, IGNORED_FILES, PUBLISH_CONTENT_TYPE, PUBLISH_REQUEST_TIMEOUT_MS;
37639
37782
  var init_publishProject = __esm({
37640
37783
  "src/utils/publishProject.ts"() {
37641
37784
  "use strict";
37642
37785
  IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".next", "coverage"]);
37643
37786
  IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db"]);
37787
+ PUBLISH_CONTENT_TYPE = "application/zip";
37788
+ PUBLISH_REQUEST_TIMEOUT_MS = 3e4;
37644
37789
  }
37645
37790
  });
37646
37791
 
@@ -65169,19 +65314,19 @@ function _moveValueRecursive(data, sourceKeys, destKeys, keyIdx, excludeKeys) {
65169
65314
  const key2 = sourceKeys[keyIdx];
65170
65315
  if (key2.endsWith("[]")) {
65171
65316
  const keyName = key2.slice(0, -2);
65172
- const dataRecord = data;
65173
- if (keyName in dataRecord && Array.isArray(dataRecord[keyName])) {
65174
- for (const item of dataRecord[keyName]) {
65317
+ const dataRecord2 = data;
65318
+ if (keyName in dataRecord2 && Array.isArray(dataRecord2[keyName])) {
65319
+ for (const item of dataRecord2[keyName]) {
65175
65320
  _moveValueRecursive(item, sourceKeys, destKeys, keyIdx + 1, excludeKeys);
65176
65321
  }
65177
65322
  }
65178
65323
  } else if (key2 === "*") {
65179
65324
  if (typeof data === "object" && data !== null && !Array.isArray(data)) {
65180
- const dataRecord = data;
65181
- const keysToMove = Object.keys(dataRecord).filter((k2) => !k2.startsWith("_") && !excludeKeys.has(k2));
65325
+ const dataRecord2 = data;
65326
+ const keysToMove = Object.keys(dataRecord2).filter((k2) => !k2.startsWith("_") && !excludeKeys.has(k2));
65182
65327
  const valuesToMove = {};
65183
65328
  for (const k2 of keysToMove) {
65184
- valuesToMove[k2] = dataRecord[k2];
65329
+ valuesToMove[k2] = dataRecord2[k2];
65185
65330
  }
65186
65331
  for (const [k2, v] of Object.entries(valuesToMove)) {
65187
65332
  const newDestKeys = [];
@@ -65192,16 +65337,16 @@ function _moveValueRecursive(data, sourceKeys, destKeys, keyIdx, excludeKeys) {
65192
65337
  newDestKeys.push(dk);
65193
65338
  }
65194
65339
  }
65195
- setValueByPath(dataRecord, newDestKeys, v);
65340
+ setValueByPath(dataRecord2, newDestKeys, v);
65196
65341
  }
65197
65342
  for (const k2 of keysToMove) {
65198
- delete dataRecord[k2];
65343
+ delete dataRecord2[k2];
65199
65344
  }
65200
65345
  }
65201
65346
  } else {
65202
- const dataRecord = data;
65203
- if (key2 in dataRecord) {
65204
- _moveValueRecursive(dataRecord[key2], sourceKeys, destKeys, keyIdx + 1, excludeKeys);
65347
+ const dataRecord2 = data;
65348
+ if (key2 in dataRecord2) {
65349
+ _moveValueRecursive(dataRecord2[key2], sourceKeys, destKeys, keyIdx + 1, excludeKeys);
65205
65350
  }
65206
65351
  }
65207
65352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperframes",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "HyperFrames CLI — create, preview, and render HTML video compositions",
5
5
  "repository": {
6
6
  "type": "git",