rwsdk 0.2.0-alpha.12 → 0.2.0-alpha.13

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.
@@ -24,15 +24,50 @@ const getPackageManagerInfo = (targetDir) => {
24
24
  }
25
25
  return pnpmResult;
26
26
  };
27
+ const cleanupViteEntries = async (targetDir) => {
28
+ const nodeModulesDir = path.join(targetDir, "node_modules");
29
+ if (!existsSync(nodeModulesDir)) {
30
+ return;
31
+ }
32
+ try {
33
+ const entries = await fs.readdir(nodeModulesDir);
34
+ const viteEntries = entries.filter((entry) => entry.startsWith(".vite"));
35
+ for (const entry of viteEntries) {
36
+ const entryPath = path.join(nodeModulesDir, entry);
37
+ try {
38
+ const stat = await fs.lstat(entryPath);
39
+ if (!stat.isSymbolicLink()) {
40
+ console.log(`Removing vite cache entry: ${entry}`);
41
+ await fs.rm(entryPath, { recursive: true, force: true });
42
+ }
43
+ else {
44
+ console.log(`Skipping symlinked vite cache entry: ${entry}`);
45
+ }
46
+ }
47
+ catch {
48
+ // If we can't stat it, try to remove it
49
+ console.log(`Removing vite cache entry: ${entry}`);
50
+ await fs.rm(entryPath, { recursive: true, force: true });
51
+ }
52
+ }
53
+ }
54
+ catch (error) {
55
+ console.log(`Failed to cleanup vite cache entries: ${error}`);
56
+ }
57
+ };
27
58
  const performFullSync = async (sdkDir, targetDir) => {
28
59
  const sdkPackageJsonPath = path.join(sdkDir, "package.json");
29
60
  let originalSdkPackageJson = null;
30
61
  let tarballPath = "";
31
62
  let tarballName = "";
63
+ // Clean up vite cache
64
+ await cleanupViteEntries(targetDir);
32
65
  try {
33
66
  console.log("📦 Packing SDK...");
34
- const packResult = await $({ cwd: sdkDir }) `npm pack`;
35
- tarballName = packResult.stdout?.trim() ?? "";
67
+ const packResult = await $({ cwd: sdkDir }) `npm pack --json`;
68
+ const json = JSON.parse(packResult.stdout || "[]");
69
+ const packInfo = Array.isArray(json) ? json[0] : undefined;
70
+ tarballName = (packInfo && (packInfo.filename || packInfo.name)) || "";
36
71
  if (!tarballName) {
37
72
  console.error("❌ Failed to get tarball name from npm pack.");
38
73
  return;
@@ -86,23 +121,63 @@ const performFullSync = async (sdkDir, targetDir) => {
86
121
  }
87
122
  }
88
123
  };
89
- const performFastSync = async (sdkDir, targetDir) => {
90
- console.log("⚡️ No dependency changes, performing fast sync...");
91
- const sdkPackageJson = JSON.parse(await fs.readFile(path.join(sdkDir, "package.json"), "utf-8"));
92
- const filesToSync = sdkPackageJson.files || [];
93
- for (const file of filesToSync) {
94
- const source = path.join(sdkDir, file);
95
- const destination = path.join(targetDir, "node_modules/rwsdk", file);
96
- if (existsSync(source)) {
97
- await fs.cp(source, destination, { recursive: true, force: true });
124
+ const syncFilesWithRsyncOrFs = async (sdkDir, destDir, filesEntries) => {
125
+ const sources = filesEntries.map((p) => path.join(sdkDir, p));
126
+ // Always include package.json in sync
127
+ const pkgJsonPath = path.join(sdkDir, "package.json");
128
+ sources.push(pkgJsonPath);
129
+ await fs.mkdir(destDir, { recursive: true });
130
+ // Try rsync across all sources in one shot
131
+ try {
132
+ if (sources.length > 0) {
133
+ const rsyncArgs = [
134
+ "-a",
135
+ "--delete",
136
+ "--omit-dir-times",
137
+ "--no-perms",
138
+ "--no-owner",
139
+ "--no-group",
140
+ ...sources,
141
+ destDir + path.sep,
142
+ ];
143
+ await $({ stdio: "inherit" })("rsync", rsyncArgs);
144
+ return;
145
+ }
146
+ }
147
+ catch {
148
+ // fall through to fs fallback
149
+ }
150
+ console.log("Rsync failed, falling back to fs");
151
+ // Fallback: destructive copy using Node fs to mirror content
152
+ await fs.rm(destDir, { recursive: true, force: true });
153
+ await fs.mkdir(destDir, { recursive: true });
154
+ for (const src of sources) {
155
+ const rel = path.relative(sdkDir, src);
156
+ const dst = path.join(destDir, rel);
157
+ await fs.mkdir(path.dirname(dst), { recursive: true });
158
+ try {
159
+ const stat = await fs.lstat(src);
160
+ if (stat.isDirectory()) {
161
+ await fs.cp(src, dst, { recursive: true, force: true });
162
+ }
163
+ else {
164
+ await fs.copyFile(src, dst);
165
+ }
166
+ }
167
+ catch {
168
+ await fs.cp(src, dst, { recursive: true, force: true }).catch(() => { });
98
169
  }
99
170
  }
100
- // Always copy package.json
101
- await fs.copyFile(path.join(sdkDir, "package.json"), path.join(targetDir, "node_modules/rwsdk/package.json"));
102
171
  };
103
- const areDependenciesEqual = (deps1, deps2) => {
104
- // Simple string comparison for this use case is sufficient
105
- return JSON.stringify(deps1 ?? {}) === JSON.stringify(deps2 ?? {});
172
+ const performFastSync = async (sdkDir, targetDir) => {
173
+ console.log("⚡️ No dependency changes, performing fast sync...");
174
+ // Clean up vite cache
175
+ await cleanupViteEntries(targetDir);
176
+ const nodeModulesPkgDir = path.join(targetDir, "node_modules", "rwsdk");
177
+ // Copy directories/files declared in package.json#files (plus package.json)
178
+ const filesToSync = JSON.parse(await fs.readFile(path.join(sdkDir, "package.json"), "utf-8"))
179
+ .files || [];
180
+ await syncFilesWithRsyncOrFs(sdkDir, nodeModulesPkgDir, filesToSync);
106
181
  };
107
182
  const performSync = async (sdkDir, targetDir) => {
108
183
  console.log("🏗️ Rebuilding SDK...");
@@ -120,13 +195,8 @@ const performSync = async (sdkDir, targetDir) => {
120
195
  if (existsSync(installedSdkPackageJsonPath)) {
121
196
  const sdkPackageJsonContent = await fs.readFile(sdkPackageJsonPath, "utf-8");
122
197
  const installedSdkPackageJsonContent = await fs.readFile(installedSdkPackageJsonPath, "utf-8");
123
- const sdkPkg = JSON.parse(sdkPackageJsonContent);
124
- const installedPkg = JSON.parse(installedSdkPackageJsonContent);
125
- if (areDependenciesEqual(sdkPkg.dependencies, installedPkg.dependencies) &&
126
- areDependenciesEqual(sdkPkg.devDependencies, installedPkg.devDependencies) &&
127
- areDependenciesEqual(sdkPkg.peerDependencies, installedPkg.peerDependencies)) {
128
- packageJsonChanged = false;
129
- }
198
+ packageJsonChanged =
199
+ sdkPackageJsonContent !== installedSdkPackageJsonContent;
130
200
  }
131
201
  if (packageJsonChanged) {
132
202
  console.log("📦 package.json changed, performing full sync...");
@@ -198,20 +268,19 @@ export const debugSync = async (opts) => {
198
268
  cwd: sdkDir,
199
269
  });
200
270
  let syncing = false;
201
- // todo(justinvdm, 2025-07-22): Figure out wtf makes the full sync
202
- // cause chokidar to find out about package.json after sync has resolved
203
- let expectingFileChanges = Boolean(process.env.RWSDK_FORCE_FULL_SYNC);
204
- watcher.on("all", async (_event, filePath) => {
205
- if (syncing || filePath.endsWith(".tgz")) {
206
- return;
207
- }
208
- if (expectingFileChanges && process.env.RWSDK_FORCE_FULL_SYNC) {
209
- expectingFileChanges = false;
271
+ let pendingResync = false;
272
+ const triggerResync = async (reason) => {
273
+ if (syncing) {
274
+ pendingResync = true;
210
275
  return;
211
276
  }
212
277
  syncing = true;
213
- expectingFileChanges = true;
214
- console.log(`\nDetected change, re-syncing... (file: ${filePath})`);
278
+ if (reason) {
279
+ console.log(`\nDetected change, re-syncing... (file: ${reason})`);
280
+ }
281
+ else {
282
+ console.log(`\nDetected change, re-syncing...`);
283
+ }
215
284
  if (childProc && !childProc.killed) {
216
285
  console.log("Stopping running process...");
217
286
  childProc.kill();
@@ -220,7 +289,6 @@ export const debugSync = async (opts) => {
220
289
  });
221
290
  }
222
291
  try {
223
- watcher.unwatch(filesToWatch);
224
292
  await performSync(sdkDir, targetDir);
225
293
  runWatchedCommand();
226
294
  }
@@ -230,8 +298,19 @@ export const debugSync = async (opts) => {
230
298
  }
231
299
  finally {
232
300
  syncing = false;
233
- watcher.add(filesToWatch);
234
301
  }
302
+ if (pendingResync) {
303
+ pendingResync = false;
304
+ // Coalesce any rapid additional events into a single follow-up sync
305
+ await new Promise((r) => setTimeout(r, 50));
306
+ return triggerResync();
307
+ }
308
+ };
309
+ watcher.on("all", async (_event, filePath) => {
310
+ if (filePath.endsWith(".tgz")) {
311
+ return;
312
+ }
313
+ await triggerResync(filePath);
235
314
  });
236
315
  const cleanup = async () => {
237
316
  console.log("\nCleaning up...");
@@ -77,6 +77,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
77
77
  type: "full-reload",
78
78
  path: "*",
79
79
  });
80
+ log("hmr: Full reload after error");
80
81
  return [];
81
82
  }
82
83
  const { clientFiles, serverFiles, viteEnvironment: { name: environment }, workerEntryPathname: entry, } = givenOptions;
@@ -84,6 +85,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
84
85
  log(`Hot update: (env=${this.environment.name}) ${ctx.file}\nModule graph:\n\n${dumpFullModuleGraph(ctx.server, this.environment.name)}`);
85
86
  }
86
87
  if (!isJsFile(ctx.file) && !ctx.file.endsWith(".css")) {
88
+ log(`hmr: not a js file, skipping`);
87
89
  return;
88
90
  }
89
91
  if (this.environment.name === "ssr") {
@@ -94,14 +96,17 @@ export const miniflareHMRPlugin = (givenOptions) => [
94
96
  server: ctx.server,
95
97
  });
96
98
  if (!isUseClientUpdate) {
99
+ log("hmr: not a use client update, short circuiting");
97
100
  return [];
98
101
  }
99
102
  invalidateModule(ctx.server, "ssr", ctx.file);
100
103
  invalidateModule(ctx.server, environment, VIRTUAL_SSR_PREFIX +
101
104
  normalizeModulePath(ctx.file, givenOptions.rootDir));
105
+ log("hmr: invalidated ssr module");
102
106
  return [];
103
107
  }
104
108
  if (!["client", environment].includes(this.environment.name)) {
109
+ log(`hmr: incorrect env, skipping (env=${this.environment.name}, worker env=${environment})`);
105
110
  return [];
106
111
  }
107
112
  const hasClientDirective = await hasDirective(ctx.file, "use client");
@@ -165,8 +170,10 @@ export const miniflareHMRPlugin = (givenOptions) => [
165
170
  server: ctx.server,
166
171
  });
167
172
  if (!isUseClientUpdate && !ctx.file.endsWith(".css")) {
173
+ log("hmr: not a use client update and not css, short circuiting");
168
174
  return [];
169
175
  }
176
+ log("hmr: returning client modules for hmr");
170
177
  return ctx.modules;
171
178
  }
172
179
  // The worker needs an update, and the hot check is for the worker environment
@@ -184,8 +191,12 @@ export const miniflareHMRPlugin = (givenOptions) => [
184
191
  if (m) {
185
192
  invalidateModule(ctx.server, environment, m);
186
193
  }
187
- const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(VIRTUAL_SSR_PREFIX +
188
- normalizeModulePath(ctx.file, givenOptions.rootDir));
194
+ let virtualSSRModuleId = VIRTUAL_SSR_PREFIX +
195
+ normalizeModulePath(ctx.file, givenOptions.rootDir);
196
+ if (ctx.file.endsWith(".css")) {
197
+ virtualSSRModuleId += ".js";
198
+ }
199
+ const virtualSSRModule = ctx.server.environments[environment].moduleGraph.idToModuleMap.get(virtualSSRModuleId);
189
200
  if (virtualSSRModule) {
190
201
  invalidateModule(ctx.server, environment, virtualSSRModule);
191
202
  }
@@ -196,6 +207,7 @@ export const miniflareHMRPlugin = (givenOptions) => [
196
207
  file: ctx.file,
197
208
  },
198
209
  });
210
+ log("hmr: sent rsc update");
199
211
  return [];
200
212
  }
201
213
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.2.0-alpha.12",
3
+ "version": "0.2.0-alpha.13",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {