rwsdk 1.0.0-alpha.8 → 1.0.0-beta.0

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 (165) hide show
  1. package/dist/lib/constants.mjs +1 -2
  2. package/dist/lib/e2e/browser.d.mts +1 -1
  3. package/dist/lib/e2e/browser.mjs +21 -5
  4. package/dist/lib/e2e/dev.mjs +62 -52
  5. package/dist/lib/e2e/environment.d.mts +2 -6
  6. package/dist/lib/e2e/environment.mjs +167 -158
  7. package/dist/lib/e2e/index.d.mts +5 -4
  8. package/dist/lib/e2e/index.mjs +5 -4
  9. package/dist/lib/e2e/poll.d.mts +8 -0
  10. package/dist/lib/e2e/poll.mjs +31 -0
  11. package/dist/lib/e2e/release.d.mts +1 -1
  12. package/dist/lib/e2e/release.mjs +56 -34
  13. package/dist/lib/e2e/retry.d.mts +4 -0
  14. package/dist/lib/e2e/retry.mjs +16 -0
  15. package/dist/lib/e2e/setup.d.mts +2 -0
  16. package/dist/lib/e2e/setup.mjs +1 -0
  17. package/dist/lib/e2e/tarball.d.mts +3 -3
  18. package/dist/lib/e2e/tarball.mjs +28 -118
  19. package/dist/lib/e2e/testHarness.d.mts +74 -40
  20. package/dist/lib/e2e/testHarness.mjs +320 -271
  21. package/dist/lib/e2e/types.d.mts +1 -0
  22. package/dist/lib/getShortName.mjs +1 -2
  23. package/dist/lib/getShortName.test.mjs +2 -2
  24. package/dist/lib/getSrcPaths.js +2 -2
  25. package/dist/lib/hasPkgScript.test.mjs +2 -2
  26. package/dist/lib/jsonUtils.test.mjs +2 -2
  27. package/dist/lib/normalizeModulePath.test.mjs +2 -2
  28. package/dist/lib/setupEnvFiles.mjs +2 -2
  29. package/dist/lib/smokeTests/artifacts.mjs +2 -2
  30. package/dist/lib/smokeTests/browser.d.mts +1 -1
  31. package/dist/lib/smokeTests/browser.mjs +6 -7
  32. package/dist/lib/smokeTests/cleanup.mjs +6 -9
  33. package/dist/lib/smokeTests/codeUpdates.mjs +5 -5
  34. package/dist/lib/smokeTests/development.mjs +2 -2
  35. package/dist/lib/smokeTests/environment.d.mts +2 -3
  36. package/dist/lib/smokeTests/environment.mjs +17 -3
  37. package/dist/lib/smokeTests/release.d.mts +2 -2
  38. package/dist/lib/smokeTests/release.mjs +3 -3
  39. package/dist/lib/smokeTests/reporting.mjs +2 -2
  40. package/dist/lib/smokeTests/runSmokeTests.mjs +4 -4
  41. package/dist/lib/smokeTests/utils.mjs +3 -3
  42. package/dist/lib/testUtils/stubEnvVars.mjs +1 -1
  43. package/dist/llms/rules/middleware.d.ts +1 -1
  44. package/dist/llms/rules/middleware.js +4 -4
  45. package/dist/runtime/client/client.d.ts +2 -2
  46. package/dist/runtime/client/client.js +2 -2
  47. package/dist/runtime/client/navigation.test.js +1 -1
  48. package/dist/runtime/client/types.d.ts +1 -1
  49. package/dist/runtime/entries/client.d.ts +2 -2
  50. package/dist/runtime/entries/client.js +2 -2
  51. package/dist/runtime/entries/router.d.ts +1 -1
  52. package/dist/runtime/entries/router.js +1 -1
  53. package/dist/runtime/entries/worker.d.ts +5 -6
  54. package/dist/runtime/entries/worker.js +5 -6
  55. package/dist/runtime/imports/worker.js +1 -1
  56. package/dist/runtime/lib/auth/session.d.ts +2 -2
  57. package/dist/runtime/lib/auth/session.js +5 -5
  58. package/dist/runtime/lib/db/DOWorkerDialect.d.ts +1 -1
  59. package/dist/runtime/lib/db/DOWorkerDialect.js +1 -1
  60. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  61. package/dist/runtime/lib/db/index.d.ts +2 -2
  62. package/dist/runtime/lib/db/index.js +2 -2
  63. package/dist/runtime/lib/db/migrations.d.ts +1 -1
  64. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +3 -3
  65. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +1 -1
  66. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +2 -2
  67. package/dist/runtime/lib/db/typeInference/builders/createView.d.ts +1 -1
  68. package/dist/runtime/lib/db/typeInference/builders/dropTable.d.ts +1 -1
  69. package/dist/runtime/lib/db/typeInference/builders/dropView.d.ts +1 -1
  70. package/dist/runtime/lib/db/typeInference/builders/schema.d.ts +3 -3
  71. package/dist/runtime/lib/db/typeInference/database.d.ts +2 -2
  72. package/dist/runtime/lib/memoizeOnId.test.js +1 -1
  73. package/dist/runtime/lib/realtime/client.js +2 -2
  74. package/dist/runtime/lib/realtime/durableObject.js +1 -1
  75. package/dist/runtime/lib/realtime/protocol.test.js +1 -1
  76. package/dist/runtime/lib/realtime/shared.test.js +1 -1
  77. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +1 -1
  78. package/dist/runtime/lib/realtime/worker.js +2 -2
  79. package/dist/runtime/lib/router.d.ts +1 -1
  80. package/dist/runtime/lib/router.test.js +2 -3
  81. package/dist/runtime/lib/rwContext.d.ts +1 -1
  82. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +18 -0
  83. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +143 -0
  84. package/dist/runtime/lib/turnstile/useTurnstile.js +1 -1
  85. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +1 -1
  86. package/dist/runtime/register/worker.d.ts +1 -1
  87. package/dist/runtime/register/worker.js +34 -22
  88. package/dist/runtime/render/assembleDocument.d.ts +1 -1
  89. package/dist/runtime/render/createThenableFromReadableStream.js +1 -1
  90. package/dist/runtime/render/preloads.d.ts +2 -2
  91. package/dist/runtime/render/renderDocumentHtmlStream.js +6 -6
  92. package/dist/runtime/render/renderHtmlStream.d.ts +1 -1
  93. package/dist/runtime/render/renderToRscStream.d.ts +4 -1
  94. package/dist/runtime/render/renderToRscStream.js +11 -1
  95. package/dist/runtime/render/renderToStream.d.ts +1 -1
  96. package/dist/runtime/render/renderToStream.js +2 -2
  97. package/dist/runtime/render/stylesheets.d.ts +1 -1
  98. package/dist/runtime/requestInfo/types.d.ts +0 -2
  99. package/dist/runtime/requestInfo/worker.d.ts +1 -1
  100. package/dist/runtime/requestInfo/worker.js +1 -9
  101. package/dist/runtime/script.js +1 -1
  102. package/dist/runtime/ssrBridge.d.ts +2 -2
  103. package/dist/runtime/ssrBridge.js +2 -2
  104. package/dist/runtime/worker.d.ts +1 -1
  105. package/dist/runtime/worker.js +3 -11
  106. package/dist/scripts/addon.d.mts +1 -0
  107. package/dist/scripts/addon.mjs +70 -0
  108. package/dist/scripts/debug-sync.mjs +106 -137
  109. package/dist/scripts/ensure-deploy-env.mjs +6 -6
  110. package/dist/scripts/migrate-new.mjs +3 -4
  111. package/dist/scripts/smoke-test.mjs +2 -2
  112. package/dist/scripts/worker-run.mjs +7 -9
  113. package/dist/vite/buildApp.d.mts +2 -1
  114. package/dist/vite/buildApp.mjs +10 -6
  115. package/dist/vite/checkIsUsingPrisma.test.mjs +1 -1
  116. package/dist/vite/configPlugin.mjs +35 -17
  117. package/dist/vite/createDirectiveLookupPlugin.mjs +1 -1
  118. package/dist/vite/createDirectiveLookupPlugin.test.mjs +2 -2
  119. package/dist/vite/createViteAwareResolver.d.mts +1 -2
  120. package/dist/vite/createViteAwareResolver.mjs +1 -1
  121. package/dist/vite/directiveModulesDevPlugin.d.mts +2 -1
  122. package/dist/vite/directiveModulesDevPlugin.mjs +6 -5
  123. package/dist/vite/directiveModulesDevPlugin.test.mjs +2 -2
  124. package/dist/vite/directivesPlugin.mjs +3 -3
  125. package/dist/vite/directivesPlugin.test.mjs +1 -1
  126. package/dist/vite/ensureAliasArray.test.mjs +1 -1
  127. package/dist/vite/findSpecifiers.mjs +1 -1
  128. package/dist/vite/findSpecifiers.test.mjs +2 -2
  129. package/dist/vite/findSsrSpecifiers.mjs +1 -1
  130. package/dist/vite/findSsrSpecifiers.test.mjs +1 -1
  131. package/dist/vite/getViteEsbuild.mjs +1 -1
  132. package/dist/vite/hasDirective.d.mts +6 -3
  133. package/dist/vite/hasDirective.mjs +43 -27
  134. package/dist/vite/hasDirective.test.mjs +73 -75
  135. package/dist/vite/index.d.mts +1 -1
  136. package/dist/vite/invalidateCacheIfPrismaClientChanged.mjs +2 -2
  137. package/dist/vite/isJsFile.test.mjs +1 -1
  138. package/dist/vite/{reactConditionsResolverPlugin.d.mts → knownDepsResolverPlugin.d.mts} +3 -3
  139. package/dist/vite/{reactConditionsResolverPlugin.mjs → knownDepsResolverPlugin.mjs} +29 -24
  140. package/dist/vite/linkerPlugin.mjs +2 -2
  141. package/dist/vite/linkerPlugin.test.mjs +1 -1
  142. package/dist/vite/miniflareHMRPlugin.mjs +5 -5
  143. package/dist/vite/miniflareHMRPlugin.test.mjs +1 -1
  144. package/dist/vite/prismaPlugin.mjs +1 -1
  145. package/dist/vite/redwoodPlugin.d.mts +2 -0
  146. package/dist/vite/redwoodPlugin.mjs +37 -17
  147. package/dist/vite/redwoodPlugin.test.mjs +2 -2
  148. package/dist/vite/resolveForcedPaths.d.mts +4 -0
  149. package/dist/vite/resolveForcedPaths.mjs +9 -0
  150. package/dist/vite/runDirectivesScan.d.mts +3 -1
  151. package/dist/vite/runDirectivesScan.mjs +60 -20
  152. package/dist/vite/runDirectivesScan.test.mjs +2 -2
  153. package/dist/vite/ssrBridgePlugin.mjs +10 -3
  154. package/dist/vite/transformClientComponents.mjs +8 -6
  155. package/dist/vite/transformClientComponents.test.mjs +117 -59
  156. package/dist/vite/transformJsxScriptTagsPlugin.mjs +1 -1
  157. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +2 -2
  158. package/dist/vite/transformServerFunctions.d.mts +1 -1
  159. package/dist/vite/transformServerFunctions.mjs +5 -5
  160. package/dist/vite/transformServerFunctions.test.mjs +3 -3
  161. package/package.json +60 -50
  162. package/dist/runtime/imports/resolveSSRValue.d.ts +0 -1
  163. package/dist/runtime/imports/resolveSSRValue.js +0 -8
  164. package/dist/runtime/lib/injectHtmlAtMarker.d.ts +0 -11
  165. package/dist/runtime/lib/injectHtmlAtMarker.js +0 -90
@@ -1,5 +1,4 @@
1
- import { resolve } from "node:path";
2
- import path from "node:path";
1
+ import path, { resolve } from "node:path";
3
2
  const __dirname = new URL(".", import.meta.url).pathname;
4
3
  export const ROOT_DIR = resolve(__dirname, "..", "..");
5
4
  export const SRC_DIR = resolve(ROOT_DIR, "src");
@@ -1,5 +1,5 @@
1
- import { SmokeTestOptions } from "./types.mjs";
2
1
  import type { Browser } from "puppeteer-core";
2
+ import { SmokeTestOptions } from "./types.mjs";
3
3
  /**
4
4
  * Launch a browser instance
5
5
  */
@@ -1,9 +1,8 @@
1
+ import { computeExecutablePath, detectBrowserPlatform, install, Browser as PuppeteerBrowser, resolveBuildId, } from "@puppeteer/browsers";
2
+ import debug from "debug";
3
+ import { mkdirp, pathExists } from "fs-extra";
1
4
  import * as os from "os";
2
5
  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
6
  import puppeteer from "puppeteer-core";
8
7
  const log = debug("rwsdk:e2e:browser");
9
8
  /**
@@ -80,7 +79,24 @@ export async function getBrowserPath(testOptions) {
80
79
  // Add better error handling for the install step
81
80
  try {
82
81
  console.log("Downloading Chrome (this may take a few minutes)...");
83
- await install(installOptions);
82
+ const attempts = 10;
83
+ let installError;
84
+ for (let i = 0; i < attempts; i++) {
85
+ try {
86
+ await install(installOptions);
87
+ installError = null; // Reset error on success
88
+ break; // Exit loop on success
89
+ }
90
+ catch (e) {
91
+ installError = e;
92
+ console.log(`Attempt ${i + 1}/${attempts} failed. Retrying in 1 second...`);
93
+ // Wait for 1 second before retrying
94
+ await new Promise((resolve) => setTimeout(resolve, 1000));
95
+ }
96
+ }
97
+ if (installError) {
98
+ throw installError;
99
+ }
84
100
  console.log("✅ Chrome installation completed successfully");
85
101
  // Now compute the path for the installed browser
86
102
  const path = computeExecutablePath(installOptions);
@@ -1,6 +1,10 @@
1
- import { setTimeout } from "node:timers/promises";
2
1
  import debug from "debug";
2
+ import { setTimeout as sleep } from "node:timers/promises";
3
3
  import { $ } from "../../lib/$.mjs";
4
+ import { poll } from "./poll.mjs";
5
+ const DEV_SERVER_CHECK_TIMEOUT = process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT
6
+ ? parseInt(process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT, 10)
7
+ : 5 * 60 * 1000;
4
8
  const log = debug("rwsdk:e2e:dev");
5
9
  /**
6
10
  * Run the local development server and return the URL
@@ -12,59 +16,47 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
12
16
  let isErrorExpected = false;
13
17
  const stopDev = async () => {
14
18
  isErrorExpected = true;
15
- if (!devProcess) {
16
- log("No dev process to stop");
19
+ if (!devProcess || !devProcess.pid) {
20
+ log("No dev process to stop or PID is missing");
17
21
  return;
18
22
  }
19
23
  console.log("Stopping development server...");
20
24
  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
- }
25
+ // Send a regular termination signal to the entire process group first
26
+ process.kill(-devProcess.pid, "SIGTERM");
64
27
  }
65
28
  catch (e) {
66
- // Process might already have exited
67
- log("Could not kill dev server process: %O", e);
29
+ log("Could not send SIGTERM to dev server process group: %O", e);
30
+ }
31
+ // Wait for the process to terminate with a timeout
32
+ const terminationTimeout = 5000; // 5 seconds
33
+ const processExitPromise = devProcess.catch(() => {
34
+ // We expect this promise to reject when the process is killed,
35
+ // so we catch and ignore the error.
36
+ });
37
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(undefined), terminationTimeout));
38
+ await Promise.race([processExitPromise, timeoutPromise]);
39
+ // Check if the process is still alive. We can't reliably check exitCode
40
+ // on a detached process, so we try sending a signal 0, which errors
41
+ // if the process doesn't exist.
42
+ let isAlive = true;
43
+ try {
44
+ // Sending signal 0 doesn't kill the process, but checks if it exists
45
+ process.kill(-devProcess.pid, 0);
46
+ }
47
+ catch (e) {
48
+ isAlive = false;
49
+ }
50
+ // If not terminated within timeout, force kill the entire process group
51
+ if (isAlive) {
52
+ log("Dev server process did not terminate within timeout, force killing with SIGKILL");
53
+ console.log("⚠️ Development server not responding after 5 seconds timeout, force killing...");
54
+ try {
55
+ process.kill(-devProcess.pid, "SIGKILL");
56
+ }
57
+ catch (e) {
58
+ log("Could not send SIGKILL to dev server process group: %O", e);
59
+ }
68
60
  }
69
61
  console.log("Development server stopped");
70
62
  };
@@ -96,7 +88,7 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
96
88
  // Use the provided cwd if available
97
89
  devProcess = $({
98
90
  all: true,
99
- detached: false, // Keep attached so we can access streams
91
+ detached: true, // Run in a new process group so we can kill the entire group
100
92
  cleanup: false, // Don't auto-kill on exit
101
93
  cwd: cwd || process.cwd(), // Use provided directory or current directory
102
94
  env, // Pass the updated environment variables
@@ -104,8 +96,10 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
104
96
  }) `${pm} run dev`;
105
97
  devProcess.catch((error) => {
106
98
  if (!isErrorExpected) {
107
- // Just throw the error, let the caller handle it.
108
- throw error;
99
+ // Don't re-throw. The error will be handled gracefully by the polling
100
+ // logic in `waitForUrl`, which will detect that the process has exited.
101
+ // Re-throwing here would cause an unhandled promise rejection.
102
+ log("Dev server process exited unexpectedly:", error.shortMessage);
109
103
  }
110
104
  });
111
105
  log("Development server process spawned in directory: %s", cwd || process.cwd());
@@ -211,7 +205,7 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
211
205
  log("ERROR: Development server process exited with code %d. Final output: %s", devProcess.exitCode, allOutput);
212
206
  throw new Error(`Development server process exited with code ${devProcess.exitCode}`);
213
207
  }
214
- await setTimeout(500); // Check every 500ms
208
+ await sleep(500); // Check every 500ms
215
209
  }
216
210
  log("ERROR: Timed out waiting for dev server URL. Final accumulated output: %s", allOutput);
217
211
  throw new Error("Timed out waiting for dev server URL");
@@ -219,6 +213,22 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
219
213
  // Wait for the URL
220
214
  const serverUrl = await waitForUrl();
221
215
  console.log(`✅ Development server started at ${serverUrl}`);
216
+ // Poll the URL to ensure it's live before proceeding
217
+ await poll(async () => {
218
+ try {
219
+ const response = await fetch(serverUrl, {
220
+ signal: AbortSignal.timeout(1000),
221
+ });
222
+ // We consider any response (even 4xx or 5xx) as success,
223
+ // as it means the worker is routable.
224
+ return response.status > 0;
225
+ }
226
+ catch (e) {
227
+ return false;
228
+ }
229
+ }, {
230
+ timeout: DEV_SERVER_CHECK_TIMEOUT,
231
+ });
222
232
  return { url: serverUrl, stopDev };
223
233
  }
224
234
  catch (error) {
@@ -1,13 +1,9 @@
1
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>;
2
+ import { PackageManager } from "./types.mjs";
7
3
  /**
8
4
  * Copy project to a temporary directory with a unique name
9
5
  */
10
- export declare function copyProjectToTempDir(projectDir: string, sync: boolean | undefined, resourceUniqueKey: string, packageManager?: PackageManager): Promise<{
6
+ export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager, monorepoRoot?: string): Promise<{
11
7
  tempDir: tmp.DirectoryResult;
12
8
  targetDir: string;
13
9
  workerName: string;
@@ -1,184 +1,190 @@
1
- import { join } from "path";
2
1
  import debug from "debug";
3
- import { pathExists, copy } from "fs-extra";
4
- import * as fs from "fs/promises";
5
- import tmp from "tmp-promise";
2
+ import { copy, pathExists } from "fs-extra";
6
3
  import ignore from "ignore";
7
- import { relative, basename, resolve } from "path";
8
- import { uniqueNamesGenerator, adjectives, animals, } from "unique-names-generator";
4
+ import * as fs from "node:fs";
5
+ import path from "node:path";
6
+ import os from "os";
7
+ import { basename, join, relative, resolve } from "path";
8
+ import tmp from "tmp-promise";
9
9
  import { $ } from "../../lib/$.mjs";
10
- import { debugSync } from "../../scripts/debug-sync.mjs";
11
- import { createHash } from "crypto";
10
+ import { ROOT_DIR } from "../constants.mjs";
11
+ import { retry } from "./retry.mjs";
12
12
  const log = debug("rwsdk:e2e:environment");
13
+ const createSdkTarball = async () => {
14
+ const packResult = await $({ cwd: ROOT_DIR, stdio: "pipe" }) `npm pack`;
15
+ const tarballName = packResult.stdout?.trim();
16
+ const tarballPath = path.join(ROOT_DIR, tarballName);
17
+ log(`📦 Created tarball: ${tarballPath}`);
18
+ const cleanupTarball = async () => {
19
+ if (fs.existsSync(tarballPath)) {
20
+ log(`🧹 Cleaning up tarball: ${tarballPath}`);
21
+ await fs.promises.rm(tarballPath, { force: true });
22
+ }
23
+ };
24
+ return { tarballPath, cleanupTarball };
25
+ };
26
+ const setTarballDependency = async (targetDir, tarballName) => {
27
+ const filePath = join(targetDir, "package.json");
28
+ const packageJson = await fs.promises.readFile(filePath, "utf-8");
29
+ const packageJsonContent = JSON.parse(packageJson);
30
+ packageJsonContent.dependencies.rwsdk = `file:${tarballName}`;
31
+ await fs.promises.writeFile(filePath, JSON.stringify(packageJsonContent, null, 2));
32
+ };
13
33
  /**
14
- * Sets up the test environment, preparing any resources needed for testing
34
+ * Copy project to a temporary directory with a unique name
15
35
  */
16
- export async function setupTestEnvironment(options = {}) {
17
- log("Setting up test environment with options: %O", options);
18
- // Generate a resource unique key for this test run right at the start
19
- const uniqueNameSuffix = uniqueNamesGenerator({
20
- dictionaries: [adjectives, animals],
21
- separator: "-",
22
- length: 2,
23
- style: "lowerCase",
24
- });
25
- // Create a short unique hash based on the timestamp
26
- const hash = createHash("md5")
27
- .update(Date.now().toString())
28
- .digest("hex")
29
- .substring(0, 8);
30
- // Create a resource unique key even if we're not copying a project
31
- const resourceUniqueKey = `${uniqueNameSuffix}-${hash}`;
32
- const resources = {
33
- tempDirCleanup: undefined,
34
- workerName: undefined,
35
- originalCwd: process.cwd(),
36
- targetDir: undefined,
37
- workerCreatedDuringTest: false,
38
- stopDev: undefined,
39
- resourceUniqueKey, // Set at initialization
40
- };
41
- log("Current working directory: %s", resources.originalCwd);
36
+ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot) {
37
+ const { tarballPath, cleanupTarball } = await createSdkTarball();
42
38
  try {
43
- // If a project dir is specified, copy it to a temp dir with a unique name
44
- if (options.projectDir) {
45
- log("Project directory specified: %s", options.projectDir);
46
- const { tempDir, targetDir, workerName } = await copyProjectToTempDir(options.projectDir, options.sync !== false, // default to true if undefined
47
- resourceUniqueKey, // Pass in the existing resourceUniqueKey
48
- options.packageManager);
49
- // Store cleanup function
50
- resources.tempDirCleanup = tempDir.cleanup;
51
- resources.workerName = workerName;
52
- resources.targetDir = targetDir;
53
- log("Target directory: %s", targetDir);
39
+ log("Creating temporary directory for project");
40
+ // Create a temporary directory
41
+ const tempDir = await tmp.dir({ unsafeCleanup: true });
42
+ // Determine the source directory to copy from
43
+ const sourceDir = monorepoRoot || projectDir;
44
+ // Create unique project directory name
45
+ const originalDirName = basename(sourceDir);
46
+ const workerName = `${originalDirName}-test-${resourceUniqueKey}`;
47
+ const tempCopyRoot = resolve(tempDir.path, workerName);
48
+ // If it's a monorepo, the targetDir for commands is a subdirectory
49
+ const targetDir = monorepoRoot
50
+ ? resolve(tempCopyRoot, relative(monorepoRoot, projectDir))
51
+ : tempCopyRoot;
52
+ console.log(`Copying project from ${sourceDir} to ${tempCopyRoot}`);
53
+ // Read project's .gitignore if it exists
54
+ let ig = ignore();
55
+ const gitignorePath = join(sourceDir, ".gitignore");
56
+ if (await pathExists(gitignorePath)) {
57
+ log("Found .gitignore file at %s", gitignorePath);
58
+ const gitignoreContent = await fs.promises.readFile(gitignorePath, "utf-8");
59
+ ig = ig.add(gitignoreContent);
54
60
  }
55
61
  else {
56
- log("No project directory specified, using current directory");
57
- // When no project dir is specified, we'll use the current directory
58
- resources.targetDir = resources.originalCwd;
62
+ log("No .gitignore found, using default ignore patterns");
63
+ // Add default ignores if no .gitignore exists
64
+ ig = ig.add([
65
+ "node_modules",
66
+ ".git",
67
+ "dist",
68
+ "build",
69
+ ".DS_Store",
70
+ "coverage",
71
+ ".cache",
72
+ ".wrangler",
73
+ ".env",
74
+ ].join("\n"));
59
75
  }
60
- return resources;
61
- }
62
- catch (error) {
63
- log("Error during test environment setup: %O", error);
64
- throw error;
65
- }
66
- }
67
- /**
68
- * Copy project to a temporary directory with a unique name
69
- */
70
- export async function copyProjectToTempDir(projectDir, sync = true, resourceUniqueKey, packageManager) {
71
- log("Creating temporary directory for project");
72
- // Create a temporary directory
73
- const tempDir = await tmp.dir({ unsafeCleanup: true });
74
- // Create unique project directory name
75
- const originalDirName = basename(projectDir);
76
- const workerName = `${originalDirName}-smoke-test-${resourceUniqueKey}`;
77
- const targetDir = resolve(tempDir.path, workerName);
78
- console.log(`Copying project from ${projectDir} to ${targetDir}`);
79
- // Read project's .gitignore if it exists
80
- let ig = ignore();
81
- const gitignorePath = join(projectDir, ".gitignore");
82
- if (await pathExists(gitignorePath)) {
83
- log("Found .gitignore file at %s", gitignorePath);
84
- const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
85
- ig = ig.add(gitignoreContent);
86
- }
87
- else {
88
- log("No .gitignore found, using default ignore patterns");
89
- // Add default ignores if no .gitignore exists
90
- ig = ig.add([
91
- "node_modules",
92
- ".git",
93
- "dist",
94
- "build",
95
- ".DS_Store",
96
- "coverage",
97
- ".cache",
98
- ".wrangler",
99
- ".env",
100
- ].join("\n"));
101
- }
102
- // Copy the project directory, respecting .gitignore
103
- log("Starting copy process with ignored patterns");
104
- await copy(projectDir, targetDir, {
105
- filter: (src) => {
106
- // Get path relative to project directory
107
- const relativePath = relative(projectDir, src);
108
- if (!relativePath)
109
- return true; // Include the root directory
110
- // Check against ignore patterns
111
- const result = !ig.ignores(relativePath);
112
- return result;
113
- },
114
- });
115
- log("Project copy completed successfully");
116
- // For yarn, create .yarnrc.yml to disable PnP and use node_modules
117
- if (packageManager === "yarn" || packageManager === "yarn-classic") {
118
- const yarnrcPath = join(targetDir, ".yarnrc.yml");
119
- await fs.writeFile(yarnrcPath, "nodeLinker: node-modules\n");
120
- log("Created .yarnrc.yml to disable PnP for yarn");
121
- }
122
- // Replace workspace:* dependencies with a placeholder before installing
123
- await replaceWorkspaceDependencies(targetDir);
124
- // Install dependencies in the target directory
125
- await installDependencies(targetDir, packageManager);
126
- // Sync SDK to the temp dir if requested
127
- if (sync) {
128
- console.log(`🔄 Syncing SDK to ${targetDir} after installing dependencies...`);
129
- await debugSync({ targetDir });
130
- }
131
- return { tempDir, targetDir, workerName };
132
- }
133
- /**
134
- * Replace workspace:* dependencies with a placeholder version to allow installation
135
- */
136
- async function replaceWorkspaceDependencies(targetDir) {
137
- const packageJsonPath = join(targetDir, "package.json");
138
- try {
139
- const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
140
- const packageJson = JSON.parse(packageJsonContent);
141
- let modified = false;
142
- // Replace workspace:* dependencies with a placeholder version
143
- if (packageJson.dependencies) {
144
- for (const [name, version] of Object.entries(packageJson.dependencies)) {
145
- if (version === "workspace:*") {
146
- packageJson.dependencies[name] = "0.0.80"; // Use latest published version as placeholder
147
- modified = true;
148
- log(`Replaced workspace dependency ${name} with placeholder version`);
76
+ // Copy the project directory, respecting .gitignore
77
+ log("Starting copy process with ignored patterns");
78
+ await copy(sourceDir, tempCopyRoot, {
79
+ filter: (src) => {
80
+ // Get path relative to project directory
81
+ const relativePath = relative(sourceDir, src);
82
+ if (!relativePath)
83
+ return true; // Include the root directory
84
+ // Check against ignore patterns
85
+ const result = !ig.ignores(relativePath);
86
+ return result;
87
+ },
88
+ });
89
+ log("Project copy completed successfully");
90
+ // Copy the SDK tarball into the target directory
91
+ const tarballFilename = basename(tarballPath);
92
+ const tempTarballPath = join(targetDir, tarballFilename);
93
+ await fs.promises.copyFile(tarballPath, tempTarballPath);
94
+ if (monorepoRoot) {
95
+ log("⚙️ Configuring monorepo workspace...");
96
+ const rwsdkWsPath = join(tempCopyRoot, "rwsdk-workspace.json");
97
+ if (await pathExists(rwsdkWsPath)) {
98
+ const rwsdkWs = JSON.parse(await fs.promises.readFile(rwsdkWsPath, "utf-8"));
99
+ const workspaces = rwsdkWs.workspaces;
100
+ if (packageManager === "pnpm") {
101
+ const pnpmWsPath = join(tempCopyRoot, "pnpm-workspace.yaml");
102
+ const pnpmWsConfig = `packages:\n${workspaces.map((w) => ` - '${w}'`).join("\n")}\n`;
103
+ await fs.promises.writeFile(pnpmWsPath, pnpmWsConfig);
104
+ log("Created pnpm-workspace.yaml");
149
105
  }
150
- }
151
- }
152
- if (packageJson.devDependencies) {
153
- for (const [name, version] of Object.entries(packageJson.devDependencies)) {
154
- if (version === "workspace:*") {
155
- packageJson.devDependencies[name] = "0.0.80"; // Use latest published version as placeholder
156
- modified = true;
157
- log(`Replaced workspace devDependency ${name} with placeholder version`);
106
+ else {
107
+ // For npm and yarn, add a workspaces property to package.json
108
+ const pkgJsonPath = join(tempCopyRoot, "package.json");
109
+ const pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, "utf-8"));
110
+ pkgJson.workspaces = workspaces;
111
+ await fs.promises.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
112
+ log("Added workspaces to package.json");
158
113
  }
159
114
  }
160
115
  }
161
- if (modified) {
162
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
163
- log("Updated package.json with placeholder versions for workspace dependencies");
116
+ // Configure temp project to not use frozen lockfile
117
+ log("⚙️ Configuring temp project to not use frozen lockfile...");
118
+ const npmrcPath = join(targetDir, ".npmrc");
119
+ await fs.promises.writeFile(npmrcPath, "frozen-lockfile=false\n");
120
+ // For yarn, create .yarnrc.yml to disable PnP and allow lockfile changes
121
+ if (packageManager === "yarn") {
122
+ const yarnrcPath = join(targetDir, ".yarnrc.yml");
123
+ const yarnCacheDir = path.join(os.tmpdir(), "yarn-cache");
124
+ await fs.promises.mkdir(yarnCacheDir, { recursive: true });
125
+ const yarnConfig = [
126
+ // todo(justinvdm, 23-09-23): Support yarn pnpm
127
+ "nodeLinker: node-modules",
128
+ "enableImmutableInstalls: false",
129
+ `cacheFolder: "${yarnCacheDir}"`,
130
+ ].join("\n");
131
+ await fs.promises.writeFile(yarnrcPath, yarnConfig);
132
+ log("Created .yarnrc.yml to allow lockfile changes for yarn");
164
133
  }
134
+ await setTarballDependency(targetDir, tarballFilename);
135
+ // Install dependencies in the target directory
136
+ const installDir = monorepoRoot ? tempCopyRoot : targetDir;
137
+ await retry(() => installDependencies(installDir, packageManager), {
138
+ retries: 3,
139
+ delay: 1000,
140
+ });
141
+ // Return the environment details
142
+ return { tempDir, targetDir, workerName };
165
143
  }
166
- catch (error) {
167
- log("Error replacing workspace dependencies: %O", error);
168
- throw new Error(`Failed to replace workspace dependencies: ${error}`);
144
+ finally {
145
+ await cleanupTarball();
169
146
  }
170
147
  }
171
- /**
172
- * Install project dependencies using pnpm
173
- */
174
148
  async function installDependencies(targetDir, packageManager = "pnpm") {
175
149
  console.log(`📦 Installing project dependencies in ${targetDir} using ${packageManager}...`);
176
150
  try {
151
+ // Clean up any pre-existing node_modules and lockfiles
152
+ log("Cleaning up pre-existing node_modules and lockfiles...");
153
+ await Promise.all([
154
+ fs.promises.rm(join(targetDir, "node_modules"), {
155
+ recursive: true,
156
+ force: true,
157
+ }),
158
+ fs.promises.rm(join(targetDir, "pnpm-lock.yaml"), { force: true }),
159
+ fs.promises.rm(join(targetDir, "yarn.lock"), { force: true }),
160
+ fs.promises.rm(join(targetDir, "package-lock.json"), { force: true }),
161
+ ]);
162
+ log("Cleanup complete.");
163
+ if (packageManager.startsWith("yarn")) {
164
+ log(`Enabling corepack...`);
165
+ await $("corepack", ["enable"], { cwd: targetDir, stdio: "pipe" });
166
+ if (packageManager === "yarn") {
167
+ log(`Preparing yarn@stable with corepack...`);
168
+ await $("corepack", ["prepare", "yarn@stable", "--activate"], {
169
+ cwd: targetDir,
170
+ stdio: "pipe",
171
+ });
172
+ }
173
+ else if (packageManager === "yarn-classic") {
174
+ log(`Preparing yarn@1.22.19 with corepack...`);
175
+ await $("corepack", ["prepare", "yarn@1.22.19", "--activate"], {
176
+ cwd: targetDir,
177
+ stdio: "pipe",
178
+ });
179
+ }
180
+ }
181
+ const npmCacheDir = path.join(os.tmpdir(), "npm-cache");
182
+ await fs.promises.mkdir(npmCacheDir, { recursive: true });
177
183
  const installCommand = {
178
184
  pnpm: ["pnpm", "install"],
179
- npm: ["npm", "install"],
180
- yarn: ["yarn", "install", "--immutable"],
181
- "yarn-classic": ["yarn", "install", "--immutable"],
185
+ npm: ["npm", "install", "--cache", npmCacheDir],
186
+ yarn: ["yarn", "install"],
187
+ "yarn-classic": ["yarn"],
182
188
  }[packageManager];
183
189
  // Run install command in the target directory
184
190
  log(`Running ${installCommand.join(" ")}`);
@@ -186,6 +192,9 @@ async function installDependencies(targetDir, packageManager = "pnpm") {
186
192
  const result = await $(command, args, {
187
193
  cwd: targetDir,
188
194
  stdio: "pipe", // Capture output
195
+ env: {
196
+ YARN_ENABLE_HARDENED_MODE: "0",
197
+ },
189
198
  });
190
199
  console.log("✅ Dependencies installed successfully");
191
200
  // Log installation details at debug level
@@ -1,7 +1,8 @@
1
- export * from "./testHarness.mjs";
2
- export * from "./tarball.mjs";
3
- export * from "./environment.mjs";
4
- export * from "./dev.mjs";
5
1
  export * from "./browser.mjs";
2
+ export * from "./dev.mjs";
3
+ export * from "./environment.mjs";
4
+ export * from "./poll.mjs";
6
5
  export * from "./release.mjs";
6
+ export * from "./tarball.mjs";
7
+ export * from "./testHarness.mjs";
7
8
  export * from "./types.mjs";
@@ -1,7 +1,8 @@
1
- export * from "./testHarness.mjs";
2
- export * from "./tarball.mjs";
3
- export * from "./environment.mjs";
4
- export * from "./dev.mjs";
5
1
  export * from "./browser.mjs";
2
+ export * from "./dev.mjs";
3
+ export * from "./environment.mjs";
4
+ export * from "./poll.mjs";
6
5
  export * from "./release.mjs";
6
+ export * from "./tarball.mjs";
7
+ export * from "./testHarness.mjs";
7
8
  export * from "./types.mjs";
@@ -0,0 +1,8 @@
1
+ export interface PollOptions {
2
+ timeout: number;
3
+ interval: number;
4
+ minTries: number;
5
+ onRetry?: (error: unknown, tries: number) => void;
6
+ }
7
+ export declare function poll(fn: () => Promise<boolean>, options?: Partial<PollOptions>): Promise<void>;
8
+ export declare function pollValue<T>(fn: () => Promise<T>, options?: Partial<PollOptions>): Promise<T>;