@jami-studio/core 0.92.37 → 0.92.39
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/deploy/build.ts +106 -13
- package/corpus/core/src/server/core-routes-plugin.ts +14 -0
- package/corpus/core/src/vite/client.ts +8 -5
- package/corpus/templates/assets/changelog/2026-07-12-image-uploads-now-work-on-serverless-runtimes-without-native.md +6 -0
- package/corpus/templates/assets/server/lib/image-processing.ts +184 -24
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +105 -12
- package/dist/deploy/build.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +10 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -5
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.92.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9390f7c: Edge runtimes mount an empty-list `/_agent-native/available-clis` fallback in the core-routes plugin: the real endpoint lives in the Node-only terminal plugin which never ships to workerd, so every page load logged a console 404 from the agent panel's CLI feature probe. Node runtimes keep the terminal plugin's real handler.
|
|
8
|
+
|
|
9
|
+
## 0.92.38
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7e2841f: Cloudflare Pages static shell: render the real landing page instead of the hydration-mismatching manifest fallback — resolve framework-transitive packages (yjs, prosemirror) via module resolution hooks, follow same-origin index redirects with mount-prefix stripping, try both prefixed and unprefixed route shapes, and make the SSR heavy-lib stub callable/constructible so module-scope `new PluginKey(...)` in stubbed client code cannot crash the shell render. Manifest-fallback shells now emit `<html lang="en-US" dir="ltr" data-locale="en-US">` when the root route has a loader, matching the default hydration state. Fixes React #418 hydration errors on served workspace apps.
|
|
14
|
+
|
|
3
15
|
## 0.92.37
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.39",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/studio-jami/jami-studio#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -1759,8 +1759,15 @@ export function generateCloudflarePagesStaticShellFromManifest(
|
|
|
1759
1759
|
const encodedInitialState = rootRoute.hasLoader
|
|
1760
1760
|
? DEFAULT_ROOT_LOADER_REACT_ROUTER_TURBO_STREAM
|
|
1761
1761
|
: EMPTY_REACT_ROUTER_TURBO_STREAM;
|
|
1762
|
-
|
|
1763
|
-
|
|
1762
|
+
// Match the <html> attributes the root route renders during hydration with
|
|
1763
|
+
// the embedded default root-loader state (locale/dir), so recovering from
|
|
1764
|
+
// the fallback shell does not add an attribute mismatch on top of the
|
|
1765
|
+
// structural one.
|
|
1766
|
+
const htmlTag = rootRoute.hasLoader
|
|
1767
|
+
? '<html lang="en-US" dir="ltr" data-locale="en-US">'
|
|
1768
|
+
: '<html lang="en">';
|
|
1769
|
+
|
|
1770
|
+
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>`;
|
|
1764
1771
|
}
|
|
1765
1772
|
|
|
1766
1773
|
function writeCloudflarePagesStaticShell({
|
|
@@ -1784,32 +1791,118 @@ function writeCloudflarePagesStaticShell({
|
|
|
1784
1791
|
renderScript,
|
|
1785
1792
|
`
|
|
1786
1793
|
import fs from "node:fs";
|
|
1787
|
-
import { createRequire } from "node:module";
|
|
1794
|
+
import Module, { createRequire } from "node:module";
|
|
1788
1795
|
import { pathToFileURL } from "node:url";
|
|
1789
1796
|
|
|
1790
1797
|
const cwd = ${JSON.stringify(cwd)};
|
|
1791
1798
|
const serverEntry = ${JSON.stringify(serverEntry)};
|
|
1792
1799
|
const outFile = ${JSON.stringify(outFile)};
|
|
1793
1800
|
const basePath = ${JSON.stringify(basePath)};
|
|
1801
|
+
// The framework package's own module URL: bundled server chunks can import
|
|
1802
|
+
// packages (yjs, prosemirror-*) that are transitive framework dependencies
|
|
1803
|
+
// and not resolvable from the app directory under strict pnpm layouts. When
|
|
1804
|
+
// app-relative resolution fails, retry from the framework's own context.
|
|
1805
|
+
const frameworkParentUrl = ${JSON.stringify(import.meta.url)};
|
|
1806
|
+
|
|
1807
|
+
if (typeof Module.registerHooks === "function") {
|
|
1808
|
+
Module.registerHooks({
|
|
1809
|
+
resolve(specifier, context, nextResolve) {
|
|
1810
|
+
try {
|
|
1811
|
+
return nextResolve(specifier, context);
|
|
1812
|
+
} catch (error) {
|
|
1813
|
+
if (context?.parentURL && context.parentURL !== frameworkParentUrl) {
|
|
1814
|
+
try {
|
|
1815
|
+
return nextResolve(specifier, { ...context, parentURL: frameworkParentUrl });
|
|
1816
|
+
} catch {}
|
|
1817
|
+
}
|
|
1818
|
+
throw error;
|
|
1819
|
+
}
|
|
1820
|
+
},
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1794
1823
|
|
|
1795
1824
|
const requireFromApp = createRequire(cwd + "/package.json");
|
|
1796
1825
|
const reactRouterEntry = requireFromApp.resolve("react-router");
|
|
1797
1826
|
const { createRequestHandler } = await import(pathToFileURL(reactRouterEntry).href);
|
|
1798
1827
|
const serverBuild = await import(pathToFileURL(serverEntry).href);
|
|
1799
1828
|
const handler = createRequestHandler(serverBuild, "production");
|
|
1800
|
-
const pathname = basePath ? basePath + "/" : "/";
|
|
1801
|
-
const response = await handler(
|
|
1802
|
-
new Request(new URL(pathname, "https://agent-native.local"), {
|
|
1803
|
-
headers: { "X-React-Router-SPA-Mode": "yes" },
|
|
1804
|
-
}),
|
|
1805
|
-
);
|
|
1806
|
-
const html = await response.text();
|
|
1807
1829
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1830
|
+
const origin = "https://agent-native.local";
|
|
1831
|
+
const normalizedBase = basePath ? basePath.replace(/\\/+$/, "") : "";
|
|
1832
|
+
|
|
1833
|
+
function isUsable(html) {
|
|
1834
|
+
return Boolean(
|
|
1835
|
+
html && html.includes("__reactRouterContext") && html.includes("entry.client"),
|
|
1836
|
+
);
|
|
1810
1837
|
}
|
|
1811
1838
|
|
|
1812
|
-
|
|
1839
|
+
// Render a path, following same-origin redirects (index routes commonly
|
|
1840
|
+
// redirect "/" to the real landing page). Server builds compiled with an
|
|
1841
|
+
// unprefixed router basename ("/") emit redirect targets that still carry
|
|
1842
|
+
// the mount prefix — strip it before re-requesting.
|
|
1843
|
+
async function renderPath(startPath) {
|
|
1844
|
+
let pathname = startPath;
|
|
1845
|
+
let response;
|
|
1846
|
+
for (let hop = 0; hop < 5; hop++) {
|
|
1847
|
+
response = await handler(
|
|
1848
|
+
new Request(new URL(pathname, origin), {
|
|
1849
|
+
headers: { "X-React-Router-SPA-Mode": "yes" },
|
|
1850
|
+
}),
|
|
1851
|
+
);
|
|
1852
|
+
if (response.status < 300 || response.status >= 400) break;
|
|
1853
|
+
const location = response.headers.get("location");
|
|
1854
|
+
if (!location) break;
|
|
1855
|
+
const target = new URL(location, new URL(pathname, origin));
|
|
1856
|
+
if (target.origin !== origin) break;
|
|
1857
|
+
let nextPath = target.pathname;
|
|
1858
|
+
if (
|
|
1859
|
+
normalizedBase &&
|
|
1860
|
+
(nextPath === normalizedBase || nextPath.startsWith(normalizedBase + "/")) &&
|
|
1861
|
+
!(startPath === normalizedBase + "/" || startPath.startsWith(normalizedBase + "/"))
|
|
1862
|
+
) {
|
|
1863
|
+
nextPath = nextPath.slice(normalizedBase.length) || "/";
|
|
1864
|
+
}
|
|
1865
|
+
if (nextPath === pathname) break;
|
|
1866
|
+
pathname = nextPath;
|
|
1867
|
+
}
|
|
1868
|
+
const html = await response.text();
|
|
1869
|
+
return { pathname, status: response.status, html };
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// Route matching varies per template: some builds register routes under the
|
|
1873
|
+
// mount prefix, others compile with basename "/" and rely on the worker
|
|
1874
|
+
// entry stripping the prefix. Try both shapes and prefer a 2xx render;
|
|
1875
|
+
// otherwise keep the first usable render of any status (apps without an
|
|
1876
|
+
// index route ship their root-boundary render as the shell).
|
|
1877
|
+
const candidates = normalizedBase ? [normalizedBase + "/", "/"] : ["/"];
|
|
1878
|
+
let best = null;
|
|
1879
|
+
for (const candidate of candidates) {
|
|
1880
|
+
let result;
|
|
1881
|
+
try {
|
|
1882
|
+
result = await renderPath(candidate);
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
console.warn("[deploy] Static shell render threw at " + candidate + ": " + (error && error.message ? error.message : error));
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
if (!isUsable(result.html)) continue;
|
|
1888
|
+
if (result.status >= 200 && result.status < 300) {
|
|
1889
|
+
best = result;
|
|
1890
|
+
break;
|
|
1891
|
+
}
|
|
1892
|
+
if (!best) best = result;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if (!best) {
|
|
1896
|
+
throw new Error(
|
|
1897
|
+
"React Router did not render a usable Cloudflare Pages static shell for any of: " +
|
|
1898
|
+
candidates.join(", "),
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
console.log(
|
|
1903
|
+
"[deploy] Static shell rendered from " + best.pathname + " (status " + best.status + ").",
|
|
1904
|
+
);
|
|
1905
|
+
fs.writeFileSync(outFile, best.html);
|
|
1813
1906
|
process.exit(0);
|
|
1814
1907
|
`,
|
|
1815
1908
|
);
|
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
MCP_EMBED_CORS_ALLOW_HEADERS,
|
|
94
94
|
shouldAllowMcpEmbedCredentials,
|
|
95
95
|
} from "../shared/mcp-embed-headers.js";
|
|
96
|
+
import { isNodeRuntime } from "../shared/runtime.js";
|
|
96
97
|
import { track } from "../tracking/index.js";
|
|
97
98
|
import { registerBuiltinProviders } from "../tracking/providers.js";
|
|
98
99
|
import { validateTrackPayload } from "../tracking/route.js";
|
|
@@ -1233,6 +1234,19 @@ export function createCoreRoutesPlugin(
|
|
|
1233
1234
|
);
|
|
1234
1235
|
}
|
|
1235
1236
|
|
|
1237
|
+
// Available CLIs — the real endpoint lives in the terminal plugin,
|
|
1238
|
+
// which is Node-only and never ships to edge runtimes. Mount an
|
|
1239
|
+
// empty-list fallback there so the agent panel's feature probe gets a
|
|
1240
|
+
// clean 200 instead of console-noise 404s (issue: optional-endpoint
|
|
1241
|
+
// graceful feature detection). Node runtimes keep the terminal
|
|
1242
|
+
// plugin's real handler.
|
|
1243
|
+
if (!isNodeRuntime()) {
|
|
1244
|
+
getH3App(nitroApp).use(
|
|
1245
|
+
`${P}/available-clis`,
|
|
1246
|
+
defineEventHandler(() => []),
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1236
1250
|
// ─── Durable sandbox execution processor ─────────────────────────
|
|
1237
1251
|
// Self-fired by run-code's background queue (see
|
|
1238
1252
|
// coding-tools/sandbox/background.ts): the enqueueing request POSTs here
|
|
@@ -1923,15 +1923,18 @@ function ssrStubPlugin(packages: string[]): Plugin | null {
|
|
|
1923
1923
|
load(id) {
|
|
1924
1924
|
if (id !== STUB_ID) return null;
|
|
1925
1925
|
// Proxy that answers any property access with itself — lets dead
|
|
1926
|
-
// import/re-export chains parse without blowing up
|
|
1927
|
-
//
|
|
1926
|
+
// import/re-export chains parse without blowing up. Callable and
|
|
1927
|
+
// constructible (module-scope `new PluginKey(...)` in stubbed client
|
|
1928
|
+
// code must not crash server-side shell renders); every call returns
|
|
1929
|
+
// another stub.
|
|
1928
1930
|
return (
|
|
1929
1931
|
"const handler = { get(_, p) { " +
|
|
1930
1932
|
"if (p === Symbol.toPrimitive) return () => ''; " +
|
|
1931
1933
|
"if (p === 'then') return undefined; " +
|
|
1932
|
-
"return new Proxy(()
|
|
1933
|
-
"} };" +
|
|
1934
|
-
"
|
|
1934
|
+
"return new Proxy(function () {}, handler); " +
|
|
1935
|
+
"}, apply() { return new Proxy(function () {}, handler); }, " +
|
|
1936
|
+
"construct() { return new Proxy(function () {}, handler); } };" +
|
|
1937
|
+
"const stub = new Proxy(function () {}, handler);" +
|
|
1935
1938
|
"export default stub;" +
|
|
1936
1939
|
namedExports.map((name) => `export const ${name} = stub;`).join("")
|
|
1937
1940
|
);
|
|
@@ -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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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[]> {
|
|
@@ -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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"}
|
package/dist/deploy/build.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1538
|
-
|
|
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
|
-
|
|
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 {
|