rwsdk 1.0.0-alpha.9 → 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 (159) 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 +3 -4
  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 +59 -72
  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.mjs +4 -4
  12. package/dist/lib/e2e/retry.d.mts +4 -0
  13. package/dist/lib/e2e/retry.mjs +16 -0
  14. package/dist/lib/e2e/setup.d.mts +2 -0
  15. package/dist/lib/e2e/setup.mjs +1 -0
  16. package/dist/lib/e2e/tarball.d.mts +3 -2
  17. package/dist/lib/e2e/tarball.mjs +5 -5
  18. package/dist/lib/e2e/testHarness.d.mts +59 -50
  19. package/dist/lib/e2e/testHarness.mjs +289 -343
  20. package/dist/lib/getShortName.mjs +1 -2
  21. package/dist/lib/getShortName.test.mjs +2 -2
  22. package/dist/lib/getSrcPaths.js +2 -2
  23. package/dist/lib/hasPkgScript.test.mjs +2 -2
  24. package/dist/lib/jsonUtils.test.mjs +2 -2
  25. package/dist/lib/normalizeModulePath.test.mjs +2 -2
  26. package/dist/lib/setupEnvFiles.mjs +2 -2
  27. package/dist/lib/smokeTests/artifacts.mjs +2 -2
  28. package/dist/lib/smokeTests/browser.d.mts +1 -1
  29. package/dist/lib/smokeTests/browser.mjs +6 -7
  30. package/dist/lib/smokeTests/cleanup.mjs +6 -9
  31. package/dist/lib/smokeTests/codeUpdates.mjs +5 -5
  32. package/dist/lib/smokeTests/development.mjs +2 -2
  33. package/dist/lib/smokeTests/environment.d.mts +2 -3
  34. package/dist/lib/smokeTests/environment.mjs +17 -3
  35. package/dist/lib/smokeTests/release.d.mts +2 -2
  36. package/dist/lib/smokeTests/release.mjs +3 -3
  37. package/dist/lib/smokeTests/reporting.mjs +2 -2
  38. package/dist/lib/smokeTests/runSmokeTests.mjs +4 -4
  39. package/dist/lib/smokeTests/utils.mjs +3 -3
  40. package/dist/lib/testUtils/stubEnvVars.mjs +1 -1
  41. package/dist/llms/rules/middleware.d.ts +1 -1
  42. package/dist/llms/rules/middleware.js +4 -4
  43. package/dist/runtime/client/client.d.ts +2 -2
  44. package/dist/runtime/client/client.js +2 -2
  45. package/dist/runtime/client/navigation.test.js +1 -1
  46. package/dist/runtime/client/types.d.ts +1 -1
  47. package/dist/runtime/entries/client.d.ts +2 -2
  48. package/dist/runtime/entries/client.js +2 -2
  49. package/dist/runtime/entries/router.d.ts +1 -1
  50. package/dist/runtime/entries/router.js +1 -1
  51. package/dist/runtime/entries/worker.d.ts +5 -6
  52. package/dist/runtime/entries/worker.js +5 -6
  53. package/dist/runtime/imports/worker.js +1 -1
  54. package/dist/runtime/lib/auth/session.d.ts +2 -2
  55. package/dist/runtime/lib/auth/session.js +5 -5
  56. package/dist/runtime/lib/db/DOWorkerDialect.d.ts +1 -1
  57. package/dist/runtime/lib/db/DOWorkerDialect.js +1 -1
  58. package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
  59. package/dist/runtime/lib/db/index.d.ts +2 -2
  60. package/dist/runtime/lib/db/index.js +2 -2
  61. package/dist/runtime/lib/db/migrations.d.ts +1 -1
  62. package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +3 -3
  63. package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +1 -1
  64. package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +2 -2
  65. package/dist/runtime/lib/db/typeInference/builders/createView.d.ts +1 -1
  66. package/dist/runtime/lib/db/typeInference/builders/dropTable.d.ts +1 -1
  67. package/dist/runtime/lib/db/typeInference/builders/dropView.d.ts +1 -1
  68. package/dist/runtime/lib/db/typeInference/builders/schema.d.ts +3 -3
  69. package/dist/runtime/lib/db/typeInference/database.d.ts +2 -2
  70. package/dist/runtime/lib/memoizeOnId.test.js +1 -1
  71. package/dist/runtime/lib/realtime/client.js +2 -2
  72. package/dist/runtime/lib/realtime/durableObject.js +1 -1
  73. package/dist/runtime/lib/realtime/protocol.test.js +1 -1
  74. package/dist/runtime/lib/realtime/shared.test.js +1 -1
  75. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +1 -1
  76. package/dist/runtime/lib/realtime/worker.js +2 -2
  77. package/dist/runtime/lib/router.d.ts +1 -1
  78. package/dist/runtime/lib/router.test.js +2 -3
  79. package/dist/runtime/lib/rwContext.d.ts +1 -1
  80. package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +18 -0
  81. package/dist/runtime/lib/stitchDocumentAndAppStreams.js +143 -0
  82. package/dist/runtime/lib/turnstile/useTurnstile.js +1 -1
  83. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +1 -1
  84. package/dist/runtime/register/worker.d.ts +1 -1
  85. package/dist/runtime/register/worker.js +34 -22
  86. package/dist/runtime/render/assembleDocument.d.ts +1 -1
  87. package/dist/runtime/render/createThenableFromReadableStream.js +1 -1
  88. package/dist/runtime/render/preloads.d.ts +2 -2
  89. package/dist/runtime/render/renderDocumentHtmlStream.js +6 -6
  90. package/dist/runtime/render/renderHtmlStream.d.ts +1 -1
  91. package/dist/runtime/render/renderToRscStream.d.ts +4 -1
  92. package/dist/runtime/render/renderToRscStream.js +11 -1
  93. package/dist/runtime/render/renderToStream.d.ts +1 -1
  94. package/dist/runtime/render/renderToStream.js +2 -2
  95. package/dist/runtime/render/stylesheets.d.ts +1 -1
  96. package/dist/runtime/requestInfo/types.d.ts +0 -2
  97. package/dist/runtime/requestInfo/worker.d.ts +1 -1
  98. package/dist/runtime/requestInfo/worker.js +1 -9
  99. package/dist/runtime/script.js +1 -1
  100. package/dist/runtime/ssrBridge.d.ts +2 -2
  101. package/dist/runtime/ssrBridge.js +2 -2
  102. package/dist/runtime/worker.d.ts +1 -1
  103. package/dist/runtime/worker.js +3 -11
  104. package/dist/scripts/addon.d.mts +1 -0
  105. package/dist/scripts/addon.mjs +70 -0
  106. package/dist/scripts/debug-sync.mjs +106 -137
  107. package/dist/scripts/ensure-deploy-env.mjs +6 -6
  108. package/dist/scripts/migrate-new.mjs +3 -4
  109. package/dist/scripts/smoke-test.mjs +2 -2
  110. package/dist/scripts/worker-run.mjs +7 -9
  111. package/dist/vite/buildApp.mjs +1 -1
  112. package/dist/vite/checkIsUsingPrisma.test.mjs +1 -1
  113. package/dist/vite/configPlugin.mjs +33 -6
  114. package/dist/vite/createDirectiveLookupPlugin.mjs +1 -1
  115. package/dist/vite/createDirectiveLookupPlugin.test.mjs +2 -2
  116. package/dist/vite/createViteAwareResolver.d.mts +1 -2
  117. package/dist/vite/createViteAwareResolver.mjs +1 -1
  118. package/dist/vite/directiveModulesDevPlugin.mjs +4 -4
  119. package/dist/vite/directiveModulesDevPlugin.test.mjs +2 -2
  120. package/dist/vite/directivesPlugin.mjs +3 -3
  121. package/dist/vite/directivesPlugin.test.mjs +1 -1
  122. package/dist/vite/ensureAliasArray.test.mjs +1 -1
  123. package/dist/vite/findSpecifiers.mjs +1 -1
  124. package/dist/vite/findSpecifiers.test.mjs +2 -2
  125. package/dist/vite/findSsrSpecifiers.mjs +1 -1
  126. package/dist/vite/findSsrSpecifiers.test.mjs +1 -1
  127. package/dist/vite/getViteEsbuild.mjs +1 -1
  128. package/dist/vite/hasDirective.test.mjs +1 -1
  129. package/dist/vite/index.d.mts +1 -1
  130. package/dist/vite/invalidateCacheIfPrismaClientChanged.mjs +2 -2
  131. package/dist/vite/isJsFile.test.mjs +1 -1
  132. package/dist/vite/{reactConditionsResolverPlugin.d.mts → knownDepsResolverPlugin.d.mts} +3 -3
  133. package/dist/vite/{reactConditionsResolverPlugin.mjs → knownDepsResolverPlugin.mjs} +29 -24
  134. package/dist/vite/linkerPlugin.mjs +2 -2
  135. package/dist/vite/linkerPlugin.test.mjs +1 -1
  136. package/dist/vite/miniflareHMRPlugin.mjs +5 -5
  137. package/dist/vite/miniflareHMRPlugin.test.mjs +1 -1
  138. package/dist/vite/prismaPlugin.mjs +1 -1
  139. package/dist/vite/redwoodPlugin.d.mts +2 -0
  140. package/dist/vite/redwoodPlugin.mjs +36 -17
  141. package/dist/vite/redwoodPlugin.test.mjs +2 -2
  142. package/dist/vite/resolveForcedPaths.d.mts +4 -0
  143. package/dist/vite/resolveForcedPaths.mjs +9 -0
  144. package/dist/vite/runDirectivesScan.d.mts +2 -1
  145. package/dist/vite/runDirectivesScan.mjs +53 -17
  146. package/dist/vite/runDirectivesScan.test.mjs +2 -2
  147. package/dist/vite/ssrBridgePlugin.mjs +10 -3
  148. package/dist/vite/transformClientComponents.mjs +8 -6
  149. package/dist/vite/transformClientComponents.test.mjs +117 -59
  150. package/dist/vite/transformJsxScriptTagsPlugin.mjs +1 -1
  151. package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +2 -2
  152. package/dist/vite/transformServerFunctions.d.mts +1 -1
  153. package/dist/vite/transformServerFunctions.mjs +5 -5
  154. package/dist/vite/transformServerFunctions.test.mjs +3 -3
  155. package/package.json +54 -44
  156. package/dist/runtime/imports/resolveSSRValue.d.ts +0 -1
  157. package/dist/runtime/imports/resolveSSRValue.js +0 -8
  158. package/dist/runtime/lib/injectHtmlAtMarker.d.ts +0 -11
  159. 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
  /**
@@ -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, 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,15 +1,14 @@
1
- import { join } from "path";
2
1
  import debug from "debug";
3
- import { pathExists, copy } from "fs-extra";
2
+ import { copy, pathExists } from "fs-extra";
3
+ import ignore from "ignore";
4
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";
5
8
  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
9
  import { $ } from "../../lib/$.mjs";
11
10
  import { ROOT_DIR } from "../constants.mjs";
12
- import path from "node:path";
11
+ import { retry } from "./retry.mjs";
13
12
  const log = debug("rwsdk:e2e:environment");
14
13
  const createSdkTarball = async () => {
15
14
  const packResult = await $({ cwd: ROOT_DIR, stdio: "pipe" }) `npm pack`;
@@ -24,83 +23,36 @@ const createSdkTarball = async () => {
24
23
  };
25
24
  return { tarballPath, cleanupTarball };
26
25
  };
27
- const setTarballDependency = async (targetDir, tarballPath) => {
26
+ const setTarballDependency = async (targetDir, tarballName) => {
28
27
  const filePath = join(targetDir, "package.json");
29
28
  const packageJson = await fs.promises.readFile(filePath, "utf-8");
30
29
  const packageJsonContent = JSON.parse(packageJson);
31
- packageJsonContent.dependencies.rwsdk = `file:${tarballPath}`;
30
+ packageJsonContent.dependencies.rwsdk = `file:${tarballName}`;
32
31
  await fs.promises.writeFile(filePath, JSON.stringify(packageJsonContent, null, 2));
33
32
  };
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
33
  /**
88
34
  * Copy project to a temporary directory with a unique name
89
35
  */
90
- export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager) {
36
+ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot) {
91
37
  const { tarballPath, cleanupTarball } = await createSdkTarball();
92
38
  try {
93
39
  log("Creating temporary directory for project");
94
40
  // Create a temporary directory
95
41
  const tempDir = await tmp.dir({ unsafeCleanup: true });
42
+ // Determine the source directory to copy from
43
+ const sourceDir = monorepoRoot || projectDir;
96
44
  // Create unique project directory name
97
- const originalDirName = basename(projectDir);
45
+ const originalDirName = basename(sourceDir);
98
46
  const workerName = `${originalDirName}-test-${resourceUniqueKey}`;
99
- const targetDir = resolve(tempDir.path, workerName);
100
- console.log(`Copying project from ${projectDir} to ${targetDir}`);
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}`);
101
53
  // Read project's .gitignore if it exists
102
54
  let ig = ignore();
103
- const gitignorePath = join(projectDir, ".gitignore");
55
+ const gitignorePath = join(sourceDir, ".gitignore");
104
56
  if (await pathExists(gitignorePath)) {
105
57
  log("Found .gitignore file at %s", gitignorePath);
106
58
  const gitignoreContent = await fs.promises.readFile(gitignorePath, "utf-8");
@@ -123,10 +75,10 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
123
75
  }
124
76
  // Copy the project directory, respecting .gitignore
125
77
  log("Starting copy process with ignored patterns");
126
- await copy(projectDir, targetDir, {
78
+ await copy(sourceDir, tempCopyRoot, {
127
79
  filter: (src) => {
128
80
  // Get path relative to project directory
129
- const relativePath = relative(projectDir, src);
81
+ const relativePath = relative(sourceDir, src);
130
82
  if (!relativePath)
131
83
  return true; // Include the root directory
132
84
  // Check against ignore patterns
@@ -135,6 +87,32 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
135
87
  },
136
88
  });
137
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");
105
+ }
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");
113
+ }
114
+ }
115
+ }
138
116
  // Configure temp project to not use frozen lockfile
139
117
  log("⚙️ Configuring temp project to not use frozen lockfile...");
140
118
  const npmrcPath = join(targetDir, ".npmrc");
@@ -142,17 +120,24 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
142
120
  // For yarn, create .yarnrc.yml to disable PnP and allow lockfile changes
143
121
  if (packageManager === "yarn") {
144
122
  const yarnrcPath = join(targetDir, ".yarnrc.yml");
123
+ const yarnCacheDir = path.join(os.tmpdir(), "yarn-cache");
124
+ await fs.promises.mkdir(yarnCacheDir, { recursive: true });
145
125
  const yarnConfig = [
146
126
  // todo(justinvdm, 23-09-23): Support yarn pnpm
147
127
  "nodeLinker: node-modules",
148
128
  "enableImmutableInstalls: false",
129
+ `cacheFolder: "${yarnCacheDir}"`,
149
130
  ].join("\n");
150
131
  await fs.promises.writeFile(yarnrcPath, yarnConfig);
151
132
  log("Created .yarnrc.yml to allow lockfile changes for yarn");
152
133
  }
153
- await setTarballDependency(targetDir, tarballPath);
134
+ await setTarballDependency(targetDir, tarballFilename);
154
135
  // Install dependencies in the target directory
155
- await installDependencies(targetDir, packageManager);
136
+ const installDir = monorepoRoot ? tempCopyRoot : targetDir;
137
+ await retry(() => installDependencies(installDir, packageManager), {
138
+ retries: 3,
139
+ delay: 1000,
140
+ });
156
141
  // Return the environment details
157
142
  return { tempDir, targetDir, workerName };
158
143
  }
@@ -193,9 +178,11 @@ async function installDependencies(targetDir, packageManager = "pnpm") {
193
178
  });
194
179
  }
195
180
  }
181
+ const npmCacheDir = path.join(os.tmpdir(), "npm-cache");
182
+ await fs.promises.mkdir(npmCacheDir, { recursive: true });
196
183
  const installCommand = {
197
184
  pnpm: ["pnpm", "install"],
198
- npm: ["npm", "install"],
185
+ npm: ["npm", "install", "--cache", npmCacheDir],
199
186
  yarn: ["yarn", "install"],
200
187
  "yarn-classic": ["yarn"],
201
188
  }[packageManager];
@@ -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>;
@@ -0,0 +1,31 @@
1
+ import { setTimeout } from "node:timers/promises";
2
+ const POLL_TIMEOUT = process.env.RWSDK_POLL_TIMEOUT
3
+ ? parseInt(process.env.RWSDK_POLL_TIMEOUT, 10)
4
+ : 2 * 60 * 1000;
5
+ export async function poll(fn, options = {}) {
6
+ const { timeout = POLL_TIMEOUT, interval = 100, minTries = 3, onRetry, } = options;
7
+ const startTime = Date.now();
8
+ let tries = 0;
9
+ while (Date.now() - startTime < timeout || tries < minTries) {
10
+ tries++;
11
+ try {
12
+ if (await fn()) {
13
+ return;
14
+ }
15
+ }
16
+ catch (error) {
17
+ onRetry?.(error, tries);
18
+ // Continue polling on errors
19
+ }
20
+ await setTimeout(interval);
21
+ }
22
+ throw new Error(`Polling timed out after ${Date.now() - startTime}ms and ${tries} attempts`);
23
+ }
24
+ export async function pollValue(fn, options = {}) {
25
+ let value;
26
+ await poll(async () => {
27
+ value = await fn();
28
+ return true;
29
+ }, options);
30
+ return value;
31
+ }
@@ -1,12 +1,12 @@
1
- import { join, basename, dirname, resolve } from "path";
2
- import { setTimeout } from "node:timers/promises";
3
1
  import debug from "debug";
4
- import { $ } from "../../lib/$.mjs";
5
2
  import { execaCommand } from "execa";
6
3
  import { existsSync, readFileSync } from "fs";
7
4
  import { pathExists } from "fs-extra";
8
- import { parse as parseJsonc } from "jsonc-parser";
9
5
  import * as fs from "fs/promises";
6
+ import { parse as parseJsonc } from "jsonc-parser";
7
+ import { setTimeout } from "node:timers/promises";
8
+ import { basename, dirname, join, resolve } from "path";
9
+ import { $ } from "../../lib/$.mjs";
10
10
  import { extractLastJson, parseJson } from "../../lib/jsonUtils.mjs";
11
11
  const log = debug("rwsdk:e2e:release");
12
12
  /**
@@ -0,0 +1,4 @@
1
+ export declare function retry<T>(fn: () => Promise<T>, options: {
2
+ retries: number;
3
+ delay: number;
4
+ }): Promise<T>;
@@ -0,0 +1,16 @@
1
+ import { setTimeout } from "node:timers/promises";
2
+ const log = console.log;
3
+ export async function retry(fn, options) {
4
+ let lastError;
5
+ for (let i = 0; i < options.retries; i++) {
6
+ try {
7
+ return await fn();
8
+ }
9
+ catch (e) {
10
+ lastError = e;
11
+ log(`Attempt ${i + 1} failed. Retrying in ${options.delay}ms...`);
12
+ await setTimeout(options.delay);
13
+ }
14
+ }
15
+ throw lastError;
16
+ }
@@ -0,0 +1,2 @@
1
+ export type { Browser } from "puppeteer-core";
2
+ export { launchBrowser } from "./browser.mjs";
@@ -0,0 +1 @@
1
+ export { launchBrowser } from "./browser.mjs";
@@ -1,6 +1,7 @@
1
1
  interface SetupTarballOptions {
2
2
  projectDir: string;
3
- packageManager?: "pnpm" | "npm" | "yarn";
3
+ monorepoRoot?: string;
4
+ packageManager?: "pnpm" | "npm" | "yarn" | "yarn-classic";
4
5
  }
5
6
  interface TarballEnvironment {
6
7
  targetDir: string;
@@ -9,5 +10,5 @@ interface TarballEnvironment {
9
10
  /**
10
11
  * Creates a tarball-based test environment similar to the release script approach
11
12
  */
12
- export declare function setupTarballEnvironment({ projectDir, packageManager, }: SetupTarballOptions): Promise<TarballEnvironment>;
13
+ export declare function setupTarballEnvironment({ projectDir, monorepoRoot, packageManager, }: SetupTarballOptions): Promise<TarballEnvironment>;
13
14
  export {};
@@ -1,10 +1,10 @@
1
+ import { createHash } from "crypto";
1
2
  import { $ } from "execa";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
- import { copyProjectToTempDir } from "./environment.mjs";
5
- import { uniqueNamesGenerator, adjectives, animals, } from "unique-names-generator";
6
- import { createHash } from "crypto";
5
+ import { adjectives, animals, uniqueNamesGenerator, } from "unique-names-generator";
7
6
  import { ROOT_DIR } from "../constants.mjs";
7
+ import { copyProjectToTempDir } from "./environment.mjs";
8
8
  const log = (message) => console.log(message);
9
9
  /**
10
10
  * Copies wrangler cache from monorepo to temp directory for deployment tests
@@ -63,7 +63,7 @@ async function copyWranglerCache(targetDir, sdkRoot) {
63
63
  /**
64
64
  * Creates a tarball-based test environment similar to the release script approach
65
65
  */
66
- export async function setupTarballEnvironment({ projectDir, packageManager = "pnpm", }) {
66
+ export async function setupTarballEnvironment({ projectDir, monorepoRoot, packageManager = "pnpm", }) {
67
67
  log(`🚀 Setting up tarball environment for ${projectDir}`);
68
68
  // Generate a resource unique key for this test run
69
69
  const uniqueNameSuffix = uniqueNamesGenerator({
@@ -79,7 +79,7 @@ export async function setupTarballEnvironment({ projectDir, packageManager = "pn
79
79
  .substring(0, 8);
80
80
  const resourceUniqueKey = `${uniqueNameSuffix}-${hash}`;
81
81
  try {
82
- const { tempDir, targetDir } = await copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager);
82
+ const { tempDir, targetDir } = await copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot);
83
83
  // Copy wrangler cache to improve deployment performance
84
84
  const sdkRoot = ROOT_DIR;
85
85
  await copyWranglerCache(targetDir, sdkRoot);