rwsdk 0.1.6 → 0.1.7-test.20250702144348

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/bin/rwsync +2 -0
  2. package/dist/runtime/client.js +6 -1
  3. package/dist/runtime/clientNavigation.d.ts +1 -0
  4. package/dist/runtime/clientNavigation.js +34 -24
  5. package/dist/runtime/clientNavigation.test.js +55 -0
  6. package/dist/runtime/lib/realtime/client.js +6 -0
  7. package/dist/runtime/lib/realtime/durableObject.js +8 -6
  8. package/dist/runtime/lib/realtime/worker.js +1 -3
  9. package/dist/runtime/render/renderRscThenableToHtmlStream.d.ts +2 -1
  10. package/dist/runtime/render/renderRscThenableToHtmlStream.js +22 -1
  11. package/dist/runtime/render/renderToStream.js +2 -1
  12. package/dist/runtime/render/transformRscToHtmlStream.d.ts +2 -1
  13. package/dist/runtime/render/transformRscToHtmlStream.js +2 -1
  14. package/dist/runtime/worker.js +1 -0
  15. package/dist/scripts/debug-sync.d.mts +1 -0
  16. package/dist/scripts/debug-sync.mjs +80 -12
  17. package/package.json +3 -2
  18. package/dist/lib/smokeTests/components.d.mts +0 -8
  19. package/dist/lib/smokeTests/components.mjs +0 -194
  20. package/dist/lib/smokeTests/templates/SmokeTestInfo.template.d.ts +0 -1
  21. package/dist/lib/smokeTests/templates/SmokeTestInfo.template.js +0 -82
  22. package/dist/runtime/components/HealthCheck.d.ts +0 -13
  23. package/dist/runtime/components/HealthCheck.js +0 -56
  24. package/dist/runtime/components/HealthCheckClient.d.ts +0 -2
  25. package/dist/runtime/components/HealthCheckClient.js +0 -78
  26. package/dist/runtime/imports/NoSSRStub.d.ts +0 -1
  27. package/dist/runtime/imports/NoSSRStub.js +0 -4
  28. package/dist/runtime/lib/db/create.d.ts +0 -3
  29. package/dist/runtime/lib/db/create.js +0 -36
  30. package/dist/runtime/lib/db/logger.d.ts +0 -2
  31. package/dist/runtime/lib/db/logger.js +0 -41
  32. package/dist/runtime/lib/db/types.d.ts +0 -0
  33. package/dist/runtime/lib/db/types.js +0 -1
  34. package/dist/runtime/render/__rwsdk_ssr_bridge.d.ts +0 -10
  35. package/dist/runtime/render/__rwsdk_ssr_bridge.js +0 -9
  36. package/dist/runtime/render/__rwsdkssr_render.d.ts +0 -9
  37. package/dist/runtime/render/__rwsdkssr_render.js +0 -13
  38. package/dist/runtime/render/injectRSCPayload.d.ts +0 -3
  39. package/dist/runtime/render/injectRSCPayload.js +0 -79
  40. package/dist/runtime/render/ssrBridge.d.ts +0 -2
  41. package/dist/runtime/render/ssrBridge.js +0 -2
  42. package/dist/runtime/render/ssrRenderToReadableStream.d.ts +0 -2
  43. package/dist/runtime/render/ssrRenderToReadableStream.js +0 -2
  44. package/dist/runtime/requestInfo/__rwsdknossr_worker.d.ts +0 -5
  45. package/dist/runtime/requestInfo/__rwsdknossr_worker.js +0 -33
  46. package/dist/scripts/build-vendor-bundles.d.mts +0 -1
  47. package/dist/scripts/build-vendor-bundles.mjs +0 -92
  48. package/dist/vite/aliasedModuleResolver.d.mts +0 -9
  49. package/dist/vite/aliasedModuleResolver.mjs +0 -62
  50. package/dist/vite/aliasedSSRResolver.d.mts +0 -5
  51. package/dist/vite/aliasedSSRResolver.mjs +0 -74
  52. package/dist/vite/copyPrismaWasmPlugin.d.mts +0 -4
  53. package/dist/vite/copyPrismaWasmPlugin.mjs +0 -32
  54. package/dist/vite/ensureConfigArrays.d.mts +0 -1
  55. package/dist/vite/ensureConfigArrays.mjs +0 -12
  56. package/dist/vite/findImportSpecifiers.d.mts +0 -30
  57. package/dist/vite/findImportSpecifiers.mjs +0 -228
  58. package/dist/vite/findImportSpecifiers.test.mjs +0 -73
  59. package/dist/vite/isBareImport.d.mts +0 -1
  60. package/dist/vite/isBareImport.mjs +0 -5
  61. package/dist/vite/miniflarePlugin.d.mts +0 -9
  62. package/dist/vite/miniflarePlugin.mjs +0 -135
  63. package/dist/vite/moduleResolver.d.mts +0 -10
  64. package/dist/vite/moduleResolver.mjs +0 -74
  65. package/dist/vite/resolveModuleId.d.mts +0 -6
  66. package/dist/vite/resolveModuleId.mjs +0 -14
  67. package/dist/vite/rscDirectivesPlugin.d.mts +0 -6
  68. package/dist/vite/rscDirectivesPlugin.mjs +0 -80
  69. package/dist/vite/transformServerReferences.d.mts +0 -11
  70. package/dist/vite/transformServerReferences.mjs +0 -74
  71. package/dist/vite/useClientPlugin.d.mts +0 -8
  72. package/dist/vite/useClientPlugin.mjs +0 -299
  73. package/dist/vite/useClientPlugin.test.d.mts +0 -1
  74. package/dist/vite/useClientPlugin.test.mjs +0 -1294
  75. package/dist/vite/useServerPlugin.test.d.mts +0 -1
  76. package/dist/vite/useServerPlugin.test.mjs +0 -99
  77. package/dist/vite/virtualizedSSRPlugin.d.mts +0 -56
  78. package/dist/vite/virtualizedSSRPlugin.mjs +0 -464
  79. package/dist/vite/wasmPlugin.d.mts +0 -2
  80. package/dist/vite/wasmPlugin.mjs +0 -14
  81. /package/dist/{vite/findImportSpecifiers.test.d.mts → runtime/clientNavigation.test.d.ts} +0 -0
package/bin/rwsync ADDED
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ DIR=$PWD && (cd "${@:-SDK_REPO}/sdk" && pnpm debug-sync $DIR)
@@ -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, {}), hydrateRootOptions);
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,4 @@
1
+ export declare function validateClickEvent(event: MouseEvent, target: HTMLElement): boolean;
1
2
  export declare function initClientNavigation(opts?: {
2
3
  onNavigate: () => void;
3
4
  }): void;
@@ -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
- // should this only work for left click?
10
- if (event.button !== 0) {
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
- // push this to the history stack.
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,12 @@ export class RealtimeDurableObject extends DurableObject {
100
100
  reader.releaseLock();
101
101
  }
102
102
  }
103
- async handleAction(ws, id, args, clientInfo, requestId) {
104
- const url = new URL(clientInfo.url);
105
- url.searchParams.set("__rsc", "true");
106
- url.searchParams.set("__rsc_action_id", id);
103
+ async handleAction(ws, id, args, clientInfo, requestId, clientUrl) {
104
+ const url = new URL(clientUrl);
105
+ url.searchParams.set("__rsc", "");
106
+ if (id != null) {
107
+ url.searchParams.set("__rsc_action_id", id);
108
+ }
107
109
  const response = await fetch(url.toString(), {
108
110
  method: "POST",
109
111
  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
  };
@@ -114,6 +114,7 @@ export const defineApp = (routes) => {
114
114
  stream: rscPayloadStream,
115
115
  Document: rw.Document,
116
116
  requestInfo: requestInfo,
117
+ onError,
117
118
  });
118
119
  if (injectRSCPayloadStream) {
119
120
  html = html.pipeThrough(injectRSCPayloadStream);
@@ -1,5 +1,6 @@
1
1
  export interface DebugSyncOptions {
2
2
  targetDir: string;
3
+ sdkDir?: string;
3
4
  dev?: boolean;
4
5
  watch?: boolean;
5
6
  build?: boolean;
@@ -1,20 +1,78 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
1
3
  import { $ } from "../lib/$.mjs";
4
+ import fs from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const getPackageManagerInfo = (targetDir) => {
8
+ if (existsSync(path.join(targetDir, "yarn.lock"))) {
9
+ return { name: "yarn", lockFile: "yarn.lock", command: "add" };
10
+ }
11
+ if (existsSync(path.join(targetDir, "pnpm-lock.yaml"))) {
12
+ return { name: "pnpm", lockFile: "pnpm-lock.yaml", command: "add" };
13
+ }
14
+ return { name: "npm", lockFile: "package-lock.json", command: "install" };
15
+ };
16
+ const performSync = async (sdkDir, targetDir) => {
17
+ console.log("🏗️ rebuilding sdk...");
18
+ await $({ cwd: sdkDir, stdio: "inherit", shell: true }) `pnpm build`;
19
+ console.log("📦 packing sdk...");
20
+ const packResult = await $({ cwd: sdkDir, shell: true }) `npm pack`;
21
+ const tarballName = packResult.stdout?.trim() ?? "";
22
+ const tarballPath = path.resolve(sdkDir, tarballName);
23
+ console.log(` installing ${tarballName} in ${targetDir}...`);
24
+ const pm = getPackageManagerInfo(targetDir);
25
+ const packageJsonPath = path.join(targetDir, "package.json");
26
+ const lockfilePath = path.join(targetDir, pm.lockFile);
27
+ const originalPackageJson = await fs
28
+ .readFile(packageJsonPath, "utf-8")
29
+ .catch(() => null);
30
+ const originalLockfile = await fs
31
+ .readFile(lockfilePath, "utf-8")
32
+ .catch(() => null);
33
+ try {
34
+ let installCommand = `${pm.name} ${pm.command} ${tarballPath}`;
35
+ if (pm.name === "yarn") {
36
+ installCommand = `yarn add file:${tarballPath}`;
37
+ }
38
+ await $({
39
+ cwd: targetDir,
40
+ stdio: "inherit",
41
+ shell: true,
42
+ }) `${installCommand}`;
43
+ }
44
+ finally {
45
+ if (originalPackageJson) {
46
+ console.log("Restoring package.json...");
47
+ await fs.writeFile(packageJsonPath, originalPackageJson);
48
+ }
49
+ if (originalLockfile) {
50
+ console.log(`Restoring ${pm.lockFile}...`);
51
+ await fs.writeFile(lockfilePath, originalLockfile);
52
+ }
53
+ await fs.unlink(tarballPath).catch(() => {
54
+ // ignore if deletion fails
55
+ });
56
+ }
57
+ console.log("✅ done syncing");
58
+ };
2
59
  export const debugSync = async (opts) => {
3
- const { targetDir, dev, watch, build } = opts;
60
+ const { targetDir, sdkDir = process.cwd(), dev, watch, build } = opts;
4
61
  if (!targetDir) {
5
62
  console.error("❌ Please provide a target directory as an argument.");
6
63
  process.exit(1);
7
64
  }
8
- const syncCommand = `echo 🏗️ rebuilding... && pnpm build && rm -rf ${targetDir}/node_modules/rwsdk/dist ${targetDir}/node_modules/rwsdk/package.json && echo 📁 syncing sdk from ${process.cwd()} to ${targetDir}/node_modules/rwsdk/... && cp -r package.json dist ${targetDir}/node_modules/rwsdk/ && echo ✅ done syncing`;
65
+ const thisScriptPath = fileURLToPath(import.meta.url);
66
+ const syncCommand = `tsx ${thisScriptPath} --_sync ${sdkDir} ${targetDir}`;
9
67
  // Run initial sync
10
- await $({ stdio: "inherit", shell: true }) `${syncCommand}`;
68
+ await performSync(sdkDir, targetDir);
11
69
  if (!process.env.NO_CLEAN_VITE) {
12
70
  console.log("🧹 Cleaning Vite cache...");
13
71
  await $({
14
72
  stdio: "inherit",
15
73
  shell: true,
16
74
  cwd: targetDir,
17
- }) `rm -rf node_modules/.vite`;
75
+ }) `rm -rf ${targetDir}/node_modules/.vite*`;
18
76
  }
19
77
  // If dev flag is present, clean vite cache and start dev server
20
78
  if (dev) {
@@ -40,12 +98,22 @@ export const debugSync = async (opts) => {
40
98
  };
41
99
  if (import.meta.url === new URL(process.argv[1], import.meta.url).href) {
42
100
  const args = process.argv.slice(2);
43
- const targetDir = args[0];
44
- const flags = new Set(args.slice(1));
45
- debugSync({
46
- targetDir,
47
- dev: flags.has("--dev"),
48
- watch: flags.has("--watch"),
49
- build: flags.has("--build"),
50
- });
101
+ const flags = new Set(args.filter((arg) => arg.startsWith("--")));
102
+ const positionalArgs = args.filter((arg) => !arg.startsWith("--"));
103
+ if (flags.has("--_sync")) {
104
+ const [sdkDir, targetDir] = positionalArgs;
105
+ await performSync(sdkDir, targetDir);
106
+ }
107
+ else {
108
+ const targetDir = positionalArgs[0] ?? process.cwd();
109
+ debugSync({
110
+ targetDir,
111
+ sdkDir: process.env.SDK_REPO
112
+ ? path.resolve(__dirname, process.env.SDK_REPO, "sdk")
113
+ : path.resolve(__dirname, "..", ".."),
114
+ dev: flags.has("--dev"),
115
+ watch: flags.has("--watch"),
116
+ build: flags.has("--build"),
117
+ });
118
+ }
51
119
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.7-test.20250702144348",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
7
- "rw-scripts": "./bin/rw-scripts.mjs"
7
+ "rw-scripts": "./bin/rw-scripts.mjs",
8
+ "rwsync": "./bin/rwsync"
8
9
  },
9
10
  "files": [
10
11
  "./README.md",
@@ -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
- }