rwsdk 1.0.0-alpha.10 → 1.0.0-alpha.12
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/e2e/dev.mjs +62 -52
- package/dist/lib/e2e/environment.d.mts +1 -1
- package/dist/lib/e2e/environment.mjs +55 -12
- package/dist/lib/e2e/index.d.mts +1 -0
- package/dist/lib/e2e/index.mjs +1 -0
- package/dist/lib/e2e/poll.d.mts +8 -0
- package/dist/lib/e2e/poll.mjs +31 -0
- package/dist/lib/e2e/retry.d.mts +4 -0
- package/dist/lib/e2e/retry.mjs +16 -0
- package/dist/lib/e2e/setup.d.mts +2 -0
- package/dist/lib/e2e/setup.mjs +1 -0
- package/dist/lib/e2e/tarball.d.mts +2 -1
- package/dist/lib/e2e/tarball.mjs +2 -2
- package/dist/lib/e2e/testHarness.d.mts +59 -50
- package/dist/lib/e2e/testHarness.mjs +255 -325
- package/dist/runtime/register/worker.js +8 -1
- package/dist/vite/directiveModulesDevPlugin.mjs +1 -1
- package/dist/vite/redwoodPlugin.d.mts +2 -0
- package/dist/vite/redwoodPlugin.mjs +19 -0
- package/dist/vite/resolveForcedPaths.d.mts +4 -0
- package/dist/vite/resolveForcedPaths.mjs +9 -0
- package/dist/vite/runDirectivesScan.d.mts +2 -1
- package/dist/vite/runDirectivesScan.mjs +39 -7
- package/dist/vite/transformClientComponents.mjs +2 -1
- package/dist/vite/transformServerFunctions.d.mts +1 -1
- package/dist/vite/transformServerFunctions.mjs +1 -1
- package/dist/vite/transformServerFunctions.test.mjs +3 -3
- package/package.json +6 -1
package/dist/lib/e2e/dev.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import { setTimeout } from "node:timers/promises";
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import { $ } from "../../lib/$.mjs";
|
|
4
|
+
import { poll } from "./poll.mjs";
|
|
5
|
+
const DEV_SERVER_CHECK_TIMEOUT = process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT
|
|
6
|
+
? parseInt(process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT, 10)
|
|
7
|
+
: 5 * 60 * 1000;
|
|
4
8
|
const log = debug("rwsdk:e2e:dev");
|
|
5
9
|
/**
|
|
6
10
|
* Run the local development server and return the URL
|
|
@@ -12,59 +16,47 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
12
16
|
let isErrorExpected = false;
|
|
13
17
|
const stopDev = async () => {
|
|
14
18
|
isErrorExpected = true;
|
|
15
|
-
if (!devProcess) {
|
|
16
|
-
log("No dev process to stop");
|
|
19
|
+
if (!devProcess || !devProcess.pid) {
|
|
20
|
+
log("No dev process to stop or PID is missing");
|
|
17
21
|
return;
|
|
18
22
|
}
|
|
19
23
|
console.log("Stopping development server...");
|
|
20
24
|
try {
|
|
21
|
-
// Send a regular termination signal first
|
|
22
|
-
|
|
23
|
-
// Wait for the process to terminate with a timeout
|
|
24
|
-
const terminationTimeout = 5000; // 5 seconds timeout
|
|
25
|
-
const terminationPromise = Promise.race([
|
|
26
|
-
// Wait for natural process termination
|
|
27
|
-
(async () => {
|
|
28
|
-
try {
|
|
29
|
-
await devProcess;
|
|
30
|
-
log("Dev server process was terminated normally");
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
// Expected error when the process is killed
|
|
35
|
-
log("Dev server process was terminated");
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
})(),
|
|
39
|
-
// Or timeout
|
|
40
|
-
(async () => {
|
|
41
|
-
await setTimeout(terminationTimeout);
|
|
42
|
-
return false;
|
|
43
|
-
})(),
|
|
44
|
-
]);
|
|
45
|
-
// Check if process terminated within timeout
|
|
46
|
-
const terminated = await terminationPromise;
|
|
47
|
-
// If not terminated within timeout, force kill
|
|
48
|
-
if (!terminated) {
|
|
49
|
-
log("Dev server process did not terminate within timeout, force killing with SIGKILL");
|
|
50
|
-
console.log("⚠️ Development server not responding after 5 seconds timeout, force killing...");
|
|
51
|
-
// Try to kill with SIGKILL if the process still has a pid
|
|
52
|
-
if (devProcess.pid) {
|
|
53
|
-
try {
|
|
54
|
-
// Use process.kill with SIGKILL for a stronger termination
|
|
55
|
-
process.kill(devProcess.pid, "SIGKILL");
|
|
56
|
-
log("Sent SIGKILL to process %d", devProcess.pid);
|
|
57
|
-
}
|
|
58
|
-
catch (killError) {
|
|
59
|
-
log("Error sending SIGKILL to process: %O", killError);
|
|
60
|
-
// Non-fatal, as the process might already be gone
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
25
|
+
// Send a regular termination signal to the entire process group first
|
|
26
|
+
process.kill(-devProcess.pid, "SIGTERM");
|
|
64
27
|
}
|
|
65
28
|
catch (e) {
|
|
66
|
-
|
|
67
|
-
|
|
29
|
+
log("Could not send SIGTERM to dev server process group: %O", e);
|
|
30
|
+
}
|
|
31
|
+
// Wait for the process to terminate with a timeout
|
|
32
|
+
const terminationTimeout = 5000; // 5 seconds
|
|
33
|
+
const processExitPromise = devProcess.catch(() => {
|
|
34
|
+
// We expect this promise to reject when the process is killed,
|
|
35
|
+
// so we catch and ignore the error.
|
|
36
|
+
});
|
|
37
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(undefined), terminationTimeout));
|
|
38
|
+
await Promise.race([processExitPromise, timeoutPromise]);
|
|
39
|
+
// Check if the process is still alive. We can't reliably check exitCode
|
|
40
|
+
// on a detached process, so we try sending a signal 0, which errors
|
|
41
|
+
// if the process doesn't exist.
|
|
42
|
+
let isAlive = true;
|
|
43
|
+
try {
|
|
44
|
+
// Sending signal 0 doesn't kill the process, but checks if it exists
|
|
45
|
+
process.kill(-devProcess.pid, 0);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
isAlive = false;
|
|
49
|
+
}
|
|
50
|
+
// If not terminated within timeout, force kill the entire process group
|
|
51
|
+
if (isAlive) {
|
|
52
|
+
log("Dev server process did not terminate within timeout, force killing with SIGKILL");
|
|
53
|
+
console.log("⚠️ Development server not responding after 5 seconds timeout, force killing...");
|
|
54
|
+
try {
|
|
55
|
+
process.kill(-devProcess.pid, "SIGKILL");
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
log("Could not send SIGKILL to dev server process group: %O", e);
|
|
59
|
+
}
|
|
68
60
|
}
|
|
69
61
|
console.log("Development server stopped");
|
|
70
62
|
};
|
|
@@ -96,7 +88,7 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
96
88
|
// Use the provided cwd if available
|
|
97
89
|
devProcess = $({
|
|
98
90
|
all: true,
|
|
99
|
-
detached:
|
|
91
|
+
detached: true, // Run in a new process group so we can kill the entire group
|
|
100
92
|
cleanup: false, // Don't auto-kill on exit
|
|
101
93
|
cwd: cwd || process.cwd(), // Use provided directory or current directory
|
|
102
94
|
env, // Pass the updated environment variables
|
|
@@ -104,8 +96,10 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
104
96
|
}) `${pm} run dev`;
|
|
105
97
|
devProcess.catch((error) => {
|
|
106
98
|
if (!isErrorExpected) {
|
|
107
|
-
//
|
|
108
|
-
|
|
99
|
+
// Don't re-throw. The error will be handled gracefully by the polling
|
|
100
|
+
// logic in `waitForUrl`, which will detect that the process has exited.
|
|
101
|
+
// Re-throwing here would cause an unhandled promise rejection.
|
|
102
|
+
log("Dev server process exited unexpectedly:", error.shortMessage);
|
|
109
103
|
}
|
|
110
104
|
});
|
|
111
105
|
log("Development server process spawned in directory: %s", cwd || process.cwd());
|
|
@@ -211,7 +205,7 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
211
205
|
log("ERROR: Development server process exited with code %d. Final output: %s", devProcess.exitCode, allOutput);
|
|
212
206
|
throw new Error(`Development server process exited with code ${devProcess.exitCode}`);
|
|
213
207
|
}
|
|
214
|
-
await
|
|
208
|
+
await sleep(500); // Check every 500ms
|
|
215
209
|
}
|
|
216
210
|
log("ERROR: Timed out waiting for dev server URL. Final accumulated output: %s", allOutput);
|
|
217
211
|
throw new Error("Timed out waiting for dev server URL");
|
|
@@ -219,6 +213,22 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
219
213
|
// Wait for the URL
|
|
220
214
|
const serverUrl = await waitForUrl();
|
|
221
215
|
console.log(`✅ Development server started at ${serverUrl}`);
|
|
216
|
+
// Poll the URL to ensure it's live before proceeding
|
|
217
|
+
await poll(async () => {
|
|
218
|
+
try {
|
|
219
|
+
const response = await fetch(serverUrl, {
|
|
220
|
+
signal: AbortSignal.timeout(1000),
|
|
221
|
+
});
|
|
222
|
+
// We consider any response (even 4xx or 5xx) as success,
|
|
223
|
+
// as it means the worker is routable.
|
|
224
|
+
return response.status > 0;
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}, {
|
|
230
|
+
timeout: DEV_SERVER_CHECK_TIMEOUT,
|
|
231
|
+
});
|
|
222
232
|
return { url: serverUrl, stopDev };
|
|
223
233
|
}
|
|
224
234
|
catch (error) {
|
|
@@ -7,7 +7,7 @@ export declare function setupTestEnvironment(options?: SmokeTestOptions): Promis
|
|
|
7
7
|
/**
|
|
8
8
|
* Copy project to a temporary directory with a unique name
|
|
9
9
|
*/
|
|
10
|
-
export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager): Promise<{
|
|
10
|
+
export declare function copyProjectToTempDir(projectDir: string, resourceUniqueKey: string, packageManager?: PackageManager, monorepoRoot?: string): Promise<{
|
|
11
11
|
tempDir: tmp.DirectoryResult;
|
|
12
12
|
targetDir: string;
|
|
13
13
|
workerName: string;
|
|
@@ -10,6 +10,8 @@ import { createHash } from "crypto";
|
|
|
10
10
|
import { $ } from "../../lib/$.mjs";
|
|
11
11
|
import { ROOT_DIR } from "../constants.mjs";
|
|
12
12
|
import path from "node:path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import { retry } from "./retry.mjs";
|
|
13
15
|
const log = debug("rwsdk:e2e:environment");
|
|
14
16
|
const createSdkTarball = async () => {
|
|
15
17
|
const packResult = await $({ cwd: ROOT_DIR, stdio: "pipe" }) `npm pack`;
|
|
@@ -24,11 +26,11 @@ const createSdkTarball = async () => {
|
|
|
24
26
|
};
|
|
25
27
|
return { tarballPath, cleanupTarball };
|
|
26
28
|
};
|
|
27
|
-
const setTarballDependency = async (targetDir,
|
|
29
|
+
const setTarballDependency = async (targetDir, tarballName) => {
|
|
28
30
|
const filePath = join(targetDir, "package.json");
|
|
29
31
|
const packageJson = await fs.promises.readFile(filePath, "utf-8");
|
|
30
32
|
const packageJsonContent = JSON.parse(packageJson);
|
|
31
|
-
packageJsonContent.dependencies.rwsdk = `file:${
|
|
33
|
+
packageJsonContent.dependencies.rwsdk = `file:${tarballName}`;
|
|
32
34
|
await fs.promises.writeFile(filePath, JSON.stringify(packageJsonContent, null, 2));
|
|
33
35
|
};
|
|
34
36
|
/**
|
|
@@ -87,20 +89,26 @@ export async function setupTestEnvironment(options = {}) {
|
|
|
87
89
|
/**
|
|
88
90
|
* Copy project to a temporary directory with a unique name
|
|
89
91
|
*/
|
|
90
|
-
export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager) {
|
|
92
|
+
export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot) {
|
|
91
93
|
const { tarballPath, cleanupTarball } = await createSdkTarball();
|
|
92
94
|
try {
|
|
93
95
|
log("Creating temporary directory for project");
|
|
94
96
|
// Create a temporary directory
|
|
95
97
|
const tempDir = await tmp.dir({ unsafeCleanup: true });
|
|
98
|
+
// Determine the source directory to copy from
|
|
99
|
+
const sourceDir = monorepoRoot || projectDir;
|
|
96
100
|
// Create unique project directory name
|
|
97
|
-
const originalDirName = basename(
|
|
101
|
+
const originalDirName = basename(sourceDir);
|
|
98
102
|
const workerName = `${originalDirName}-test-${resourceUniqueKey}`;
|
|
99
|
-
const
|
|
100
|
-
|
|
103
|
+
const tempCopyRoot = resolve(tempDir.path, workerName);
|
|
104
|
+
// If it's a monorepo, the targetDir for commands is a subdirectory
|
|
105
|
+
const targetDir = monorepoRoot
|
|
106
|
+
? resolve(tempCopyRoot, relative(monorepoRoot, projectDir))
|
|
107
|
+
: tempCopyRoot;
|
|
108
|
+
console.log(`Copying project from ${sourceDir} to ${tempCopyRoot}`);
|
|
101
109
|
// Read project's .gitignore if it exists
|
|
102
110
|
let ig = ignore();
|
|
103
|
-
const gitignorePath = join(
|
|
111
|
+
const gitignorePath = join(sourceDir, ".gitignore");
|
|
104
112
|
if (await pathExists(gitignorePath)) {
|
|
105
113
|
log("Found .gitignore file at %s", gitignorePath);
|
|
106
114
|
const gitignoreContent = await fs.promises.readFile(gitignorePath, "utf-8");
|
|
@@ -123,10 +131,10 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
|
|
|
123
131
|
}
|
|
124
132
|
// Copy the project directory, respecting .gitignore
|
|
125
133
|
log("Starting copy process with ignored patterns");
|
|
126
|
-
await copy(
|
|
134
|
+
await copy(sourceDir, tempCopyRoot, {
|
|
127
135
|
filter: (src) => {
|
|
128
136
|
// Get path relative to project directory
|
|
129
|
-
const relativePath = relative(
|
|
137
|
+
const relativePath = relative(sourceDir, src);
|
|
130
138
|
if (!relativePath)
|
|
131
139
|
return true; // Include the root directory
|
|
132
140
|
// Check against ignore patterns
|
|
@@ -135,6 +143,32 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
|
|
|
135
143
|
},
|
|
136
144
|
});
|
|
137
145
|
log("Project copy completed successfully");
|
|
146
|
+
// Copy the SDK tarball into the target directory
|
|
147
|
+
const tarballFilename = basename(tarballPath);
|
|
148
|
+
const tempTarballPath = join(targetDir, tarballFilename);
|
|
149
|
+
await fs.promises.copyFile(tarballPath, tempTarballPath);
|
|
150
|
+
if (monorepoRoot) {
|
|
151
|
+
log("⚙️ Configuring monorepo workspace...");
|
|
152
|
+
const rwsdkWsPath = join(tempCopyRoot, "rwsdk-workspace.json");
|
|
153
|
+
if (await pathExists(rwsdkWsPath)) {
|
|
154
|
+
const rwsdkWs = JSON.parse(await fs.promises.readFile(rwsdkWsPath, "utf-8"));
|
|
155
|
+
const workspaces = rwsdkWs.workspaces;
|
|
156
|
+
if (packageManager === "pnpm") {
|
|
157
|
+
const pnpmWsPath = join(tempCopyRoot, "pnpm-workspace.yaml");
|
|
158
|
+
const pnpmWsConfig = `packages:\n${workspaces.map((w) => ` - '${w}'`).join("\n")}\n`;
|
|
159
|
+
await fs.promises.writeFile(pnpmWsPath, pnpmWsConfig);
|
|
160
|
+
log("Created pnpm-workspace.yaml");
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// For npm and yarn, add a workspaces property to package.json
|
|
164
|
+
const pkgJsonPath = join(tempCopyRoot, "package.json");
|
|
165
|
+
const pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, "utf-8"));
|
|
166
|
+
pkgJson.workspaces = workspaces;
|
|
167
|
+
await fs.promises.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
168
|
+
log("Added workspaces to package.json");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
138
172
|
// Configure temp project to not use frozen lockfile
|
|
139
173
|
log("⚙️ Configuring temp project to not use frozen lockfile...");
|
|
140
174
|
const npmrcPath = join(targetDir, ".npmrc");
|
|
@@ -142,17 +176,24 @@ export async function copyProjectToTempDir(projectDir, resourceUniqueKey, packag
|
|
|
142
176
|
// For yarn, create .yarnrc.yml to disable PnP and allow lockfile changes
|
|
143
177
|
if (packageManager === "yarn") {
|
|
144
178
|
const yarnrcPath = join(targetDir, ".yarnrc.yml");
|
|
179
|
+
const yarnCacheDir = path.join(os.tmpdir(), "yarn-cache");
|
|
180
|
+
await fs.promises.mkdir(yarnCacheDir, { recursive: true });
|
|
145
181
|
const yarnConfig = [
|
|
146
182
|
// todo(justinvdm, 23-09-23): Support yarn pnpm
|
|
147
183
|
"nodeLinker: node-modules",
|
|
148
184
|
"enableImmutableInstalls: false",
|
|
185
|
+
`cacheFolder: "${yarnCacheDir}"`,
|
|
149
186
|
].join("\n");
|
|
150
187
|
await fs.promises.writeFile(yarnrcPath, yarnConfig);
|
|
151
188
|
log("Created .yarnrc.yml to allow lockfile changes for yarn");
|
|
152
189
|
}
|
|
153
|
-
await setTarballDependency(targetDir,
|
|
190
|
+
await setTarballDependency(targetDir, tarballFilename);
|
|
154
191
|
// Install dependencies in the target directory
|
|
155
|
-
|
|
192
|
+
const installDir = monorepoRoot ? tempCopyRoot : targetDir;
|
|
193
|
+
await retry(() => installDependencies(installDir, packageManager), {
|
|
194
|
+
retries: 3,
|
|
195
|
+
delay: 1000,
|
|
196
|
+
});
|
|
156
197
|
// Return the environment details
|
|
157
198
|
return { tempDir, targetDir, workerName };
|
|
158
199
|
}
|
|
@@ -193,9 +234,11 @@ async function installDependencies(targetDir, packageManager = "pnpm") {
|
|
|
193
234
|
});
|
|
194
235
|
}
|
|
195
236
|
}
|
|
237
|
+
const npmCacheDir = path.join(os.tmpdir(), "npm-cache");
|
|
238
|
+
await fs.promises.mkdir(npmCacheDir, { recursive: true });
|
|
196
239
|
const installCommand = {
|
|
197
240
|
pnpm: ["pnpm", "install"],
|
|
198
|
-
npm: ["npm", "install"],
|
|
241
|
+
npm: ["npm", "install", "--cache", npmCacheDir],
|
|
199
242
|
yarn: ["yarn", "install"],
|
|
200
243
|
"yarn-classic": ["yarn"],
|
|
201
244
|
}[packageManager];
|
package/dist/lib/e2e/index.d.mts
CHANGED
package/dist/lib/e2e/index.mjs
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PollOptions {
|
|
2
|
+
timeout: number;
|
|
3
|
+
interval: number;
|
|
4
|
+
minTries: number;
|
|
5
|
+
onRetry?: (error: unknown, tries: number) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function poll(fn: () => Promise<boolean>, options?: Partial<PollOptions>): Promise<void>;
|
|
8
|
+
export declare function pollValue<T>(fn: () => Promise<T>, options?: Partial<PollOptions>): Promise<T>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { setTimeout } from "node:timers/promises";
|
|
2
|
+
const POLL_TIMEOUT = process.env.RWSDK_POLL_TIMEOUT
|
|
3
|
+
? parseInt(process.env.RWSDK_POLL_TIMEOUT, 10)
|
|
4
|
+
: 2 * 60 * 1000;
|
|
5
|
+
export async function poll(fn, options = {}) {
|
|
6
|
+
const { timeout = POLL_TIMEOUT, interval = 100, minTries = 3, onRetry, } = options;
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
let tries = 0;
|
|
9
|
+
while (Date.now() - startTime < timeout || tries < minTries) {
|
|
10
|
+
tries++;
|
|
11
|
+
try {
|
|
12
|
+
if (await fn()) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
onRetry?.(error, tries);
|
|
18
|
+
// Continue polling on errors
|
|
19
|
+
}
|
|
20
|
+
await setTimeout(interval);
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Polling timed out after ${Date.now() - startTime}ms and ${tries} attempts`);
|
|
23
|
+
}
|
|
24
|
+
export async function pollValue(fn, options = {}) {
|
|
25
|
+
let value;
|
|
26
|
+
await poll(async () => {
|
|
27
|
+
value = await fn();
|
|
28
|
+
return true;
|
|
29
|
+
}, options);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { setTimeout } from "node:timers/promises";
|
|
2
|
+
const log = console.log;
|
|
3
|
+
export async function retry(fn, options) {
|
|
4
|
+
let lastError;
|
|
5
|
+
for (let i = 0; i < options.retries; i++) {
|
|
6
|
+
try {
|
|
7
|
+
return await fn();
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
lastError = e;
|
|
11
|
+
log(`Attempt ${i + 1} failed. Retrying in ${options.delay}ms...`);
|
|
12
|
+
await setTimeout(options.delay);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
throw lastError;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { launchBrowser } from "./browser.mjs";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
interface SetupTarballOptions {
|
|
2
2
|
projectDir: string;
|
|
3
|
+
monorepoRoot?: string;
|
|
3
4
|
packageManager?: "pnpm" | "npm" | "yarn";
|
|
4
5
|
}
|
|
5
6
|
interface TarballEnvironment {
|
|
@@ -9,5 +10,5 @@ interface TarballEnvironment {
|
|
|
9
10
|
/**
|
|
10
11
|
* Creates a tarball-based test environment similar to the release script approach
|
|
11
12
|
*/
|
|
12
|
-
export declare function setupTarballEnvironment({ projectDir, packageManager, }: SetupTarballOptions): Promise<TarballEnvironment>;
|
|
13
|
+
export declare function setupTarballEnvironment({ projectDir, monorepoRoot, packageManager, }: SetupTarballOptions): Promise<TarballEnvironment>;
|
|
13
14
|
export {};
|
package/dist/lib/e2e/tarball.mjs
CHANGED
|
@@ -63,7 +63,7 @@ async function copyWranglerCache(targetDir, sdkRoot) {
|
|
|
63
63
|
/**
|
|
64
64
|
* Creates a tarball-based test environment similar to the release script approach
|
|
65
65
|
*/
|
|
66
|
-
export async function setupTarballEnvironment({ projectDir, packageManager = "pnpm", }) {
|
|
66
|
+
export async function setupTarballEnvironment({ projectDir, monorepoRoot, packageManager = "pnpm", }) {
|
|
67
67
|
log(`🚀 Setting up tarball environment for ${projectDir}`);
|
|
68
68
|
// Generate a resource unique key for this test run
|
|
69
69
|
const uniqueNameSuffix = uniqueNamesGenerator({
|
|
@@ -79,7 +79,7 @@ export async function setupTarballEnvironment({ projectDir, packageManager = "pn
|
|
|
79
79
|
.substring(0, 8);
|
|
80
80
|
const resourceUniqueKey = `${uniqueNameSuffix}-${hash}`;
|
|
81
81
|
try {
|
|
82
|
-
const { tempDir, targetDir } = await copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager);
|
|
82
|
+
const { tempDir, targetDir } = await copyProjectToTempDir(projectDir, resourceUniqueKey, packageManager, monorepoRoot);
|
|
83
83
|
// Copy wrangler cache to improve deployment performance
|
|
84
84
|
const sdkRoot = ROOT_DIR;
|
|
85
85
|
await copyWranglerCache(targetDir, sdkRoot);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
cleanup: () => Promise<void>;
|
|
5
|
-
}
|
|
1
|
+
import { test } from "vitest";
|
|
2
|
+
import { type Browser, type Page } from "puppeteer-core";
|
|
3
|
+
export type { Browser, Page } from "puppeteer-core";
|
|
6
4
|
interface DevServerInstance {
|
|
7
5
|
url: string;
|
|
8
6
|
stopDev: () => Promise<void>;
|
|
@@ -12,39 +10,48 @@ interface DeploymentInstance {
|
|
|
12
10
|
workerName: string;
|
|
13
11
|
resourceUniqueKey: string;
|
|
14
12
|
projectDir: string;
|
|
13
|
+
cleanup: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface SetupPlaygroundEnvironmentOptions {
|
|
16
|
+
/**
|
|
17
|
+
* The directory of the playground project to set up.
|
|
18
|
+
* Can be an absolute path, or a `import.meta.url` `file://` string.
|
|
19
|
+
* If not provided, it will be inferred from the test file's path.
|
|
20
|
+
*/
|
|
21
|
+
sourceProjectDir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The root directory of the monorepo, if the project is part of one.
|
|
24
|
+
* This is used to correctly set up the test environment for monorepo projects.
|
|
25
|
+
*/
|
|
26
|
+
monorepoRoot?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether to provision a dev server for the test suite.
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
dev?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to provision a deployment for the test suite.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
deploy?: boolean;
|
|
15
37
|
}
|
|
16
38
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
export declare function setupPlaygroundEnvironment(sourceProjectDir?: string): void;
|
|
23
|
-
/**
|
|
24
|
-
* Gets the current playground environment.
|
|
25
|
-
* Throws if no environment has been set up.
|
|
39
|
+
* A Vitest hook that sets up a playground environment for a test file.
|
|
40
|
+
* It creates a temporary directory, copies the playground project into it,
|
|
41
|
+
* and installs dependencies using a tarball of the SDK.
|
|
42
|
+
* This ensures that tests run in a clean, isolated environment.
|
|
26
43
|
*/
|
|
27
|
-
export declare function
|
|
44
|
+
export declare function setupPlaygroundEnvironment(options?: string | SetupPlaygroundEnvironmentOptions): void;
|
|
28
45
|
/**
|
|
29
46
|
* Creates a dev server instance using the shared playground environment.
|
|
30
47
|
* Automatically registers cleanup to run after the test.
|
|
31
48
|
*/
|
|
32
|
-
export declare function createDevServer(): Promise<DevServerInstance>;
|
|
49
|
+
export declare function createDevServer(projectDir: string): Promise<DevServerInstance>;
|
|
33
50
|
/**
|
|
34
51
|
* Creates a deployment instance using the shared playground environment.
|
|
35
52
|
* Automatically registers cleanup to run after the test.
|
|
36
53
|
*/
|
|
37
|
-
export declare function createDeployment(): Promise<DeploymentInstance>;
|
|
38
|
-
/**
|
|
39
|
-
* Manually cleans up a deployment instance (deletes worker and D1 database).
|
|
40
|
-
* This is optional since cleanup happens automatically after each test.
|
|
41
|
-
*/
|
|
42
|
-
export declare function cleanupDeployment(deployment: DeploymentInstance): Promise<void>;
|
|
43
|
-
/**
|
|
44
|
-
* Creates a browser instance for testing.
|
|
45
|
-
* Automatically registers cleanup to run after the test.
|
|
46
|
-
*/
|
|
47
|
-
export declare function createBrowser(): Promise<Browser>;
|
|
54
|
+
export declare function createDeployment(projectDir: string): Promise<DeploymentInstance>;
|
|
48
55
|
/**
|
|
49
56
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
50
57
|
* @param name - The name of the test, used for logging.
|
|
@@ -53,23 +60,24 @@ export declare function createBrowser(): Promise<Browser>;
|
|
|
53
60
|
* return a cleanup function. The cleanup function will be
|
|
54
61
|
* called automatically on failure.
|
|
55
62
|
*/
|
|
56
|
-
export declare function runTestWithRetries(name: string, attemptFn: () => Promise<
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
export declare function runTestWithRetries(name: string, attemptFn: () => Promise<void>): Promise<void>;
|
|
64
|
+
declare function createTestRunner(testFn: (typeof test | typeof test.only)["concurrent"], envType: "dev" | "deploy"): (name: string, testLogic: (context: {
|
|
65
|
+
devServer?: DevServerInstance;
|
|
66
|
+
deployment?: DeploymentInstance;
|
|
67
|
+
browser: Browser;
|
|
68
|
+
page: Page;
|
|
69
|
+
url: string;
|
|
70
|
+
}) => Promise<void>) => void;
|
|
59
71
|
/**
|
|
60
72
|
* High-level test wrapper for dev server tests.
|
|
61
73
|
* Automatically skips if RWSDK_SKIP_DEV=1
|
|
62
74
|
*/
|
|
63
|
-
export declare function testDev(
|
|
64
|
-
devServer: DevServerInstance;
|
|
65
|
-
browser: Browser;
|
|
66
|
-
page: Page;
|
|
67
|
-
url: string;
|
|
68
|
-
}) => Promise<void>): void;
|
|
75
|
+
export declare function testDev(...args: Parameters<ReturnType<typeof createTestRunner>>): void;
|
|
69
76
|
export declare namespace testDev {
|
|
70
77
|
var skip: (name: string, testFn?: any) => void;
|
|
71
|
-
var only: (name: string,
|
|
72
|
-
devServer
|
|
78
|
+
var only: (name: string, testLogic: (context: {
|
|
79
|
+
devServer?: DevServerInstance;
|
|
80
|
+
deployment?: DeploymentInstance;
|
|
73
81
|
browser: Browser;
|
|
74
82
|
page: Page;
|
|
75
83
|
url: string;
|
|
@@ -79,16 +87,12 @@ export declare namespace testDev {
|
|
|
79
87
|
* High-level test wrapper for deployment tests.
|
|
80
88
|
* Automatically skips if RWSDK_SKIP_DEPLOY=1
|
|
81
89
|
*/
|
|
82
|
-
export declare function testDeploy(
|
|
83
|
-
deployment: DeploymentInstance;
|
|
84
|
-
browser: Browser;
|
|
85
|
-
page: Page;
|
|
86
|
-
url: string;
|
|
87
|
-
}) => Promise<void>): void;
|
|
90
|
+
export declare function testDeploy(...args: Parameters<ReturnType<typeof createTestRunner>>): void;
|
|
88
91
|
export declare namespace testDeploy {
|
|
89
92
|
var skip: (name: string, testFn?: any) => void;
|
|
90
|
-
var only: (name: string,
|
|
91
|
-
|
|
93
|
+
var only: (name: string, testLogic: (context: {
|
|
94
|
+
devServer?: DevServerInstance;
|
|
95
|
+
deployment?: DeploymentInstance;
|
|
92
96
|
browser: Browser;
|
|
93
97
|
page: Page;
|
|
94
98
|
url: string;
|
|
@@ -116,8 +120,13 @@ export declare namespace testDevAndDeploy {
|
|
|
116
120
|
}) => Promise<void>) => void;
|
|
117
121
|
}
|
|
118
122
|
/**
|
|
119
|
-
*
|
|
123
|
+
* Waits for the page to be fully loaded and hydrated.
|
|
124
|
+
* This should be used before any user interaction is simulated.
|
|
120
125
|
*/
|
|
121
|
-
export declare function
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
export declare function waitForHydration(page: Page): Promise<void>;
|
|
127
|
+
export declare function trackPageErrors(page: Page): {
|
|
128
|
+
get: () => {
|
|
129
|
+
consoleErrors: string[];
|
|
130
|
+
failedRequests: string[];
|
|
131
|
+
};
|
|
132
|
+
};
|