rwsdk 1.0.0-beta.4 → 1.0.0-beta.41
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/browser.mjs +6 -2
- package/dist/lib/e2e/constants.d.mts +4 -0
- package/dist/lib/e2e/constants.mjs +49 -12
- package/dist/lib/e2e/dev.mjs +37 -49
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- 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/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +16 -32
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +34 -3
- package/dist/lib/e2e/testHarness.mjs +219 -90
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/runtime/client/client.d.ts +35 -0
- package/dist/runtime/client/client.js +35 -0
- package/dist/runtime/client/navigation.d.ts +49 -0
- package/dist/runtime/client/navigation.js +80 -31
- package/dist/runtime/entries/clientSSR.d.ts +1 -0
- package/dist/runtime/entries/clientSSR.js +3 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/routerClient.d.ts +1 -0
- package/dist/runtime/entries/routerClient.js +1 -0
- package/dist/runtime/entries/worker.d.ts +2 -0
- package/dist/runtime/entries/worker.js +2 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
- package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
- package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
- package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
- package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
- package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
- package/dist/runtime/lib/links.d.ts +21 -7
- package/dist/runtime/lib/links.js +82 -24
- package/dist/runtime/lib/links.test.js +20 -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/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +153 -36
- package/dist/runtime/lib/router.js +169 -20
- package/dist/runtime/lib/router.test.js +241 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/lib/types.js +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 +53 -24
- package/dist/runtime/render/renderToString.d.ts +3 -6
- 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.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +3 -10
- 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.d.ts +3 -1
- package/dist/runtime/worker.js +32 -0
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +59 -113
- package/dist/use-synced-state/SyncedStateServer.d.mts +21 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +128 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +109 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
- package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/worker.test.mjs +69 -0
- package/dist/use-synced-state/client-core.d.ts +26 -0
- package/dist/use-synced-state/client-core.js +39 -0
- package/dist/use-synced-state/client.d.ts +3 -0
- package/dist/use-synced-state/client.js +4 -0
- package/dist/use-synced-state/constants.d.mts +1 -0
- package/dist/use-synced-state/constants.mjs +1 -0
- package/dist/use-synced-state/useSyncedState.d.ts +20 -0
- package/dist/use-synced-state/useSyncedState.js +58 -0
- package/dist/use-synced-state/worker.d.mts +13 -0
- package/dist/use-synced-state/worker.mjs +69 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/configPlugin.mjs +9 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
- package/dist/vite/devServerTimingPlugin.mjs +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
- package/dist/vite/directivesPlugin.mjs +4 -4
- 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 +73 -0
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
- 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/miniflareHMRPlugin.mjs +6 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
- package/dist/vite/redwoodPlugin.mjs +8 -10
- package/dist/vite/runDirectivesScan.mjs +72 -18
- package/dist/vite/ssrBridgePlugin.mjs +132 -40
- package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
- package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +74 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +27 -10
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/lib/{rwContext.js → links.test.d.ts} +0 -0
|
@@ -5,6 +5,7 @@ export type { Browser, Page } from "puppeteer-core";
|
|
|
5
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, };
|
|
6
6
|
interface DevServerInstance {
|
|
7
7
|
url: string;
|
|
8
|
+
projectDir: string;
|
|
8
9
|
stopDev: () => Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
interface DeploymentInstance {
|
|
@@ -36,6 +37,11 @@ export interface SetupPlaygroundEnvironmentOptions {
|
|
|
36
37
|
* @default true
|
|
37
38
|
*/
|
|
38
39
|
deploy?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to automatically start the dev server.
|
|
42
|
+
* @default true
|
|
43
|
+
*/
|
|
44
|
+
autoStartDevServer?: boolean;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* A Vitest hook that sets up a playground environment for a test file.
|
|
@@ -43,17 +49,29 @@ export interface SetupPlaygroundEnvironmentOptions {
|
|
|
43
49
|
* and installs dependencies using a tarball of the SDK.
|
|
44
50
|
* This ensures that tests run in a clean, isolated environment.
|
|
45
51
|
*/
|
|
46
|
-
export declare function setupPlaygroundEnvironment(options
|
|
52
|
+
export declare function setupPlaygroundEnvironment(options: SetupPlaygroundEnvironmentOptions | string): void;
|
|
47
53
|
/**
|
|
48
54
|
* Creates a dev server instance using the shared playground environment.
|
|
49
55
|
* Automatically registers cleanup to run after the test.
|
|
50
56
|
*/
|
|
51
|
-
export declare function createDevServer(
|
|
57
|
+
export declare function createDevServer(): {
|
|
58
|
+
projectDir: string;
|
|
59
|
+
start: () => Promise<DevServerInstance>;
|
|
60
|
+
};
|
|
52
61
|
/**
|
|
53
62
|
* Creates a deployment instance using the shared playground environment.
|
|
54
63
|
* Automatically registers cleanup to run after the test.
|
|
55
64
|
*/
|
|
56
|
-
export declare function createDeployment(
|
|
65
|
+
export declare function createDeployment(): {
|
|
66
|
+
projectDir: string;
|
|
67
|
+
start: () => Promise<{
|
|
68
|
+
url: string;
|
|
69
|
+
workerName: string;
|
|
70
|
+
resourceUniqueKey: string;
|
|
71
|
+
projectDir: string;
|
|
72
|
+
cleanup: () => Promise<void>;
|
|
73
|
+
}>;
|
|
74
|
+
};
|
|
57
75
|
/**
|
|
58
76
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
59
77
|
* @param name - The name of the test, used for logging.
|
|
@@ -63,13 +81,23 @@ export declare function createDeployment(projectDir: string): Promise<Deployment
|
|
|
63
81
|
* called automatically on failure.
|
|
64
82
|
*/
|
|
65
83
|
export declare function runTestWithRetries(name: string, attemptFn: () => Promise<void>): Promise<void>;
|
|
84
|
+
type SDKRunner = (name: string, testLogic: (context: {
|
|
85
|
+
browser: Browser;
|
|
86
|
+
page: Page;
|
|
87
|
+
projectDir: string;
|
|
88
|
+
}) => Promise<void>) => void;
|
|
66
89
|
declare function createTestRunner(testFn: (typeof test | typeof test.only)["concurrent"], envType: "dev" | "deploy"): (name: string, testLogic: (context: {
|
|
67
90
|
devServer?: DevServerInstance;
|
|
68
91
|
deployment?: DeploymentInstance;
|
|
69
92
|
browser: Browser;
|
|
70
93
|
page: Page;
|
|
71
94
|
url: string;
|
|
95
|
+
projectDir: string;
|
|
72
96
|
}) => Promise<void>) => void;
|
|
97
|
+
export declare const testSDK: SDKRunner & {
|
|
98
|
+
only: SDKRunner;
|
|
99
|
+
skip: typeof test.skip;
|
|
100
|
+
};
|
|
73
101
|
/**
|
|
74
102
|
* High-level test wrapper for dev server tests.
|
|
75
103
|
* Automatically skips if RWSDK_SKIP_DEV=1
|
|
@@ -83,6 +111,7 @@ export declare namespace testDev {
|
|
|
83
111
|
browser: Browser;
|
|
84
112
|
page: Page;
|
|
85
113
|
url: string;
|
|
114
|
+
projectDir: string;
|
|
86
115
|
}) => Promise<void>) => void;
|
|
87
116
|
}
|
|
88
117
|
/**
|
|
@@ -98,6 +127,7 @@ export declare namespace testDeploy {
|
|
|
98
127
|
browser: Browser;
|
|
99
128
|
page: Page;
|
|
100
129
|
url: string;
|
|
130
|
+
projectDir: string;
|
|
101
131
|
}) => Promise<void>) => void;
|
|
102
132
|
}
|
|
103
133
|
/**
|
|
@@ -110,6 +140,7 @@ export declare function testDevAndDeploy(name: string, testFn: (context: {
|
|
|
110
140
|
browser: Browser;
|
|
111
141
|
page: Page;
|
|
112
142
|
url: string;
|
|
143
|
+
projectDir: string;
|
|
113
144
|
}) => Promise<void>): void;
|
|
114
145
|
export declare namespace testDevAndDeploy {
|
|
115
146
|
var skip: (name: string, testFn?: any) => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs-extra";
|
|
2
|
-
import os from "os";
|
|
3
2
|
import path, { basename, dirname, join as pathJoin } from "path";
|
|
4
3
|
import puppeteer from "puppeteer-core";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
5
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, test, } from "vitest";
|
|
6
6
|
import { launchBrowser } from "./browser.mjs";
|
|
7
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";
|
|
@@ -9,6 +9,7 @@ import { runDevServer } from "./dev.mjs";
|
|
|
9
9
|
import { poll, pollValue } from "./poll.mjs";
|
|
10
10
|
import { deleteD1Database, deleteWorker, isRelatedToTest, runRelease, } from "./release.mjs";
|
|
11
11
|
import { setupTarballEnvironment } from "./tarball.mjs";
|
|
12
|
+
import { ensureTmpDir } from "./utils.mjs";
|
|
12
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, };
|
|
13
14
|
// Environment variable flags for skipping tests
|
|
14
15
|
const SKIP_DEV_SERVER_TESTS = process.env.RWSDK_SKIP_DEV === "1";
|
|
@@ -20,6 +21,8 @@ let globalDevInstancePromise = null;
|
|
|
20
21
|
let globalDeploymentInstancePromise = null;
|
|
21
22
|
let globalDevInstance = null;
|
|
22
23
|
let globalDeploymentInstance = null;
|
|
24
|
+
const devInstances = [];
|
|
25
|
+
const deploymentInstances = [];
|
|
23
26
|
let hooksRegistered = false;
|
|
24
27
|
/**
|
|
25
28
|
* Registers global cleanup hooks automatically
|
|
@@ -30,21 +33,33 @@ function ensureHooksRegistered() {
|
|
|
30
33
|
// Register global afterAll to clean up the playground environment
|
|
31
34
|
afterAll(async () => {
|
|
32
35
|
const cleanupPromises = [];
|
|
33
|
-
|
|
34
|
-
cleanupPromises.push(
|
|
36
|
+
for (const instance of devInstances) {
|
|
37
|
+
cleanupPromises.push(instance.stopDev().catch((error) => {
|
|
38
|
+
// Suppress all cleanup errors - they don't affect test results
|
|
39
|
+
console.warn(`Suppressing error during dev server cleanup: ${error instanceof Error ? error.message : String(error)}`);
|
|
40
|
+
}));
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
cleanupPromises.push(
|
|
42
|
+
for (const instance of deploymentInstances) {
|
|
43
|
+
cleanupPromises.push(instance.cleanup().catch((error) => {
|
|
44
|
+
// Suppress all cleanup errors - they don't affect test results
|
|
45
|
+
console.warn(`Suppressing error during deployment cleanup: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}));
|
|
38
47
|
}
|
|
39
48
|
if (globalDevPlaygroundEnv) {
|
|
40
|
-
cleanupPromises.push(globalDevPlaygroundEnv.cleanup())
|
|
49
|
+
cleanupPromises.push(globalDevPlaygroundEnv.cleanup().catch((error) => {
|
|
50
|
+
// Suppress all cleanup errors - they don't affect test results
|
|
51
|
+
console.warn(`Suppressing error during dev environment cleanup: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}));
|
|
41
53
|
}
|
|
42
54
|
if (globalDeployPlaygroundEnv) {
|
|
43
|
-
cleanupPromises.push(globalDeployPlaygroundEnv.cleanup())
|
|
55
|
+
cleanupPromises.push(globalDeployPlaygroundEnv.cleanup().catch((error) => {
|
|
56
|
+
// Suppress all cleanup errors - they don't affect test results
|
|
57
|
+
console.warn(`Suppressing error during deploy environment cleanup: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}));
|
|
44
59
|
}
|
|
45
60
|
await Promise.all(cleanupPromises);
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
devInstances.length = 0;
|
|
62
|
+
deploymentInstances.length = 0;
|
|
48
63
|
globalDevPlaygroundEnv = null;
|
|
49
64
|
globalDeployPlaygroundEnv = null;
|
|
50
65
|
});
|
|
@@ -62,11 +77,11 @@ function getProjectDirectory() {
|
|
|
62
77
|
* Derive the playground directory from import.meta.url by finding the nearest package.json
|
|
63
78
|
*/
|
|
64
79
|
function getPlaygroundDirFromImportMeta(importMetaUrl) {
|
|
65
|
-
const
|
|
66
|
-
const testFilePath = url.pathname;
|
|
80
|
+
const testFilePath = fileURLToPath(importMetaUrl);
|
|
67
81
|
let currentDir = dirname(testFilePath);
|
|
68
82
|
// Walk up the tree from the test file's directory
|
|
69
|
-
|
|
83
|
+
// Stop when the parent directory is the same as the current directory (we've reached the root)
|
|
84
|
+
while (dirname(currentDir) !== currentDir) {
|
|
70
85
|
// Check if a package.json exists in the current directory
|
|
71
86
|
if (fs.existsSync(pathJoin(currentDir, "package.json"))) {
|
|
72
87
|
return currentDir;
|
|
@@ -82,8 +97,10 @@ function getPlaygroundDirFromImportMeta(importMetaUrl) {
|
|
|
82
97
|
* and installs dependencies using a tarball of the SDK.
|
|
83
98
|
* This ensures that tests run in a clean, isolated environment.
|
|
84
99
|
*/
|
|
85
|
-
export function setupPlaygroundEnvironment(options
|
|
86
|
-
const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, } = typeof options === "string"
|
|
100
|
+
export function setupPlaygroundEnvironment(options) {
|
|
101
|
+
const { sourceProjectDir, monorepoRoot, dev = true, deploy = true, autoStartDevServer = true, } = typeof options === "string"
|
|
102
|
+
? { sourceProjectDir: options, autoStartDevServer: true }
|
|
103
|
+
: options;
|
|
87
104
|
ensureHooksRegistered();
|
|
88
105
|
beforeAll(async () => {
|
|
89
106
|
let projectDir;
|
|
@@ -109,18 +126,21 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
109
126
|
projectDir: devEnv.targetDir,
|
|
110
127
|
cleanup: devEnv.cleanup,
|
|
111
128
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
if (autoStartDevServer) {
|
|
130
|
+
const devControl = createDevServer();
|
|
131
|
+
globalDevInstancePromise = devControl.start().then((instance) => {
|
|
132
|
+
globalDevInstance = instance;
|
|
133
|
+
return instance;
|
|
134
|
+
});
|
|
135
|
+
// Prevent unhandled promise rejections. The error will be handled inside
|
|
136
|
+
// the test's beforeEach hook where this promise is awaited.
|
|
137
|
+
globalDevInstancePromise.catch(() => { });
|
|
138
|
+
}
|
|
119
139
|
}
|
|
120
140
|
else {
|
|
121
|
-
|
|
141
|
+
globalDevPlaygroundEnv = null;
|
|
122
142
|
}
|
|
123
|
-
if (deploy) {
|
|
143
|
+
if (deploy && !SKIP_DEPLOYMENT_TESTS) {
|
|
124
144
|
const deployEnv = await setupTarballEnvironment({
|
|
125
145
|
projectDir,
|
|
126
146
|
monorepoRoot,
|
|
@@ -130,7 +150,10 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
130
150
|
projectDir: deployEnv.targetDir,
|
|
131
151
|
cleanup: deployEnv.cleanup,
|
|
132
152
|
};
|
|
133
|
-
|
|
153
|
+
const deployControl = createDeployment();
|
|
154
|
+
globalDeploymentInstancePromise = deployControl
|
|
155
|
+
.start()
|
|
156
|
+
.then((instance) => {
|
|
134
157
|
globalDeploymentInstance = instance;
|
|
135
158
|
return instance;
|
|
136
159
|
});
|
|
@@ -138,7 +161,7 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
138
161
|
globalDeploymentInstancePromise.catch(() => { });
|
|
139
162
|
}
|
|
140
163
|
else {
|
|
141
|
-
|
|
164
|
+
globalDeployPlaygroundEnv = null;
|
|
142
165
|
}
|
|
143
166
|
}, SETUP_PLAYGROUND_ENV_TIMEOUT);
|
|
144
167
|
}
|
|
@@ -146,82 +169,109 @@ export function setupPlaygroundEnvironment(options = {}) {
|
|
|
146
169
|
* Creates a dev server instance using the shared playground environment.
|
|
147
170
|
* Automatically registers cleanup to run after the test.
|
|
148
171
|
*/
|
|
149
|
-
export
|
|
150
|
-
|
|
151
|
-
|
|
172
|
+
export function createDevServer() {
|
|
173
|
+
ensureHooksRegistered();
|
|
174
|
+
if (!globalDevPlaygroundEnv) {
|
|
175
|
+
throw new Error("Dev playground environment not initialized. Enable `dev: true` in setupPlaygroundEnvironment.");
|
|
152
176
|
}
|
|
177
|
+
const { projectDir } = globalDevPlaygroundEnv;
|
|
153
178
|
const packageManager = process.env.PACKAGE_MANAGER || "pnpm";
|
|
154
|
-
|
|
155
|
-
timeout: DEV_SERVER_TIMEOUT,
|
|
156
|
-
minTries: DEV_SERVER_MIN_TRIES,
|
|
157
|
-
onRetry: (error, tries) => {
|
|
158
|
-
console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
179
|
+
let instance = null;
|
|
161
180
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
projectDir,
|
|
182
|
+
start: async () => {
|
|
183
|
+
if (instance)
|
|
184
|
+
return instance;
|
|
185
|
+
if (SKIP_DEV_SERVER_TESTS) {
|
|
186
|
+
throw new Error("Dev server tests are skipped via RWSDK_SKIP_DEV=1");
|
|
187
|
+
}
|
|
188
|
+
const devResult = await pollValue(() => runDevServer(packageManager, projectDir), {
|
|
189
|
+
timeout: DEV_SERVER_TIMEOUT,
|
|
190
|
+
minTries: DEV_SERVER_MIN_TRIES,
|
|
191
|
+
onRetry: (error, tries) => {
|
|
192
|
+
console.log(`Retrying dev server creation (attempt ${tries})... Error: ${error.message}`);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
instance = {
|
|
196
|
+
url: devResult.url,
|
|
197
|
+
projectDir,
|
|
198
|
+
stopDev: devResult.stopDev,
|
|
199
|
+
};
|
|
200
|
+
devInstances.push(instance);
|
|
201
|
+
return instance;
|
|
202
|
+
},
|
|
164
203
|
};
|
|
165
204
|
}
|
|
166
205
|
/**
|
|
167
206
|
* Creates a deployment instance using the shared playground environment.
|
|
168
207
|
* Automatically registers cleanup to run after the test.
|
|
169
208
|
*/
|
|
170
|
-
export
|
|
171
|
-
|
|
172
|
-
|
|
209
|
+
export function createDeployment() {
|
|
210
|
+
ensureHooksRegistered();
|
|
211
|
+
if (!globalDeployPlaygroundEnv) {
|
|
212
|
+
throw new Error("Deploy playground environment not initialized. Enable `deploy: true` in setupPlaygroundEnvironment.");
|
|
173
213
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Poll the URL to ensure it's live before proceeding
|
|
184
|
-
await poll(async () => {
|
|
185
|
-
try {
|
|
186
|
-
const response = await fetch(deployResult.url);
|
|
187
|
-
// We consider any response (even 4xx or 5xx) as success,
|
|
188
|
-
// as it means the worker is routable.
|
|
189
|
-
return response.status > 0;
|
|
190
|
-
}
|
|
191
|
-
catch (e) {
|
|
192
|
-
return false;
|
|
214
|
+
const { projectDir } = globalDeployPlaygroundEnv;
|
|
215
|
+
let instance = null;
|
|
216
|
+
return {
|
|
217
|
+
projectDir,
|
|
218
|
+
start: async () => {
|
|
219
|
+
if (instance)
|
|
220
|
+
return instance;
|
|
221
|
+
if (SKIP_DEPLOYMENT_TESTS) {
|
|
222
|
+
throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
|
|
193
223
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
224
|
+
const newInstance = await pollValue(async () => {
|
|
225
|
+
const dirName = basename(projectDir);
|
|
226
|
+
// Match formats: {projectName}-t-{hash}, {projectName}-test-{hash}, or {projectName}-e2e-test-{hash}
|
|
227
|
+
const match = dirName.match(/-t-([a-f0-9]+)$/) ||
|
|
228
|
+
dirName.match(/-test-([a-f0-9]+)$/) ||
|
|
229
|
+
dirName.match(/-e2e-test-([a-f0-9]+)$/);
|
|
230
|
+
const resourceUniqueKey = match
|
|
231
|
+
? match[1]
|
|
232
|
+
: Math.random().toString(36).substring(2, 15);
|
|
233
|
+
const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
|
|
234
|
+
await poll(async () => {
|
|
235
|
+
try {
|
|
236
|
+
const response = await fetch(deployResult.url);
|
|
237
|
+
return response.status > 0;
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}, {
|
|
243
|
+
timeout: DEPLOYMENT_CHECK_TIMEOUT,
|
|
244
|
+
});
|
|
245
|
+
const cleanup = async () => {
|
|
246
|
+
const performCleanup = async () => {
|
|
247
|
+
if (isRelatedToTest(deployResult.workerName, resourceUniqueKey)) {
|
|
248
|
+
await deleteWorker(deployResult.workerName, projectDir, resourceUniqueKey);
|
|
249
|
+
}
|
|
250
|
+
await deleteD1Database(resourceUniqueKey, projectDir, resourceUniqueKey);
|
|
251
|
+
};
|
|
252
|
+
performCleanup().catch((error) => {
|
|
253
|
+
console.warn(`Warning: Background deployment cleanup failed: ${error.message}`);
|
|
254
|
+
});
|
|
255
|
+
return Promise.resolve();
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
url: deployResult.url,
|
|
259
|
+
workerName: deployResult.workerName,
|
|
260
|
+
resourceUniqueKey,
|
|
261
|
+
projectDir: projectDir,
|
|
262
|
+
cleanup,
|
|
263
|
+
};
|
|
264
|
+
}, {
|
|
265
|
+
timeout: DEPLOYMENT_TIMEOUT,
|
|
266
|
+
minTries: DEPLOYMENT_MIN_TRIES,
|
|
267
|
+
onRetry: (error, tries) => {
|
|
268
|
+
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
269
|
+
},
|
|
208
270
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
url: deployResult.url,
|
|
213
|
-
workerName: deployResult.workerName,
|
|
214
|
-
resourceUniqueKey,
|
|
215
|
-
projectDir: projectDir,
|
|
216
|
-
cleanup,
|
|
217
|
-
};
|
|
218
|
-
}, {
|
|
219
|
-
timeout: DEPLOYMENT_TIMEOUT,
|
|
220
|
-
minTries: DEPLOYMENT_MIN_TRIES,
|
|
221
|
-
onRetry: (error, tries) => {
|
|
222
|
-
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
271
|
+
deploymentInstances.push(newInstance);
|
|
272
|
+
return newInstance;
|
|
223
273
|
},
|
|
224
|
-
}
|
|
274
|
+
};
|
|
225
275
|
}
|
|
226
276
|
/**
|
|
227
277
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
@@ -270,7 +320,7 @@ function createTestRunner(testFn, envType) {
|
|
|
270
320
|
return (name, testLogic) => {
|
|
271
321
|
if ((envType === "dev" && SKIP_DEV_SERVER_TESTS) ||
|
|
272
322
|
(envType === "deploy" && SKIP_DEPLOYMENT_TESTS)) {
|
|
273
|
-
test.skip(name
|
|
323
|
+
test.skip(`${name} (${envType})`, () => { });
|
|
274
324
|
return;
|
|
275
325
|
}
|
|
276
326
|
describe.concurrent(name, () => {
|
|
@@ -278,7 +328,7 @@ function createTestRunner(testFn, envType) {
|
|
|
278
328
|
let instance;
|
|
279
329
|
let browser;
|
|
280
330
|
beforeAll(async () => {
|
|
281
|
-
const tempDir = path.join(
|
|
331
|
+
const tempDir = path.join(await ensureTmpDir(), "rwsdk-e2e-tests");
|
|
282
332
|
const wsEndpointFile = path.join(tempDir, "wsEndpoint");
|
|
283
333
|
try {
|
|
284
334
|
const wsEndpoint = await fs.readFile(wsEndpointFile, "utf-8");
|
|
@@ -332,12 +382,91 @@ function createTestRunner(testFn, envType) {
|
|
|
332
382
|
browser: browser,
|
|
333
383
|
page: page,
|
|
334
384
|
url: instance.url,
|
|
385
|
+
projectDir: instance
|
|
386
|
+
.projectDir,
|
|
335
387
|
});
|
|
336
388
|
});
|
|
337
389
|
});
|
|
338
390
|
});
|
|
339
391
|
};
|
|
340
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Creates a low-level test runner that provides utilities for creating
|
|
395
|
+
* tests that need to perform setup actions before the server starts.
|
|
396
|
+
*/
|
|
397
|
+
function createSDKTestRunner() {
|
|
398
|
+
const internalRunner = (testFn) => {
|
|
399
|
+
return (name, testLogic) => {
|
|
400
|
+
describe.concurrent(name, () => {
|
|
401
|
+
let page;
|
|
402
|
+
let browser;
|
|
403
|
+
beforeAll(async () => {
|
|
404
|
+
const tempDir = path.join(await ensureTmpDir(), "rwsdk-e2e-tests");
|
|
405
|
+
const wsEndpointFile = path.join(tempDir, "wsEndpoint");
|
|
406
|
+
try {
|
|
407
|
+
const wsEndpoint = await fs.readFile(wsEndpointFile, "utf-8");
|
|
408
|
+
browser = await puppeteer.connect({
|
|
409
|
+
browserWSEndpoint: wsEndpoint,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
console.warn("Failed to connect to existing browser instance. " +
|
|
414
|
+
"This might happen if you are running a single test file. " +
|
|
415
|
+
"Launching a new browser instance instead.");
|
|
416
|
+
// Check for RWSDK_HEADLESS environment variable (default to true if not set)
|
|
417
|
+
// Set RWSDK_HEADLESS=0 or RWSDK_HEADLESS=false to run in headed mode
|
|
418
|
+
const headless = process.env.RWSDK_HEADLESS === undefined ||
|
|
419
|
+
process.env.RWSDK_HEADLESS === "1" ||
|
|
420
|
+
process.env.RWSDK_HEADLESS === "true";
|
|
421
|
+
browser = await launchBrowser(undefined, headless);
|
|
422
|
+
}
|
|
423
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
424
|
+
afterAll(async () => {
|
|
425
|
+
if (browser) {
|
|
426
|
+
await browser.disconnect();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
beforeEach(async () => {
|
|
430
|
+
if (!globalDevPlaygroundEnv && !globalDeployPlaygroundEnv) {
|
|
431
|
+
throw new Error("Test environment not initialized. Call setupPlaygroundEnvironment() in your test file.");
|
|
432
|
+
}
|
|
433
|
+
page = await browser.newPage();
|
|
434
|
+
page.setDefaultTimeout(PUPPETEER_TIMEOUT);
|
|
435
|
+
}, SETUP_WAIT_TIMEOUT);
|
|
436
|
+
afterEach(async () => {
|
|
437
|
+
if (page) {
|
|
438
|
+
try {
|
|
439
|
+
await page.close();
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
console.warn(`Suppressing error during page.close() in test "${name}":`, error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
testFn(">", async () => {
|
|
447
|
+
if (!browser) {
|
|
448
|
+
throw new Error("Test environment not ready.");
|
|
449
|
+
}
|
|
450
|
+
await runTestWithRetries(name, async () => {
|
|
451
|
+
await testLogic({
|
|
452
|
+
browser: browser,
|
|
453
|
+
page: page,
|
|
454
|
+
projectDir: globalDevPlaygroundEnv?.projectDir ||
|
|
455
|
+
globalDeployPlaygroundEnv?.projectDir ||
|
|
456
|
+
"",
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
};
|
|
462
|
+
};
|
|
463
|
+
const main = internalRunner(test);
|
|
464
|
+
return Object.assign(main, {
|
|
465
|
+
only: internalRunner(test.only),
|
|
466
|
+
skip: test.skip,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
export const testSDK = createSDKTestRunner();
|
|
341
470
|
/**
|
|
342
471
|
* High-level test wrapper for dev server tests.
|
|
343
472
|
* Automatically skips if RWSDK_SKIP_DEV=1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureTmpDir(): Promise<string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mkdirp } from "fs-extra";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export async function ensureTmpDir() {
|
|
6
|
+
let baseTmpDir = os.tmpdir();
|
|
7
|
+
// context(justinvdm, 2 Nov 2025): Normalize the base temp dir on Windows
|
|
8
|
+
// to prevent short/long path mismatches that break Vite's alias resolution.
|
|
9
|
+
if (process.platform === "win32") {
|
|
10
|
+
baseTmpDir = fs.realpathSync.native(baseTmpDir);
|
|
11
|
+
}
|
|
12
|
+
const tmpDir = path.join(baseTmpDir, "rwsdk-e2e");
|
|
13
|
+
await mkdirp(tmpDir);
|
|
14
|
+
return tmpDir;
|
|
15
|
+
}
|
|
@@ -1,8 +1,43 @@
|
|
|
1
1
|
import "./setWebpackRequire";
|
|
2
2
|
export { default as React } from "react";
|
|
3
|
+
export type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
|
3
4
|
export { ClientOnly } from "./ClientOnly.js";
|
|
5
|
+
export { initClientNavigation, navigate } from "./navigation.js";
|
|
4
6
|
import type { HydrationOptions, Transport } from "./types";
|
|
5
7
|
export declare const fetchTransport: Transport;
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the React client and hydrates the RSC payload.
|
|
10
|
+
*
|
|
11
|
+
* This function sets up client-side hydration for React Server Components,
|
|
12
|
+
* making the page interactive. Call this from your client entry point.
|
|
13
|
+
*
|
|
14
|
+
* @param transport - Custom transport for server communication (defaults to fetchTransport)
|
|
15
|
+
* @param hydrateRootOptions - Options passed to React's hydrateRoot
|
|
16
|
+
* @param handleResponse - Custom response handler for navigation errors
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Basic usage
|
|
20
|
+
* import { initClient } from "rwsdk/client";
|
|
21
|
+
*
|
|
22
|
+
* initClient();
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // With client-side navigation
|
|
26
|
+
* import { initClient, initClientNavigation } from "rwsdk/client";
|
|
27
|
+
*
|
|
28
|
+
* const { handleResponse } = initClientNavigation();
|
|
29
|
+
* initClient({ handleResponse });
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // With custom React hydration options
|
|
33
|
+
* initClient({
|
|
34
|
+
* hydrateRootOptions: {
|
|
35
|
+
* onRecoverableError: (error) => {
|
|
36
|
+
* console.warn("Recoverable error:", error);
|
|
37
|
+
* },
|
|
38
|
+
* },
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
6
41
|
export declare const initClient: ({ transport, hydrateRootOptions, handleResponse, }?: {
|
|
7
42
|
transport?: Transport;
|
|
8
43
|
hydrateRootOptions?: HydrationOptions;
|
|
@@ -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);
|
|
@@ -48,6 +50,39 @@ export const fetchTransport = (transportContext) => {
|
|
|
48
50
|
};
|
|
49
51
|
return fetchCallServer;
|
|
50
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Initializes the React client and hydrates the RSC payload.
|
|
55
|
+
*
|
|
56
|
+
* This function sets up client-side hydration for React Server Components,
|
|
57
|
+
* making the page interactive. Call this from your client entry point.
|
|
58
|
+
*
|
|
59
|
+
* @param transport - Custom transport for server communication (defaults to fetchTransport)
|
|
60
|
+
* @param hydrateRootOptions - Options passed to React's hydrateRoot
|
|
61
|
+
* @param handleResponse - Custom response handler for navigation errors
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Basic usage
|
|
65
|
+
* import { initClient } from "rwsdk/client";
|
|
66
|
+
*
|
|
67
|
+
* initClient();
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With client-side navigation
|
|
71
|
+
* import { initClient, initClientNavigation } from "rwsdk/client";
|
|
72
|
+
*
|
|
73
|
+
* const { handleResponse } = initClientNavigation();
|
|
74
|
+
* initClient({ handleResponse });
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // With custom React hydration options
|
|
78
|
+
* initClient({
|
|
79
|
+
* hydrateRootOptions: {
|
|
80
|
+
* onRecoverableError: (error) => {
|
|
81
|
+
* console.warn("Recoverable error:", error);
|
|
82
|
+
* },
|
|
83
|
+
* },
|
|
84
|
+
* });
|
|
85
|
+
*/
|
|
51
86
|
export const initClient = async ({ transport = fetchTransport, hydrateRootOptions, handleResponse, } = {}) => {
|
|
52
87
|
const transportContext = {
|
|
53
88
|
setRscPayload: () => { },
|