rwsdk 1.0.0-alpha.1-test.20250911154541 → 1.0.0-alpha.10

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 (137) hide show
  1. package/dist/lib/e2e/browser.d.mts +10 -0
  2. package/dist/lib/e2e/browser.mjs +124 -0
  3. package/dist/lib/e2e/dev.d.mts +8 -0
  4. package/dist/lib/e2e/dev.mjs +232 -0
  5. package/dist/lib/e2e/environment.d.mts +14 -0
  6. package/dist/lib/e2e/environment.mjs +223 -0
  7. package/dist/lib/e2e/index.d.mts +7 -0
  8. package/dist/lib/e2e/index.mjs +7 -0
  9. package/dist/lib/e2e/release.d.mts +56 -0
  10. package/dist/lib/e2e/release.mjs +559 -0
  11. package/dist/lib/e2e/tarball.d.mts +13 -0
  12. package/dist/lib/e2e/tarball.mjs +99 -0
  13. package/dist/lib/e2e/testHarness.d.mts +123 -0
  14. package/dist/lib/e2e/testHarness.mjs +507 -0
  15. package/dist/lib/e2e/types.d.mts +32 -0
  16. package/dist/lib/getShortName.mjs +6 -1
  17. package/dist/lib/getShortName.test.d.mts +1 -0
  18. package/dist/lib/getShortName.test.mjs +25 -0
  19. package/dist/lib/hasPkgScript.d.mts +4 -1
  20. package/dist/lib/hasPkgScript.mjs +9 -6
  21. package/dist/lib/hasPkgScript.test.d.mts +1 -0
  22. package/dist/lib/hasPkgScript.test.mjs +33 -0
  23. package/dist/lib/jsonUtils.mjs +3 -0
  24. package/dist/lib/jsonUtils.test.d.mts +1 -0
  25. package/dist/lib/jsonUtils.test.mjs +90 -0
  26. package/dist/lib/normalizeModulePath.d.mts +5 -0
  27. package/dist/lib/normalizeModulePath.mjs +1 -1
  28. package/dist/lib/normalizeModulePath.test.d.mts +1 -0
  29. package/dist/lib/{normalizeModulePath.test.js → normalizeModulePath.test.mjs} +20 -1
  30. package/dist/lib/smokeTests/browser.mjs +3 -94
  31. package/dist/lib/smokeTests/development.mjs +2 -223
  32. package/dist/lib/smokeTests/environment.d.mts +4 -11
  33. package/dist/lib/smokeTests/environment.mjs +10 -158
  34. package/dist/lib/smokeTests/release.d.mts +2 -49
  35. package/dist/lib/smokeTests/release.mjs +3 -503
  36. package/dist/runtime/lib/injectHtmlAtMarker.d.ts +11 -0
  37. package/dist/runtime/lib/injectHtmlAtMarker.js +90 -0
  38. package/dist/runtime/lib/memoizeOnId.test.d.ts +1 -0
  39. package/dist/runtime/lib/memoizeOnId.test.js +49 -0
  40. package/dist/runtime/lib/realtime/protocol.test.d.ts +1 -0
  41. package/dist/runtime/lib/realtime/protocol.test.js +107 -0
  42. package/dist/runtime/lib/realtime/shared.test.d.ts +1 -0
  43. package/dist/runtime/lib/realtime/shared.test.js +18 -0
  44. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.d.ts +1 -0
  45. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +66 -0
  46. package/dist/runtime/lib/realtime/worker.d.ts +1 -1
  47. package/dist/runtime/lib/router.js +40 -22
  48. package/dist/runtime/lib/router.test.js +591 -2
  49. package/dist/runtime/lib/rwContext.d.ts +22 -0
  50. package/dist/runtime/lib/rwContext.js +1 -0
  51. package/dist/runtime/lib/turnstile/verifyTurnstileToken.d.ts +2 -1
  52. package/dist/runtime/lib/turnstile/verifyTurnstileToken.js +6 -6
  53. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.d.ts +1 -0
  54. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +49 -0
  55. package/dist/runtime/register/worker.d.ts +1 -1
  56. package/dist/runtime/register/worker.js +26 -21
  57. package/dist/runtime/render/assembleDocument.d.ts +6 -0
  58. package/dist/runtime/render/assembleDocument.js +22 -0
  59. package/dist/runtime/render/createThenableFromReadableStream.d.ts +1 -0
  60. package/dist/runtime/render/createThenableFromReadableStream.js +9 -0
  61. package/dist/runtime/render/normalizeActionResult.d.ts +1 -0
  62. package/dist/runtime/render/normalizeActionResult.js +43 -0
  63. package/dist/runtime/render/preloads.d.ts +2 -2
  64. package/dist/runtime/render/preloads.js +2 -3
  65. package/dist/runtime/render/{renderRscThenableToHtmlStream.d.ts → renderDocumentHtmlStream.d.ts} +3 -3
  66. package/dist/runtime/render/renderDocumentHtmlStream.js +39 -0
  67. package/dist/runtime/render/renderHtmlStream.d.ts +7 -0
  68. package/dist/runtime/render/renderHtmlStream.js +31 -0
  69. package/dist/runtime/render/renderToRscStream.d.ts +2 -3
  70. package/dist/runtime/render/renderToRscStream.js +2 -41
  71. package/dist/runtime/render/renderToStream.d.ts +2 -1
  72. package/dist/runtime/render/renderToStream.js +15 -8
  73. package/dist/runtime/render/stylesheets.d.ts +2 -2
  74. package/dist/runtime/render/stylesheets.js +2 -3
  75. package/dist/runtime/ssrBridge.d.ts +2 -1
  76. package/dist/runtime/ssrBridge.js +2 -1
  77. package/dist/runtime/worker.d.ts +1 -0
  78. package/dist/runtime/worker.js +11 -6
  79. package/dist/scripts/debug-sync.mjs +102 -133
  80. package/dist/vite/buildApp.d.mts +2 -1
  81. package/dist/vite/buildApp.mjs +9 -5
  82. package/dist/vite/checkIsUsingPrisma.d.mts +4 -0
  83. package/dist/vite/checkIsUsingPrisma.mjs +2 -2
  84. package/dist/vite/checkIsUsingPrisma.test.d.mts +1 -0
  85. package/dist/vite/checkIsUsingPrisma.test.mjs +30 -0
  86. package/dist/vite/configPlugin.mjs +35 -14
  87. package/dist/vite/createDirectiveLookupPlugin.d.mts +9 -0
  88. package/dist/vite/createDirectiveLookupPlugin.mjs +33 -29
  89. package/dist/vite/createDirectiveLookupPlugin.test.d.mts +1 -0
  90. package/dist/vite/createDirectiveLookupPlugin.test.mjs +40 -0
  91. package/dist/vite/directiveModulesDevPlugin.d.mts +4 -1
  92. package/dist/vite/directiveModulesDevPlugin.mjs +5 -4
  93. package/dist/vite/directiveModulesDevPlugin.test.d.mts +1 -0
  94. package/dist/vite/directiveModulesDevPlugin.test.mjs +59 -0
  95. package/dist/vite/directivesPlugin.d.mts +1 -0
  96. package/dist/vite/directivesPlugin.mjs +1 -1
  97. package/dist/vite/directivesPlugin.test.d.mts +1 -0
  98. package/dist/vite/directivesPlugin.test.mjs +24 -0
  99. package/dist/vite/ensureAliasArray.test.d.mts +1 -0
  100. package/dist/vite/ensureAliasArray.test.mjs +71 -0
  101. package/dist/vite/findSpecifiers.mjs +2 -1
  102. package/dist/vite/findSpecifiers.test.d.mts +1 -0
  103. package/dist/vite/findSpecifiers.test.mjs +202 -0
  104. package/dist/vite/findSsrSpecifiers.test.d.mts +1 -0
  105. package/dist/vite/findSsrSpecifiers.test.mjs +99 -0
  106. package/dist/vite/hasDirective.d.mts +6 -3
  107. package/dist/vite/hasDirective.mjs +43 -27
  108. package/dist/vite/hasDirective.test.d.mts +1 -0
  109. package/dist/vite/hasDirective.test.mjs +107 -0
  110. package/dist/vite/isJsFile.test.d.mts +1 -0
  111. package/dist/vite/isJsFile.test.mjs +38 -0
  112. package/dist/vite/{reactConditionsResolverPlugin.d.mts → knownDepsResolverPlugin.d.mts} +2 -2
  113. package/dist/vite/{reactConditionsResolverPlugin.mjs → knownDepsResolverPlugin.mjs} +28 -23
  114. package/dist/vite/linkerPlugin.d.mts +8 -0
  115. package/dist/vite/linkerPlugin.mjs +30 -22
  116. package/dist/vite/linkerPlugin.test.d.mts +1 -0
  117. package/dist/vite/linkerPlugin.test.mjs +41 -0
  118. package/dist/vite/miniflareHMRPlugin.d.mts +5 -0
  119. package/dist/vite/miniflareHMRPlugin.mjs +2 -2
  120. package/dist/vite/miniflareHMRPlugin.test.d.mts +1 -0
  121. package/dist/vite/miniflareHMRPlugin.test.mjs +42 -0
  122. package/dist/vite/redwoodPlugin.d.mts +7 -0
  123. package/dist/vite/redwoodPlugin.mjs +10 -5
  124. package/dist/vite/redwoodPlugin.test.d.mts +1 -0
  125. package/dist/vite/redwoodPlugin.test.mjs +34 -0
  126. package/dist/vite/runDirectivesScan.d.mts +21 -1
  127. package/dist/vite/runDirectivesScan.mjs +67 -52
  128. package/dist/vite/runDirectivesScan.test.d.mts +1 -0
  129. package/dist/vite/runDirectivesScan.test.mjs +73 -0
  130. package/dist/vite/ssrBridgePlugin.mjs +8 -1
  131. package/dist/vite/transformClientComponents.mjs +4 -3
  132. package/dist/vite/transformClientComponents.test.mjs +116 -58
  133. package/package.json +8 -4
  134. package/dist/runtime/render/renderRscThenableToHtmlStream.js +0 -54
  135. package/dist/runtime/render/transformRscToHtmlStream.d.ts +0 -8
  136. package/dist/runtime/render/transformRscToHtmlStream.js +0 -19
  137. /package/dist/lib/{normalizeModulePath.test.d.ts → e2e/types.mjs} +0 -0
@@ -0,0 +1,10 @@
1
+ import { SmokeTestOptions } from "./types.mjs";
2
+ import type { Browser } from "puppeteer-core";
3
+ /**
4
+ * Launch a browser instance
5
+ */
6
+ export declare function launchBrowser(browserPath?: string, headless?: boolean): Promise<Browser>;
7
+ /**
8
+ * Get the browser executable path
9
+ */
10
+ export declare function getBrowserPath(testOptions?: SmokeTestOptions): Promise<string>;
@@ -0,0 +1,124 @@
1
+ import * as os from "os";
2
+ import { join } from "path";
3
+ import { pathExists } from "fs-extra";
4
+ import debug from "debug";
5
+ import { mkdirp } from "fs-extra";
6
+ import { install, resolveBuildId, computeExecutablePath, detectBrowserPlatform, Browser as PuppeteerBrowser, } from "@puppeteer/browsers";
7
+ import puppeteer from "puppeteer-core";
8
+ const log = debug("rwsdk:e2e:browser");
9
+ /**
10
+ * Launch a browser instance
11
+ */
12
+ export async function launchBrowser(browserPath, headless = true) {
13
+ // Get browser path if not provided
14
+ if (!browserPath) {
15
+ log("Getting browser executable path");
16
+ browserPath = await getBrowserPath({ headless });
17
+ }
18
+ console.log(`🚀 Launching browser from ${browserPath} (headless: ${headless})`);
19
+ log("Starting browser with puppeteer");
20
+ return await puppeteer.launch({
21
+ executablePath: browserPath,
22
+ headless,
23
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
24
+ });
25
+ }
26
+ /**
27
+ * Get the browser executable path
28
+ */
29
+ export async function getBrowserPath(testOptions) {
30
+ console.log("Finding Chrome executable...");
31
+ // First try using environment variable if set
32
+ if (process.env.CHROME_PATH) {
33
+ console.log(`Using Chrome from environment variable: ${process.env.CHROME_PATH}`);
34
+ return process.env.CHROME_PATH;
35
+ }
36
+ // Detect platform
37
+ log("Detecting platform");
38
+ const platform = detectBrowserPlatform();
39
+ if (!platform) {
40
+ log("ERROR: Failed to detect browser platform");
41
+ throw new Error("Failed to detect browser platform");
42
+ }
43
+ log("Detected platform: %s", platform);
44
+ // Define a consistent cache directory path in system temp folder
45
+ const rwCacheDir = join(os.tmpdir(), "redwoodjs-smoke-test-cache");
46
+ await mkdirp(rwCacheDir);
47
+ log("Using cache directory: %s", rwCacheDir);
48
+ // Determine browser type based on headless option
49
+ const browser = testOptions?.headless === false
50
+ ? PuppeteerBrowser.CHROME
51
+ : PuppeteerBrowser.CHROMEHEADLESSSHELL;
52
+ log(`Using browser type: ${browser}`);
53
+ // Resolve the buildId for the stable Chrome version - do this once
54
+ log("Resolving Chrome buildId for stable channel");
55
+ const buildId = await resolveBuildId(browser, platform, "stable");
56
+ log("Resolved buildId: %s", buildId);
57
+ // Create installation options - use them consistently
58
+ const installOptions = {
59
+ browser,
60
+ platform,
61
+ cacheDir: rwCacheDir,
62
+ buildId,
63
+ unpack: true,
64
+ };
65
+ try {
66
+ // Try to compute the path first (this will check if it's installed)
67
+ log("Attempting to find existing Chrome installation");
68
+ const path = computeExecutablePath(installOptions);
69
+ if (await pathExists(path)) {
70
+ console.log(`Found existing Chrome at: ${path}`);
71
+ return path;
72
+ }
73
+ else {
74
+ throw new Error("Chrome not found at: " + path);
75
+ }
76
+ }
77
+ catch (error) {
78
+ // If path computation fails, install Chrome
79
+ console.log("No Chrome installation found. Installing Chrome...");
80
+ // Add better error handling for the install step
81
+ try {
82
+ console.log("Downloading Chrome (this may take a few minutes)...");
83
+ const attempts = 10;
84
+ let installError;
85
+ for (let i = 0; i < attempts; i++) {
86
+ try {
87
+ await install(installOptions);
88
+ installError = null; // Reset error on success
89
+ break; // Exit loop on success
90
+ }
91
+ catch (e) {
92
+ installError = e;
93
+ console.log(`Attempt ${i + 1}/${attempts} failed. Retrying in 1 second...`);
94
+ // Wait for 1 second before retrying
95
+ await new Promise((resolve) => setTimeout(resolve, 1000));
96
+ }
97
+ }
98
+ if (installError) {
99
+ throw installError;
100
+ }
101
+ console.log("✅ Chrome installation completed successfully");
102
+ // Now compute the path for the installed browser
103
+ const path = computeExecutablePath(installOptions);
104
+ console.log(`Installed and using Chrome at: ${path}`);
105
+ return path;
106
+ }
107
+ catch (installError) {
108
+ // Provide more detailed error about the browser download failure
109
+ log("ERROR: Failed to download/install Chrome: %O", installError);
110
+ console.error(`❌ Failed to download/install Chrome browser.`);
111
+ console.error(`This is likely a network issue or the browser download URL is unavailable.`);
112
+ console.error(`Error details: ${installError instanceof Error ? installError.message : String(installError)}`);
113
+ // For debug builds, show the full error stack if available
114
+ if (installError instanceof Error && installError.stack) {
115
+ log("Error stack: %s", installError.stack);
116
+ }
117
+ console.log("\nPossible solutions:");
118
+ console.log("1. Check your internet connection");
119
+ console.log("2. Set CHROME_PATH environment variable to an existing Chrome installation");
120
+ console.log("3. Install Chrome manually and run the tests again");
121
+ throw new Error(`Failed to install Chrome browser: ${installError instanceof Error ? installError.message : String(installError)}`);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,8 @@
1
+ import { PackageManager } from "./types.mjs";
2
+ /**
3
+ * Run the local development server and return the URL
4
+ */
5
+ export declare function runDevServer(packageManager?: PackageManager, cwd?: string): Promise<{
6
+ url: string;
7
+ stopDev: () => Promise<void>;
8
+ }>;
@@ -0,0 +1,232 @@
1
+ import { setTimeout } from "node:timers/promises";
2
+ import debug from "debug";
3
+ import { $ } from "../../lib/$.mjs";
4
+ const log = debug("rwsdk:e2e:dev");
5
+ /**
6
+ * Run the local development server and return the URL
7
+ */
8
+ export async function runDevServer(packageManager = "pnpm", cwd) {
9
+ console.log("🚀 Starting development server...");
10
+ // Function to stop the dev server - defined early so we can use it in error handling
11
+ let devProcess = null;
12
+ let isErrorExpected = false;
13
+ const stopDev = async () => {
14
+ isErrorExpected = true;
15
+ if (!devProcess) {
16
+ log("No dev process to stop");
17
+ return;
18
+ }
19
+ console.log("Stopping development server...");
20
+ try {
21
+ // Send a regular termination signal first
22
+ devProcess.kill();
23
+ // Wait for the process to terminate with a timeout
24
+ const terminationTimeout = 5000; // 5 seconds timeout
25
+ const terminationPromise = Promise.race([
26
+ // Wait for natural process termination
27
+ (async () => {
28
+ try {
29
+ await devProcess;
30
+ log("Dev server process was terminated normally");
31
+ return true;
32
+ }
33
+ catch (e) {
34
+ // Expected error when the process is killed
35
+ log("Dev server process was terminated");
36
+ return true;
37
+ }
38
+ })(),
39
+ // Or timeout
40
+ (async () => {
41
+ await setTimeout(terminationTimeout);
42
+ return false;
43
+ })(),
44
+ ]);
45
+ // Check if process terminated within timeout
46
+ const terminated = await terminationPromise;
47
+ // If not terminated within timeout, force kill
48
+ if (!terminated) {
49
+ log("Dev server process did not terminate within timeout, force killing with SIGKILL");
50
+ console.log("⚠️ Development server not responding after 5 seconds timeout, force killing...");
51
+ // Try to kill with SIGKILL if the process still has a pid
52
+ if (devProcess.pid) {
53
+ try {
54
+ // Use process.kill with SIGKILL for a stronger termination
55
+ process.kill(devProcess.pid, "SIGKILL");
56
+ log("Sent SIGKILL to process %d", devProcess.pid);
57
+ }
58
+ catch (killError) {
59
+ log("Error sending SIGKILL to process: %O", killError);
60
+ // Non-fatal, as the process might already be gone
61
+ }
62
+ }
63
+ }
64
+ }
65
+ catch (e) {
66
+ // Process might already have exited
67
+ log("Could not kill dev server process: %O", e);
68
+ }
69
+ console.log("Development server stopped");
70
+ };
71
+ try {
72
+ // Check if we're in CI mode
73
+ const inCIMode = process.env.CI === "true" || process.env.CI === "1";
74
+ // Start dev server with stdout pipe to capture URL
75
+ // Create environment variables object
76
+ const env = {
77
+ ...process.env,
78
+ NODE_ENV: "development",
79
+ };
80
+ // Disable colors when running in CI mode to make URL parsing more reliable
81
+ if (inCIMode) {
82
+ log("Running in CI mode, disabling colors for dev server output");
83
+ env.NO_COLOR = "1";
84
+ env.FORCE_COLOR = "0";
85
+ }
86
+ // Map package manager names to actual commands
87
+ const getPackageManagerCommand = (pm) => {
88
+ switch (pm) {
89
+ case "yarn-classic":
90
+ return "yarn";
91
+ default:
92
+ return pm;
93
+ }
94
+ };
95
+ const pm = getPackageManagerCommand(packageManager);
96
+ // Use the provided cwd if available
97
+ devProcess = $({
98
+ all: true,
99
+ detached: false, // Keep attached so we can access streams
100
+ cleanup: false, // Don't auto-kill on exit
101
+ cwd: cwd || process.cwd(), // Use provided directory or current directory
102
+ env, // Pass the updated environment variables
103
+ stdio: "pipe", // Ensure streams are piped
104
+ }) `${pm} run dev`;
105
+ devProcess.catch((error) => {
106
+ if (!isErrorExpected) {
107
+ // Just throw the error, let the caller handle it.
108
+ throw error;
109
+ }
110
+ });
111
+ log("Development server process spawned in directory: %s", cwd || process.cwd());
112
+ // Store chunks to parse the URL
113
+ let url = "";
114
+ let allOutput = "";
115
+ // Listen for all output to get the URL
116
+ const handleOutput = (data, source) => {
117
+ const output = data.toString();
118
+ console.log(output);
119
+ allOutput += output; // Accumulate all output
120
+ log("Received output from %s: %s", source, output.replace(/\n/g, "\\n"));
121
+ if (!url) {
122
+ // Multiple patterns to catch different package manager outputs
123
+ const patterns = [
124
+ // Standard Vite output: "Local: http://localhost:5173/"
125
+ /Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
126
+ // Alternative Vite output: "➜ Local: http://localhost:5173/"
127
+ /[➜→]\s*Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
128
+ // Unicode-safe arrow pattern
129
+ /[\u27A1\u2192\u279C]\s*Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
130
+ // Direct URL pattern: "http://localhost:5173"
131
+ /(https?:\/\/localhost:\d+)/i,
132
+ // Port-only pattern: "localhost:5173"
133
+ /localhost:(\d+)/i,
134
+ // Server ready messages
135
+ /server.*ready.*localhost:(\d+)/i,
136
+ /dev server.*localhost:(\d+)/i,
137
+ ];
138
+ for (const pattern of patterns) {
139
+ const match = output.match(pattern);
140
+ log("Testing pattern %s against output: %s", pattern.source, output.replace(/\n/g, "\\n"));
141
+ if (match) {
142
+ log("Pattern matched: %s, groups: %o", pattern.source, match);
143
+ if (match[1] && match[1].startsWith("http")) {
144
+ url = match[1];
145
+ log("Found development server URL with pattern %s: %s", pattern.source, url);
146
+ break;
147
+ }
148
+ else if (match[1] && /^\d+$/.test(match[1])) {
149
+ url = `http://localhost:${match[1]}`;
150
+ log("Found development server URL with port pattern %s: %s", pattern.source, url);
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ // Log potential matches for debugging
156
+ if (!url &&
157
+ (output.includes("localhost") ||
158
+ output.includes("Local") ||
159
+ output.includes("server"))) {
160
+ log("Potential URL pattern found but not matched: %s", output.trim());
161
+ }
162
+ }
163
+ };
164
+ // Listen to all possible output streams
165
+ log("Setting up stream listeners. Available streams: all=%s, stdout=%s, stderr=%s", !!devProcess.all, !!devProcess.stdout, !!devProcess.stderr);
166
+ devProcess.all?.on("data", (data) => handleOutput(data, "all"));
167
+ devProcess.stdout?.on("data", (data) => handleOutput(data, "stdout"));
168
+ devProcess.stderr?.on("data", (data) => handleOutput(data, "stderr"));
169
+ // Also try listening to the raw process output
170
+ if (devProcess.child) {
171
+ log("Setting up child process stream listeners");
172
+ devProcess.child.stdout?.on("data", (data) => handleOutput(data, "child.stdout"));
173
+ devProcess.child.stderr?.on("data", (data) => handleOutput(data, "child.stderr"));
174
+ }
175
+ // Wait for URL with timeout
176
+ const waitForUrl = async () => {
177
+ const start = Date.now();
178
+ const timeout = 60000; // 60 seconds
179
+ while (Date.now() - start < timeout) {
180
+ if (url) {
181
+ return url;
182
+ }
183
+ // Fallback: check accumulated output if stream listeners aren't working
184
+ if (!url && allOutput) {
185
+ log("Checking accumulated output for URL patterns: %s", allOutput.replace(/\n/g, "\\n"));
186
+ const patterns = [
187
+ /Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
188
+ /[➜→]\s*Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
189
+ /[\u27A1\u2192\u279C]\s*Local:\s*(?:\u001b\[\d+m)?(https?:\/\/localhost:\d+)/i,
190
+ /(https?:\/\/localhost:\d+)/i,
191
+ /localhost:(\d+)/i,
192
+ ];
193
+ for (const pattern of patterns) {
194
+ const match = allOutput.match(pattern);
195
+ if (match) {
196
+ if (match[1] && match[1].startsWith("http")) {
197
+ url = match[1];
198
+ log("Found URL in accumulated output with pattern %s: %s", pattern.source, url);
199
+ return url;
200
+ }
201
+ else if (match[1] && /^\d+$/.test(match[1])) {
202
+ url = `http://localhost:${match[1]}`;
203
+ log("Found URL in accumulated output with port pattern %s: %s", pattern.source, url);
204
+ return url;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ // Check if the process is still running
210
+ if (devProcess.exitCode !== null) {
211
+ log("ERROR: Development server process exited with code %d. Final output: %s", devProcess.exitCode, allOutput);
212
+ throw new Error(`Development server process exited with code ${devProcess.exitCode}`);
213
+ }
214
+ await setTimeout(500); // Check every 500ms
215
+ }
216
+ log("ERROR: Timed out waiting for dev server URL. Final accumulated output: %s", allOutput);
217
+ throw new Error("Timed out waiting for dev server URL");
218
+ };
219
+ // Wait for the URL
220
+ const serverUrl = await waitForUrl();
221
+ console.log(`✅ Development server started at ${serverUrl}`);
222
+ return { url: serverUrl, stopDev };
223
+ }
224
+ catch (error) {
225
+ // Make sure to try to stop the server on error
226
+ log("Error during dev server startup: %O", error);
227
+ await stopDev().catch((e) => {
228
+ log("Failed to stop dev server during error handling: %O", e);
229
+ });
230
+ throw error;
231
+ }
232
+ }
@@ -0,0 +1,14 @@
1
+ import tmp from "tmp-promise";
2
+ import { SmokeTestOptions, TestResources, PackageManager } from "./types.mjs";
3
+ /**
4
+ * Sets up the test environment, preparing any resources needed for testing
5
+ */
6
+ export declare function setupTestEnvironment(options?: SmokeTestOptions): Promise<TestResources>;
7
+ /**
8
+ * Copy project to a temporary directory with a unique name
9
+ */
10
+ export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager): Promise<{
11
+ tempDir: tmp.DirectoryResult;
12
+ targetDir: string;
13
+ workerName: string;
14
+ }>;
@@ -0,0 +1,223 @@
1
+ import { join } from "path";
2
+ import debug from "debug";
3
+ import { pathExists, copy } from "fs-extra";
4
+ import * as fs from "node:fs";
5
+ import tmp from "tmp-promise";
6
+ import ignore from "ignore";
7
+ import { relative, basename, resolve } from "path";
8
+ import { uniqueNamesGenerator, adjectives, animals, } from "unique-names-generator";
9
+ import { createHash } from "crypto";
10
+ import { $ } from "../../lib/$.mjs";
11
+ import { ROOT_DIR } from "../constants.mjs";
12
+ import path from "node:path";
13
+ const log = debug("rwsdk:e2e:environment");
14
+ const createSdkTarball = async () => {
15
+ const packResult = await $({ cwd: ROOT_DIR, stdio: "pipe" }) `npm pack`;
16
+ const tarballName = packResult.stdout?.trim();
17
+ const tarballPath = path.join(ROOT_DIR, tarballName);
18
+ log(`📦 Created tarball: ${tarballPath}`);
19
+ const cleanupTarball = async () => {
20
+ if (fs.existsSync(tarballPath)) {
21
+ log(`🧹 Cleaning up tarball: ${tarballPath}`);
22
+ await fs.promises.rm(tarballPath, { force: true });
23
+ }
24
+ };
25
+ return { tarballPath, cleanupTarball };
26
+ };
27
+ const setTarballDependency = async (targetDir, tarballPath) => {
28
+ const filePath = join(targetDir, "package.json");
29
+ const packageJson = await fs.promises.readFile(filePath, "utf-8");
30
+ const packageJsonContent = JSON.parse(packageJson);
31
+ packageJsonContent.dependencies.rwsdk = `file:${tarballPath}`;
32
+ await fs.promises.writeFile(filePath, JSON.stringify(packageJsonContent, null, 2));
33
+ };
34
+ /**
35
+ * Sets up the test environment, preparing any resources needed for testing
36
+ */
37
+ export async function setupTestEnvironment(options = {}) {
38
+ log("Setting up test environment with options: %O", options);
39
+ // Generate a resource unique key for this test run right at the start
40
+ const uniqueNameSuffix = uniqueNamesGenerator({
41
+ dictionaries: [adjectives, animals],
42
+ separator: "-",
43
+ length: 2,
44
+ style: "lowerCase",
45
+ });
46
+ // Create a short unique hash based on the timestamp
47
+ const hash = createHash("md5")
48
+ .update(Date.now().toString())
49
+ .digest("hex")
50
+ .substring(0, 8);
51
+ // Create a resource unique key even if we're not copying a project
52
+ const resourceUniqueKey = `${uniqueNameSuffix}-${hash}`;
53
+ const resources = {
54
+ tempDirCleanup: undefined,
55
+ workerName: undefined,
56
+ originalCwd: process.cwd(),
57
+ targetDir: undefined,
58
+ workerCreatedDuringTest: false,
59
+ stopDev: undefined,
60
+ resourceUniqueKey, // Set at initialization
61
+ };
62
+ log("Current working directory: %s", resources.originalCwd);
63
+ try {
64
+ // If a project dir is specified, copy it to a temp dir with a unique name
65
+ if (options.projectDir) {
66
+ log("Project directory specified: %s", options.projectDir);
67
+ const { tempDir, targetDir, workerName } = await copyProjectToTempDir(options.projectDir, resourceUniqueKey, // Pass in the existing resourceUniqueKey
68
+ options.packageManager);
69
+ // Store cleanup function
70
+ resources.tempDirCleanup = tempDir.cleanup;
71
+ resources.workerName = workerName;
72
+ resources.targetDir = targetDir;
73
+ log("Target directory: %s", targetDir);
74
+ }
75
+ else {
76
+ log("No project directory specified, using current directory");
77
+ // When no project dir is specified, we'll use the current directory
78
+ resources.targetDir = resources.originalCwd;
79
+ }
80
+ return resources;
81
+ }
82
+ catch (error) {
83
+ log("Error during test environment setup: %O", error);
84
+ throw error;
85
+ }
86
+ }
87
+ /**
88
+ * Copy project to a temporary directory with a unique name
89
+ */
90
+ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager) {
91
+ const { tarballPath, cleanupTarball } = await createSdkTarball();
92
+ try {
93
+ log("Creating temporary directory for project");
94
+ // Create a temporary directory
95
+ const tempDir = await tmp.dir({ unsafeCleanup: true });
96
+ // Create unique project directory name
97
+ const originalDirName = basename(projectDir);
98
+ const workerName = `${originalDirName}-test-${resourceUniqueKey}`;
99
+ const targetDir = resolve(tempDir.path, workerName);
100
+ console.log(`Copying project from ${projectDir} to ${targetDir}`);
101
+ // Read project's .gitignore if it exists
102
+ let ig = ignore();
103
+ const gitignorePath = join(projectDir, ".gitignore");
104
+ if (await pathExists(gitignorePath)) {
105
+ log("Found .gitignore file at %s", gitignorePath);
106
+ const gitignoreContent = await fs.promises.readFile(gitignorePath, "utf-8");
107
+ ig = ig.add(gitignoreContent);
108
+ }
109
+ else {
110
+ log("No .gitignore found, using default ignore patterns");
111
+ // Add default ignores if no .gitignore exists
112
+ ig = ig.add([
113
+ "node_modules",
114
+ ".git",
115
+ "dist",
116
+ "build",
117
+ ".DS_Store",
118
+ "coverage",
119
+ ".cache",
120
+ ".wrangler",
121
+ ".env",
122
+ ].join("\n"));
123
+ }
124
+ // Copy the project directory, respecting .gitignore
125
+ log("Starting copy process with ignored patterns");
126
+ await copy(projectDir, targetDir, {
127
+ filter: (src) => {
128
+ // Get path relative to project directory
129
+ const relativePath = relative(projectDir, src);
130
+ if (!relativePath)
131
+ return true; // Include the root directory
132
+ // Check against ignore patterns
133
+ const result = !ig.ignores(relativePath);
134
+ return result;
135
+ },
136
+ });
137
+ log("Project copy completed successfully");
138
+ // Configure temp project to not use frozen lockfile
139
+ log("⚙️ Configuring temp project to not use frozen lockfile...");
140
+ const npmrcPath = join(targetDir, ".npmrc");
141
+ await fs.promises.writeFile(npmrcPath, "frozen-lockfile=false\n");
142
+ // For yarn, create .yarnrc.yml to disable PnP and allow lockfile changes
143
+ if (packageManager === "yarn") {
144
+ const yarnrcPath = join(targetDir, ".yarnrc.yml");
145
+ const yarnConfig = [
146
+ // todo(justinvdm, 23-09-23): Support yarn pnpm
147
+ "nodeLinker: node-modules",
148
+ "enableImmutableInstalls: false",
149
+ ].join("\n");
150
+ await fs.promises.writeFile(yarnrcPath, yarnConfig);
151
+ log("Created .yarnrc.yml to allow lockfile changes for yarn");
152
+ }
153
+ await setTarballDependency(targetDir, tarballPath);
154
+ // Install dependencies in the target directory
155
+ await installDependencies(targetDir, packageManager);
156
+ // Return the environment details
157
+ return { tempDir, targetDir, workerName };
158
+ }
159
+ finally {
160
+ await cleanupTarball();
161
+ }
162
+ }
163
+ async function installDependencies(targetDir, packageManager = "pnpm") {
164
+ console.log(`📦 Installing project dependencies in ${targetDir} using ${packageManager}...`);
165
+ try {
166
+ // Clean up any pre-existing node_modules and lockfiles
167
+ log("Cleaning up pre-existing node_modules and lockfiles...");
168
+ await Promise.all([
169
+ fs.promises.rm(join(targetDir, "node_modules"), {
170
+ recursive: true,
171
+ force: true,
172
+ }),
173
+ fs.promises.rm(join(targetDir, "pnpm-lock.yaml"), { force: true }),
174
+ fs.promises.rm(join(targetDir, "yarn.lock"), { force: true }),
175
+ fs.promises.rm(join(targetDir, "package-lock.json"), { force: true }),
176
+ ]);
177
+ log("Cleanup complete.");
178
+ if (packageManager.startsWith("yarn")) {
179
+ log(`Enabling corepack...`);
180
+ await $("corepack", ["enable"], { cwd: targetDir, stdio: "pipe" });
181
+ if (packageManager === "yarn") {
182
+ log(`Preparing yarn@stable with corepack...`);
183
+ await $("corepack", ["prepare", "yarn@stable", "--activate"], {
184
+ cwd: targetDir,
185
+ stdio: "pipe",
186
+ });
187
+ }
188
+ else if (packageManager === "yarn-classic") {
189
+ log(`Preparing yarn@1.22.19 with corepack...`);
190
+ await $("corepack", ["prepare", "yarn@1.22.19", "--activate"], {
191
+ cwd: targetDir,
192
+ stdio: "pipe",
193
+ });
194
+ }
195
+ }
196
+ const installCommand = {
197
+ pnpm: ["pnpm", "install"],
198
+ npm: ["npm", "install"],
199
+ yarn: ["yarn", "install"],
200
+ "yarn-classic": ["yarn"],
201
+ }[packageManager];
202
+ // Run install command in the target directory
203
+ log(`Running ${installCommand.join(" ")}`);
204
+ const [command, ...args] = installCommand;
205
+ const result = await $(command, args, {
206
+ cwd: targetDir,
207
+ stdio: "pipe", // Capture output
208
+ env: {
209
+ YARN_ENABLE_HARDENED_MODE: "0",
210
+ },
211
+ });
212
+ console.log("✅ Dependencies installed successfully");
213
+ // Log installation details at debug level
214
+ if (result.stdout) {
215
+ log(`${packageManager} install output: %s`, result.stdout);
216
+ }
217
+ }
218
+ catch (error) {
219
+ log("ERROR: Failed to install dependencies: %O", error);
220
+ console.error(`❌ Failed to install dependencies: ${error instanceof Error ? error.message : String(error)}`);
221
+ throw new Error(`Failed to install project dependencies. Please ensure the project can be installed with ${packageManager}.`);
222
+ }
223
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./testHarness.mjs";
2
+ export * from "./tarball.mjs";
3
+ export * from "./environment.mjs";
4
+ export * from "./dev.mjs";
5
+ export * from "./browser.mjs";
6
+ export * from "./release.mjs";
7
+ export * from "./types.mjs";
@@ -0,0 +1,7 @@
1
+ export * from "./testHarness.mjs";
2
+ export * from "./tarball.mjs";
3
+ export * from "./environment.mjs";
4
+ export * from "./dev.mjs";
5
+ export * from "./browser.mjs";
6
+ export * from "./release.mjs";
7
+ export * from "./types.mjs";