@hyperframes/studio-server 0.8.6 → 0.8.8

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.
@@ -1,4 +1,4 @@
1
- export { P as PreviewApiAdapter, i as injectMediaCodecMap, e as injectMediaCodecMapIntoHtml, f as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-DVTxBkRO.js';
1
+ export { P as PreviewApiAdapter, i as injectMediaCodecMap, e as injectMediaCodecMapIntoHtml, f as isAutoProxyEnabled, p as proxyEtagSalt, r as resolvePreviewMediaCodecProbeCache } from '../mediaProxyPreview-CGJ7f9JA.js';
2
2
  import './mediaCodecMap.js';
3
3
  import '@hyperframes/core';
4
4
  import '@hyperframes/parsers';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { S as StudioApiAdapter, M as MediaProcessingJobState } from './mediaProxyPreview-DVTxBkRO.js';
3
- export { L as LintResult, P as PreviewApiAdapter, R as RenderJobState, a as ResolvedProject, b as StudioSelectionResponse, c as StudioSelectionSnapshot, d as StudioSelectionTextField } from './mediaProxyPreview-DVTxBkRO.js';
2
+ import { S as StudioApiAdapter, M as MediaProcessingJobState } from './mediaProxyPreview-CGJ7f9JA.js';
3
+ export { L as LintResult, P as PreviewApiAdapter, R as RenderJobState, a as ResolvedProject, b as StudioSelectionResponse, c as StudioSelectionSnapshot, d as StudioSelectionTextField } from './mediaProxyPreview-CGJ7f9JA.js';
4
4
  export { ScreenshotClip, getElementScreenshotClip } from './helpers/screenshotClip.js';
5
5
  export { STUDIO_MANUAL_EDITS_PATH, StudioManualEditsRenderScriptOptions, createStudioManualEditsRenderBodyScript, createStudioPositionSeekReapplyScript } from './helpers/manualEditsRenderScript.js';
6
6
  export { STUDIO_MOTION_PATH, StudioMotionRenderScriptOptions, createStudioMotionRenderBodyScript } from './helpers/studioMotionRenderScript.js';
@@ -16,6 +16,27 @@ import './helpers/mediaCodecMap.js';
16
16
  */
17
17
  declare function createStudioApi(adapter: StudioApiAdapter): Hono;
18
18
 
19
+ /**
20
+ * Whether a write at `changedPath` can change `createProjectSignature(projectDir)`.
21
+ *
22
+ * Owned here because it is the exact complement of what the walk below collects,
23
+ * and a second copy of that reasoning drifts the moment either set changes. A
24
+ * watcher that invalidates on everything is not merely wasteful: `.thumbnails/`
25
+ * is written by the thumbnail route on every capture, and each capture reads the
26
+ * preview, so an unfiltered watcher discards the memo on roughly every request of
27
+ * the one workload the memo exists for.
28
+ *
29
+ * Note this is not `WATCHER_EXCLUDED_DIRS`, which is character-identical but
30
+ * excludes all of `.hyperframes/` — the signature deliberately reads two manifest
31
+ * files from inside it, so filtering with that set would stop motion-state saves
32
+ * from ever invalidating.
33
+ *
34
+ * Every segment is tested, not just the parents, so a directory event on an
35
+ * excluded dir itself (`unlinkDir .thumbnails`) is filtered too. The cost is that
36
+ * a *file* literally named `dist` at the project root reads as excluded; the walk
37
+ * would collect it, so it is a false negative, and no real project has one.
38
+ */
39
+ declare function affectsProjectSignature(projectDir: string, changedPath: string): boolean;
19
40
  /**
20
41
  * Creates a stable preview cache-busting signature for project source plus Studio manifests.
21
42
  */
@@ -97,4 +118,4 @@ type BackgroundRemovalRender = (options: {
97
118
  }>;
98
119
  declare function createBackgroundRemovalJob(opts: BackgroundRemovalJobOptions, render: BackgroundRemovalRender): MediaProcessingJobState;
99
120
 
100
- export { type BackgroundRemovalRender, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, StudioApiAdapter, type ThumbnailOutputDimensions, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, fileContentVersion, getMimeType, thumbnailDeviceScaleFactor, walkDir };
121
+ export { type BackgroundRemovalRender, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, fileContentVersion, getMimeType, thumbnailDeviceScaleFactor, walkDir };
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ function walkDir(dir, prefix = "") {
85
85
  // src/helpers/projectSignature.ts
86
86
  import { createHash } from "crypto";
87
87
  import { lstatSync, readFileSync, readdirSync as readdirSync2 } from "fs";
88
- import { extname, isAbsolute, relative, resolve } from "path";
88
+ import { extname, isAbsolute, relative, resolve, sep } from "path";
89
89
  var SIGNATURE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
90
90
  ".cjs",
91
91
  ".css",
@@ -119,6 +119,15 @@ var STUDIO_SIGNATURE_MANIFEST_PATHS = [
119
119
  ".hyperframes/studio-manual-edits.json",
120
120
  ".hyperframes/studio-motion.json"
121
121
  ];
122
+ function affectsProjectSignature(projectDir, changedPath) {
123
+ const relativePath = relative(resolve(projectDir), resolve(changedPath));
124
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
125
+ return false;
126
+ }
127
+ const segments = relativePath.split(sep);
128
+ if (STUDIO_SIGNATURE_MANIFEST_PATHS.includes(segments.join("/"))) return true;
129
+ return !segments.some((segment) => SIGNATURE_EXCLUDED_DIRS.has(segment));
130
+ }
122
131
  var projectSignatureCache = /* @__PURE__ */ new Map();
123
132
  function isPathWithin(parentDir, childPath) {
124
133
  const childRelativePath = relative(parentDir, childPath);
@@ -679,7 +688,7 @@ import { parseHTML as parseHTML2 } from "linkedom";
679
688
  // src/helpers/compositionInsertion.ts
680
689
  import { existsSync as existsSync3, readFileSync as readFileSync4, realpathSync } from "fs";
681
690
  import { randomUUID as randomUUID2 } from "crypto";
682
- import { dirname, relative as relative3, resolve as resolve2, sep } from "path";
691
+ import { dirname, relative as relative3, resolve as resolve2, sep as sep2 } from "path";
683
692
  import { parseHTML } from "linkedom";
684
693
  var CompositionInsertionError = class extends Error {
685
694
  constructor(message, status) {
@@ -807,7 +816,7 @@ function uniqueHostId(root, base) {
807
816
  return `${base}_${suffix}`;
808
817
  }
809
818
  function relativeSourcePath(targetAbs, sourceAbs) {
810
- return relative3(dirname(targetAbs), sourceAbs).split(sep).join("/");
819
+ return relative3(dirname(targetAbs), sourceAbs).split(sep2).join("/");
811
820
  }
812
821
  function insertCompositionIntoSource(input) {
813
822
  const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
@@ -3718,6 +3727,31 @@ ${runtimeTag}`;
3718
3727
  // src/routes/lint.ts
3719
3728
  import { readFileSync as readFileSync9 } from "fs";
3720
3729
  import { join as join9 } from "path";
3730
+ async function collectProjectFindings(adapter, projectDir) {
3731
+ const findings = [];
3732
+ const coveredFiles = /* @__PURE__ */ new Set();
3733
+ if (!adapter.lintProject) return { findings, coveredFiles };
3734
+ const result = await adapter.lintProject(projectDir);
3735
+ for (const entry of result.results) {
3736
+ coveredFiles.add(entry.file);
3737
+ for (const finding of entry.result.findings) {
3738
+ findings.push({ ...finding, file: entry.file });
3739
+ }
3740
+ }
3741
+ return { findings, coveredFiles };
3742
+ }
3743
+ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
3744
+ const findings = [];
3745
+ for (const file of htmlFiles) {
3746
+ if (coveredFiles.has(file)) continue;
3747
+ const content = readFileSync9(join9(projectDir, file), "utf-8");
3748
+ const result = await adapter.lint(content, { filePath: file });
3749
+ for (const finding of result?.findings ?? []) {
3750
+ findings.push({ ...finding, file });
3751
+ }
3752
+ }
3753
+ return findings;
3754
+ }
3721
3755
  function registerLintRoutes(api, adapter) {
3722
3756
  api.get("/projects/:id/lint", async (c) => {
3723
3757
  const project = await adapter.resolveProject(c.req.param("id"));
@@ -3726,17 +3760,14 @@ function registerLintRoutes(api, adapter) {
3726
3760
  const htmlFiles = walkDir(project.dir).filter(
3727
3761
  (f) => f.endsWith(".html") && !isInHiddenOrVendorDir(f)
3728
3762
  );
3729
- const allFindings = [];
3730
- for (const file of htmlFiles) {
3731
- const content = readFileSync9(join9(project.dir, file), "utf-8");
3732
- const result = await adapter.lint(content, { filePath: file });
3733
- if (result?.findings) {
3734
- for (const f of result.findings) {
3735
- allFindings.push({ ...f, file });
3736
- }
3737
- }
3738
- }
3739
- return c.json({ findings: allFindings });
3763
+ const projectLint = await collectProjectFindings(adapter, project.dir);
3764
+ const extraFindings = await lintUncoveredHtml(
3765
+ adapter,
3766
+ project.dir,
3767
+ htmlFiles,
3768
+ projectLint.coveredFiles
3769
+ );
3770
+ return c.json({ findings: [...projectLint.findings, ...extraFindings] });
3740
3771
  } catch (err) {
3741
3772
  const msg = err instanceof Error ? err.message : String(err);
3742
3773
  return c.json({ error: `Lint failed: ${msg}` }, 500);
@@ -4890,6 +4921,7 @@ export {
4890
4921
  MIME_TYPES,
4891
4922
  STUDIO_MANUAL_EDITS_PATH,
4892
4923
  STUDIO_MOTION_PATH,
4924
+ affectsProjectSignature,
4893
4925
  buildSubCompositionHtml,
4894
4926
  consumeFileWriteReceipt,
4895
4927
  createBackgroundRemovalJob,