rwsdk 1.0.0-beta.1 → 1.0.0-beta.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 (42) hide show
  1. package/dist/lib/e2e/constants.d.mts +13 -0
  2. package/dist/lib/e2e/constants.mjs +67 -0
  3. package/dist/lib/e2e/environment.d.mts +1 -1
  4. package/dist/lib/e2e/environment.mjs +16 -6
  5. package/dist/lib/e2e/index.d.mts +1 -0
  6. package/dist/lib/e2e/index.mjs +1 -0
  7. package/dist/lib/e2e/testHarness.d.mts +33 -3
  8. package/dist/lib/e2e/testHarness.mjs +181 -109
  9. package/dist/runtime/client/client.d.ts +1 -0
  10. package/dist/runtime/client/client.js +2 -0
  11. package/dist/runtime/client/navigation.d.ts +8 -0
  12. package/dist/runtime/client/navigation.js +39 -31
  13. package/dist/runtime/entries/clientSSR.d.ts +1 -0
  14. package/dist/runtime/entries/clientSSR.js +3 -0
  15. package/dist/runtime/lib/db/createDb.d.ts +1 -2
  16. package/dist/runtime/lib/db/createDb.js +4 -0
  17. package/dist/runtime/lib/manifest.d.ts +1 -1
  18. package/dist/runtime/lib/manifest.js +7 -4
  19. package/dist/runtime/lib/realtime/client.js +8 -2
  20. package/dist/runtime/lib/router.d.ts +1 -19
  21. package/dist/runtime/lib/router.test.js +2 -0
  22. package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
  23. package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
  24. package/dist/runtime/render/renderToStream.d.ts +1 -1
  25. package/dist/runtime/render/renderToString.d.ts +1 -1
  26. package/dist/runtime/requestInfo/types.d.ts +1 -1
  27. package/dist/runtime/script.d.ts +1 -3
  28. package/dist/runtime/script.js +1 -10
  29. package/dist/runtime/worker.js +25 -0
  30. package/dist/scripts/addon.mjs +1 -1
  31. package/dist/scripts/smoke-test.mjs +4 -2
  32. package/dist/scripts/worker-run.d.mts +1 -1
  33. package/dist/scripts/worker-run.mjs +50 -113
  34. package/dist/vite/buildApp.mjs +2 -0
  35. package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
  36. package/dist/vite/linkerPlugin.mjs +1 -1
  37. package/dist/vite/redwoodPlugin.mjs +0 -4
  38. package/dist/vite/runDirectivesScan.mjs +57 -12
  39. package/package.json +9 -7
  40. package/dist/vite/manifestPlugin.d.mts +0 -4
  41. package/dist/vite/manifestPlugin.mjs +0 -63
  42. /package/dist/runtime/lib/{rwContext.js → types.js} +0 -0
@@ -0,0 +1,13 @@
1
+ export declare const IS_DEBUG_MODE: string | boolean;
2
+ export declare const SETUP_PLAYGROUND_ENV_TIMEOUT: number;
3
+ export declare const DEPLOYMENT_TIMEOUT: number;
4
+ export declare const DEPLOYMENT_MIN_TRIES: number;
5
+ export declare const DEPLOYMENT_CHECK_TIMEOUT: number;
6
+ export declare const PUPPETEER_TIMEOUT: number;
7
+ export declare const HYDRATION_TIMEOUT: number;
8
+ export declare const DEV_SERVER_TIMEOUT: number;
9
+ export declare const DEV_SERVER_MIN_TRIES: number;
10
+ export declare const SETUP_WAIT_TIMEOUT: number;
11
+ export declare const TEST_MAX_RETRIES: number;
12
+ export declare const TEST_MAX_RETRIES_PER_CODE: number;
13
+ export declare const INSTALL_DEPENDENCIES_RETRIES: number;
@@ -0,0 +1,67 @@
1
+ export const IS_DEBUG_MODE = process.env.RWSDK_E2E_DEBUG
2
+ ? process.env.RWSDK_E2E_DEBUG
3
+ : !process.env.CI;
4
+ export const SETUP_PLAYGROUND_ENV_TIMEOUT = process.env
5
+ .RWSDK_SETUP_PLAYGROUND_ENV_TIMEOUT
6
+ ? parseInt(process.env.RWSDK_SETUP_PLAYGROUND_ENV_TIMEOUT, 10)
7
+ : IS_DEBUG_MODE
8
+ ? 10 * 60 * 1000
9
+ : 15 * 60 * 1000;
10
+ export const DEPLOYMENT_TIMEOUT = process.env.RWSDK_DEPLOYMENT_TIMEOUT
11
+ ? parseInt(process.env.RWSDK_DEPLOYMENT_TIMEOUT, 10)
12
+ : IS_DEBUG_MODE
13
+ ? 5 * 30 * 1000
14
+ : 5 * 60 * 1000;
15
+ export const DEPLOYMENT_MIN_TRIES = process.env.RWSDK_DEPLOYMENT_MIN_TRIES
16
+ ? parseInt(process.env.RWSDK_DEPLOYMENT_MIN_TRIES, 10)
17
+ : IS_DEBUG_MODE
18
+ ? 1
19
+ : 5;
20
+ export const DEPLOYMENT_CHECK_TIMEOUT = process.env
21
+ .RWSDK_DEPLOYMENT_CHECK_TIMEOUT
22
+ ? parseInt(process.env.RWSDK_DEPLOYMENT_CHECK_TIMEOUT, 10)
23
+ : IS_DEBUG_MODE
24
+ ? 30 * 1000
25
+ : 5 * 60 * 1000;
26
+ export const PUPPETEER_TIMEOUT = process.env.RWSDK_PUPPETEER_TIMEOUT
27
+ ? parseInt(process.env.RWSDK_PUPPETEER_TIMEOUT, 10)
28
+ : IS_DEBUG_MODE
29
+ ? 30 * 1000
30
+ : 60 * 1000 * 2;
31
+ export const HYDRATION_TIMEOUT = process.env.RWSDK_HYDRATION_TIMEOUT
32
+ ? parseInt(process.env.RWSDK_HYDRATION_TIMEOUT, 10)
33
+ : IS_DEBUG_MODE
34
+ ? 2000
35
+ : 5000;
36
+ export const DEV_SERVER_TIMEOUT = process.env.RWSDK_DEV_SERVER_TIMEOUT
37
+ ? parseInt(process.env.RWSDK_DEV_SERVER_TIMEOUT, 10)
38
+ : IS_DEBUG_MODE
39
+ ? 60 * 1000
40
+ : 5 * 60 * 1000;
41
+ export const DEV_SERVER_MIN_TRIES = process.env.RWSDK_DEV_SERVER_MIN_TRIES
42
+ ? parseInt(process.env.RWSDK_DEV_SERVER_MIN_TRIES, 10)
43
+ : IS_DEBUG_MODE
44
+ ? 1
45
+ : 5;
46
+ export const SETUP_WAIT_TIMEOUT = process.env.RWSDK_SETUP_WAIT_TIMEOUT
47
+ ? parseInt(process.env.RWSDK_SETUP_WAIT_TIMEOUT, 10)
48
+ : IS_DEBUG_MODE
49
+ ? 60 * 1000
50
+ : 10 * 60 * 1000;
51
+ export const TEST_MAX_RETRIES = process.env.RWSDK_TEST_MAX_RETRIES
52
+ ? parseInt(process.env.RWSDK_TEST_MAX_RETRIES, 10)
53
+ : IS_DEBUG_MODE
54
+ ? 1
55
+ : 10;
56
+ export const TEST_MAX_RETRIES_PER_CODE = process.env
57
+ .RWSDK_TEST_MAX_RETRIES_PER_CODE
58
+ ? parseInt(process.env.RWSDK_TEST_MAX_RETRIES_PER_CODE, 10)
59
+ : IS_DEBUG_MODE
60
+ ? 0
61
+ : 6;
62
+ export const INSTALL_DEPENDENCIES_RETRIES = process.env
63
+ .RWSDK_INSTALL_DEPENDENCIES_RETRIES
64
+ ? parseInt(process.env.RWSDK_INSTALL_DEPENDENCIES_RETRIES, 10)
65
+ : IS_DEBUG_MODE
66
+ ? 1
67
+ : 10;
@@ -3,7 +3,7 @@ import { PackageManager } from "./types.mjs";
3
3
  /**
4
4
  * Copy project to a temporary directory with a unique name
5
5
  */
6
- export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager, monorepoRoot?: string): Promise<{
6
+ export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager, monorepoRoot?: string, installDependenciesRetries?: number): Promise<{
7
7
  tempDir: tmp.DirectoryResult;
8
8
  targetDir: string;
9
9
  workerName: string;
@@ -8,8 +8,12 @@ import { basename, join, relative, resolve } from "path";
8
8
  import tmp from "tmp-promise";
9
9
  import { $ } from "../../lib/$.mjs";
10
10
  import { ROOT_DIR } from "../constants.mjs";
11
+ import { INSTALL_DEPENDENCIES_RETRIES } from "./constants.mjs";
11
12
  import { retry } from "./retry.mjs";
12
13
  const log = debug("rwsdk:e2e:environment");
14
+ const getTempDir = async () => {
15
+ return tmp.dir({ unsafeCleanup: true });
16
+ };
13
17
  const createSdkTarball = async () => {
14
18
  const existingTarballPath = process.env.RWSKD_SMOKE_TEST_TARBALL_PATH;
15
19
  if (existingTarballPath) {
@@ -46,12 +50,11 @@ const setTarballDependency = async (targetDir, tarballName) => {
46
50
  /**
47
51
  * Copy project to a temporary directory with a unique name
48
52
  */
49
- export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot) {
53
+ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot, installDependenciesRetries) {
50
54
  const { tarballPath, cleanupTarball } = await createSdkTarball();
51
55
  try {
52
56
  log("Creating temporary directory for project");
53
- // Create a temporary directory
54
- const tempDir = await tmp.dir({ unsafeCleanup: true });
57
+ const tempDir = await getTempDir();
55
58
  // Determine the source directory to copy from
56
59
  const sourceDir = monorepoRoot || projectDir;
57
60
  // Create unique project directory name
@@ -130,7 +133,6 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
130
133
  log("⚙️ Configuring temp project to not use frozen lockfile...");
131
134
  const npmrcPath = join(targetDir, ".npmrc");
132
135
  await fs.promises.writeFile(npmrcPath, "frozen-lockfile=false\n");
133
- // For yarn, create .yarnrc.yml to disable PnP and allow lockfile changes
134
136
  if (packageManager === "yarn") {
135
137
  const yarnrcPath = join(targetDir, ".yarnrc.yml");
136
138
  const yarnCacheDir = path.join(os.tmpdir(), "yarn-cache");
@@ -144,11 +146,19 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
144
146
  await fs.promises.writeFile(yarnrcPath, yarnConfig);
145
147
  log("Created .yarnrc.yml to allow lockfile changes for yarn");
146
148
  }
149
+ if (packageManager === "yarn-classic") {
150
+ const yarnrcPath = join(targetDir, ".yarnrc");
151
+ const yarnCacheDir = path.join(os.tmpdir(), "yarn-classic-cache");
152
+ await fs.promises.mkdir(yarnCacheDir, { recursive: true });
153
+ const yarnConfig = `cache-folder "${yarnCacheDir}"`;
154
+ await fs.promises.writeFile(yarnrcPath, yarnConfig);
155
+ log("Created .yarnrc with cache-folder for yarn-classic");
156
+ }
147
157
  await setTarballDependency(targetDir, tarballFilename);
148
158
  // Install dependencies in the target directory
149
159
  const installDir = monorepoRoot ? tempCopyRoot : targetDir;
150
160
  await retry(() => installDependencies(installDir, packageManager), {
151
- retries: 3,
161
+ retries: INSTALL_DEPENDENCIES_RETRIES,
152
162
  delay: 1000,
153
163
  });
154
164
  // Return the environment details
@@ -185,7 +195,7 @@ async function installDependencies(targetDir, packageManager = "pnpm") {
185
195
  }
186
196
  else if (packageManager === "yarn-classic") {
187
197
  log(`Preparing yarn@1.22.19 with corepack...`);
188
- await $("corepack", ["prepare", "yarn@1.22.19", "--activate"], {
198
+ await $("corepack", ["prepare", "yarn@1.x", "--activate"], {
189
199
  cwd: targetDir,
190
200
  stdio: "pipe",
191
201
  });
@@ -1,3 +1,4 @@
1
+ export * from "../$.mjs";
1
2
  export * from "./browser.mjs";
2
3
  export * from "./dev.mjs";
3
4
  export * from "./environment.mjs";
@@ -1,3 +1,4 @@
1
+ export * from "../$.mjs";
1
2
  export * from "./browser.mjs";
2
3
  export * from "./dev.mjs";
3
4
  export * from "./environment.mjs";
@@ -1,8 +1,11 @@
1
1
  import { type Browser, type Page } from "puppeteer-core";
2
2
  import { test } from "vitest";
3
+ import { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE } from "./constants.mjs";
3
4
  export type { Browser, Page } from "puppeteer-core";
5
+ export { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, };
4
6
  interface DevServerInstance {
5
7
  url: string;
8
+ projectDir: string;
6
9
  stopDev: () => Promise<void>;
7
10
  }
8
11
  interface DeploymentInstance {
@@ -41,17 +44,29 @@ export interface SetupPlaygroundEnvironmentOptions {
41
44
  * and installs dependencies using a tarball of the SDK.
42
45
  * This ensures that tests run in a clean, isolated environment.
43
46
  */
44
- export declare function setupPlaygroundEnvironment(options?: string | SetupPlaygroundEnvironmentOptions): void;
47
+ export declare function setupPlaygroundEnvironment(options: SetupPlaygroundEnvironmentOptions | string): void;
45
48
  /**
46
49
  * Creates a dev server instance using the shared playground environment.
47
50
  * Automatically registers cleanup to run after the test.
48
51
  */
49
- export declare function createDevServer(projectDir: string): Promise<DevServerInstance>;
52
+ export declare function createDevServer(): {
53
+ projectDir: string;
54
+ start: () => Promise<DevServerInstance>;
55
+ };
50
56
  /**
51
57
  * Creates a deployment instance using the shared playground environment.
52
58
  * Automatically registers cleanup to run after the test.
53
59
  */
54
- export declare function createDeployment(projectDir: string): Promise<DeploymentInstance>;
60
+ export declare function createDeployment(): {
61
+ projectDir: string;
62
+ start: () => Promise<{
63
+ url: string;
64
+ workerName: string;
65
+ resourceUniqueKey: string;
66
+ projectDir: string;
67
+ cleanup: () => Promise<void>;
68
+ }>;
69
+ };
55
70
  /**
56
71
  * Executes a test function with a retry mechanism for specific error codes.
57
72
  * @param name - The name of the test, used for logging.
@@ -61,13 +76,25 @@ export declare function createDeployment(projectDir: string): Promise<Deployment
61
76
  * called automatically on failure.
62
77
  */
63
78
  export declare function runTestWithRetries(name: string, attemptFn: () => Promise<void>): Promise<void>;
79
+ type SDKRunner = (name: string, testLogic: (context: {
80
+ createDevServer: () => Promise<DevServerInstance>;
81
+ createDeployment: () => Promise<DeploymentInstance>;
82
+ browser: Browser;
83
+ page: Page;
84
+ projectDir: string;
85
+ }) => Promise<void>) => void;
64
86
  declare function createTestRunner(testFn: (typeof test | typeof test.only)["concurrent"], envType: "dev" | "deploy"): (name: string, testLogic: (context: {
65
87
  devServer?: DevServerInstance;
66
88
  deployment?: DeploymentInstance;
67
89
  browser: Browser;
68
90
  page: Page;
69
91
  url: string;
92
+ projectDir: string;
70
93
  }) => Promise<void>) => void;
94
+ export declare const testSDK: SDKRunner & {
95
+ only: SDKRunner;
96
+ skip: typeof test.skip;
97
+ };
71
98
  /**
72
99
  * High-level test wrapper for dev server tests.
73
100
  * Automatically skips if RWSDK_SKIP_DEV=1
@@ -81,6 +108,7 @@ export declare namespace testDev {
81
108
  browser: Browser;
82
109
  page: Page;
83
110
  url: string;
111
+ projectDir: string;
84
112
  }) => Promise<void>) => void;
85
113
  }
86
114
  /**
@@ -96,6 +124,7 @@ export declare namespace testDeploy {
96
124
  browser: Browser;
97
125
  page: Page;
98
126
  url: string;
127
+ projectDir: string;
99
128
  }) => Promise<void>) => void;
100
129
  }
101
130
  /**
@@ -108,6 +137,7 @@ export declare function testDevAndDeploy(name: string, testFn: (context: {
108
137
  browser: Browser;
109
138
  page: Page;
110
139
  url: string;
140
+ projectDir: string;
111
141
  }) => Promise<void>): void;
112
142
  export declare namespace testDevAndDeploy {
113
143
  var skip: (name: string, testFn?: any) => void;
@@ -4,44 +4,12 @@ import path, { basename, dirname, join as pathJoin } from "path";
4
4
  import puppeteer from "puppeteer-core";
5
5
  import { afterAll, afterEach, beforeAll, beforeEach, describe, test, } from "vitest";
6
6
  import { launchBrowser } from "./browser.mjs";
7
+ import { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, } from "./constants.mjs";
7
8
  import { runDevServer } from "./dev.mjs";
8
9
  import { poll, pollValue } from "./poll.mjs";
9
10
  import { deleteD1Database, deleteWorker, isRelatedToTest, runRelease, } from "./release.mjs";
10
11
  import { setupTarballEnvironment } from "./tarball.mjs";
11
- const SETUP_PLAYGROUND_ENV_TIMEOUT = process.env
12
- .RWSDK_SETUP_PLAYGROUND_ENV_TIMEOUT
13
- ? parseInt(process.env.RWSDK_SETUP_PLAYGROUND_ENV_TIMEOUT, 10)
14
- : 15 * 60 * 1000;
15
- const DEPLOYMENT_TIMEOUT = process.env.RWSDK_DEPLOYMENT_TIMEOUT
16
- ? parseInt(process.env.RWSDK_DEPLOYMENT_TIMEOUT, 10)
17
- : 5 * 60 * 1000;
18
- const DEPLOYMENT_MIN_TRIES = process.env.RWSDK_DEPLOYMENT_MIN_TRIES
19
- ? parseInt(process.env.RWSDK_DEPLOYMENT_MIN_TRIES, 10)
20
- : 5;
21
- const DEPLOYMENT_CHECK_TIMEOUT = process.env.RWSDK_DEPLOYMENT_CHECK_TIMEOUT
22
- ? parseInt(process.env.RWSDK_DEPLOYMENT_CHECK_TIMEOUT, 10)
23
- : 5 * 60 * 1000;
24
- const PUPPETEER_TIMEOUT = process.env.RWSDK_PUPPETEER_TIMEOUT
25
- ? parseInt(process.env.RWSDK_PUPPETEER_TIMEOUT, 10)
26
- : 60 * 1000 * 2;
27
- const HYDRATION_TIMEOUT = process.env.RWSDK_HYDRATION_TIMEOUT
28
- ? parseInt(process.env.RWSDK_HYDRATION_TIMEOUT, 10)
29
- : 5000;
30
- const DEV_SERVER_TIMEOUT = process.env.RWSDK_DEV_SERVER_TIMEOUT
31
- ? parseInt(process.env.RWSDK_DEV_SERVER_TIMEOUT, 10)
32
- : 5 * 60 * 1000;
33
- const DEV_SERVER_MIN_TRIES = process.env.RWSDK_DEV_SERVER_MIN_TRIES
34
- ? parseInt(process.env.RWSDK_DEV_SERVER_MIN_TRIES, 10)
35
- : 5;
36
- const SETUP_WAIT_TIMEOUT = process.env.RWSDK_SETUP_WAIT_TIMEOUT
37
- ? parseInt(process.env.RWSDK_SETUP_WAIT_TIMEOUT, 10)
38
- : 10 * 60 * 1000;
39
- const TEST_MAX_RETRIES = process.env.RWSDK_TEST_MAX_RETRIES
40
- ? parseInt(process.env.RWSDK_TEST_MAX_RETRIES, 10)
41
- : 10;
42
- const TEST_MAX_RETRIES_PER_CODE = process.env.RWSDK_TEST_MAX_RETRIES_PER_CODE
43
- ? parseInt(process.env.RWSDK_TEST_MAX_RETRIES_PER_CODE, 10)
44
- : 6;
12
+ export { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, };
45
13
  // Environment variable flags for skipping tests
46
14
  const SKIP_DEV_SERVER_TESTS = process.env.RWSDK_SKIP_DEV === "1";
47
15
  const SKIP_DEPLOYMENT_TESTS = process.env.RWSDK_SKIP_DEPLOY === "1";
@@ -52,6 +20,8 @@ let globalDevInstancePromise = null;
52
20
  let globalDeploymentInstancePromise = null;
53
21
  let globalDevInstance = null;
54
22
  let globalDeploymentInstance = null;
23
+ const devInstances = [];
24
+ const deploymentInstances = [];
55
25
  let hooksRegistered = false;
56
26
  /**
57
27
  * Registers global cleanup hooks automatically
@@ -62,11 +32,11 @@ function ensureHooksRegistered() {
62
32
  // Register global afterAll to clean up the playground environment
63
33
  afterAll(async () => {
64
34
  const cleanupPromises = [];
65
- if (globalDevInstance) {
66
- cleanupPromises.push(globalDevInstance.stopDev());
35
+ for (const instance of devInstances) {
36
+ cleanupPromises.push(instance.stopDev());
67
37
  }
68
- if (globalDeploymentInstance) {
69
- cleanupPromises.push(globalDeploymentInstance.cleanup());
38
+ for (const instance of deploymentInstances) {
39
+ cleanupPromises.push(instance.cleanup());
70
40
  }
71
41
  if (globalDevPlaygroundEnv) {
72
42
  cleanupPromises.push(globalDevPlaygroundEnv.cleanup());
@@ -75,8 +45,8 @@ function ensureHooksRegistered() {
75
45
  cleanupPromises.push(globalDeployPlaygroundEnv.cleanup());
76
46
  }
77
47
  await Promise.all(cleanupPromises);
78
- globalDevInstance = null;
79
- globalDeploymentInstance = null;
48
+ devInstances.length = 0;
49
+ deploymentInstances.length = 0;
80
50
  globalDevPlaygroundEnv = null;
81
51
  globalDeployPlaygroundEnv = null;
82
52
  });
@@ -114,7 +84,7 @@ function getPlaygroundDirFromImportMeta(importMetaUrl) {
114
84
  * and installs dependencies using a tarball of the SDK.
115
85
  * This ensures that tests run in a clean, isolated environment.
116
86
  */
117
- export function setupPlaygroundEnvironment(options = {}) {
87
+ export function setupPlaygroundEnvironment(options) {
118
88
  const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, } = typeof options === "string" ? { sourceProjectDir: options } : options;
119
89
  ensureHooksRegistered();
120
90
  beforeAll(async () => {
@@ -141,7 +111,8 @@ export function setupPlaygroundEnvironment(options = {}) {
141
111
  projectDir: devEnv.targetDir,
142
112
  cleanup: devEnv.cleanup,
143
113
  };
144
- globalDevInstancePromise = createDevServer(devEnv.targetDir).then((instance) => {
114
+ const devControl = createDevServer();
115
+ globalDevInstancePromise = devControl.start().then((instance) => {
145
116
  globalDevInstance = instance;
146
117
  return instance;
147
118
  });
@@ -150,7 +121,7 @@ export function setupPlaygroundEnvironment(options = {}) {
150
121
  globalDevInstancePromise.catch(() => { });
151
122
  }
152
123
  else {
153
- globalDevInstancePromise = Promise.resolve(null);
124
+ globalDevPlaygroundEnv = null;
154
125
  }
155
126
  if (deploy) {
156
127
  const deployEnv = await setupTarballEnvironment({
@@ -162,7 +133,10 @@ export function setupPlaygroundEnvironment(options = {}) {
162
133
  projectDir: deployEnv.targetDir,
163
134
  cleanup: deployEnv.cleanup,
164
135
  };
165
- globalDeploymentInstancePromise = createDeployment(deployEnv.targetDir).then((instance) => {
136
+ const deployControl = createDeployment();
137
+ globalDeploymentInstancePromise = deployControl
138
+ .start()
139
+ .then((instance) => {
166
140
  globalDeploymentInstance = instance;
167
141
  return instance;
168
142
  });
@@ -170,7 +144,7 @@ export function setupPlaygroundEnvironment(options = {}) {
170
144
  globalDeploymentInstancePromise.catch(() => { });
171
145
  }
172
146
  else {
173
- globalDeploymentInstancePromise = Promise.resolve(null);
147
+ globalDeployPlaygroundEnv = null;
174
148
  }
175
149
  }, SETUP_PLAYGROUND_ENV_TIMEOUT);
176
150
  }
@@ -178,82 +152,106 @@ export function setupPlaygroundEnvironment(options = {}) {
178
152
  * Creates a dev server instance using the shared playground environment.
179
153
  * Automatically registers cleanup to run after the test.
180
154
  */
181
- export async function createDevServer(projectDir) {
182
- if (SKIP_DEV_SERVER_TESTS) {
183
- throw new Error("Dev server tests are skipped via RWSDK_SKIP_DEV=1");
155
+ export function createDevServer() {
156
+ ensureHooksRegistered();
157
+ if (!globalDevPlaygroundEnv) {
158
+ throw new Error("Dev playground environment not initialized. Enable `dev: true` in setupPlaygroundEnvironment.");
184
159
  }
160
+ const { projectDir } = globalDevPlaygroundEnv;
185
161
  const packageManager = process.env.PACKAGE_MANAGER || "pnpm";
186
- const devResult = await pollValue(() => runDevServer(packageManager, projectDir), {
187
- timeout: DEV_SERVER_TIMEOUT,
188
- minTries: DEV_SERVER_MIN_TRIES,
189
- onRetry: (error, tries) => {
190
- console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
191
- },
192
- });
162
+ let instance = null;
193
163
  return {
194
- url: devResult.url,
195
- stopDev: devResult.stopDev,
164
+ projectDir,
165
+ start: async () => {
166
+ if (instance)
167
+ return instance;
168
+ if (SKIP_DEV_SERVER_TESTS) {
169
+ throw new Error("Dev server tests are skipped via RWSDK_SKIP_DEV=1");
170
+ }
171
+ const devResult = await pollValue(() => runDevServer(packageManager, projectDir), {
172
+ timeout: DEV_SERVER_TIMEOUT,
173
+ minTries: DEV_SERVER_MIN_TRIES,
174
+ onRetry: (error, tries) => {
175
+ console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
176
+ },
177
+ });
178
+ instance = {
179
+ url: devResult.url,
180
+ projectDir,
181
+ stopDev: devResult.stopDev,
182
+ };
183
+ devInstances.push(instance);
184
+ return instance;
185
+ },
196
186
  };
197
187
  }
198
188
  /**
199
189
  * Creates a deployment instance using the shared playground environment.
200
190
  * Automatically registers cleanup to run after the test.
201
191
  */
202
- export async function createDeployment(projectDir) {
203
- if (SKIP_DEPLOYMENT_TESTS) {
204
- throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
192
+ export function createDeployment() {
193
+ ensureHooksRegistered();
194
+ if (!globalDeployPlaygroundEnv) {
195
+ throw new Error("Deploy playground environment not initialized. Enable `deploy: true` in setupPlaygroundEnvironment.");
205
196
  }
206
- return await pollValue(async () => {
207
- // Extract the unique key from the project directory name instead of generating a new one
208
- // The directory name format is: {projectName}-e2e-test-{randomId}
209
- const dirName = basename(projectDir);
210
- const match = dirName.match(/-e2e-test-([a-f0-9]+)$/);
211
- const resourceUniqueKey = match
212
- ? match[1]
213
- : Math.random().toString(36).substring(2, 15);
214
- const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
215
- // Poll the URL to ensure it's live before proceeding
216
- await poll(async () => {
217
- try {
218
- const response = await fetch(deployResult.url);
219
- // We consider any response (even 4xx or 5xx) as success,
220
- // as it means the worker is routable.
221
- return response.status > 0;
222
- }
223
- catch (e) {
224
- return false;
197
+ const { projectDir } = globalDeployPlaygroundEnv;
198
+ let instance = null;
199
+ return {
200
+ projectDir,
201
+ start: async () => {
202
+ if (instance)
203
+ return instance;
204
+ if (SKIP_DEPLOYMENT_TESTS) {
205
+ throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
225
206
  }
226
- }, {
227
- timeout: DEPLOYMENT_CHECK_TIMEOUT,
228
- });
229
- const cleanup = async () => {
230
- // Run deployment cleanup in background without blocking
231
- const performCleanup = async () => {
232
- if (isRelatedToTest(deployResult.workerName, resourceUniqueKey)) {
233
- await deleteWorker(deployResult.workerName, projectDir, resourceUniqueKey);
234
- }
235
- await deleteD1Database(resourceUniqueKey, projectDir, resourceUniqueKey);
236
- };
237
- // Start cleanup in background and return immediately
238
- performCleanup().catch((error) => {
239
- console.warn(`Warning: Background deployment cleanup failed: ${error.message}`);
207
+ const newInstance = await pollValue(async () => {
208
+ const dirName = basename(projectDir);
209
+ const match = dirName.match(/-e2e-test-([a-f0-9]+)$/);
210
+ const resourceUniqueKey = match
211
+ ? match[1]
212
+ : Math.random().toString(36).substring(2, 15);
213
+ const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
214
+ await poll(async () => {
215
+ try {
216
+ const response = await fetch(deployResult.url);
217
+ return response.status > 0;
218
+ }
219
+ catch (e) {
220
+ return false;
221
+ }
222
+ }, {
223
+ timeout: DEPLOYMENT_CHECK_TIMEOUT,
224
+ });
225
+ const cleanup = async () => {
226
+ const performCleanup = async () => {
227
+ if (isRelatedToTest(deployResult.workerName, resourceUniqueKey)) {
228
+ await deleteWorker(deployResult.workerName, projectDir, resourceUniqueKey);
229
+ }
230
+ await deleteD1Database(resourceUniqueKey, projectDir, resourceUniqueKey);
231
+ };
232
+ performCleanup().catch((error) => {
233
+ console.warn(`Warning: Background deployment cleanup failed: ${error.message}`);
234
+ });
235
+ return Promise.resolve();
236
+ };
237
+ return {
238
+ url: deployResult.url,
239
+ workerName: deployResult.workerName,
240
+ resourceUniqueKey,
241
+ projectDir: projectDir,
242
+ cleanup,
243
+ };
244
+ }, {
245
+ timeout: DEPLOYMENT_TIMEOUT,
246
+ minTries: DEPLOYMENT_MIN_TRIES,
247
+ onRetry: (error, tries) => {
248
+ console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
249
+ },
240
250
  });
241
- return Promise.resolve();
242
- };
243
- return {
244
- url: deployResult.url,
245
- workerName: deployResult.workerName,
246
- resourceUniqueKey,
247
- projectDir: projectDir,
248
- cleanup,
249
- };
250
- }, {
251
- timeout: DEPLOYMENT_TIMEOUT,
252
- minTries: DEPLOYMENT_MIN_TRIES,
253
- onRetry: (error, tries) => {
254
- console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
251
+ deploymentInstances.push(newInstance);
252
+ return newInstance;
255
253
  },
256
- });
254
+ };
257
255
  }
258
256
  /**
259
257
  * Executes a test function with a retry mechanism for specific error codes.
@@ -302,7 +300,7 @@ function createTestRunner(testFn, envType) {
302
300
  return (name, testLogic) => {
303
301
  if ((envType === "dev" && SKIP_DEV_SERVER_TESTS) ||
304
302
  (envType === "deploy" && SKIP_DEPLOYMENT_TESTS)) {
305
- test.skip(name, () => { });
303
+ test.skip(`${name} (${envType})`, () => { });
306
304
  return;
307
305
  }
308
306
  describe.concurrent(name, () => {
@@ -364,12 +362,86 @@ function createTestRunner(testFn, envType) {
364
362
  browser: browser,
365
363
  page: page,
366
364
  url: instance.url,
365
+ projectDir: instance
366
+ .projectDir,
367
367
  });
368
368
  });
369
369
  });
370
370
  });
371
371
  };
372
372
  }
373
+ /**
374
+ * Creates a low-level test runner that provides utilities for creating
375
+ * tests that need to perform setup actions before the server starts.
376
+ */
377
+ function createSDKTestRunner() {
378
+ const internalRunner = (testFn) => {
379
+ return (name, testLogic) => {
380
+ describe.concurrent(name, () => {
381
+ let page;
382
+ let browser;
383
+ beforeAll(async () => {
384
+ const tempDir = path.join(os.tmpdir(), "rwsdk-e2e-tests");
385
+ const wsEndpointFile = path.join(tempDir, "wsEndpoint");
386
+ try {
387
+ const wsEndpoint = await fs.readFile(wsEndpointFile, "utf-8");
388
+ browser = await puppeteer.connect({
389
+ browserWSEndpoint: wsEndpoint,
390
+ });
391
+ }
392
+ catch (error) {
393
+ console.warn("Failed to connect to existing browser instance. " +
394
+ "This might happen if you are running a single test file. " +
395
+ "Launching a new browser instance instead.");
396
+ browser = await launchBrowser();
397
+ }
398
+ }, SETUP_WAIT_TIMEOUT);
399
+ afterAll(async () => {
400
+ if (browser) {
401
+ await browser.disconnect();
402
+ }
403
+ });
404
+ beforeEach(async () => {
405
+ if (!globalDevPlaygroundEnv && !globalDeployPlaygroundEnv) {
406
+ throw new Error("Test environment not initialized. Call setupPlaygroundEnvironment() in your test file.");
407
+ }
408
+ page = await browser.newPage();
409
+ page.setDefaultTimeout(PUPPETEER_TIMEOUT);
410
+ }, SETUP_WAIT_TIMEOUT);
411
+ afterEach(async () => {
412
+ if (page) {
413
+ try {
414
+ await page.close();
415
+ }
416
+ catch (error) {
417
+ console.warn(`Suppressing error during page.close() in test "${name}":`, error);
418
+ }
419
+ }
420
+ });
421
+ testFn(">", async () => {
422
+ if (!browser) {
423
+ throw new Error("Test environment not ready.");
424
+ }
425
+ await runTestWithRetries(name, async () => {
426
+ await testLogic({
427
+ browser: browser,
428
+ page: page,
429
+ projectDir: globalDevPlaygroundEnv?.projectDir ||
430
+ globalDeployPlaygroundEnv?.projectDir ||
431
+ "",
432
+ });
433
+ });
434
+ });
435
+ });
436
+ };
437
+ };
438
+ const main = internalRunner(test);
439
+ return Object.assign(main, {
440
+ only: internalRunner(test.only),
441
+ skip: test.skip,
442
+ });
443
+ }
444
+ export const testSDK = createSDKTestRunner();
373
445
  /**
374
446
  * High-level test wrapper for dev server tests.
375
447
  * Automatically skips if RWSDK_SKIP_DEV=1
@@ -1,6 +1,7 @@
1
1
  import "./setWebpackRequire";
2
2
  export { default as React } from "react";
3
3
  export { ClientOnly } from "./ClientOnly.js";
4
+ export { initClientNavigation, navigate } from "./navigation.js";
4
5
  import type { HydrationOptions, Transport } from "./types";
5
6
  export declare const fetchTransport: Transport;
6
7
  export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {