rwsdk 1.0.0-beta.2-test.20250930092748 → 1.0.0-beta.21
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.
- package/dist/lib/constants.d.mts +1 -0
- package/dist/lib/constants.mjs +7 -4
- package/dist/lib/e2e/constants.d.mts +14 -0
- package/dist/lib/e2e/constants.mjs +74 -0
- package/dist/lib/e2e/dev.mjs +21 -34
- package/dist/lib/e2e/environment.d.mts +1 -1
- package/dist/lib/e2e/environment.mjs +118 -18
- package/dist/lib/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/poll.d.mts +1 -1
- package/dist/lib/e2e/testHarness.d.mts +36 -3
- package/dist/lib/e2e/testHarness.mjs +196 -119
- package/dist/runtime/client/client.d.ts +1 -0
- package/dist/runtime/client/client.js +2 -0
- package/dist/runtime/client/navigation.d.ts +8 -0
- package/dist/runtime/client/navigation.js +39 -31
- package/dist/runtime/entries/clientSSR.d.ts +1 -0
- package/dist/runtime/entries/clientSSR.js +3 -0
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/manifest.d.ts +1 -1
- package/dist/runtime/lib/manifest.js +7 -4
- package/dist/runtime/lib/realtime/client.js +8 -2
- package/dist/runtime/lib/router.d.ts +16 -21
- package/dist/runtime/lib/router.js +67 -1
- package/dist/runtime/lib/router.test.js +241 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
- package/dist/runtime/render/renderToStream.d.ts +4 -2
- package/dist/runtime/render/renderToStream.js +21 -2
- package/dist/runtime/render/renderToString.d.ts +3 -1
- package/dist/runtime/requestInfo/types.d.ts +4 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +44 -0
- package/dist/runtime/requestInfo/worker.js +3 -2
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +13 -0
- package/dist/runtime/worker.js +25 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +52 -113
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
- package/dist/vite/envResolvers.d.mts +11 -0
- package/dist/vite/envResolvers.mjs +20 -0
- package/dist/vite/getViteEsbuild.mjs +2 -1
- package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
- package/dist/vite/hmrStabilityPlugin.mjs +68 -0
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +1 -12
- package/dist/vite/linkerPlugin.d.mts +2 -1
- package/dist/vite/linkerPlugin.mjs +11 -3
- package/dist/vite/linkerPlugin.test.mjs +15 -0
- package/dist/vite/moveStaticAssetsPlugin.mjs +14 -4
- package/dist/vite/redwoodPlugin.mjs +7 -9
- package/dist/vite/runDirectivesScan.mjs +59 -14
- package/dist/vite/ssrBridgePlugin.mjs +104 -34
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +69 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/package.json +13 -7
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/lib/{rwContext.js → types.js} +0 -0
|
@@ -4,44 +4,13 @@ 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
|
-
|
|
12
|
-
|
|
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
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
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
14
|
// Environment variable flags for skipping tests
|
|
46
15
|
const SKIP_DEV_SERVER_TESTS = process.env.RWSDK_SKIP_DEV === "1";
|
|
47
16
|
const SKIP_DEPLOYMENT_TESTS = process.env.RWSDK_SKIP_DEPLOY === "1";
|
|
@@ -52,6 +21,8 @@ let globalDevInstancePromise = null;
|
|
|
52
21
|
let globalDeploymentInstancePromise = null;
|
|
53
22
|
let globalDevInstance = null;
|
|
54
23
|
let globalDeploymentInstance = null;
|
|
24
|
+
const devInstances = [];
|
|
25
|
+
const deploymentInstances = [];
|
|
55
26
|
let hooksRegistered = false;
|
|
56
27
|
/**
|
|
57
28
|
* Registers global cleanup hooks automatically
|
|
@@ -62,11 +33,11 @@ function ensureHooksRegistered() {
|
|
|
62
33
|
// Register global afterAll to clean up the playground environment
|
|
63
34
|
afterAll(async () => {
|
|
64
35
|
const cleanupPromises = [];
|
|
65
|
-
|
|
66
|
-
cleanupPromises.push(
|
|
36
|
+
for (const instance of devInstances) {
|
|
37
|
+
cleanupPromises.push(instance.stopDev());
|
|
67
38
|
}
|
|
68
|
-
|
|
69
|
-
cleanupPromises.push(
|
|
39
|
+
for (const instance of deploymentInstances) {
|
|
40
|
+
cleanupPromises.push(instance.cleanup());
|
|
70
41
|
}
|
|
71
42
|
if (globalDevPlaygroundEnv) {
|
|
72
43
|
cleanupPromises.push(globalDevPlaygroundEnv.cleanup());
|
|
@@ -75,8 +46,8 @@ function ensureHooksRegistered() {
|
|
|
75
46
|
cleanupPromises.push(globalDeployPlaygroundEnv.cleanup());
|
|
76
47
|
}
|
|
77
48
|
await Promise.all(cleanupPromises);
|
|
78
|
-
|
|
79
|
-
|
|
49
|
+
devInstances.length = 0;
|
|
50
|
+
deploymentInstances.length = 0;
|
|
80
51
|
globalDevPlaygroundEnv = null;
|
|
81
52
|
globalDeployPlaygroundEnv = null;
|
|
82
53
|
});
|
|
@@ -94,11 +65,11 @@ function getProjectDirectory() {
|
|
|
94
65
|
* Derive the playground directory from import.meta.url by finding the nearest package.json
|
|
95
66
|
*/
|
|
96
67
|
function getPlaygroundDirFromImportMeta(importMetaUrl) {
|
|
97
|
-
const
|
|
98
|
-
const testFilePath = url.pathname;
|
|
68
|
+
const testFilePath = fileURLToPath(importMetaUrl);
|
|
99
69
|
let currentDir = dirname(testFilePath);
|
|
100
70
|
// Walk up the tree from the test file's directory
|
|
101
|
-
|
|
71
|
+
// Stop when the parent directory is the same as the current directory (we've reached the root)
|
|
72
|
+
while (dirname(currentDir) !== currentDir) {
|
|
102
73
|
// Check if a package.json exists in the current directory
|
|
103
74
|
if (fs.existsSync(pathJoin(currentDir, "package.json"))) {
|
|
104
75
|
return currentDir;
|
|
@@ -114,8 +85,10 @@ function getPlaygroundDirFromImportMeta(importMetaUrl) {
|
|
|
114
85
|
* and installs dependencies using a tarball of the SDK.
|
|
115
86
|
* This ensures that tests run in a clean, isolated environment.
|
|
116
87
|
*/
|
|
117
|
-
export function setupPlaygroundEnvironment(options
|
|
118
|
-
const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, } = typeof options === "string"
|
|
88
|
+
export function setupPlaygroundEnvironment(options) {
|
|
89
|
+
const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, autoStartDevServer = true, } = typeof options === "string"
|
|
90
|
+
? { sourceProjectDir: options, autoStartDevServer: true }
|
|
91
|
+
: options;
|
|
119
92
|
ensureHooksRegistered();
|
|
120
93
|
beforeAll(async () => {
|
|
121
94
|
let projectDir;
|
|
@@ -141,16 +114,19 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
141
114
|
projectDir: devEnv.targetDir,
|
|
142
115
|
cleanup: devEnv.cleanup,
|
|
143
116
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
117
|
+
if (autoStartDevServer) {
|
|
118
|
+
const devControl = createDevServer();
|
|
119
|
+
globalDevInstancePromise = devControl.start().then((instance) => {
|
|
120
|
+
globalDevInstance = instance;
|
|
121
|
+
return instance;
|
|
122
|
+
});
|
|
123
|
+
// Prevent unhandled promise rejections. The error will be handled inside
|
|
124
|
+
// the test's beforeEach hook where this promise is awaited.
|
|
125
|
+
globalDevInstancePromise.catch(() => { });
|
|
126
|
+
}
|
|
151
127
|
}
|
|
152
128
|
else {
|
|
153
|
-
|
|
129
|
+
globalDevPlaygroundEnv = null;
|
|
154
130
|
}
|
|
155
131
|
if (deploy) {
|
|
156
132
|
const deployEnv = await setupTarballEnvironment({
|
|
@@ -162,7 +138,10 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
162
138
|
projectDir: deployEnv.targetDir,
|
|
163
139
|
cleanup: deployEnv.cleanup,
|
|
164
140
|
};
|
|
165
|
-
|
|
141
|
+
const deployControl = createDeployment();
|
|
142
|
+
globalDeploymentInstancePromise = deployControl
|
|
143
|
+
.start()
|
|
144
|
+
.then((instance) => {
|
|
166
145
|
globalDeploymentInstance = instance;
|
|
167
146
|
return instance;
|
|
168
147
|
});
|
|
@@ -170,7 +149,7 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
170
149
|
globalDeploymentInstancePromise.catch(() => { });
|
|
171
150
|
}
|
|
172
151
|
else {
|
|
173
|
-
|
|
152
|
+
globalDeployPlaygroundEnv = null;
|
|
174
153
|
}
|
|
175
154
|
}, SETUP_PLAYGROUND_ENV_TIMEOUT);
|
|
176
155
|
}
|
|
@@ -178,82 +157,106 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
178
157
|
* Creates a dev server instance using the shared playground environment.
|
|
179
158
|
* Automatically registers cleanup to run after the test.
|
|
180
159
|
*/
|
|
181
|
-
export
|
|
182
|
-
|
|
183
|
-
|
|
160
|
+
export function createDevServer() {
|
|
161
|
+
ensureHooksRegistered();
|
|
162
|
+
if (!globalDevPlaygroundEnv) {
|
|
163
|
+
throw new Error("Dev playground environment not initialized. Enable `dev: true` in setupPlaygroundEnvironment.");
|
|
184
164
|
}
|
|
165
|
+
const { projectDir } = globalDevPlaygroundEnv;
|
|
185
166
|
const packageManager = process.env.PACKAGE_MANAGER || "pnpm";
|
|
186
|
-
|
|
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
|
-
});
|
|
167
|
+
let instance = null;
|
|
193
168
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
169
|
+
projectDir,
|
|
170
|
+
start: async () => {
|
|
171
|
+
if (instance)
|
|
172
|
+
return instance;
|
|
173
|
+
if (SKIP_DEV_SERVER_TESTS) {
|
|
174
|
+
throw new Error("Dev server tests are skipped via RWSDK_SKIP_DEV=1");
|
|
175
|
+
}
|
|
176
|
+
const devResult = await pollValue(() => runDevServer(packageManager, projectDir), {
|
|
177
|
+
timeout: DEV_SERVER_TIMEOUT,
|
|
178
|
+
minTries: DEV_SERVER_MIN_TRIES,
|
|
179
|
+
onRetry: (error, tries) => {
|
|
180
|
+
console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
instance = {
|
|
184
|
+
url: devResult.url,
|
|
185
|
+
projectDir,
|
|
186
|
+
stopDev: devResult.stopDev,
|
|
187
|
+
};
|
|
188
|
+
devInstances.push(instance);
|
|
189
|
+
return instance;
|
|
190
|
+
},
|
|
196
191
|
};
|
|
197
192
|
}
|
|
198
193
|
/**
|
|
199
194
|
* Creates a deployment instance using the shared playground environment.
|
|
200
195
|
* Automatically registers cleanup to run after the test.
|
|
201
196
|
*/
|
|
202
|
-
export
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
export function createDeployment() {
|
|
198
|
+
ensureHooksRegistered();
|
|
199
|
+
if (!globalDeployPlaygroundEnv) {
|
|
200
|
+
throw new Error("Deploy playground environment not initialized. Enable `deploy: true` in setupPlaygroundEnvironment.");
|
|
205
201
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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;
|
|
202
|
+
const { projectDir } = globalDeployPlaygroundEnv;
|
|
203
|
+
let instance = null;
|
|
204
|
+
return {
|
|
205
|
+
projectDir,
|
|
206
|
+
start: async () => {
|
|
207
|
+
if (instance)
|
|
208
|
+
return instance;
|
|
209
|
+
if (SKIP_DEPLOYMENT_TESTS) {
|
|
210
|
+
throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
|
|
225
211
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
212
|
+
const newInstance = await pollValue(async () => {
|
|
213
|
+
const dirName = basename(projectDir);
|
|
214
|
+
const match = dirName.match(/-e2e-test-([a-f0-9]+)$/);
|
|
215
|
+
const resourceUniqueKey = match
|
|
216
|
+
? match[1]
|
|
217
|
+
: Math.random().toString(36).substring(2, 15);
|
|
218
|
+
const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
|
|
219
|
+
await poll(async () => {
|
|
220
|
+
try {
|
|
221
|
+
const response = await fetch(deployResult.url);
|
|
222
|
+
return response.status > 0;
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}, {
|
|
228
|
+
timeout: DEPLOYMENT_CHECK_TIMEOUT,
|
|
229
|
+
});
|
|
230
|
+
const cleanup = async () => {
|
|
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
|
+
performCleanup().catch((error) => {
|
|
238
|
+
console.warn(`Warning: Background deployment cleanup failed: ${error.message}`);
|
|
239
|
+
});
|
|
240
|
+
return Promise.resolve();
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
url: deployResult.url,
|
|
244
|
+
workerName: deployResult.workerName,
|
|
245
|
+
resourceUniqueKey,
|
|
246
|
+
projectDir: projectDir,
|
|
247
|
+
cleanup,
|
|
248
|
+
};
|
|
249
|
+
}, {
|
|
250
|
+
timeout: DEPLOYMENT_TIMEOUT,
|
|
251
|
+
minTries: DEPLOYMENT_MIN_TRIES,
|
|
252
|
+
onRetry: (error, tries) => {
|
|
253
|
+
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
254
|
+
},
|
|
240
255
|
});
|
|
241
|
-
|
|
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}`);
|
|
256
|
+
deploymentInstances.push(newInstance);
|
|
257
|
+
return newInstance;
|
|
255
258
|
},
|
|
256
|
-
}
|
|
259
|
+
};
|
|
257
260
|
}
|
|
258
261
|
/**
|
|
259
262
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
@@ -302,7 +305,7 @@ function createTestRunner(testFn, envType) {
|
|
|
302
305
|
return (name, testLogic) => {
|
|
303
306
|
if ((envType === "dev" && SKIP_DEV_SERVER_TESTS) ||
|
|
304
307
|
(envType === "deploy" && SKIP_DEPLOYMENT_TESTS)) {
|
|
305
|
-
test.skip(name
|
|
308
|
+
test.skip(`${name} (${envType})`, () => { });
|
|
306
309
|
return;
|
|
307
310
|
}
|
|
308
311
|
describe.concurrent(name, () => {
|
|
@@ -364,12 +367,86 @@ function createTestRunner(testFn, envType) {
|
|
|
364
367
|
browser: browser,
|
|
365
368
|
page: page,
|
|
366
369
|
url: instance.url,
|
|
370
|
+
projectDir: instance
|
|
371
|
+
.projectDir,
|
|
367
372
|
});
|
|
368
373
|
});
|
|
369
374
|
});
|
|
370
375
|
});
|
|
371
376
|
};
|
|
372
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Creates a low-level test runner that provides utilities for creating
|
|
380
|
+
* tests that need to perform setup actions before the server starts.
|
|
381
|
+
*/
|
|
382
|
+
function createSDKTestRunner() {
|
|
383
|
+
const internalRunner = (testFn) => {
|
|
384
|
+
return (name, testLogic) => {
|
|
385
|
+
describe.concurrent(name, () => {
|
|
386
|
+
let page;
|
|
387
|
+
let browser;
|
|
388
|
+
beforeAll(async () => {
|
|
389
|
+
const tempDir = path.join(os.tmpdir(), "rwsdk-e2e-tests");
|
|
390
|
+
const wsEndpointFile = path.join(tempDir, "wsEndpoint");
|
|
391
|
+
try {
|
|
392
|
+
const wsEndpoint = await fs.readFile(wsEndpointFile, "utf-8");
|
|
393
|
+
browser = await puppeteer.connect({
|
|
394
|
+
browserWSEndpoint: wsEndpoint,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
console.warn("Failed to connect to existing browser instance. " +
|
|
399
|
+
"This might happen if you are running a single test file. " +
|
|
400
|
+
"Launching a new browser instance instead.");
|
|
401
|
+
browser = await launchBrowser();
|
|
402
|
+
}
|
|
403
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
404
|
+
afterAll(async () => {
|
|
405
|
+
if (browser) {
|
|
406
|
+
await browser.disconnect();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
beforeEach(async () => {
|
|
410
|
+
if (!globalDevPlaygroundEnv && !globalDeployPlaygroundEnv) {
|
|
411
|
+
throw new Error("Test environment not initialized. Call setupPlaygroundEnvironment() in your test file.");
|
|
412
|
+
}
|
|
413
|
+
page = await browser.newPage();
|
|
414
|
+
page.setDefaultTimeout(PUPPETEER_TIMEOUT);
|
|
415
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
416
|
+
afterEach(async () => {
|
|
417
|
+
if (page) {
|
|
418
|
+
try {
|
|
419
|
+
await page.close();
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
console.warn(`Suppressing error during page.close() in test "${name}":`, error);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
testFn(">", async () => {
|
|
427
|
+
if (!browser) {
|
|
428
|
+
throw new Error("Test environment not ready.");
|
|
429
|
+
}
|
|
430
|
+
await runTestWithRetries(name, async () => {
|
|
431
|
+
await testLogic({
|
|
432
|
+
browser: browser,
|
|
433
|
+
page: page,
|
|
434
|
+
projectDir: globalDevPlaygroundEnv?.projectDir ||
|
|
435
|
+
globalDeployPlaygroundEnv?.projectDir ||
|
|
436
|
+
"",
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
const main = internalRunner(test);
|
|
444
|
+
return Object.assign(main, {
|
|
445
|
+
only: internalRunner(test.only),
|
|
446
|
+
skip: test.skip,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
export const testSDK = createSDKTestRunner();
|
|
373
450
|
/**
|
|
374
451
|
* High-level test wrapper for dev server tests.
|
|
375
452
|
* 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, }?: {
|
|
@@ -4,6 +4,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
// context(justinvdm, 14 Aug 2025): `react-server-dom-webpack` uses this global
|
|
5
5
|
// to load modules, so we need to define it here before importing
|
|
6
6
|
// "react-server-dom-webpack."
|
|
7
|
+
// prettier-ignore
|
|
7
8
|
import "./setWebpackRequire";
|
|
8
9
|
import React from "react";
|
|
9
10
|
import { hydrateRoot } from "react-dom/client";
|
|
@@ -11,6 +12,7 @@ import { createFromFetch, createFromReadableStream, encodeReply, } from "react-s
|
|
|
11
12
|
import { rscStream } from "rsc-html-stream/client";
|
|
12
13
|
export { default as React } from "react";
|
|
13
14
|
export { ClientOnly } from "./ClientOnly.js";
|
|
15
|
+
export { initClientNavigation, navigate } from "./navigation.js";
|
|
14
16
|
export const fetchTransport = (transportContext) => {
|
|
15
17
|
const fetchCallServer = async (id, args) => {
|
|
16
18
|
const url = new URL(window.location.href);
|
|
@@ -4,6 +4,14 @@ export interface ClientNavigationOptions {
|
|
|
4
4
|
scrollBehavior?: "auto" | "smooth" | "instant";
|
|
5
5
|
}
|
|
6
6
|
export declare function validateClickEvent(event: MouseEvent, target: HTMLElement): boolean;
|
|
7
|
+
export interface NavigateOptions {
|
|
8
|
+
history?: "push" | "replace";
|
|
9
|
+
info?: {
|
|
10
|
+
scrollToTop?: boolean;
|
|
11
|
+
scrollBehavior?: "auto" | "smooth" | "instant";
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function navigate(href: string, options?: NavigateOptions): Promise<void>;
|
|
7
15
|
export declare function initClientNavigation(opts?: ClientNavigationOptions): {
|
|
8
16
|
handleResponse: (response: Response) => boolean;
|
|
9
17
|
};
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
function saveScrollPosition(x, y) {
|
|
2
|
-
window.history.replaceState({
|
|
3
|
-
...window.history.state,
|
|
4
|
-
scrollX: x,
|
|
5
|
-
scrollY: y,
|
|
6
|
-
}, "", window.location.href);
|
|
7
|
-
}
|
|
8
1
|
export function validateClickEvent(event, target) {
|
|
9
2
|
// should this only work for left click?
|
|
10
3
|
if (event.button !== 0) {
|
|
@@ -37,19 +30,44 @@ export function validateClickEvent(event, target) {
|
|
|
37
30
|
}
|
|
38
31
|
return true;
|
|
39
32
|
}
|
|
33
|
+
let IS_CLIENT_NAVIGATION = false;
|
|
34
|
+
export async function navigate(href, options = { history: "push" }) {
|
|
35
|
+
if (!IS_CLIENT_NAVIGATION) {
|
|
36
|
+
window.location.href = href;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
saveScrollPosition(window.scrollX, window.scrollY);
|
|
40
|
+
const url = window.location.origin + href;
|
|
41
|
+
if (options.history === "push") {
|
|
42
|
+
window.history.pushState({ path: href }, "", url);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
window.history.replaceState({ path: href }, "", url);
|
|
46
|
+
}
|
|
47
|
+
// @ts-expect-error
|
|
48
|
+
await globalThis.__rsc_callServer();
|
|
49
|
+
const scrollToTop = options.info?.scrollToTop ?? true;
|
|
50
|
+
const scrollBehavior = options.info?.scrollBehavior ?? "instant";
|
|
51
|
+
if (scrollToTop && history.scrollRestoration === "auto") {
|
|
52
|
+
window.scrollTo({
|
|
53
|
+
top: 0,
|
|
54
|
+
left: 0,
|
|
55
|
+
behavior: scrollBehavior,
|
|
56
|
+
});
|
|
57
|
+
saveScrollPosition(0, 0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function saveScrollPosition(x, y) {
|
|
61
|
+
window.history.replaceState({
|
|
62
|
+
...window.history.state,
|
|
63
|
+
scrollX: x,
|
|
64
|
+
scrollY: y,
|
|
65
|
+
}, "", window.location.href);
|
|
66
|
+
}
|
|
40
67
|
export function initClientNavigation(opts = {}) {
|
|
41
|
-
|
|
42
|
-
onNavigate: async function onNavigate() {
|
|
43
|
-
// @ts-expect-error
|
|
44
|
-
await globalThis.__rsc_callServer();
|
|
45
|
-
},
|
|
46
|
-
scrollToTop: true,
|
|
47
|
-
scrollBehavior: "instant",
|
|
48
|
-
...opts,
|
|
49
|
-
};
|
|
68
|
+
IS_CLIENT_NAVIGATION = true;
|
|
50
69
|
history.scrollRestoration = "auto";
|
|
51
70
|
document.addEventListener("click", async function handleClickEvent(event) {
|
|
52
|
-
// Prevent default navigation
|
|
53
71
|
if (!validateClickEvent(event, event.target)) {
|
|
54
72
|
return;
|
|
55
73
|
}
|
|
@@ -57,28 +75,18 @@ export function initClientNavigation(opts = {}) {
|
|
|
57
75
|
const el = event.target;
|
|
58
76
|
const a = el.closest("a");
|
|
59
77
|
const href = a?.getAttribute("href");
|
|
60
|
-
|
|
61
|
-
window.history.pushState({ path: href }, "", window.location.origin + href);
|
|
62
|
-
await options.onNavigate();
|
|
63
|
-
if (options.scrollToTop && history.scrollRestoration === "auto") {
|
|
64
|
-
window.scrollTo({
|
|
65
|
-
top: 0,
|
|
66
|
-
left: 0,
|
|
67
|
-
behavior: options.scrollBehavior,
|
|
68
|
-
});
|
|
69
|
-
saveScrollPosition(0, 0);
|
|
70
|
-
}
|
|
71
|
-
history.scrollRestoration = "auto";
|
|
78
|
+
navigate(href);
|
|
72
79
|
}, true);
|
|
73
80
|
window.addEventListener("popstate", async function handlePopState() {
|
|
74
|
-
|
|
75
|
-
await
|
|
81
|
+
// @ts-expect-error
|
|
82
|
+
await globalThis.__rsc_callServer();
|
|
76
83
|
});
|
|
77
84
|
// Return a handleResponse function for use with initClient
|
|
78
85
|
return {
|
|
79
86
|
handleResponse: function handleResponse(response) {
|
|
80
87
|
if (!response.ok) {
|
|
81
88
|
// Redirect to the current page (window.location) to show the error
|
|
89
|
+
// This means the page that produced the error is called twice.
|
|
82
90
|
window.location.href = window.location.href;
|
|
83
91
|
return false;
|
|
84
92
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import "./types/worker";
|
|
2
2
|
export * from "../error";
|
|
3
|
+
export * from "../lib/types";
|
|
3
4
|
export * from "../lib/utils";
|
|
4
5
|
export * from "../register/worker";
|
|
5
6
|
export * from "../render/renderToStream";
|
|
6
7
|
export * from "../render/renderToString";
|
|
7
8
|
export * from "../requestInfo/types";
|
|
9
|
+
export * from "../requestInfo/utils";
|
|
8
10
|
export * from "../requestInfo/worker";
|
|
9
11
|
export * from "../script";
|
|
10
12
|
export * from "../worker";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import "./types/worker";
|
|
2
2
|
export * from "../error";
|
|
3
|
+
export * from "../lib/types";
|
|
3
4
|
export * from "../lib/utils";
|
|
4
5
|
export * from "../register/worker";
|
|
5
6
|
export * from "../render/renderToStream";
|
|
6
7
|
export * from "../render/renderToString";
|
|
7
8
|
export * from "../requestInfo/types";
|
|
9
|
+
export * from "../requestInfo/utils";
|
|
8
10
|
export * from "../requestInfo/worker";
|
|
9
11
|
export * from "../script";
|
|
10
12
|
export * from "../worker";
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { Kysely } from "kysely";
|
|
2
|
-
|
|
3
|
-
export declare function createDb<T>(durableObjectBinding: DurableObjectNamespace<SqliteDurableObject>, name?: string): Kysely<T>;
|
|
2
|
+
export declare function createDb<DatabaseType>(durableObjectBinding: DurableObjectNamespace<any>, name?: string): Kysely<DatabaseType>;
|
|
@@ -5,6 +5,10 @@ export function createDb(durableObjectBinding, name = "main") {
|
|
|
5
5
|
dialect: new DOWorkerDialect({
|
|
6
6
|
kyselyExecuteQuery: (...args) => {
|
|
7
7
|
const durableObjectId = durableObjectBinding.idFromName(name);
|
|
8
|
+
// context(justinvdm, 2 Oct 2025): First prize would be a type parameter
|
|
9
|
+
// for the durable object and then use it for `durableObjectBinding`'s
|
|
10
|
+
// type, rather than casting like this. However, that would prevent
|
|
11
|
+
// users from being able to do createDb<InferredDbType> then though.
|
|
8
12
|
const stub = durableObjectBinding.get(durableObjectId);
|
|
9
13
|
stub.initialize();
|
|
10
14
|
return stub.kyselyExecuteQuery(...args);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
let manifest;
|
|
2
|
-
export
|
|
2
|
+
export async function getManifest() {
|
|
3
3
|
if (manifest) {
|
|
4
4
|
return manifest;
|
|
5
5
|
}
|
|
6
6
|
if (import.meta.env.VITE_IS_DEV_SERVER) {
|
|
7
|
+
// In dev, there's no manifest, so we can use an empty object.
|
|
7
8
|
manifest = {};
|
|
8
9
|
}
|
|
9
10
|
else {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// context(justinvdm, 2 Oct 2025): In production, the manifest is a
|
|
12
|
+
// placeholder string that will be replaced by the linker plugin with the
|
|
13
|
+
// actual manifest JSON object.
|
|
14
|
+
manifest = "__RWSDK_MANIFEST_PLACEHOLDER__";
|
|
12
15
|
}
|
|
13
16
|
return manifest;
|
|
14
|
-
}
|
|
17
|
+
}
|