rwsdk 0.1.5 → 0.1.6-test.20250702132719
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/runtime/client.js +6 -1
- package/dist/runtime/clientNavigation.d.ts +1 -0
- package/dist/runtime/clientNavigation.js +34 -24
- package/dist/runtime/clientNavigation.test.js +55 -0
- package/dist/runtime/lib/realtime/client.js +6 -0
- package/dist/runtime/lib/realtime/durableObject.js +8 -3
- package/dist/runtime/lib/realtime/worker.js +1 -3
- package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +2 -1
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +22 -1
- package/dist/runtime/render/renderToStream.js +2 -1
- package/dist/runtime/render/transformRscToHtmlStream.d.ts +2 -1
- package/dist/runtime/render/transformRscToHtmlStream.js +2 -1
- package/dist/runtime/worker.js +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +2 -1
- package/package.json +1 -1
- package/dist/lib/smokeTests/components.d.mts +0 -8
- package/dist/lib/smokeTests/components.mjs +0 -194
- package/dist/lib/smokeTests/templates/SmokeTestInfo.template.d.ts +0 -1
- package/dist/lib/smokeTests/templates/SmokeTestInfo.template.js +0 -82
- package/dist/runtime/components/HealthCheck.d.ts +0 -13
- package/dist/runtime/components/HealthCheck.js +0 -56
- package/dist/runtime/components/HealthCheckClient.d.ts +0 -2
- package/dist/runtime/components/HealthCheckClient.js +0 -78
- package/dist/runtime/imports/NoSSRStub.d.ts +0 -1
- package/dist/runtime/imports/NoSSRStub.js +0 -4
- package/dist/runtime/lib/db/create.d.ts +0 -3
- package/dist/runtime/lib/db/create.js +0 -36
- package/dist/runtime/lib/db/logger.d.ts +0 -2
- package/dist/runtime/lib/db/logger.js +0 -41
- package/dist/runtime/lib/db/types.d.ts +0 -0
- package/dist/runtime/lib/db/types.js +0 -1
- package/dist/runtime/render/__rwsdk_ssr_bridge.d.ts +0 -10
- package/dist/runtime/render/__rwsdk_ssr_bridge.js +0 -9
- package/dist/runtime/render/__rwsdkssr_render.d.ts +0 -9
- package/dist/runtime/render/__rwsdkssr_render.js +0 -13
- package/dist/runtime/render/injectRSCPayload.d.ts +0 -3
- package/dist/runtime/render/injectRSCPayload.js +0 -79
- package/dist/runtime/render/ssrBridge.d.ts +0 -2
- package/dist/runtime/render/ssrBridge.js +0 -2
- package/dist/runtime/render/ssrRenderToReadableStream.d.ts +0 -2
- package/dist/runtime/render/ssrRenderToReadableStream.js +0 -2
- package/dist/runtime/requestInfo/__rwsdknossr_worker.d.ts +0 -5
- package/dist/runtime/requestInfo/__rwsdknossr_worker.js +0 -33
- package/dist/scripts/build-vendor-bundles.d.mts +0 -1
- package/dist/scripts/build-vendor-bundles.mjs +0 -92
- package/dist/vite/aliasedModuleResolver.d.mts +0 -9
- package/dist/vite/aliasedModuleResolver.mjs +0 -62
- package/dist/vite/aliasedSSRResolver.d.mts +0 -5
- package/dist/vite/aliasedSSRResolver.mjs +0 -74
- package/dist/vite/copyPrismaWasmPlugin.d.mts +0 -4
- package/dist/vite/copyPrismaWasmPlugin.mjs +0 -32
- package/dist/vite/ensureConfigArrays.d.mts +0 -1
- package/dist/vite/ensureConfigArrays.mjs +0 -12
- package/dist/vite/findImportSpecifiers.d.mts +0 -30
- package/dist/vite/findImportSpecifiers.mjs +0 -228
- package/dist/vite/findImportSpecifiers.test.mjs +0 -73
- package/dist/vite/isBareImport.d.mts +0 -1
- package/dist/vite/isBareImport.mjs +0 -5
- package/dist/vite/miniflarePlugin.d.mts +0 -9
- package/dist/vite/miniflarePlugin.mjs +0 -135
- package/dist/vite/moduleResolver.d.mts +0 -10
- package/dist/vite/moduleResolver.mjs +0 -74
- package/dist/vite/resolveModuleId.d.mts +0 -6
- package/dist/vite/resolveModuleId.mjs +0 -14
- package/dist/vite/rscDirectivesPlugin.d.mts +0 -6
- package/dist/vite/rscDirectivesPlugin.mjs +0 -80
- package/dist/vite/transformServerReferences.d.mts +0 -11
- package/dist/vite/transformServerReferences.mjs +0 -74
- package/dist/vite/useClientPlugin.d.mts +0 -8
- package/dist/vite/useClientPlugin.mjs +0 -299
- package/dist/vite/useClientPlugin.test.d.mts +0 -1
- package/dist/vite/useClientPlugin.test.mjs +0 -1294
- package/dist/vite/useServerPlugin.test.d.mts +0 -1
- package/dist/vite/useServerPlugin.test.mjs +0 -99
- package/dist/vite/virtualizedSSRPlugin.d.mts +0 -56
- package/dist/vite/virtualizedSSRPlugin.mjs +0 -464
- package/dist/vite/wasmPlugin.d.mts +0 -2
- package/dist/vite/wasmPlugin.mjs +0 -14
- /package/dist/{vite/findImportSpecifiers.test.d.mts → runtime/clientNavigation.test.d.ts} +0 -0
package/dist/runtime/client.js
CHANGED
|
@@ -59,7 +59,12 @@ export const initClient = async ({ transport = fetchTransport, hydrateRootOption
|
|
|
59
59
|
transportContext.setRscPayload = (v) => startTransition(() => setStreamData(v));
|
|
60
60
|
return _jsx(_Fragment, { children: React.use(streamData).node });
|
|
61
61
|
}
|
|
62
|
-
hydrateRoot(rootEl, _jsx(Content, {}),
|
|
62
|
+
hydrateRoot(rootEl, _jsx(Content, {}), {
|
|
63
|
+
onUncaughtError: (error, { componentStack }) => {
|
|
64
|
+
console.error("Uncaught error: %O\n\nComponent stack:%s", error, componentStack);
|
|
65
|
+
},
|
|
66
|
+
...hydrateRootOptions,
|
|
67
|
+
});
|
|
63
68
|
if (import.meta.hot) {
|
|
64
69
|
import.meta.hot.on("rsc:update", (e) => {
|
|
65
70
|
console.log("[rwsdk] hot update", e.file);
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
export function validateClickEvent(event, target) {
|
|
2
|
+
// should this only work for left click?
|
|
3
|
+
if (event.button !== 0) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const link = target.closest("a");
|
|
10
|
+
if (!link) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const href = link.getAttribute("href");
|
|
14
|
+
if (!href) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// Skip if target="_blank" or similar
|
|
18
|
+
if (link.target && link.target !== "_self") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (href.startsWith("http")) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// Skip if download attribute
|
|
25
|
+
if (link.hasAttribute("download")) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
1
30
|
export function initClientNavigation(opts = {
|
|
2
31
|
onNavigate: async function onNavigate() {
|
|
3
32
|
// @ts-expect-error
|
|
@@ -6,33 +35,14 @@ export function initClientNavigation(opts = {
|
|
|
6
35
|
}) {
|
|
7
36
|
// Intercept all anchor tag clicks
|
|
8
37
|
document.addEventListener("click", async function handleClickEvent(event) {
|
|
9
|
-
//
|
|
10
|
-
if (event.
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const target = event.target;
|
|
17
|
-
const link = target.closest("a");
|
|
18
|
-
if (!link) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const href = link.getAttribute("href");
|
|
22
|
-
if (!href) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
// Skip if target="_blank" or similar
|
|
26
|
-
if (link.target && link.target !== "_self") {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// Skip if download attribute
|
|
30
|
-
if (link.hasAttribute("download")) {
|
|
38
|
+
// Prevent default navigation
|
|
39
|
+
if (!validateClickEvent(event, event.target)) {
|
|
31
40
|
return;
|
|
32
41
|
}
|
|
33
|
-
// Prevent default navigation
|
|
34
42
|
event.preventDefault();
|
|
35
|
-
|
|
43
|
+
const el = event.target;
|
|
44
|
+
const a = el.closest("a");
|
|
45
|
+
const href = a?.getAttribute("href");
|
|
36
46
|
window.history.pushState({ path: href }, "", window.location.origin + href);
|
|
37
47
|
await opts.onNavigate();
|
|
38
48
|
}, true);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { validateClickEvent } from "./clientNavigation";
|
|
3
|
+
describe("clientNavigation", () => {
|
|
4
|
+
let mockEvent = {
|
|
5
|
+
button: 0, // right click
|
|
6
|
+
metaKey: false,
|
|
7
|
+
altKey: false,
|
|
8
|
+
shiftKey: false,
|
|
9
|
+
ctrlKey: false,
|
|
10
|
+
};
|
|
11
|
+
let mockTarget = {
|
|
12
|
+
closest: () => {
|
|
13
|
+
return {
|
|
14
|
+
getAttribute: () => "/test",
|
|
15
|
+
hasAttribute: () => false,
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
it("should return true", () => {
|
|
20
|
+
expect(validateClickEvent(mockEvent, mockTarget)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it("should return false if the event is not a left click", () => {
|
|
23
|
+
expect(validateClickEvent({ ...mockEvent, button: 1 }, mockTarget)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it("none of the modifier keys are pressed", () => {
|
|
26
|
+
expect(validateClickEvent({ ...mockEvent, metaKey: true }, mockTarget)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it("the target is not an anchor tag", () => {
|
|
29
|
+
expect(validateClickEvent(mockEvent, {
|
|
30
|
+
closest: () => undefined,
|
|
31
|
+
})).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it("should have an href attribute", () => {
|
|
34
|
+
expect(validateClickEvent(mockEvent, {
|
|
35
|
+
closest: () => ({ getAttribute: () => undefined }),
|
|
36
|
+
})).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it("should not have a target attribute", () => {
|
|
39
|
+
expect(validateClickEvent(mockEvent, {
|
|
40
|
+
closest: () => ({
|
|
41
|
+
target: "_blank",
|
|
42
|
+
getAttribute: () => "/test",
|
|
43
|
+
hasAttribute: () => false,
|
|
44
|
+
}),
|
|
45
|
+
})).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
it("should be a relative link", () => {
|
|
48
|
+
expect(validateClickEvent(mockEvent, {
|
|
49
|
+
closest: () => ({
|
|
50
|
+
getAttribute: () => "/test",
|
|
51
|
+
hasAttribute: () => false,
|
|
52
|
+
}),
|
|
53
|
+
})).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -82,12 +82,18 @@ export const realtimeTransport = ({ key = DEFAULT_KEY }) => (transportContext) =
|
|
|
82
82
|
try {
|
|
83
83
|
const socket = ensureWs();
|
|
84
84
|
const { encodeReply } = await import("react-server-dom-webpack/client.browser");
|
|
85
|
+
// Note(peterp, 2025-07-02): We need to send the "current URL" per message,
|
|
86
|
+
// in case the user has enabled client side navigation.
|
|
87
|
+
const clientUrl = new URL(window.location.href);
|
|
88
|
+
clientUrl.protocol = "";
|
|
89
|
+
clientUrl.host = "";
|
|
85
90
|
const encodedArgs = args != null ? await encodeReply(args) : null;
|
|
86
91
|
const requestId = crypto.randomUUID();
|
|
87
92
|
const messageData = JSON.stringify({
|
|
88
93
|
id,
|
|
89
94
|
args: encodedArgs,
|
|
90
95
|
requestId,
|
|
96
|
+
clientUrl,
|
|
91
97
|
});
|
|
92
98
|
const encoder = new TextEncoder();
|
|
93
99
|
const messageBytes = encoder.encode(messageData);
|
|
@@ -56,9 +56,9 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
56
56
|
if (messageType === MESSAGE_TYPE.ACTION_REQUEST) {
|
|
57
57
|
const decoder = new TextDecoder();
|
|
58
58
|
const jsonData = decoder.decode(message.slice(1));
|
|
59
|
-
const { id, args, requestId } = JSON.parse(jsonData);
|
|
59
|
+
const { id, args, requestId, clientUrl } = JSON.parse(jsonData);
|
|
60
60
|
try {
|
|
61
|
-
await this.handleAction(ws, id, args, clientInfo, requestId);
|
|
61
|
+
await this.handleAction(ws, id, args, clientInfo, requestId, clientUrl);
|
|
62
62
|
}
|
|
63
63
|
catch (error) {
|
|
64
64
|
const encoder = new TextEncoder();
|
|
@@ -100,10 +100,15 @@ export class RealtimeDurableObject extends DurableObject {
|
|
|
100
100
|
reader.releaseLock();
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
async handleAction(ws, id, args, clientInfo, requestId) {
|
|
103
|
+
async handleAction(ws, id, args, clientInfo, requestId, clientUrl) {
|
|
104
104
|
const url = new URL(clientInfo.url);
|
|
105
105
|
url.searchParams.set("__rsc", "true");
|
|
106
106
|
url.searchParams.set("__rsc_action_id", id);
|
|
107
|
+
//const url = new URL(clientUrl);
|
|
108
|
+
//url.searchParams.set("__rsc", "");
|
|
109
|
+
//if (id != null) {
|
|
110
|
+
// url.searchParams.set("__rsc_action_id", id);
|
|
111
|
+
//}
|
|
107
112
|
const response = await fetch(url.toString(), {
|
|
108
113
|
method: "POST",
|
|
109
114
|
body: args,
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { route } from "../../entries/router";
|
|
2
2
|
import { validateUpgradeRequest } from "./validateUpgradeRequest";
|
|
3
3
|
import { DEFAULT_REALTIME_KEY } from "./constants";
|
|
4
|
-
import { requestInfo } from "../../requestInfo/worker";
|
|
5
4
|
import { env } from "cloudflare:workers";
|
|
6
5
|
export { renderRealtimeClients } from "./renderRealtimeClients";
|
|
7
|
-
export const realtimeRoute = (getDurableObjectNamespace) => route("/__realtime", async function () {
|
|
8
|
-
const { request } = requestInfo;
|
|
6
|
+
export const realtimeRoute = (getDurableObjectNamespace) => route("/__realtime", async function ({ request }) {
|
|
9
7
|
const validation = validateUpgradeRequest(request);
|
|
10
8
|
if (!validation.valid) {
|
|
11
9
|
return validation.response;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type DocumentProps } from "../lib/router";
|
|
2
2
|
import { type RequestInfo } from "../requestInfo/types";
|
|
3
|
-
export declare const renderRscThenableToHtmlStream: ({ thenable, Document, requestInfo, shouldSSR, }: {
|
|
3
|
+
export declare const renderRscThenableToHtmlStream: ({ thenable, Document, requestInfo, shouldSSR, onError, }: {
|
|
4
4
|
thenable: any;
|
|
5
5
|
Document: React.FC<DocumentProps>;
|
|
6
6
|
requestInfo: RequestInfo;
|
|
7
7
|
shouldSSR: boolean;
|
|
8
|
+
onError: (error: unknown) => void;
|
|
8
9
|
}) => Promise<import("react-dom/server").ReactDOMServerReadableStream>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { use } from "react";
|
|
3
3
|
import { renderToReadableStream } from "react-dom/server.edge";
|
|
4
|
-
export const renderRscThenableToHtmlStream = async ({ thenable, Document, requestInfo, shouldSSR, }) => {
|
|
4
|
+
export const renderRscThenableToHtmlStream = async ({ thenable, Document, requestInfo, shouldSSR, onError, }) => {
|
|
5
5
|
const Component = () => {
|
|
6
6
|
const node = use(thenable).node;
|
|
7
7
|
// todo(justinvdm, 18 Jun 2025): We can build on this later to allow users
|
|
@@ -21,5 +21,26 @@ export const renderRscThenableToHtmlStream = async ({ thenable, Document, reques
|
|
|
21
21
|
};
|
|
22
22
|
return await renderToReadableStream(_jsx(Component, {}), {
|
|
23
23
|
nonce: requestInfo.rw.nonce,
|
|
24
|
+
onError(error, { componentStack }) {
|
|
25
|
+
try {
|
|
26
|
+
const message = error
|
|
27
|
+
? (error.stack ?? error.message ?? error)
|
|
28
|
+
: error;
|
|
29
|
+
const wrappedMessage = `Error rendering RSC to HTML stream: ${message}\n\nComponent stack:\n${componentStack}`;
|
|
30
|
+
if (error instanceof Error) {
|
|
31
|
+
const wrappedError = new Error(wrappedMessage);
|
|
32
|
+
wrappedError.stack = error.stack;
|
|
33
|
+
error = wrappedError;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
error = new Error(wrappedMessage);
|
|
37
|
+
error.stack = componentStack;
|
|
38
|
+
}
|
|
39
|
+
onError(error);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
onError(error);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
24
45
|
});
|
|
25
46
|
};
|
|
@@ -4,7 +4,7 @@ import { transformRscToHtmlStream } from "./transformRscToHtmlStream";
|
|
|
4
4
|
import { requestInfo } from "../requestInfo/worker";
|
|
5
5
|
import { injectRSCPayload } from "rsc-html-stream/server";
|
|
6
6
|
export const IdentityDocument = ({ children }) => (_jsx(_Fragment, { children: children }));
|
|
7
|
-
export const renderToStream = async (element, { Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = false, onError, } = {}) => {
|
|
7
|
+
export const renderToStream = async (element, { Document = IdentityDocument, injectRSCPayload: shouldInjectRSCPayload = false, onError = () => { }, } = {}) => {
|
|
8
8
|
let rscStream = renderToRscStream({
|
|
9
9
|
node: element,
|
|
10
10
|
actionResult: null,
|
|
@@ -21,6 +21,7 @@ export const renderToStream = async (element, { Document = IdentityDocument, inj
|
|
|
21
21
|
stream: rscStream,
|
|
22
22
|
Document,
|
|
23
23
|
requestInfo,
|
|
24
|
+
onError,
|
|
24
25
|
});
|
|
25
26
|
return htmlStream;
|
|
26
27
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { DocumentProps } from "../lib/router";
|
|
2
2
|
import { RequestInfo } from "../requestInfo/types";
|
|
3
|
-
export declare const transformRscToHtmlStream: ({ stream, Document, requestInfo, }: {
|
|
3
|
+
export declare const transformRscToHtmlStream: ({ stream, Document, requestInfo, onError, }: {
|
|
4
4
|
stream: ReadableStream;
|
|
5
5
|
Document: React.FC<DocumentProps>;
|
|
6
6
|
requestInfo: RequestInfo;
|
|
7
|
+
onError: (error: unknown) => void;
|
|
7
8
|
}) => Promise<import("react-dom/server.js").ReactDOMServerReadableStream>;
|
|
@@ -2,7 +2,7 @@ import { createModuleMap } from "./createModuleMap.js";
|
|
|
2
2
|
import ReactServerDom from "react-server-dom-webpack/client.edge";
|
|
3
3
|
import { renderRscThenableToHtmlStream } from "rwsdk/__ssr_bridge";
|
|
4
4
|
const { createFromReadableStream } = ReactServerDom;
|
|
5
|
-
export const transformRscToHtmlStream = ({ stream, Document, requestInfo, }) => {
|
|
5
|
+
export const transformRscToHtmlStream = ({ stream, Document, requestInfo, onError, }) => {
|
|
6
6
|
const thenable = createFromReadableStream(stream, {
|
|
7
7
|
serverConsumerManifest: {
|
|
8
8
|
moduleMap: createModuleMap(),
|
|
@@ -14,5 +14,6 @@ export const transformRscToHtmlStream = ({ stream, Document, requestInfo, }) =>
|
|
|
14
14
|
Document,
|
|
15
15
|
requestInfo,
|
|
16
16
|
shouldSSR: requestInfo.rw.ssr,
|
|
17
|
+
onError,
|
|
17
18
|
});
|
|
18
19
|
};
|
package/dist/runtime/worker.js
CHANGED
|
@@ -182,7 +182,8 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
|
|
|
182
182
|
resolveId(source) {
|
|
183
183
|
verboseLog("Resolving id=%s", source);
|
|
184
184
|
if (source === config.virtualModuleName ||
|
|
185
|
-
source === `/@id/${config.virtualModuleName}`
|
|
185
|
+
source === `/@id/${config.virtualModuleName}` ||
|
|
186
|
+
source === `/@id/${config.virtualModuleName}.js`) {
|
|
186
187
|
log("Resolving %s module", config.virtualModuleName);
|
|
187
188
|
// context(justinvdm, 16 Jun 2025): Include .js extension
|
|
188
189
|
// so it goes through vite processing chain
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creates the smoke test components in the target project directory
|
|
3
|
-
*/
|
|
4
|
-
export declare function createSmokeTestComponents(targetDir: string, skipClient?: boolean): Promise<void>;
|
|
5
|
-
/**
|
|
6
|
-
* Modifies the worker.tsx and wrangler.jsonc files to add realtime support
|
|
7
|
-
*/
|
|
8
|
-
export declare function modifyAppForRealtime(targetDir: string): Promise<void>;
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import * as fs from "fs/promises";
|
|
3
|
-
import { log } from "./constants.mjs";
|
|
4
|
-
import { getSmokeTestFunctionsTemplate } from "./templates/smokeTestFunctions.template";
|
|
5
|
-
import { getSmokeTestTemplate } from "./templates/SmokeTest.template";
|
|
6
|
-
import { getSmokeTestClientTemplate } from "./templates/SmokeTestClient.template";
|
|
7
|
-
import MagicString from "magic-string";
|
|
8
|
-
import { parse as parseJsonc } from "jsonc-parser";
|
|
9
|
-
/**
|
|
10
|
-
* Creates the smoke test components in the target project directory
|
|
11
|
-
*/
|
|
12
|
-
export async function createSmokeTestComponents(targetDir, skipClient = false) {
|
|
13
|
-
console.log("Creating smoke test components in project...");
|
|
14
|
-
// Create directories if they don't exist
|
|
15
|
-
const componentsDir = join(targetDir, "src", "app", "components");
|
|
16
|
-
log("Creating components directory: %s", componentsDir);
|
|
17
|
-
await fs.mkdir(componentsDir, { recursive: true });
|
|
18
|
-
// Create __smokeTestFunctions.ts
|
|
19
|
-
const smokeTestFunctionsPath = join(componentsDir, "__smokeTestFunctions.ts");
|
|
20
|
-
log("Creating __smokeTestFunctions.ts at: %s", smokeTestFunctionsPath);
|
|
21
|
-
const smokeTestFunctionsContent = getSmokeTestFunctionsTemplate();
|
|
22
|
-
// Create SmokeTest.tsx with conditional client component import
|
|
23
|
-
const smokeTestPath = join(componentsDir, "__SmokeTest.tsx");
|
|
24
|
-
log("Creating __SmokeTest.tsx at: %s", smokeTestPath);
|
|
25
|
-
const smokeTestContent = getSmokeTestTemplate(skipClient);
|
|
26
|
-
// Write the server files
|
|
27
|
-
log("Writing SmokeTestFunctions file");
|
|
28
|
-
await fs.writeFile(smokeTestFunctionsPath, smokeTestFunctionsContent);
|
|
29
|
-
log("Writing SmokeTest component file");
|
|
30
|
-
await fs.writeFile(smokeTestPath, smokeTestContent);
|
|
31
|
-
// Only create client component if not skipping client-side tests
|
|
32
|
-
if (!skipClient) {
|
|
33
|
-
// Create SmokeTestClient.tsx
|
|
34
|
-
const smokeTestClientPath = join(componentsDir, "__SmokeTestClient.tsx");
|
|
35
|
-
log("Creating __SmokeTestClient.tsx at: %s", smokeTestClientPath);
|
|
36
|
-
const smokeTestClientContent = getSmokeTestClientTemplate();
|
|
37
|
-
log("Writing SmokeTestClient component file");
|
|
38
|
-
await fs.writeFile(smokeTestClientPath, smokeTestClientContent);
|
|
39
|
-
log("Created client-side smoke test component");
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
log("Skipping client-side smoke test component creation");
|
|
43
|
-
}
|
|
44
|
-
// Modify worker.tsx and wrangler.jsonc for realtime support
|
|
45
|
-
await modifyAppForRealtime(targetDir);
|
|
46
|
-
log("Smoke test components created successfully");
|
|
47
|
-
console.log("Created smoke test components:");
|
|
48
|
-
console.log(`- ${smokeTestFunctionsPath}`);
|
|
49
|
-
console.log(`- ${smokeTestPath}`);
|
|
50
|
-
if (!skipClient) {
|
|
51
|
-
console.log(`- ${join(componentsDir, "__SmokeTestClient.tsx")}`);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
console.log("- Client component skipped (--skip-client was specified)");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Modifies the worker.tsx and wrangler.jsonc files to add realtime support
|
|
59
|
-
*/
|
|
60
|
-
export async function modifyAppForRealtime(targetDir) {
|
|
61
|
-
log("Modifying worker.tsx and wrangler.jsonc for realtime support");
|
|
62
|
-
// Modify worker.tsx
|
|
63
|
-
const workerPath = join(targetDir, "src", "worker.tsx");
|
|
64
|
-
if (await fs
|
|
65
|
-
.access(workerPath)
|
|
66
|
-
.then(() => true)
|
|
67
|
-
.catch(() => false)) {
|
|
68
|
-
log("Found worker.tsx, checking for realtime code");
|
|
69
|
-
const workerContent = await fs.readFile(workerPath, "utf-8");
|
|
70
|
-
// Check if the realtime export line already exists
|
|
71
|
-
const hasRealtimeExport = workerContent.includes('export { RealtimeDurableObject } from "rwsdk/realtime/durableObject"');
|
|
72
|
-
const hasRealtimeRoute = workerContent.includes("realtimeRoute(");
|
|
73
|
-
if (!hasRealtimeExport || !hasRealtimeRoute) {
|
|
74
|
-
log("Need to modify worker.tsx for realtime support");
|
|
75
|
-
const s = new MagicString(workerContent);
|
|
76
|
-
// Add the export line if it doesn't exist
|
|
77
|
-
if (!hasRealtimeExport) {
|
|
78
|
-
const importRegex = /import.*?from.*?;\n/g;
|
|
79
|
-
let lastImportMatch;
|
|
80
|
-
let lastImportPosition = 0;
|
|
81
|
-
// Find the position after the last import statement
|
|
82
|
-
while ((lastImportMatch = importRegex.exec(workerContent)) !== null) {
|
|
83
|
-
lastImportPosition =
|
|
84
|
-
lastImportMatch.index + lastImportMatch[0].length;
|
|
85
|
-
}
|
|
86
|
-
if (lastImportPosition > 0) {
|
|
87
|
-
s.appendRight(lastImportPosition, 'export { RealtimeDurableObject } from "rwsdk/realtime/durableObject";\n');
|
|
88
|
-
log("Added RealtimeDurableObject export");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Add the realtimeRoute line if it doesn't exist
|
|
92
|
-
if (!hasRealtimeRoute) {
|
|
93
|
-
const defineAppMatch = workerContent.match(/export default defineApp\(\[/);
|
|
94
|
-
if (defineAppMatch && defineAppMatch.index !== undefined) {
|
|
95
|
-
const insertPosition = defineAppMatch.index + defineAppMatch[0].length;
|
|
96
|
-
s.appendRight(insertPosition, "\n realtimeRoute(() => env.REALTIME_DURABLE_OBJECT),");
|
|
97
|
-
log("Added realtimeRoute to defineApp");
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Import realtimeRoute if it's not already imported
|
|
101
|
-
if (!workerContent.includes("realtimeRoute")) {
|
|
102
|
-
// Find the router import to append to it
|
|
103
|
-
const routerImportMatch = workerContent.match(/import \{(.*?)\} from "rwsdk\/router";/);
|
|
104
|
-
if (routerImportMatch) {
|
|
105
|
-
const importList = routerImportMatch[1];
|
|
106
|
-
if (!importList.includes("realtimeRoute")) {
|
|
107
|
-
s.replace(routerImportMatch[0], routerImportMatch[0].replace(/import \{(.*?)\} from "rwsdk\/router";/, (match, imports) => `import { ${imports}, realtimeRoute } from "rwsdk/router";`));
|
|
108
|
-
log("Added realtimeRoute to router imports");
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// Write the modified file
|
|
113
|
-
await fs.writeFile(workerPath, s.toString(), "utf-8");
|
|
114
|
-
log("Successfully modified worker.tsx");
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
log("worker.tsx already has realtime support, no changes needed");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
log("worker.tsx not found, skipping modification");
|
|
122
|
-
}
|
|
123
|
-
// Modify wrangler.jsonc
|
|
124
|
-
const wranglerPath = join(targetDir, "wrangler.jsonc");
|
|
125
|
-
if (await fs
|
|
126
|
-
.access(wranglerPath)
|
|
127
|
-
.then(() => true)
|
|
128
|
-
.catch(() => false)) {
|
|
129
|
-
log("Found wrangler.jsonc, checking for realtime durable objects");
|
|
130
|
-
const wranglerContent = await fs.readFile(wranglerPath, "utf-8");
|
|
131
|
-
const wranglerConfig = parseJsonc(wranglerContent);
|
|
132
|
-
let modified = false;
|
|
133
|
-
// Check if REALTIME_DURABLE_OBJECT already exists in durable_objects bindings
|
|
134
|
-
const hasDurableObjectBinding = wranglerConfig.durable_objects?.bindings?.some((binding) => binding.name === "REALTIME_DURABLE_OBJECT");
|
|
135
|
-
// Check if RealtimeDurableObject is already in migrations
|
|
136
|
-
const hasMigration = wranglerConfig.migrations?.some((migration) => migration.new_sqlite_classes?.includes("RealtimeDurableObject"));
|
|
137
|
-
if (!hasDurableObjectBinding || !hasMigration) {
|
|
138
|
-
log("Need to modify wrangler.jsonc for realtime support");
|
|
139
|
-
// Create a deep copy of the config to make modifications
|
|
140
|
-
const newConfig = JSON.parse(JSON.stringify(wranglerConfig));
|
|
141
|
-
// Add durable objects binding if needed
|
|
142
|
-
if (!hasDurableObjectBinding) {
|
|
143
|
-
if (!newConfig.durable_objects) {
|
|
144
|
-
newConfig.durable_objects = {};
|
|
145
|
-
}
|
|
146
|
-
if (!newConfig.durable_objects.bindings) {
|
|
147
|
-
newConfig.durable_objects.bindings = [];
|
|
148
|
-
}
|
|
149
|
-
newConfig.durable_objects.bindings.push({
|
|
150
|
-
name: "REALTIME_DURABLE_OBJECT",
|
|
151
|
-
class_name: "RealtimeDurableObject",
|
|
152
|
-
});
|
|
153
|
-
modified = true;
|
|
154
|
-
log("Added REALTIME_DURABLE_OBJECT to durable_objects bindings");
|
|
155
|
-
}
|
|
156
|
-
// Add migration if needed
|
|
157
|
-
if (!hasMigration) {
|
|
158
|
-
if (!newConfig.migrations) {
|
|
159
|
-
newConfig.migrations = [
|
|
160
|
-
{
|
|
161
|
-
tag: "v1",
|
|
162
|
-
new_sqlite_classes: ["RealtimeDurableObject"],
|
|
163
|
-
},
|
|
164
|
-
];
|
|
165
|
-
modified = true;
|
|
166
|
-
log("Added new migrations with RealtimeDurableObject");
|
|
167
|
-
}
|
|
168
|
-
else if (newConfig.migrations.length > 0) {
|
|
169
|
-
// Add RealtimeDurableObject to the first migration's sqlite classes
|
|
170
|
-
const firstMigration = newConfig.migrations[0];
|
|
171
|
-
if (!firstMigration.new_sqlite_classes) {
|
|
172
|
-
firstMigration.new_sqlite_classes = ["RealtimeDurableObject"];
|
|
173
|
-
}
|
|
174
|
-
else if (!firstMigration.new_sqlite_classes.includes("RealtimeDurableObject")) {
|
|
175
|
-
firstMigration.new_sqlite_classes.push("RealtimeDurableObject");
|
|
176
|
-
}
|
|
177
|
-
modified = true;
|
|
178
|
-
log("Added RealtimeDurableObject to existing migration");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (modified) {
|
|
182
|
-
// Write the modified config back to the file
|
|
183
|
-
await fs.writeFile(wranglerPath, JSON.stringify(newConfig, null, 2), "utf-8");
|
|
184
|
-
log("Successfully modified wrangler.jsonc");
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
log("wrangler.jsonc already has realtime support, no changes needed");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
log("wrangler.jsonc not found, skipping modification");
|
|
193
|
-
}
|
|
194
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function getSmokeTestTemplate(skipClient?: boolean): string;
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
export function getSmokeTestTemplate(skipClient = false) {
|
|
2
|
-
return `
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { RequestInfo } from "rwsdk/worker";
|
|
5
|
-
${skipClient ? "" : 'import { SmokeTestClient } from "./__SmokeTestClient";'}
|
|
6
|
-
import { smokeTestAction } from "./__smokeTestFunctions";
|
|
7
|
-
|
|
8
|
-
export const SmokeTestInfo: React.FC = async () => {
|
|
9
|
-
const timestamp = Date.now();
|
|
10
|
-
let status = "error";
|
|
11
|
-
let verificationPassed = false;
|
|
12
|
-
let serverStoredTimestamp = 0;
|
|
13
|
-
let result: any = null;
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// Call the smoke test action to verify basic server functionality
|
|
17
|
-
result = await smokeTestAction(timestamp);
|
|
18
|
-
status = result.status || "error";
|
|
19
|
-
verificationPassed = result.timestamp === timestamp;
|
|
20
|
-
serverStoredTimestamp = result.serverStoredTimestamp;
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.error("Smoke test failed:", error);
|
|
23
|
-
status = "error";
|
|
24
|
-
result = { error: error instanceof Error ? error.message : String(error) };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div
|
|
29
|
-
id="smoke-test-container"
|
|
30
|
-
data-testid="health-status"
|
|
31
|
-
data-status={status}
|
|
32
|
-
data-timestamp={timestamp}
|
|
33
|
-
data-server-timestamp={Date.now()}
|
|
34
|
-
data-server-stored-timestamp={serverStoredTimestamp}
|
|
35
|
-
data-verified={verificationPassed ? "true" : "false"}
|
|
36
|
-
style={{
|
|
37
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
38
|
-
margin: "20px",
|
|
39
|
-
padding: "15px",
|
|
40
|
-
border: "1px solid #ddd",
|
|
41
|
-
borderRadius: "4px",
|
|
42
|
-
background: "#f9f9f9",
|
|
43
|
-
}}
|
|
44
|
-
>
|
|
45
|
-
<h2
|
|
46
|
-
style={{
|
|
47
|
-
color: status === "ok" ? "#0c9" : "#f44",
|
|
48
|
-
margin: "0 0 10px 0",
|
|
49
|
-
}}
|
|
50
|
-
>
|
|
51
|
-
Smoke Test: {status}
|
|
52
|
-
</h2>
|
|
53
|
-
<div
|
|
54
|
-
id="smoke-test-result"
|
|
55
|
-
>
|
|
56
|
-
{verificationPassed
|
|
57
|
-
? "Timestamp verification passed ✅"
|
|
58
|
-
: "Timestamp verification failed ⚠️"}
|
|
59
|
-
</div>
|
|
60
|
-
<div id="server-stored-timestamp">
|
|
61
|
-
Server Stored Timestamp: {serverStoredTimestamp}
|
|
62
|
-
</div>
|
|
63
|
-
<details style={{ marginTop: "10px" }}>
|
|
64
|
-
<summary>Details</summary>
|
|
65
|
-
<pre
|
|
66
|
-
style={{
|
|
67
|
-
background: "#f5f5f5",
|
|
68
|
-
padding: "10px",
|
|
69
|
-
borderRadius: "4px",
|
|
70
|
-
fontSize: "12px",
|
|
71
|
-
overflow: "auto",
|
|
72
|
-
}}
|
|
73
|
-
>
|
|
74
|
-
{JSON.stringify({ timestamp, serverStoredTimestamp, result, verificationPassed }, null, 2)}
|
|
75
|
-
</pre>
|
|
76
|
-
</details>
|
|
77
|
-
|
|
78
|
-
${!skipClient ? "<SmokeTestClient/>" : ""}
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
};`;
|
|
82
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { RequestInfo } from "../requestInfo/types";
|
|
3
|
-
export declare const HealthCheckInfo: React.FC;
|
|
4
|
-
/**
|
|
5
|
-
* Wrapper component that displays health check info above the original page content
|
|
6
|
-
*/
|
|
7
|
-
export declare const HealthCheckWrapper: React.FC<{
|
|
8
|
-
children: React.ReactNode;
|
|
9
|
-
}>;
|
|
10
|
-
/**
|
|
11
|
-
* Standalone health check page that conforms to the RouteComponent type
|
|
12
|
-
*/
|
|
13
|
-
export declare const HealthCheckPage: (requestInfo: RequestInfo) => React.JSX.Element;
|