@jami-studio/core 0.92.36 → 0.92.38

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.
@@ -7,41 +7,201 @@ import type {
7
7
  } from "../../shared/api.js";
8
8
  import { aspectRatioValue } from "./preset-skeleton.js";
9
9
 
10
+ /**
11
+ * True when the error means the sharp native module cannot run in this
12
+ * runtime (e.g. the Cloudflare Pages worker build stubs `sharp` with a
13
+ * throwing proxy, and serverless Linux builds can fail to load the native
14
+ * binary). Used to degrade gracefully instead of failing the whole upload.
15
+ */
16
+ function isSharpUnavailableError(error: unknown): boolean {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ return /sharp unavailable|Could not load the "?sharp"? module/i.test(
19
+ message,
20
+ );
21
+ }
22
+
23
+ /**
24
+ * Pure-JS dimension sniffing for the upload formats the app accepts.
25
+ * Fallback path for runtimes where sharp is unavailable (workerd); parses
26
+ * container headers only — no decoding.
27
+ */
28
+ export function sniffImageDimensions(data: Uint8Array): {
29
+ width: number | null;
30
+ height: number | null;
31
+ mimeType: string | null;
32
+ } {
33
+ const none = { width: null, height: null, mimeType: null };
34
+ if (data.length < 16) return none;
35
+ // PNG: IHDR width/height are big-endian uint32 at offsets 16/20.
36
+ if (
37
+ data[0] === 0x89 &&
38
+ data[1] === 0x50 &&
39
+ data[2] === 0x4e &&
40
+ data[3] === 0x47
41
+ ) {
42
+ if (data.length < 24) return { ...none, mimeType: "image/png" };
43
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
44
+ return {
45
+ width: view.getUint32(16),
46
+ height: view.getUint32(20),
47
+ mimeType: "image/png",
48
+ };
49
+ }
50
+ // JPEG: walk segment markers to the first SOF frame header.
51
+ if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
52
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
53
+ let offset = 2;
54
+ while (offset + 9 < data.length) {
55
+ if (data[offset] !== 0xff) {
56
+ offset += 1;
57
+ continue;
58
+ }
59
+ const marker = data[offset + 1];
60
+ // Standalone markers without length payloads.
61
+ if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) {
62
+ offset += 2;
63
+ continue;
64
+ }
65
+ const isSof =
66
+ marker >= 0xc0 &&
67
+ marker <= 0xcf &&
68
+ marker !== 0xc4 &&
69
+ marker !== 0xc8 &&
70
+ marker !== 0xcc;
71
+ if (isSof) {
72
+ return {
73
+ height: view.getUint16(offset + 5),
74
+ width: view.getUint16(offset + 7),
75
+ mimeType: "image/jpeg",
76
+ };
77
+ }
78
+ const length = view.getUint16(offset + 2);
79
+ if (length < 2) break;
80
+ offset += 2 + length;
81
+ }
82
+ return { ...none, mimeType: "image/jpeg" };
83
+ }
84
+ // WebP: RIFF container; VP8/VP8L/VP8X chunks carry dimensions.
85
+ if (
86
+ Buffer.from(data.subarray(0, 4)).toString("ascii") === "RIFF" &&
87
+ Buffer.from(data.subarray(8, 12)).toString("ascii") === "WEBP" &&
88
+ data.length >= 30
89
+ ) {
90
+ const chunk = Buffer.from(data.subarray(12, 16)).toString("ascii");
91
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
92
+ if (chunk === "VP8X") {
93
+ return {
94
+ width: 1 + (data[24] | (data[25] << 8) | (data[26] << 16)),
95
+ height: 1 + (data[27] | (data[28] << 8) | (data[29] << 16)),
96
+ mimeType: "image/webp",
97
+ };
98
+ }
99
+ if (chunk === "VP8L" && data[20] === 0x2f) {
100
+ const bits = view.getUint32(21, true);
101
+ return {
102
+ width: (bits & 0x3fff) + 1,
103
+ height: ((bits >> 14) & 0x3fff) + 1,
104
+ mimeType: "image/webp",
105
+ };
106
+ }
107
+ if (chunk === "VP8 ") {
108
+ // Lossy keyframe: 3-byte frame tag then 9d 01 2a start code.
109
+ if (data[23] === 0x9d && data[24] === 0x01 && data[25] === 0x2a) {
110
+ return {
111
+ width: view.getUint16(26, true) & 0x3fff,
112
+ height: view.getUint16(28, true) & 0x3fff,
113
+ mimeType: "image/webp",
114
+ };
115
+ }
116
+ return { ...none, mimeType: "image/webp" };
117
+ }
118
+ return { ...none, mimeType: "image/webp" };
119
+ }
120
+ // AVIF: scan for the `ispe` (image spatial extents) property box —
121
+ // version/flags(4) width(4) height(4) big-endian after the fourcc.
122
+ if (
123
+ Buffer.from(data.subarray(4, 12)).toString("ascii").includes("ftyp") &&
124
+ Buffer.from(data.subarray(8, 12)).toString("ascii") === "avif"
125
+ ) {
126
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
127
+ const limit = Math.min(data.length - 16, 65536);
128
+ for (let i = 12; i < limit; i++) {
129
+ if (
130
+ data[i] === 0x69 && // i
131
+ data[i + 1] === 0x73 && // s
132
+ data[i + 2] === 0x70 && // p
133
+ data[i + 3] === 0x65 // e
134
+ ) {
135
+ return {
136
+ width: view.getUint32(i + 8),
137
+ height: view.getUint32(i + 12),
138
+ mimeType: "image/avif",
139
+ };
140
+ }
141
+ }
142
+ return { ...none, mimeType: "image/avif" };
143
+ }
144
+ return none;
145
+ }
146
+
10
147
  export async function imageInfo(buffer: Buffer): Promise<{
11
148
  width: number | null;
12
149
  height: number | null;
13
150
  mimeType: string;
14
151
  sizeBytes: number;
15
152
  }> {
16
- const img = sharp(buffer, { failOn: "none" });
17
- const meta = await img.metadata();
18
- const format = meta.format === "jpeg" ? "jpeg" : meta.format || "png";
19
- return {
20
- width: meta.width ?? null,
21
- height: meta.height ?? null,
22
- mimeType:
23
- format === "jpg" || format === "jpeg" ? "image/jpeg" : `image/${format}`,
24
- sizeBytes: buffer.byteLength,
25
- };
153
+ try {
154
+ const img = sharp(buffer, { failOn: "none" });
155
+ const meta = await img.metadata();
156
+ const format = meta.format === "jpeg" ? "jpeg" : meta.format || "png";
157
+ return {
158
+ width: meta.width ?? null,
159
+ height: meta.height ?? null,
160
+ mimeType:
161
+ format === "jpg" || format === "jpeg"
162
+ ? "image/jpeg"
163
+ : `image/${format}`,
164
+ sizeBytes: buffer.byteLength,
165
+ };
166
+ } catch (error) {
167
+ if (!isSharpUnavailableError(error)) throw error;
168
+ // Passthrough-for-originals: header-parse dimensions so uploads keep
169
+ // working on runtimes without sharp. Callers fall back to the declared
170
+ // mime type when the sniffer returns null.
171
+ const sniffed = sniffImageDimensions(buffer);
172
+ return {
173
+ width: sniffed.width,
174
+ height: sniffed.height,
175
+ mimeType: sniffed.mimeType ?? "",
176
+ sizeBytes: buffer.byteLength,
177
+ };
178
+ }
26
179
  }
27
180
 
28
181
  export async function makeThumbnail(buffer: Buffer): Promise<{
29
182
  buffer: Buffer;
30
183
  mimeType: string;
31
- }> {
32
- return {
33
- buffer: await sharp(buffer, { failOn: "none" })
34
- .rotate()
35
- .resize({
36
- width: 640,
37
- height: 640,
38
- fit: "inside",
39
- withoutEnlargement: true,
40
- })
41
- .webp({ quality: 82 })
42
- .toBuffer(),
43
- mimeType: "image/webp",
44
- };
184
+ } | null> {
185
+ try {
186
+ return {
187
+ buffer: await sharp(buffer, { failOn: "none" })
188
+ .rotate()
189
+ .resize({
190
+ width: 640,
191
+ height: 640,
192
+ fit: "inside",
193
+ withoutEnlargement: true,
194
+ })
195
+ .webp({ quality: 82 })
196
+ .toBuffer(),
197
+ mimeType: "image/webp",
198
+ };
199
+ } catch (error) {
200
+ if (!isSharpUnavailableError(error)) throw error;
201
+ // No thumbnail on runtimes without sharp — readers already fall back to
202
+ // the original asset when `thumbnailObjectKey` is null.
203
+ return null;
204
+ }
45
205
  }
46
206
 
47
207
  export async function extractDominantColors(buffer: Buffer): Promise<string[]> {
@@ -239,7 +239,10 @@ export default function Root() {
239
239
  }),
240
240
  );
241
241
  const location = useLocation();
242
- const loaderData = useLoaderData<typeof loader>();
242
+ // Static-shell serverless deployments never run the root loader (the
243
+ // client manifest strips hasLoader), so loader data can be null at
244
+ // hydration — fall back exactly like Layout does.
245
+ const loaderData = useLoaderData<typeof loader>() ?? DEFAULT_LOADER_DATA;
243
246
  const isPublicPath = isPublicBookingPath(location.pathname);
244
247
 
245
248
  return (
@@ -379,7 +379,10 @@ function AppContent() {
379
379
  */
380
380
  export default function Root() {
381
381
  const location = useLocation();
382
- const loaderData = useLoaderData<typeof loader>();
382
+ // Static-shell serverless deployments never run the root loader (the
383
+ // client manifest strips hasLoader), so loader data can be null at
384
+ // hydration — fall back exactly like Layout does.
385
+ const loaderData = useLoaderData<typeof loader>() ?? DEFAULT_LOADER_DATA;
383
386
  const [queryClient] = useState(() => createAgentNativeQueryClient());
384
387
  return (
385
388
  <AppToolkitProvider>
@@ -547,7 +547,10 @@ export default function Root() {
547
547
  const [queryClient] = useState(() => createAgentNativeQueryClient());
548
548
  const [cmdkOpen, setCmdkOpen] = useState(false);
549
549
  const location = useLocation();
550
- const loaderData = useLoaderData<typeof loader>();
550
+ // Static-shell serverless deployments never run the root loader (the
551
+ // client manifest strips hasLoader), so loader data can be null at
552
+ // hydration — fall back exactly like Layout does.
553
+ const loaderData = useLoaderData<typeof loader>() ?? DEFAULT_LOADER_DATA;
551
554
  useCommandMenuShortcut(
552
555
  useCallback(() => setCmdkOpen(true), []),
553
556
  {
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":"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"}
@@ -1501,7 +1501,14 @@ export function generateCloudflarePagesStaticShellFromManifest(manifest, basePat
1501
1501
  const encodedInitialState = rootRoute.hasLoader
1502
1502
  ? DEFAULT_ROOT_LOADER_REACT_ROUTER_TURBO_STREAM
1503
1503
  : EMPTY_REACT_ROUTER_TURBO_STREAM;
1504
- return `<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1504
+ // Match the <html> attributes the root route renders during hydration with
1505
+ // the embedded default root-loader state (locale/dir), so recovering from
1506
+ // the fallback shell does not add an attribute mismatch on top of the
1507
+ // structural one.
1508
+ const htmlTag = rootRoute.hasLoader
1509
+ ? '<html lang="en-US" dir="ltr" data-locale="en-US">'
1510
+ : '<html lang="en">';
1511
+ return `<!DOCTYPE html>${htmlTag}<head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1505
1512
  }
1506
1513
  function writeCloudflarePagesStaticShell({ serverDir, distDir, tmpDir, }) {
1507
1514
  const serverEntry = path.join(serverDir, "index.js");
@@ -1513,32 +1520,118 @@ function writeCloudflarePagesStaticShell({ serverDir, distDir, tmpDir, }) {
1513
1520
  const basePath = normalizeConfiguredAppBasePath();
1514
1521
  fs.writeFileSync(renderScript, `
1515
1522
  import fs from "node:fs";
1516
- import { createRequire } from "node:module";
1523
+ import Module, { createRequire } from "node:module";
1517
1524
  import { pathToFileURL } from "node:url";
1518
1525
 
1519
1526
  const cwd = ${JSON.stringify(cwd)};
1520
1527
  const serverEntry = ${JSON.stringify(serverEntry)};
1521
1528
  const outFile = ${JSON.stringify(outFile)};
1522
1529
  const basePath = ${JSON.stringify(basePath)};
1530
+ // The framework package's own module URL: bundled server chunks can import
1531
+ // packages (yjs, prosemirror-*) that are transitive framework dependencies
1532
+ // and not resolvable from the app directory under strict pnpm layouts. When
1533
+ // app-relative resolution fails, retry from the framework's own context.
1534
+ const frameworkParentUrl = ${JSON.stringify(import.meta.url)};
1535
+
1536
+ if (typeof Module.registerHooks === "function") {
1537
+ Module.registerHooks({
1538
+ resolve(specifier, context, nextResolve) {
1539
+ try {
1540
+ return nextResolve(specifier, context);
1541
+ } catch (error) {
1542
+ if (context?.parentURL && context.parentURL !== frameworkParentUrl) {
1543
+ try {
1544
+ return nextResolve(specifier, { ...context, parentURL: frameworkParentUrl });
1545
+ } catch {}
1546
+ }
1547
+ throw error;
1548
+ }
1549
+ },
1550
+ });
1551
+ }
1523
1552
 
1524
1553
  const requireFromApp = createRequire(cwd + "/package.json");
1525
1554
  const reactRouterEntry = requireFromApp.resolve("react-router");
1526
1555
  const { createRequestHandler } = await import(pathToFileURL(reactRouterEntry).href);
1527
1556
  const serverBuild = await import(pathToFileURL(serverEntry).href);
1528
1557
  const handler = createRequestHandler(serverBuild, "production");
1529
- const pathname = basePath ? basePath + "/" : "/";
1530
- const response = await handler(
1531
- new Request(new URL(pathname, "https://agent-native.local"), {
1532
- headers: { "X-React-Router-SPA-Mode": "yes" },
1533
- }),
1534
- );
1535
- const html = await response.text();
1536
1558
 
1537
- if (!html || !html.includes("__reactRouterContext") || !html.includes("entry.client")) {
1538
- throw new Error("React Router did not render a usable Cloudflare Pages static shell");
1559
+ const origin = "https://agent-native.local";
1560
+ const normalizedBase = basePath ? basePath.replace(/\\/+$/, "") : "";
1561
+
1562
+ function isUsable(html) {
1563
+ return Boolean(
1564
+ html && html.includes("__reactRouterContext") && html.includes("entry.client"),
1565
+ );
1539
1566
  }
1540
1567
 
1541
- fs.writeFileSync(outFile, html);
1568
+ // Render a path, following same-origin redirects (index routes commonly
1569
+ // redirect "/" to the real landing page). Server builds compiled with an
1570
+ // unprefixed router basename ("/") emit redirect targets that still carry
1571
+ // the mount prefix — strip it before re-requesting.
1572
+ async function renderPath(startPath) {
1573
+ let pathname = startPath;
1574
+ let response;
1575
+ for (let hop = 0; hop < 5; hop++) {
1576
+ response = await handler(
1577
+ new Request(new URL(pathname, origin), {
1578
+ headers: { "X-React-Router-SPA-Mode": "yes" },
1579
+ }),
1580
+ );
1581
+ if (response.status < 300 || response.status >= 400) break;
1582
+ const location = response.headers.get("location");
1583
+ if (!location) break;
1584
+ const target = new URL(location, new URL(pathname, origin));
1585
+ if (target.origin !== origin) break;
1586
+ let nextPath = target.pathname;
1587
+ if (
1588
+ normalizedBase &&
1589
+ (nextPath === normalizedBase || nextPath.startsWith(normalizedBase + "/")) &&
1590
+ !(startPath === normalizedBase + "/" || startPath.startsWith(normalizedBase + "/"))
1591
+ ) {
1592
+ nextPath = nextPath.slice(normalizedBase.length) || "/";
1593
+ }
1594
+ if (nextPath === pathname) break;
1595
+ pathname = nextPath;
1596
+ }
1597
+ const html = await response.text();
1598
+ return { pathname, status: response.status, html };
1599
+ }
1600
+
1601
+ // Route matching varies per template: some builds register routes under the
1602
+ // mount prefix, others compile with basename "/" and rely on the worker
1603
+ // entry stripping the prefix. Try both shapes and prefer a 2xx render;
1604
+ // otherwise keep the first usable render of any status (apps without an
1605
+ // index route ship their root-boundary render as the shell).
1606
+ const candidates = normalizedBase ? [normalizedBase + "/", "/"] : ["/"];
1607
+ let best = null;
1608
+ for (const candidate of candidates) {
1609
+ let result;
1610
+ try {
1611
+ result = await renderPath(candidate);
1612
+ } catch (error) {
1613
+ console.warn("[deploy] Static shell render threw at " + candidate + ": " + (error && error.message ? error.message : error));
1614
+ continue;
1615
+ }
1616
+ if (!isUsable(result.html)) continue;
1617
+ if (result.status >= 200 && result.status < 300) {
1618
+ best = result;
1619
+ break;
1620
+ }
1621
+ if (!best) best = result;
1622
+ }
1623
+
1624
+ if (!best) {
1625
+ throw new Error(
1626
+ "React Router did not render a usable Cloudflare Pages static shell for any of: " +
1627
+ candidates.join(", "),
1628
+ );
1629
+ }
1630
+
1631
+ console.log(
1632
+ "[deploy] Static shell rendered from " + best.pathname + " (status " + best.status + ").",
1633
+ );
1634
+ fs.writeFileSync(outFile, best.html);
1542
1635
  process.exit(0);
1543
1636
  `);
1544
1637
  try {