rwsdk 0.2.0-alpha.16 → 0.2.0-alpha.16-test.20250825032050

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,9 @@ import chokidar from "chokidar";
7
7
  import { lock } from "proper-lockfile";
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const getPackageManagerInfo = (targetDir) => {
10
+ if (existsSync(path.join(targetDir, "bun.lock"))) {
11
+ return { name: "bun", lockFile: "bun.lock", command: "add" };
12
+ }
10
13
  const pnpmResult = {
11
14
  name: "pnpm",
12
15
  lockFile: "pnpm-lock.yaml",
@@ -63,6 +66,23 @@ const performFullSync = async (sdkDir, targetDir) => {
63
66
  // Clean up vite cache
64
67
  await cleanupViteEntries(targetDir);
65
68
  try {
69
+ try {
70
+ originalSdkPackageJson = await fs.readFile(sdkPackageJsonPath, "utf-8");
71
+ const packageJson = JSON.parse(originalSdkPackageJson);
72
+ const originalVersion = packageJson.version;
73
+ const timestamp = new Date()
74
+ .toISOString()
75
+ .replace(/[-:T.]/g, "")
76
+ .slice(0, 14);
77
+ const newVersion = `${originalVersion}+build.${timestamp}`;
78
+ console.log(`Temporarily setting version to ${newVersion}`);
79
+ packageJson.version = newVersion;
80
+ await fs.writeFile(sdkPackageJsonPath, JSON.stringify(packageJson, null, 2));
81
+ }
82
+ catch (e) {
83
+ console.warn("Could not modify package.json version, proceeding without it.");
84
+ originalSdkPackageJson = null; // don't restore if we failed to modify
85
+ }
66
86
  console.log("📦 Packing SDK...");
67
87
  const packResult = await $({ cwd: sdkDir }) `npm pack --json`;
68
88
  const json = JSON.parse(packResult.stdout || "[]");
@@ -84,6 +104,29 @@ const performFullSync = async (sdkDir, targetDir) => {
84
104
  .readFile(lockfilePath, "utf-8")
85
105
  .catch(() => null);
86
106
  try {
107
+ // For bun, we need to remove the existing dependency from package.json
108
+ // before adding the tarball to avoid a dependency loop error.
109
+ if (pm.name === "bun" && originalPackageJson) {
110
+ try {
111
+ const targetPackageJson = JSON.parse(originalPackageJson);
112
+ let modified = false;
113
+ if (targetPackageJson.dependencies?.rwsdk) {
114
+ delete targetPackageJson.dependencies.rwsdk;
115
+ modified = true;
116
+ }
117
+ if (targetPackageJson.devDependencies?.rwsdk) {
118
+ delete targetPackageJson.devDependencies.rwsdk;
119
+ modified = true;
120
+ }
121
+ if (modified) {
122
+ console.log("Temporarily removing rwsdk from target package.json to prevent dependency loop with bun.");
123
+ await fs.writeFile(packageJsonPath, JSON.stringify(targetPackageJson, null, 2));
124
+ }
125
+ }
126
+ catch (e) {
127
+ console.warn("Could not modify target package.json, proceeding anyway.");
128
+ }
129
+ }
87
130
  const cmd = pm.name;
88
131
  const args = [pm.command];
89
132
  if (pm.name === "yarn") {
@@ -16,13 +16,18 @@ export const runWorkerScript = async (relativeScriptPath) => {
16
16
  console.error("Error: Script path is required");
17
17
  console.log("\nUsage:");
18
18
  console.log(" npm run worker:run <script-path>");
19
- console.log("\nExample:");
20
- console.log(" npm run worker:run src/scripts/seed.ts\n");
19
+ console.log("\nOptions:");
20
+ console.log(" RWSDK_WRANGLER_CONFIG Environment variable for config path");
21
+ console.log("\nExamples:");
22
+ console.log(" npm run worker:run src/scripts/seed.ts");
23
+ console.log(" RWSDK_WRANGLER_CONFIG=custom.toml npm run worker:run src/scripts/seed.ts\n");
21
24
  process.exit(1);
22
25
  }
23
26
  const scriptPath = resolve(process.cwd(), relativeScriptPath);
24
27
  debug("Running worker script: %s", scriptPath);
25
- const workerConfigPath = await findWranglerConfig(process.cwd());
28
+ const workerConfigPath = process.env.RWSDK_WRANGLER_CONFIG
29
+ ? resolve(process.cwd(), process.env.RWSDK_WRANGLER_CONFIG)
30
+ : await findWranglerConfig(process.cwd());
26
31
  debug("Using wrangler config: %s", workerConfigPath);
27
32
  const workerConfig = unstable_readConfig({
28
33
  config: workerConfigPath,
@@ -33,7 +33,10 @@ const determineWorkerEntryPathname = async (projectRootDir, workerConfigPath, op
33
33
  };
34
34
  export const redwoodPlugin = async (options = {}) => {
35
35
  const projectRootDir = process.cwd();
36
- const workerConfigPath = options.configPath ?? (await findWranglerConfig(projectRootDir));
36
+ const workerConfigPath = options.configPath ??
37
+ (process.env.RWSDK_WRANGLER_CONFIG
38
+ ? resolve(projectRootDir, process.env.RWSDK_WRANGLER_CONFIG)
39
+ : await findWranglerConfig(projectRootDir));
37
40
  const workerEntryPathname = await determineWorkerEntryPathname(projectRootDir, workerConfigPath, options);
38
41
  const clientEntryPathnames = (Array.isArray(options.entry?.client)
39
42
  ? options.entry.client
@@ -1,4 +1,4 @@
1
- import { Project, Node, SyntaxKind } from "ts-morph";
1
+ import { Project, Node, SyntaxKind, } from "ts-morph";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { pathExists } from "fs-extra";
4
4
  import debug from "debug";
@@ -90,8 +90,9 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
90
90
  }
91
91
  const project = new Project({ useInMemoryFileSystem: true });
92
92
  const sourceFile = project.createSourceFile("temp.tsx", code);
93
- let hasModifications = false;
93
+ const modifications = [];
94
94
  const needsRequestInfoImportRef = { value: false };
95
+ const entryPointsPerCallExpr = new Map();
95
96
  // Check for existing imports up front
96
97
  let hasRequestInfoImport = false;
97
98
  let sdkWorkerImportDecl;
@@ -169,8 +170,11 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
169
170
  const path = srcValue.slice(1); // Remove leading slash
170
171
  if (manifest[path]) {
171
172
  const transformedSrc = `/${manifest[path].file}`;
172
- initializer.setLiteralValue(transformedSrc);
173
- hasModifications = true;
173
+ modifications.push({
174
+ type: "literalValue",
175
+ node: initializer,
176
+ value: transformedSrc,
177
+ });
174
178
  }
175
179
  }
176
180
  }
@@ -188,14 +192,14 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
188
192
  if (contentHasChanges && transformedContent) {
189
193
  // Get the raw text with quotes to determine the exact format
190
194
  const isTemplateLiteral = Node.isNoSubstitutionTemplateLiteral(initializer);
191
- if (isTemplateLiteral) {
192
- // Simply wrap the transformed content in backticks
193
- initializer.replaceWithText("`" + transformedContent + "`");
194
- }
195
- else {
196
- initializer.replaceWithText(JSON.stringify(transformedContent));
197
- }
198
- hasModifications = true;
195
+ const replacementText = isTemplateLiteral
196
+ ? "`" + transformedContent + "`"
197
+ : JSON.stringify(transformedContent);
198
+ modifications.push({
199
+ type: "replaceText",
200
+ node: initializer,
201
+ text: replacementText,
202
+ });
199
203
  }
200
204
  }
201
205
  // For link tags, first check if it's a preload/modulepreload
@@ -221,15 +225,16 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
221
225
  !hasNonce &&
222
226
  !hasDangerouslySetInnerHTML &&
223
227
  (hasStringLiteralChildren || hasSrc)) {
224
- // Add nonce property to the props object
225
- propsArg.addPropertyAssignment({
228
+ // Collect nonce property addition
229
+ modifications.push({
230
+ type: "addProperty",
231
+ node: propsArg,
226
232
  name: "nonce",
227
233
  initializer: "requestInfo.rw.nonce",
228
234
  });
229
235
  if (!hasRequestInfoImport) {
230
236
  needsRequestInfoImportRef.value = true;
231
237
  }
232
- hasModifications = true;
233
238
  }
234
239
  // Transform href if this is a preload link
235
240
  if (tagName === "link" &&
@@ -250,17 +255,22 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
250
255
  const quote = isTemplateLiteral
251
256
  ? "`"
252
257
  : originalText.charAt(0);
253
- // Preserve the original quote style
258
+ // Preserve the original quote style and prepare replacement text
259
+ let replacementText;
254
260
  if (isTemplateLiteral) {
255
- initializer.replaceWithText(`\`/${transformedHref}\``);
261
+ replacementText = `\`/${transformedHref}\``;
256
262
  }
257
263
  else if (quote === '"') {
258
- initializer.replaceWithText(`"/${transformedHref}"`);
264
+ replacementText = `"/${transformedHref}"`;
259
265
  }
260
266
  else {
261
- initializer.replaceWithText(`'/${transformedHref}'`);
267
+ replacementText = `'/${transformedHref}'`;
262
268
  }
263
- hasModifications = true;
269
+ modifications.push({
270
+ type: "replaceText",
271
+ node: initializer,
272
+ text: replacementText,
273
+ });
264
274
  }
265
275
  }
266
276
  }
@@ -274,51 +284,103 @@ export async function transformJsxScriptTagsCode(code, manifest = {}) {
274
284
  .join(",\n");
275
285
  const leadingCommentRanges = callExpr.getLeadingCommentRanges();
276
286
  const pureComment = leadingCommentRanges.find((r) => r.getText().includes("@__PURE__"));
287
+ // Store position and static data for later processing
288
+ const wrapInfo = {
289
+ callExpr: callExpr,
290
+ sideEffects: sideEffects,
291
+ pureCommentText: pureComment?.getText(),
292
+ };
293
+ // We'll collect the actual wrap modifications after simple modifications are applied
294
+ if (!entryPointsPerCallExpr.has(callExpr)) {
295
+ entryPointsPerCallExpr.set(callExpr, wrapInfo);
296
+ }
297
+ needsRequestInfoImportRef.value = true;
298
+ }
299
+ });
300
+ // Apply all collected modifications
301
+ if (modifications.length > 0 || entryPointsPerCallExpr.size > 0) {
302
+ // Apply modifications in the right order to avoid invalidating nodes
303
+ // Apply simple modifications first (these are less likely to invalidate other nodes)
304
+ for (const mod of modifications) {
305
+ if (mod.type === "literalValue") {
306
+ mod.node.setLiteralValue(mod.value);
307
+ }
308
+ else if (mod.type === "replaceText") {
309
+ mod.node.replaceWithText(mod.text);
310
+ }
311
+ else if (mod.type === "addProperty") {
312
+ mod.node.addPropertyAssignment({
313
+ name: mod.name,
314
+ initializer: mod.initializer,
315
+ });
316
+ }
317
+ }
318
+ // Apply CallExpr wrapping last (these can invalidate other nodes)
319
+ // Now collect the wrap modifications with fresh data after simple modifications
320
+ const wrapModifications = [];
321
+ for (const [callExpr, wrapInfo] of entryPointsPerCallExpr) {
322
+ const fullStart = callExpr.getFullStart();
323
+ const end = callExpr.getEnd();
277
324
  const callExprText = callExpr.getText();
278
- if (pureComment) {
279
- const pureCommentText = pureComment.getText();
325
+ const fullText = callExpr.getFullText();
326
+ // Extract leading whitespace/newlines before the call expression
327
+ const leadingWhitespace = fullText.substring(0, fullText.length - callExprText.length);
328
+ let pureCommentText;
329
+ let leadingTriviaText;
330
+ if (wrapInfo.pureCommentText) {
331
+ pureCommentText = wrapInfo.pureCommentText;
332
+ leadingTriviaText = leadingWhitespace;
333
+ }
334
+ wrapModifications.push({
335
+ type: "wrapCallExpr",
336
+ sideEffects: wrapInfo.sideEffects,
337
+ pureCommentText: pureCommentText,
338
+ leadingTriviaText: leadingTriviaText,
339
+ fullStart: fullStart,
340
+ end: end,
341
+ callExprText: callExprText,
342
+ leadingWhitespace: leadingWhitespace,
343
+ });
344
+ }
345
+ // Sort by position in reverse order to avoid invalidating later nodes
346
+ wrapModifications.sort((a, b) => b.fullStart - a.fullStart);
347
+ for (const mod of wrapModifications) {
348
+ if (mod.pureCommentText && mod.leadingTriviaText) {
280
349
  const newText = `(
281
- ${sideEffects},
282
- ${pureCommentText} ${callExprText}
283
- )`;
284
- const fullText = callExpr.getFullText();
285
- const leadingTriviaText = fullText.substring(0, fullText.length - callExprText.length);
286
- const newLeadingTriviaText = leadingTriviaText.replace(pureCommentText, "");
350
+ ${mod.sideEffects},
351
+ ${mod.pureCommentText} ${mod.callExprText}
352
+ )`;
353
+ const newLeadingTriviaText = mod.leadingTriviaText.replace(mod.pureCommentText, "");
287
354
  // By replacing from `getFullStart`, we remove the original node and all its leading trivia
288
355
  // and replace it with our manually reconstructed string.
289
356
  // This should correctly move the pure comment and preserve other comments and whitespace.
290
- callExpr
291
- .getSourceFile()
292
- .replaceText([callExpr.getFullStart(), callExpr.getEnd()], newLeadingTriviaText + newText);
357
+ sourceFile.replaceText([mod.fullStart, mod.end], newLeadingTriviaText + newText);
293
358
  }
294
359
  else {
295
- callExpr.replaceWithText(`(
296
- ${sideEffects},
297
- ${callExprText}
298
- )`);
360
+ // Extract just the newlines and basic indentation, ignore extra padding
361
+ const leadingNewlines = mod.leadingWhitespace.match(/\n\s*/)?.[0] || "";
362
+ sourceFile.replaceText([mod.fullStart, mod.end], `${leadingNewlines}(
363
+ ${mod.sideEffects},
364
+ ${mod.callExprText}
365
+ )`);
299
366
  }
300
- needsRequestInfoImportRef.value = true;
301
- hasModifications = true;
302
367
  }
303
- });
304
- // Add requestInfo import if needed and not already imported
305
- if (needsRequestInfoImportRef.value && hasModifications) {
306
- if (sdkWorkerImportDecl) {
307
- // Module is imported but need to add requestInfo
308
- if (!hasRequestInfoImport) {
309
- sdkWorkerImportDecl.addNamedImport("requestInfo");
368
+ // Add requestInfo import if needed and not already imported
369
+ if (needsRequestInfoImportRef.value) {
370
+ if (sdkWorkerImportDecl) {
371
+ // Module is imported but need to add requestInfo
372
+ if (!hasRequestInfoImport) {
373
+ sdkWorkerImportDecl.addNamedImport("requestInfo");
374
+ }
375
+ }
376
+ else {
377
+ // Add new import declaration
378
+ sourceFile.addImportDeclaration({
379
+ moduleSpecifier: "rwsdk/worker",
380
+ namedImports: ["requestInfo"],
381
+ });
310
382
  }
311
383
  }
312
- else {
313
- // Add new import declaration
314
- sourceFile.addImportDeclaration({
315
- moduleSpecifier: "rwsdk/worker",
316
- namedImports: ["requestInfo"],
317
- });
318
- }
319
- }
320
- // Return the transformed code only if modifications were made
321
- if (hasModifications) {
322
384
  return {
323
385
  code: sourceFile.getFullText(),
324
386
  map: null,
@@ -338,6 +400,8 @@ export const transformJsxScriptTagsPlugin = ({ manifestPath, }) => {
338
400
  id.endsWith(".tsx") &&
339
401
  !id.includes("node_modules") &&
340
402
  hasJsxFunctions(code)) {
403
+ log("Transforming JSX script tags in %s", id);
404
+ process.env.VERBOSE && log("Code:\n%s", code);
341
405
  const manifest = isBuild ? await readManifest(manifestPath) : {};
342
406
  const result = await transformJsxScriptTagsCode(code, manifest);
343
407
  if (result) {
@@ -1,5 +1,10 @@
1
1
  import { describe, it, expect } from "vitest";
2
2
  import { transformJsxScriptTagsCode } from "./transformJsxScriptTagsPlugin.mjs";
3
+ import jsBeautify from "js-beautify";
4
+ // Helper function to normalize code formatting for test comparisons
5
+ function normalizeCode(code) {
6
+ return jsBeautify(code, { indent_size: 2 });
7
+ }
3
8
  describe("transformJsxScriptTagsCode", () => {
4
9
  const mockManifest = {
5
10
  "src/client.tsx": { file: "assets/client-a1b2c3d4.js" },
@@ -14,17 +19,17 @@ describe("transformJsxScriptTagsCode", () => {
14
19
  })
15
20
  `;
16
21
  const result = await transformJsxScriptTagsCode(code, mockManifest);
17
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
22
+ const expected = `import { requestInfo } from "rwsdk/worker";
18
23
 
19
- (
20
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
21
- jsx("script", {
22
- src: "/assets/client-a1b2c3d4.js",
23
- type: "module",
24
- nonce: requestInfo.rw.nonce
25
- })
26
- )
27
- `);
24
+ (
25
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
26
+ jsx("script", {
27
+ src: "/assets/client-a1b2c3d4.js",
28
+ type: "module",
29
+ nonce: requestInfo.rw.nonce
30
+ })
31
+ )`;
32
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
28
33
  });
29
34
  it("transforms inline scripts with dynamic imports", async () => {
30
35
  const code = `
@@ -34,32 +39,32 @@ describe("transformJsxScriptTagsCode", () => {
34
39
  })
35
40
  `;
36
41
  const result = await transformJsxScriptTagsCode(code, mockManifest);
37
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
42
+ const expected = `import { requestInfo } from "rwsdk/worker";
38
43
 
39
- (
40
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
41
- jsx("script", {
42
- type: "module",
43
- children: "import('/assets/client-a1b2c3d4.js').then(module => { console.log(module); })",
44
- nonce: requestInfo.rw.nonce
45
- })
46
- )
47
- `);
44
+ (
45
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
46
+ jsx("script", {
47
+ type: "module",
48
+ children: "import('/assets/client-a1b2c3d4.js').then(module => { console.log(module); })",
49
+ nonce: requestInfo.rw.nonce
50
+ })
51
+ )`;
52
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
48
53
  });
49
54
  it("transforms inline scripts with type=module", async () => {
50
55
  const code = `
51
56
  jsx("script", { type: "module", children: "import('/src/client.tsx')" })
52
57
  `;
53
58
  const result = await transformJsxScriptTagsCode(code, mockManifest);
54
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
59
+ const expected = `import { requestInfo } from "rwsdk/worker";
55
60
 
56
- (
57
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
58
- jsx("script", { type: "module", children: "import('/assets/client-a1b2c3d4.js')",
59
- nonce: requestInfo.rw.nonce
60
- })
61
- )
62
- `);
61
+ (
62
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
63
+ jsx("script", { type: "module", children: "import('/assets/client-a1b2c3d4.js')",
64
+ nonce: requestInfo.rw.nonce
65
+ })
66
+ )`;
67
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
63
68
  });
64
69
  it("transforms inline scripts with multiline content", async () => {
65
70
  const code = `
@@ -76,13 +81,13 @@ describe("transformJsxScriptTagsCode", () => {
76
81
  })
77
82
  `;
78
83
  const result = await transformJsxScriptTagsCode(code, mockManifest);
79
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
84
+ const expected = `import { requestInfo } from "rwsdk/worker";
80
85
 
81
- (
82
- (requestInfo.rw.scriptsToBeLoaded.add("/src/entry.js")),
83
- jsx("script", {
84
- type: "module",
85
- children: \`
86
+ (
87
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/entry.js")),
88
+ jsx("script", {
89
+ type: "module",
90
+ children: \`
86
91
  // Some comments here
87
92
  const init = async () => {
88
93
  await import('/assets/entry-e5f6g7h8.js');
@@ -90,37 +95,37 @@ describe("transformJsxScriptTagsCode", () => {
90
95
  };
91
96
  init();
92
97
  \`,
93
- nonce: requestInfo.rw.nonce
94
- })
95
- )
96
- `);
98
+ nonce: requestInfo.rw.nonce
99
+ })
100
+ )`;
101
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
97
102
  });
98
103
  it("transforms multiple imports in the same inline script", async () => {
99
104
  const code = `
100
105
  jsx("script", {
101
106
  type: "module",
102
107
  children: \`
103
- import('/src/client.tsx');
104
- import('/src/entry.js');
105
- \`
108
+ import('/src/client.tsx');
109
+ import('/src/entry.js');
110
+ \`
106
111
  })
107
112
  `;
108
113
  const result = await transformJsxScriptTagsCode(code, mockManifest);
109
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
114
+ const expected = `import { requestInfo } from "rwsdk/worker";
110
115
 
111
- (
112
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
116
+ (
117
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
113
118
  (requestInfo.rw.scriptsToBeLoaded.add("/src/entry.js")),
114
- jsx("script", {
115
- type: "module",
116
- children: \`
117
- import('/assets/client-a1b2c3d4.js');
118
- import('/assets/entry-e5f6g7h8.js');
119
- \`,
120
- nonce: requestInfo.rw.nonce
121
- })
122
- )
123
- `);
119
+ jsx("script", {
120
+ type: "module",
121
+ children: \`
122
+ import('/assets/client-a1b2c3d4.js');
123
+ import('/assets/entry-e5f6g7h8.js');
124
+ \`,
125
+ nonce: requestInfo.rw.nonce
126
+ })
127
+ )`;
128
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
124
129
  });
125
130
  it("transforms link href attributes with preload rel", async () => {
126
131
  const code = `
@@ -177,33 +182,33 @@ describe("transformJsxScriptTagsCode", () => {
177
182
  })
178
183
  `;
179
184
  const result = await transformJsxScriptTagsCode(code, mockManifest);
180
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
185
+ const expected = `import { requestInfo } from "rwsdk/worker";
181
186
 
182
- jsx("html", {
183
- lang: "en",
184
- children: [
185
- jsx("head", {
186
- children: [
187
- jsx("meta", { charSet: "utf-8" }),
188
- jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
189
- jsx("title", { children: "@redwoodjs/starter-standard" }),
190
- jsx("link", { rel: "modulepreload", href: "/assets/client-a1b2c3d4.js", as: "script" })
191
- ]
192
- }),
193
- jsx("body", {
194
- children: [
195
- jsx("div", { id: "root", children: props.children }),
196
- (
197
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
198
- jsx("script", { children: "import(\\"\/assets\/client-a1b2c3d4.js\\")",
199
- nonce: requestInfo.rw.nonce
200
- })
201
- )
202
- ]
203
- })
204
- ]
205
- })
206
- `);
187
+ jsx("html", {
188
+ lang: "en",
189
+ children: [
190
+ jsx("head", {
191
+ children: [
192
+ jsx("meta", { charSet: "utf-8" }),
193
+ jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
194
+ jsx("title", { children: "@redwoodjs/starter-standard" }),
195
+ jsx("link", { rel: "modulepreload", href: "/assets/client-a1b2c3d4.js", as: "script" })
196
+ ]
197
+ }),
198
+ jsx("body", {
199
+ children: [
200
+ jsx("div", { id: "root", children: props.children }),
201
+ (
202
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
203
+ jsx("script", { children: "import(\\"/assets/client-a1b2c3d4.js\\")",
204
+ nonce: requestInfo.rw.nonce
205
+ })
206
+ )
207
+ ]
208
+ })
209
+ ]
210
+ })`;
211
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
207
212
  });
208
213
  it("returns null when no transformations are needed", async () => {
209
214
  const code = `
@@ -220,17 +225,17 @@ describe("transformJsxScriptTagsCode", () => {
220
225
  })
221
226
  `;
222
227
  const result = await transformJsxScriptTagsCode(code, mockManifest);
223
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
228
+ const expected = `import { requestInfo } from "rwsdk/worker";
224
229
 
225
- (
226
- (requestInfo.rw.scriptsToBeLoaded.add("/src/non-existent.js")),
227
- jsx("script", {
228
- src: "/src/non-existent.js",
229
- type: "module",
230
- nonce: requestInfo.rw.nonce
231
- })
232
- )
233
- `);
230
+ (
231
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/non-existent.js")),
232
+ jsx("script", {
233
+ src: "/src/non-existent.js",
234
+ type: "module",
235
+ nonce: requestInfo.rw.nonce
236
+ })
237
+ )`;
238
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
234
239
  });
235
240
  it("adds nonce to script tags with src attribute and imports requestInfo", async () => {
236
241
  const code = `
@@ -240,17 +245,17 @@ describe("transformJsxScriptTagsCode", () => {
240
245
  })
241
246
  `;
242
247
  const result = await transformJsxScriptTagsCode(code, mockManifest);
243
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
248
+ const expected = `import { requestInfo } from "rwsdk/worker";
244
249
 
245
- (
246
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
247
- jsx("script", {
248
- src: "/assets/client-a1b2c3d4.js",
249
- type: "module",
250
- nonce: requestInfo.rw.nonce
251
- })
252
- )
253
- `);
250
+ (
251
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
252
+ jsx("script", {
253
+ src: "/assets/client-a1b2c3d4.js",
254
+ type: "module",
255
+ nonce: requestInfo.rw.nonce
256
+ })
257
+ )`;
258
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
254
259
  });
255
260
  it("adds nonce to script tags with string literal children", async () => {
256
261
  const code = `
@@ -347,16 +352,242 @@ describe("transformJsxScriptTagsCode", () => {
347
352
  `;
348
353
  // Call without providing manifest (simulating dev mode)
349
354
  const result = await transformJsxScriptTagsCode(code);
350
- expect(result?.code).toEqual(`import { requestInfo } from "rwsdk/worker";
355
+ const expected = `import { requestInfo } from "rwsdk/worker";
351
356
 
352
- (
353
- (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
354
- jsx("script", {
355
- src: "/src/client.tsx",
356
- type: "module",
357
- nonce: requestInfo.rw.nonce
358
- })
359
- )
360
- `);
357
+ (
358
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
359
+ jsx("script", {
360
+ src: "/src/client.tsx",
361
+ type: "module",
362
+ nonce: requestInfo.rw.nonce
363
+ })
364
+ )`;
365
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
366
+ });
367
+ it("regression favicon links", async () => {
368
+ const code = `
369
+ import { jsxDEV } from "react/jsx-dev-runtime";
370
+ import styles from "./index.css?url";
371
+ export const Document = ({
372
+ children
373
+ }) => /* @__PURE__ */ jsxDEV("html", { lang: "en", children: [
374
+ /* @__PURE__ */ jsxDEV("head", { children: [
375
+ /* @__PURE__ */ jsxDEV("meta", { charSet: "utf-8" }, void 0, false, {
376
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
377
+ lineNumber: 8,
378
+ columnNumber: 4
379
+ }, this),
380
+ /* @__PURE__ */ jsxDEV("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }, void 0, false, {
381
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
382
+ lineNumber: 9,
383
+ columnNumber: 4
384
+ }, this),
385
+ /* @__PURE__ */ jsxDEV("title", { children: "rwsdk-guestbook" }, void 0, false, {
386
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
387
+ lineNumber: 10,
388
+ columnNumber: 4
389
+ }, this),
390
+ /* @__PURE__ */ jsxDEV("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }, void 0, false, {
391
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
392
+ lineNumber: 11,
393
+ columnNumber: 4
394
+ }, this),
395
+ /* @__PURE__ */ jsxDEV(
396
+ "link",
397
+ {
398
+ rel: "preconnect",
399
+ href: "https://fonts.gstatic.com",
400
+ crossOrigin: "anonymous"
401
+ },
402
+ void 0,
403
+ false,
404
+ {
405
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
406
+ lineNumber: 12,
407
+ columnNumber: 4
408
+ },
409
+ this
410
+ ),
411
+ /* @__PURE__ */ jsxDEV(
412
+ "link",
413
+ {
414
+ href: "https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap",
415
+ rel: "stylesheet"
416
+ },
417
+ void 0,
418
+ false,
419
+ {
420
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
421
+ lineNumber: 17,
422
+ columnNumber: 4
423
+ },
424
+ this
425
+ ),
426
+ /* @__PURE__ */ jsxDEV("script", { src: "/theme-script.js" }, void 0, false, {
427
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
428
+ lineNumber: 21,
429
+ columnNumber: 4
430
+ }, this),
431
+ /* @__PURE__ */ jsxDEV("link", { rel: "icon", href: "/favicon.svg" }, void 0, false, {
432
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
433
+ lineNumber: 22,
434
+ columnNumber: 4
435
+ }, this),
436
+ /* @__PURE__ */ jsxDEV("link", { rel: "modulepreload", href: "/src/client.tsx" }, void 0, false, {
437
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
438
+ lineNumber: 23,
439
+ columnNumber: 4
440
+ }, this),
441
+ /* @__PURE__ */ jsxDEV("link", { rel: "stylesheet", href: styles }, void 0, false, {
442
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
443
+ lineNumber: 24,
444
+ columnNumber: 4
445
+ }, this)
446
+ ] }, void 0, true, {
447
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
448
+ lineNumber: 7,
449
+ columnNumber: 3
450
+ }, this),
451
+ /* @__PURE__ */ jsxDEV("body", { children: [
452
+ /* @__PURE__ */ jsxDEV("div", { id: "root", children }, void 0, false, {
453
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
454
+ lineNumber: 27,
455
+ columnNumber: 4
456
+ }, this),
457
+ /* @__PURE__ */ jsxDEV("script", { children: 'import("/src/client.tsx")' }, void 0, false, {
458
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
459
+ lineNumber: 28,
460
+ columnNumber: 4
461
+ }, this)
462
+ ] }, void 0, true, {
463
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
464
+ lineNumber: 26,
465
+ columnNumber: 3
466
+ }, this)
467
+ ] }, void 0, true, {
468
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
469
+ lineNumber: 6,
470
+ columnNumber: 2
471
+ }, this);
472
+ `;
473
+ const result = await transformJsxScriptTagsCode(code, mockManifest);
474
+ // For this complex test, we'll just verify the key transformations
475
+ const expected = `
476
+ import { jsxDEV } from "react/jsx-dev-runtime";
477
+ import styles from "./index.css?url";
478
+ import { requestInfo } from "rwsdk/worker";
479
+
480
+ export const Document = ({
481
+ children
482
+ }) => /* @__PURE__ */ jsxDEV("html", { lang: "en", children: [
483
+ /* @__PURE__ */ jsxDEV("head", { children: [
484
+ /* @__PURE__ */ jsxDEV("meta", { charSet: "utf-8" }, void 0, false, {
485
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
486
+ lineNumber: 8,
487
+ columnNumber: 4
488
+ }, this),
489
+ /* @__PURE__ */ jsxDEV("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }, void 0, false, {
490
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
491
+ lineNumber: 9,
492
+ columnNumber: 4
493
+ }, this),
494
+ /* @__PURE__ */ jsxDEV("title", { children: "rwsdk-guestbook" }, void 0, false, {
495
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
496
+ lineNumber: 10,
497
+ columnNumber: 4
498
+ }, this),
499
+ /* @__PURE__ */ jsxDEV("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }, void 0, false, {
500
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
501
+ lineNumber: 11,
502
+ columnNumber: 4
503
+ }, this),
504
+ /* @__PURE__ */ jsxDEV(
505
+ "link",
506
+ {
507
+ rel: "preconnect",
508
+ href: "https://fonts.gstatic.com",
509
+ crossOrigin: "anonymous"
510
+ },
511
+ void 0,
512
+ false,
513
+ {
514
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
515
+ lineNumber: 12,
516
+ columnNumber: 4
517
+ },
518
+ this
519
+ ),
520
+ /* @__PURE__ */ jsxDEV(
521
+ "link",
522
+ {
523
+ href: "https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&display=swap",
524
+ rel: "stylesheet"
525
+ },
526
+ void 0,
527
+ false,
528
+ {
529
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
530
+ lineNumber: 17,
531
+ columnNumber: 4
532
+ },
533
+ this
534
+ ),
535
+ (
536
+ (requestInfo.rw.scriptsToBeLoaded.add("/theme-script.js")),
537
+ /* @__PURE__ */ jsxDEV("script", { src: "/theme-script.js",
538
+ nonce: requestInfo.rw.nonce
539
+ }, void 0, false, {
540
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
541
+ lineNumber: 21,
542
+ columnNumber: 4
543
+ }, this)
544
+ ),
545
+ /* @__PURE__ */ jsxDEV("link", { rel: "icon", href: "/favicon.svg" }, void 0, false, {
546
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
547
+ lineNumber: 22,
548
+ columnNumber: 4
549
+ }, this),
550
+ /* @__PURE__ */ jsxDEV("link", { rel: "modulepreload", href: "/assets/client-a1b2c3d4.js" }, void 0, false, {
551
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
552
+ lineNumber: 23,
553
+ columnNumber: 4
554
+ }, this),
555
+ /* @__PURE__ */ jsxDEV("link", { rel: "stylesheet", href: styles }, void 0, false, {
556
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
557
+ lineNumber: 24,
558
+ columnNumber: 4
559
+ }, this)
560
+ ] }, void 0, true, {
561
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
562
+ lineNumber: 7,
563
+ columnNumber: 3
564
+ }, this),
565
+ /* @__PURE__ */ jsxDEV("body", { children: [
566
+ /* @__PURE__ */ jsxDEV("div", { id: "root", children }, void 0, false, {
567
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
568
+ lineNumber: 27,
569
+ columnNumber: 4
570
+ }, this),
571
+ (
572
+ (requestInfo.rw.scriptsToBeLoaded.add("/src/client.tsx")),
573
+ /* @__PURE__ */ jsxDEV("script", { children: "import(\\"/assets/client-a1b2c3d4.js\\")",
574
+ nonce: requestInfo.rw.nonce
575
+ }, void 0, false, {
576
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
577
+ lineNumber: 28,
578
+ columnNumber: 4
579
+ }, this)
580
+ )
581
+ ] }, void 0, true, {
582
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
583
+ lineNumber: 26,
584
+ columnNumber: 3
585
+ }, this)
586
+ ] }, void 0, true, {
587
+ fileName: "/Users/justin/rw/blotter/rwsdk-guestbook/src/app/document/Document.tsx",
588
+ lineNumber: 6,
589
+ columnNumber: 2
590
+ }, this);`;
591
+ expect(normalizeCode(result?.code || "")).toEqual(normalizeCode(expected));
361
592
  });
362
593
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rwsdk",
3
- "version": "0.2.0-alpha.16",
3
+ "version": "0.2.0-alpha.16-test.20250825032050",
4
4
  "description": "Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -154,10 +154,10 @@
154
154
  "wrangler": "^4.20.5"
155
155
  },
156
156
  "peerDependencies": {
157
- "vite": "^6.2.6",
158
157
  "react": ">=19.2.0-0 <20",
159
158
  "react-dom": ">=19.2.0-0 <20",
160
- "react-server-dom-webpack": ">=19.2.0-0 <20"
159
+ "react-server-dom-webpack": ">=19.2.0-0 <20",
160
+ "vite": "^6.2.6"
161
161
  },
162
162
  "optionalDependencies": {
163
163
  "react": "19.2.0-canary-39cad7af-20250411",
@@ -178,9 +178,11 @@
178
178
  "packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab",
179
179
  "devDependencies": {
180
180
  "@types/debug": "^4.1.12",
181
+ "@types/js-beautify": "^1.14.3",
181
182
  "@types/lodash": "^4.17.16",
182
183
  "@types/node": "^22.14.0",
183
184
  "@types/proper-lockfile": "^4.1.4",
185
+ "js-beautify": "^1.15.4",
184
186
  "semver": "^7.7.1",
185
187
  "tsx": "^4.19.4",
186
188
  "typescript": "^5.8.3",