rwsdk 1.2.8 ā 1.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/e2e/browser.d.mts +4 -0
- package/dist/lib/e2e/browser.mjs +58 -0
- package/dist/lib/e2e/constants.d.mts +2 -0
- package/dist/lib/e2e/constants.mjs +6 -0
- package/dist/lib/e2e/dev.mjs +7 -18
- package/dist/lib/e2e/release.d.mts +7 -0
- package/dist/lib/e2e/release.mjs +78 -2
- package/dist/lib/e2e/tarball.mjs +4 -1
- package/dist/lib/e2e/testHarness.d.mts +1 -7
- package/dist/lib/e2e/testHarness.mjs +30 -10
- package/dist/lib/smokeTests/browser.d.mts +1 -1
- package/dist/lib/smokeTests/browser.mjs +36 -30
- package/dist/lib/smokeTests/release.d.mts +8 -1
- package/dist/lib/smokeTests/release.mjs +54 -29
- package/dist/lib/smokeTests/runSmokeTests.mjs +1 -1
- package/dist/runtime/client/navigation.js +42 -61
- package/dist/runtime/client/navigation.test.js +145 -8
- package/dist/runtime/client/scrollRestoration.d.ts +25 -0
- package/dist/runtime/client/scrollRestoration.js +157 -0
- package/dist/runtime/client/scrollRestoration.test.d.ts +1 -0
- package/dist/runtime/client/scrollRestoration.test.js +93 -0
- package/dist/runtime/lib/router.d.ts +1 -0
- package/dist/runtime/lib/router.js +27 -2
- package/dist/runtime/lib/router.test.js +96 -0
- package/dist/scripts/worker-run.mjs +42 -8
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +14 -7
- package/dist/use-synced-state/__tests__/worker.test.mjs +41 -2
- package/dist/use-synced-state/worker.mjs +34 -3
- package/dist/vite/miniflareHMRPlugin.mjs +1 -1
- package/dist/vite/ssrBridgePlugin.d.mts +0 -1
- package/dist/vite/ssrBridgePlugin.mjs +17 -14
- package/dist/vite/ssrVirtualModule.d.mts +3 -0
- package/dist/vite/ssrVirtualModule.mjs +11 -0
- package/dist/vite/transformJsxScriptTagsPlugin.hook.test.d.mts +1 -0
- package/dist/vite/transformJsxScriptTagsPlugin.hook.test.mjs +73 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +5 -0
- package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +4 -3
- package/package.json +2 -3
|
@@ -4,6 +4,10 @@ import { SmokeTestOptions } from "./types.mjs";
|
|
|
4
4
|
* Launch a browser instance
|
|
5
5
|
*/
|
|
6
6
|
export declare function launchBrowser(browserPath?: string, headless?: boolean): Promise<Browser>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a server is up, trying localhost and loopback host variants.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkServerUp(baseUrl: string, customPath?: string, retries?: number, includeRoot?: boolean): Promise<string>;
|
|
7
11
|
/**
|
|
8
12
|
* Get the browser executable path
|
|
9
13
|
*/
|
package/dist/lib/e2e/browser.mjs
CHANGED
|
@@ -2,7 +2,9 @@ import { computeExecutablePath, detectBrowserPlatform, install, Browser as Puppe
|
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import { mkdirp, pathExists } from "fs-extra";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
import { chromium as playwrightChromium } from "playwright-core";
|
|
5
6
|
import puppeteer from "puppeteer-core";
|
|
7
|
+
import { $ } from "../$.mjs";
|
|
6
8
|
import { ensureTmpDir } from "./utils.mjs";
|
|
7
9
|
const log = debug("rwsdk:e2e:browser");
|
|
8
10
|
/**
|
|
@@ -26,6 +28,52 @@ export async function launchBrowser(browserPath, headless = true) {
|
|
|
26
28
|
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
27
29
|
});
|
|
28
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if a server is up, trying localhost and loopback host variants.
|
|
33
|
+
*/
|
|
34
|
+
export async function checkServerUp(baseUrl, customPath = "/", retries = 30, includeRoot = true) {
|
|
35
|
+
const pathsToCheck = includeRoot ? ["/"] : [];
|
|
36
|
+
if (customPath !== "/" && customPath !== "") {
|
|
37
|
+
pathsToCheck.push(customPath);
|
|
38
|
+
}
|
|
39
|
+
if (pathsToCheck.length === 0) {
|
|
40
|
+
pathsToCheck.push("/");
|
|
41
|
+
}
|
|
42
|
+
for (const path of pathsToCheck) {
|
|
43
|
+
const normalizedPath = path.startsWith("/") ? path : "/" + path;
|
|
44
|
+
const baseUrlObject = new URL(baseUrl);
|
|
45
|
+
const candidateBaseUrls = Array.from(new Set([
|
|
46
|
+
baseUrl,
|
|
47
|
+
`${baseUrlObject.protocol}//127.0.0.1:${baseUrlObject.port}`,
|
|
48
|
+
`${baseUrlObject.protocol}//[::1]:${baseUrlObject.port}`,
|
|
49
|
+
]));
|
|
50
|
+
const candidateUrls = candidateBaseUrls.map((candidateBaseUrl) => candidateBaseUrl + normalizedPath);
|
|
51
|
+
log("Checking if server is up at %s (max retries: %d)", candidateUrls[0], retries);
|
|
52
|
+
for (let i = 0; i < retries; i++) {
|
|
53
|
+
for (const candidateUrl of candidateUrls) {
|
|
54
|
+
try {
|
|
55
|
+
log("Attempt %d/%d to check server at %s", i + 1, retries, candidateUrl);
|
|
56
|
+
console.log(`Checking if server is up at ${candidateUrl} (attempt ${i + 1}/${retries})...`);
|
|
57
|
+
await $ `curl --max-time 1 -s -o /dev/null -w "%{http_code}" ${candidateUrl}`;
|
|
58
|
+
log("Server is up at %s", candidateUrl);
|
|
59
|
+
console.log(`ā
Server is up at ${candidateUrl}`);
|
|
60
|
+
return candidateUrl;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Try the next host variant.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (i === retries - 1) {
|
|
67
|
+
log("ERROR: Server at %s did not become available after %d attempts", candidateUrls[0], retries);
|
|
68
|
+
throw new Error(`Server at ${candidateUrls[0]} did not become available after ${retries} attempts`);
|
|
69
|
+
}
|
|
70
|
+
log("Server not up yet, retrying in 2 seconds");
|
|
71
|
+
console.log(`Server not up yet, retrying in 2 seconds...`);
|
|
72
|
+
await new Promise((resolve) => setTimeout(() => resolve(), 2000));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
29
77
|
/**
|
|
30
78
|
* Get the browser executable path
|
|
31
79
|
*/
|
|
@@ -36,6 +84,16 @@ export async function getBrowserPath(testOptions) {
|
|
|
36
84
|
console.log(`Using Chrome from environment variable: ${process.env.CHROME_PATH}`);
|
|
37
85
|
return process.env.CHROME_PATH;
|
|
38
86
|
}
|
|
87
|
+
try {
|
|
88
|
+
const playwrightPath = playwrightChromium.executablePath();
|
|
89
|
+
if (await pathExists(playwrightPath)) {
|
|
90
|
+
console.log(`Found Playwright Chrome at: ${playwrightPath}`);
|
|
91
|
+
return playwrightPath;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
log("Playwright browser path unavailable: %O", error);
|
|
96
|
+
}
|
|
39
97
|
// Detect platform
|
|
40
98
|
log("Detecting platform");
|
|
41
99
|
const platform = detectBrowserPlatform();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const IS_CI: boolean;
|
|
2
|
+
export declare const IS_PULL_REQUEST: boolean;
|
|
2
3
|
export declare const RWSDK_SKIP_DEV: boolean;
|
|
3
4
|
export declare const RWSDK_SKIP_DEPLOY: boolean;
|
|
4
5
|
export declare const IS_DEBUG_MODE: boolean;
|
|
@@ -6,6 +7,7 @@ export declare const SETUP_PLAYGROUND_ENV_TIMEOUT: number;
|
|
|
6
7
|
export declare const DEPLOYMENT_TIMEOUT: number;
|
|
7
8
|
export declare const DEPLOYMENT_MIN_TRIES: number;
|
|
8
9
|
export declare const DEPLOYMENT_CHECK_TIMEOUT: number;
|
|
10
|
+
export declare const PREVIEW_SERVER_TIMEOUT: number;
|
|
9
11
|
export declare const PUPPETEER_TIMEOUT: number;
|
|
10
12
|
export declare const HYDRATION_TIMEOUT: number;
|
|
11
13
|
export declare const DEV_SERVER_TIMEOUT: number;
|
|
@@ -5,6 +5,7 @@ export const IS_CI = !!((process.env.CI && !process.env.NOT_CI) ||
|
|
|
5
5
|
process.env.TRAVIS ||
|
|
6
6
|
process.env.JENKINS_URL ||
|
|
7
7
|
process.env.NETLIFY);
|
|
8
|
+
export const IS_PULL_REQUEST = process.env.GITHUB_EVENT_NAME === "pull_request";
|
|
8
9
|
export const RWSDK_SKIP_DEV = Boolean(process.env.RWSDK_SKIP_DEV);
|
|
9
10
|
export const RWSDK_SKIP_DEPLOY = process.env.RWSDK_SKIP_DEPLOY === "true" ||
|
|
10
11
|
process.env.RWSDK_SKIP_DEPLOY === "1";
|
|
@@ -33,6 +34,11 @@ export const DEPLOYMENT_CHECK_TIMEOUT = process.env
|
|
|
33
34
|
: IS_DEBUG_MODE
|
|
34
35
|
? 30 * 1000
|
|
35
36
|
: 5 * 60 * 1000;
|
|
37
|
+
export const PREVIEW_SERVER_TIMEOUT = process.env.RWSDK_PREVIEW_SERVER_TIMEOUT
|
|
38
|
+
? parseInt(process.env.RWSDK_PREVIEW_SERVER_TIMEOUT, 10)
|
|
39
|
+
: IS_DEBUG_MODE
|
|
40
|
+
? 60 * 1000
|
|
41
|
+
: 10 * 60 * 1000;
|
|
36
42
|
export const PUPPETEER_TIMEOUT = process.env.RWSDK_PUPPETEER_TIMEOUT
|
|
37
43
|
? parseInt(process.env.RWSDK_PUPPETEER_TIMEOUT, 10)
|
|
38
44
|
: IS_DEBUG_MODE
|
package/dist/lib/e2e/dev.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
3
|
import { $, $sh } from "../../lib/$.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { checkServerUp } from "./browser.mjs";
|
|
5
5
|
import { IS_DEBUG_MODE } from "./constants.mjs";
|
|
6
6
|
const DEV_SERVER_CHECK_TIMEOUT = process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT
|
|
7
7
|
? parseInt(process.env.RWSDK_DEV_SERVER_CHECK_TIMEOUT, 10)
|
|
@@ -205,23 +205,12 @@ export async function runDevServer(packageManager = "pnpm", cwd) {
|
|
|
205
205
|
// Wait for the URL
|
|
206
206
|
const serverUrl = await waitForUrl();
|
|
207
207
|
console.log(`ā
Development server started at ${serverUrl}`);
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// We consider any response (even 4xx or 5xx) as success,
|
|
215
|
-
// as it means the worker is routable.
|
|
216
|
-
return response.status > 0;
|
|
217
|
-
}
|
|
218
|
-
catch (e) {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
}, {
|
|
222
|
-
timeout: DEV_SERVER_CHECK_TIMEOUT,
|
|
223
|
-
});
|
|
224
|
-
return { url: serverUrl, stopDev };
|
|
208
|
+
// context(justinvdm, 2026-05-13): Probe the debug endpoint instead of
|
|
209
|
+
// the app root because the root response can take several seconds to wake
|
|
210
|
+
// up even after Vite prints its local URL.
|
|
211
|
+
const readinessRetries = Math.max(1, Math.ceil(DEV_SERVER_CHECK_TIMEOUT / 2000));
|
|
212
|
+
const reachableDebugUrl = await checkServerUp(serverUrl, "/__debug", readinessRetries, false);
|
|
213
|
+
return { url: new URL(reachableDebugUrl).origin, stopDev };
|
|
225
214
|
}
|
|
226
215
|
catch (error) {
|
|
227
216
|
// Make sure to try to stop the server on error
|
|
@@ -36,6 +36,13 @@ export declare function runRelease(cwd: string, projectDir: string, resourceUniq
|
|
|
36
36
|
url: string;
|
|
37
37
|
workerName: string;
|
|
38
38
|
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Run a local production preview server (build + preview) and return the URL.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runPreviewServer(packageManager?: string, cwd?: string): Promise<{
|
|
43
|
+
url: string;
|
|
44
|
+
stopPreview: () => Promise<void>;
|
|
45
|
+
}>;
|
|
39
46
|
/**
|
|
40
47
|
* Check if a resource name includes a specific resource unique key
|
|
41
48
|
* This is used to identify resources created during our tests
|
package/dist/lib/e2e/release.mjs
CHANGED
|
@@ -5,10 +5,11 @@ import { pathExists } from "fs-extra";
|
|
|
5
5
|
import * as fs from "fs/promises";
|
|
6
6
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
7
7
|
import { setTimeout } from "node:timers/promises";
|
|
8
|
-
import { basename, dirname, join, resolve } from "path";
|
|
8
|
+
import { basename, dirname, join, relative, resolve } from "path";
|
|
9
9
|
import { $ } from "../../lib/$.mjs";
|
|
10
|
+
import { checkServerUp } from "./browser.mjs";
|
|
10
11
|
import { extractLastJson, parseJson } from "../../lib/jsonUtils.mjs";
|
|
11
|
-
import { IS_DEBUG_MODE } from "./constants.mjs";
|
|
12
|
+
import { IS_DEBUG_MODE, PREVIEW_SERVER_TIMEOUT } from "./constants.mjs";
|
|
12
13
|
const log = debug("rwsdk:e2e:release");
|
|
13
14
|
/**
|
|
14
15
|
* Find wrangler cache by searching up the directory tree for node_modules/.cache/wrangler
|
|
@@ -400,6 +401,81 @@ export async function runRelease(cwd, projectDir, resourceUniqueKey) {
|
|
|
400
401
|
throw error;
|
|
401
402
|
}
|
|
402
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Run a local production preview server (build + preview) and return the URL.
|
|
406
|
+
*/
|
|
407
|
+
export async function runPreviewServer(packageManager = "pnpm", cwd) {
|
|
408
|
+
console.log("š Building for production preview...");
|
|
409
|
+
const pm = packageManager === "yarn-classic" ? "yarn" : packageManager;
|
|
410
|
+
await $(pm, ["run", "build"], {
|
|
411
|
+
cwd: cwd || process.cwd(),
|
|
412
|
+
stdio: "pipe",
|
|
413
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
414
|
+
});
|
|
415
|
+
console.log("ā
Build complete. Starting preview server...");
|
|
416
|
+
let previewProcess = null;
|
|
417
|
+
let isErrorExpected = false;
|
|
418
|
+
const stopPreview = async () => {
|
|
419
|
+
isErrorExpected = true;
|
|
420
|
+
if (!previewProcess || !previewProcess.pid) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
console.log("Stopping preview server...");
|
|
424
|
+
if (process.platform !== "win32") {
|
|
425
|
+
try {
|
|
426
|
+
process.kill(-previewProcess.pid, "SIGKILL");
|
|
427
|
+
}
|
|
428
|
+
catch { }
|
|
429
|
+
}
|
|
430
|
+
await previewProcess.catch(() => { });
|
|
431
|
+
console.log("Preview server stopped");
|
|
432
|
+
};
|
|
433
|
+
previewProcess = $(pm, ["run", "preview", "--", "--port", "4173", "--strictPort"], {
|
|
434
|
+
all: true,
|
|
435
|
+
detached: process.platform !== "win32",
|
|
436
|
+
cleanup: true,
|
|
437
|
+
forceKillAfterTimeout: 2000,
|
|
438
|
+
cwd: cwd || process.cwd(),
|
|
439
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
440
|
+
stdio: "pipe",
|
|
441
|
+
});
|
|
442
|
+
previewProcess.catch((error) => {
|
|
443
|
+
if (!isErrorExpected) {
|
|
444
|
+
log("Preview server process exited unexpectedly: %O", error);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
const ensurePreviewDeployConfig = async () => {
|
|
448
|
+
const deployConfigPath = resolve(cwd || process.cwd(), ".wrangler/deploy/config.json");
|
|
449
|
+
if (await pathExists(deployConfigPath)) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
let workerConfigPath = null;
|
|
453
|
+
for (const candidate of ["wrangler.jsonc", "wrangler.json"]) {
|
|
454
|
+
const resolvedCandidate = resolve(cwd || process.cwd(), candidate);
|
|
455
|
+
if (await pathExists(resolvedCandidate)) {
|
|
456
|
+
workerConfigPath = resolvedCandidate;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (!workerConfigPath) {
|
|
461
|
+
throw new Error(`Unable to create preview deploy config because no wrangler.jsonc or wrangler.json was found in ${cwd || process.cwd()}`);
|
|
462
|
+
}
|
|
463
|
+
await fs.mkdir(dirname(deployConfigPath), { recursive: true });
|
|
464
|
+
const deployConfig = {
|
|
465
|
+
configPath: relative(dirname(deployConfigPath), workerConfigPath),
|
|
466
|
+
auxiliaryWorkers: [],
|
|
467
|
+
};
|
|
468
|
+
await fs.writeFile(deployConfigPath, JSON.stringify(deployConfig, null, 2));
|
|
469
|
+
};
|
|
470
|
+
await ensurePreviewDeployConfig();
|
|
471
|
+
// context(justinvdm, 2026-05-13): Give the CI preview path the same
|
|
472
|
+
// readiness budget as the dev server so local agent-ci runs can absorb build
|
|
473
|
+
// and startup latency without falling back to Cloudflare.
|
|
474
|
+
const reachableDebugUrl = await checkServerUp("http://localhost:4173", "/__debug", Math.max(1, Math.ceil(PREVIEW_SERVER_TIMEOUT / 2000)), false);
|
|
475
|
+
const serverUrl = new URL(reachableDebugUrl).origin;
|
|
476
|
+
console.log(`ā
Preview server started at ${serverUrl}`);
|
|
477
|
+
return { url: serverUrl, stopPreview };
|
|
478
|
+
}
|
|
403
479
|
/**
|
|
404
480
|
* Check if a resource name includes a specific resource unique key
|
|
405
481
|
* This is used to identify resources created during our tests
|
package/dist/lib/e2e/tarball.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { $ } from "execa";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { ROOT_DIR } from "../constants.mjs";
|
|
6
|
+
import { IS_CI } from "./constants.mjs";
|
|
6
7
|
import { copyProjectToTempDir } from "./environment.mjs";
|
|
7
8
|
const log = (message) => console.log(message);
|
|
8
9
|
/**
|
|
@@ -51,7 +52,9 @@ async function copyWranglerCache(targetDir, sdkRoot) {
|
|
|
51
52
|
log(` ā
Wrangler cache copied successfully`);
|
|
52
53
|
}
|
|
53
54
|
else {
|
|
54
|
-
log(
|
|
55
|
+
log(IS_CI
|
|
56
|
+
? ` ā¹ļø No wrangler cache found in monorepo; CI preview tests do not require it`
|
|
57
|
+
: ` ā ļø No wrangler cache found in monorepo, deployment tests may require authentication`);
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
catch (error) {
|
|
@@ -66,13 +66,7 @@ export declare function createDevServer(): {
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function createDeployment(): {
|
|
68
68
|
projectDir: string;
|
|
69
|
-
start: () => Promise<
|
|
70
|
-
url: string;
|
|
71
|
-
workerName: string;
|
|
72
|
-
resourceUniqueKey: string;
|
|
73
|
-
projectDir: string;
|
|
74
|
-
cleanup: () => Promise<void>;
|
|
75
|
-
}>;
|
|
69
|
+
start: () => Promise<DeploymentInstance>;
|
|
76
70
|
};
|
|
77
71
|
/**
|
|
78
72
|
* Executes a test function with a retry mechanism for specific error codes.
|
|
@@ -4,10 +4,10 @@ import puppeteer from "puppeteer-core";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
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
|
+
import { DEPLOYMENT_CHECK_TIMEOUT, DEPLOYMENT_MIN_TRIES, DEPLOYMENT_TIMEOUT, DEV_SERVER_MIN_TRIES, DEV_SERVER_TIMEOUT, HYDRATION_TIMEOUT, INSTALL_DEPENDENCIES_RETRIES, IS_PULL_REQUEST, PUPPETEER_TIMEOUT, SETUP_PLAYGROUND_ENV_TIMEOUT, SETUP_WAIT_TIMEOUT, TEST_MAX_RETRIES, TEST_MAX_RETRIES_PER_CODE, } from "./constants.mjs";
|
|
8
8
|
import { runDevServer } from "./dev.mjs";
|
|
9
9
|
import { poll, pollValue } from "./poll.mjs";
|
|
10
|
-
import { deleteD1Database, deleteWorker, isRelatedToTest, runRelease, } from "./release.mjs";
|
|
10
|
+
import { deleteD1Database, deleteWorker, isRelatedToTest, runPreviewServer, runRelease, } from "./release.mjs";
|
|
11
11
|
import { setupTarballEnvironment } from "./tarball.mjs";
|
|
12
12
|
import { ensureTmpDir } from "./utils.mjs";
|
|
13
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, };
|
|
@@ -221,15 +221,34 @@ export function createDeployment() {
|
|
|
221
221
|
if (SKIP_DEPLOYMENT_TESTS) {
|
|
222
222
|
throw new Error("Deployment tests are skipped via RWSDK_SKIP_DEPLOY=1");
|
|
223
223
|
}
|
|
224
|
+
const dirName = basename(projectDir);
|
|
225
|
+
// Match formats: {projectName}-t-{hash}, {projectName}-test-{hash}, or {projectName}-e2e-test-{hash}
|
|
226
|
+
const match = dirName.match(/-t-([a-f0-9]+)$/) ||
|
|
227
|
+
dirName.match(/-test-([a-f0-9]+)$/) ||
|
|
228
|
+
dirName.match(/-e2e-test-([a-f0-9]+)$/);
|
|
229
|
+
const resourceUniqueKey = match
|
|
230
|
+
? match[1]
|
|
231
|
+
: Math.random().toString(36).substring(2, 15);
|
|
232
|
+
if (IS_PULL_REQUEST) {
|
|
233
|
+
console.log("PR mode detected ā using local preview instead of deploy");
|
|
234
|
+
const previewResult = await runPreviewServer(process.env.PACKAGE_MANAGER ||
|
|
235
|
+
"pnpm", projectDir);
|
|
236
|
+
instance = {
|
|
237
|
+
url: previewResult.url,
|
|
238
|
+
workerName: `preview-${resourceUniqueKey}`,
|
|
239
|
+
resourceUniqueKey,
|
|
240
|
+
projectDir,
|
|
241
|
+
cleanup: async () => {
|
|
242
|
+
await previewResult.stopPreview().catch((error) => {
|
|
243
|
+
console.warn(`Warning: Background preview cleanup failed: ${error.message}`);
|
|
244
|
+
});
|
|
245
|
+
return Promise.resolve();
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
deploymentInstances.push(instance);
|
|
249
|
+
return instance;
|
|
250
|
+
}
|
|
224
251
|
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
252
|
const deployResult = await runRelease(projectDir, projectDir, resourceUniqueKey);
|
|
234
253
|
// A fresh *.workers.dev subdomain can return 200 with Cloudflare's
|
|
235
254
|
// "There is nothing here yet" placeholder before the worker code
|
|
@@ -273,6 +292,7 @@ export function createDeployment() {
|
|
|
273
292
|
console.log(`Retrying deployment creation (attempt ${tries})... Error: ${error.message}`);
|
|
274
293
|
},
|
|
275
294
|
});
|
|
295
|
+
instance = newInstance;
|
|
276
296
|
deploymentInstances.push(newInstance);
|
|
277
297
|
return newInstance;
|
|
278
298
|
},
|
|
@@ -37,7 +37,7 @@ bail?: boolean): Promise<void>;
|
|
|
37
37
|
/**
|
|
38
38
|
* DRY: checkServerUp now checks both root and custom path if needed
|
|
39
39
|
*/
|
|
40
|
-
export declare function checkServerUp(baseUrl: string, customPath?: string, retries?: number, bail?: boolean): Promise<
|
|
40
|
+
export declare function checkServerUp(baseUrl: string, customPath?: string, retries?: number, bail?: boolean): Promise<string>;
|
|
41
41
|
/**
|
|
42
42
|
* HMR test for server component
|
|
43
43
|
* Updates the server component and verifies that HMR applies the changes
|
|
@@ -749,42 +749,48 @@ export async function checkServerUp(baseUrl, customPath = "/", retries = RETRIES
|
|
|
749
749
|
pathsToCheck.push(customPath);
|
|
750
750
|
}
|
|
751
751
|
for (const path of pathsToCheck) {
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
752
|
+
const normalizedPath = path.startsWith("/") ? path : "/" + path;
|
|
753
|
+
const baseUrlObject = new URL(baseUrl);
|
|
754
|
+
const candidateBaseUrls = Array.from(new Set([
|
|
755
|
+
baseUrl,
|
|
756
|
+
`${baseUrlObject.protocol}//127.0.0.1:${baseUrlObject.port}`,
|
|
757
|
+
`${baseUrlObject.protocol}//[::1]:${baseUrlObject.port}`,
|
|
758
|
+
]));
|
|
759
|
+
const candidateUrls = candidateBaseUrls.map((candidateBaseUrl) => candidateBaseUrl + normalizedPath);
|
|
760
|
+
log("Checking if server is up at %s (max retries: %d)", candidateUrls[0], retries);
|
|
755
761
|
for (let i = 0; i < retries; i++) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
762
|
+
for (const candidateUrl of candidateUrls) {
|
|
763
|
+
try {
|
|
764
|
+
log("Attempt %d/%d to check server at %s", i + 1, retries, candidateUrl);
|
|
765
|
+
console.log(`Checking if server is up at ${candidateUrl} (attempt ${i + 1}/${retries})...`);
|
|
766
|
+
await $ `curl --max-time 1 -s -o /dev/null -w "%{http_code}" ${candidateUrl}`;
|
|
767
|
+
log("Server is up at %s", candidateUrl);
|
|
768
|
+
console.log(`ā
Server is up at ${candidateUrl}`);
|
|
769
|
+
return candidateUrl;
|
|
770
|
+
}
|
|
771
|
+
catch {
|
|
772
|
+
// Try the next host variant.
|
|
773
|
+
}
|
|
764
774
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
throw new Error(errorMessage);
|
|
777
|
-
}
|
|
775
|
+
if (i === retries - 1) {
|
|
776
|
+
log("ERROR: Server at %s did not become available after %d attempts", candidateUrls[0], retries);
|
|
777
|
+
const errorMessage = `Server at ${candidateUrls[0]} did not become available after ${retries} attempts`;
|
|
778
|
+
if (bail) {
|
|
779
|
+
// If bail is true, call fail() which will exit the process
|
|
780
|
+
await fail(new Error(errorMessage), 1, `Server Availability Check: ${candidateUrls[0]}`);
|
|
781
|
+
return ""; // This will never be reached due to fail() exiting
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// Otherwise throw an error that can be caught by the caller
|
|
785
|
+
throw new Error(errorMessage);
|
|
778
786
|
}
|
|
779
|
-
log("Server not up yet, retrying in 2 seconds");
|
|
780
|
-
console.log(`Server not up yet, retrying in 2 seconds...`);
|
|
781
|
-
await new Promise((resolve) => setTimeout(() => resolve(), 2000));
|
|
782
787
|
}
|
|
788
|
+
log("Server not up yet, retrying in 2 seconds");
|
|
789
|
+
console.log(`Server not up yet, retrying in 2 seconds...`);
|
|
790
|
+
await new Promise((resolve) => setTimeout(() => resolve(), 2000));
|
|
783
791
|
}
|
|
784
|
-
if (!up)
|
|
785
|
-
return false;
|
|
786
792
|
}
|
|
787
|
-
return
|
|
793
|
+
return "";
|
|
788
794
|
}
|
|
789
795
|
/**
|
|
790
796
|
* Perform only the realtime upgrade and tests without doing initial checks
|
|
@@ -8,7 +8,14 @@ export declare function runRelease(cwd: string, projectDir: string, resourceUniq
|
|
|
8
8
|
url: string;
|
|
9
9
|
workerName: string;
|
|
10
10
|
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Run the local preview server (build + preview) and return the URL
|
|
13
|
+
*/
|
|
14
|
+
export declare function runPreviewServer(packageManager?: string, cwd?: string): Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
stopPreview: () => Promise<void>;
|
|
17
|
+
}>;
|
|
11
18
|
/**
|
|
12
19
|
* Runs tests against the production deployment
|
|
13
20
|
*/
|
|
14
|
-
export declare function runReleaseTest(artifactDir: string, resources: TestResources, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, projectDir?: string, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean): Promise<void>;
|
|
21
|
+
export declare function runReleaseTest(artifactDir: string, resources: TestResources, browserPath?: string, headless?: boolean, bail?: boolean, skipClient?: boolean, projectDir?: string, realtime?: boolean, skipHmr?: boolean, skipStyleTests?: boolean, ci?: boolean): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { setTimeout } from "node:timers/promises";
|
|
2
|
-
import { $expect, deleteD1Database, deleteWorker, isRelatedToTest, listD1Databases, runRelease as runE2ERelease, } from "../../lib/e2e/release.mjs";
|
|
2
|
+
import { $expect, deleteD1Database, deleteWorker, isRelatedToTest, listD1Databases, runPreviewServer as runE2EPreviewServer, runRelease as runE2ERelease, } from "../../lib/e2e/release.mjs";
|
|
3
3
|
import { checkServerUp, checkUrl } from "./browser.mjs";
|
|
4
4
|
import { log } from "./constants.mjs";
|
|
5
|
+
import { state } from "./state.mjs";
|
|
5
6
|
export { $expect, deleteD1Database, deleteWorker, isRelatedToTest, listD1Databases, };
|
|
6
7
|
/**
|
|
7
8
|
* Run the release command to deploy to Cloudflare
|
|
@@ -9,6 +10,12 @@ export { $expect, deleteD1Database, deleteWorker, isRelatedToTest, listD1Databas
|
|
|
9
10
|
export async function runRelease(cwd, projectDir, resourceUniqueKey) {
|
|
10
11
|
return runE2ERelease(cwd, projectDir, resourceUniqueKey);
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Run the local preview server (build + preview) and return the URL
|
|
15
|
+
*/
|
|
16
|
+
export async function runPreviewServer(packageManager = "pnpm", cwd) {
|
|
17
|
+
return runE2EPreviewServer(packageManager, cwd);
|
|
18
|
+
}
|
|
12
19
|
async function waitForDeploymentContent(baseUrl, { timeoutMs = 60_000, intervalMs = 2_000, } = {}) {
|
|
13
20
|
const marker = "__RWSDK_CONTEXT";
|
|
14
21
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -41,46 +48,64 @@ async function waitForDeploymentContent(baseUrl, { timeoutMs = 60_000, intervalM
|
|
|
41
48
|
/**
|
|
42
49
|
* Runs tests against the production deployment
|
|
43
50
|
*/
|
|
44
|
-
export async function runReleaseTest(artifactDir, resources, browserPath, headless = true, bail = false, skipClient = false, projectDir, realtime = false, skipHmr = false, skipStyleTests = false) {
|
|
51
|
+
export async function runReleaseTest(artifactDir, resources, browserPath, headless = true, bail = false, skipClient = false, projectDir, realtime = false, skipHmr = false, skipStyleTests = false, ci = false) {
|
|
45
52
|
log("Starting release test");
|
|
46
53
|
console.log("\nš Testing production deployment");
|
|
54
|
+
let url;
|
|
55
|
+
let stopPreview;
|
|
47
56
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request") {
|
|
58
|
+
log("PR mode detected ā using local preview instead of deploy");
|
|
59
|
+
const previewResult = await runPreviewServer(state.options.packageManager, resources.targetDir || "");
|
|
60
|
+
url = previewResult.url;
|
|
61
|
+
stopPreview = previewResult.stopPreview;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
log("Running release process");
|
|
65
|
+
const { url: deployUrl, workerName } = await runRelease(resources.targetDir || "", projectDir || "", resources.resourceUniqueKey);
|
|
66
|
+
url = deployUrl;
|
|
67
|
+
// Wait a moment before checking server availability
|
|
68
|
+
log("Waiting 1s before checking server...");
|
|
69
|
+
await setTimeout(1000);
|
|
70
|
+
// DRY: check both root and custom path
|
|
71
|
+
await checkServerUp(url, "/");
|
|
72
|
+
// A fresh *.workers.dev subdomain can return 200 with Cloudflare's
|
|
73
|
+
// "There is nothing here yet" placeholder before the worker code is
|
|
74
|
+
// globally propagated. Poll the URL until the response body contains
|
|
75
|
+
// an rwsdk-rendered marker so we don't run the browser tests against
|
|
76
|
+
// the placeholder.
|
|
77
|
+
await waitForDeploymentContent(url);
|
|
78
|
+
// Store the worker name if we didn't set it earlier
|
|
79
|
+
if (resources && !resources.workerName) {
|
|
80
|
+
log("Storing worker name: %s", workerName);
|
|
81
|
+
resources.workerName = workerName;
|
|
82
|
+
}
|
|
83
|
+
// Mark that we created this worker during the test
|
|
84
|
+
if (resources) {
|
|
85
|
+
log("Marking worker %s as created during this test", workerName);
|
|
86
|
+
resources.workerCreatedDuringTest = true;
|
|
87
|
+
// Update the global state
|
|
88
|
+
if (resources.workerCreatedDuringTest !== undefined) {
|
|
89
|
+
resources.workerCreatedDuringTest = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
61
93
|
// Now run the tests with the custom path
|
|
62
94
|
const testUrl = new URL("/__smoke_test", url).toString();
|
|
63
95
|
await checkUrl(testUrl, artifactDir, browserPath, headless, bail, skipClient, "Production", realtime, resources.targetDir, // Add target directory parameter
|
|
64
96
|
true, // Always skip HMR in production
|
|
65
97
|
skipStyleTests);
|
|
66
98
|
log("Release test completed successfully");
|
|
67
|
-
// Store the worker name if we didn't set it earlier
|
|
68
|
-
if (resources && !resources.workerName) {
|
|
69
|
-
log("Storing worker name: %s", workerName);
|
|
70
|
-
resources.workerName = workerName;
|
|
71
|
-
}
|
|
72
|
-
// Mark that we created this worker during the test
|
|
73
|
-
if (resources) {
|
|
74
|
-
log("Marking worker %s as created during this test", workerName);
|
|
75
|
-
resources.workerCreatedDuringTest = true;
|
|
76
|
-
// Update the global state
|
|
77
|
-
if (resources.workerCreatedDuringTest !== undefined) {
|
|
78
|
-
resources.workerCreatedDuringTest = true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
99
|
}
|
|
82
100
|
catch (error) {
|
|
83
101
|
log("Error during release testing: %O", error);
|
|
84
102
|
throw error;
|
|
85
103
|
}
|
|
104
|
+
finally {
|
|
105
|
+
if (stopPreview) {
|
|
106
|
+
await stopPreview().catch((e) => {
|
|
107
|
+
log("Error stopping preview server: %O", e);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
86
111
|
}
|
|
@@ -95,7 +95,7 @@ export async function runSmokeTests(options = {}) {
|
|
|
95
95
|
// Update status when release command runs
|
|
96
96
|
try {
|
|
97
97
|
console.log("\nš Running release command smoke test");
|
|
98
|
-
await runReleaseTest(options.artifactDir, resources, browserPath, options.headless !== false, options.bail, options.skipClient, options.projectDir, options.realtime, options.skipHmr, options.skipStyleTests);
|
|
98
|
+
await runReleaseTest(options.artifactDir, resources, browserPath, options.headless !== false, options.bail, options.skipClient, options.projectDir, options.realtime, options.skipHmr, options.skipStyleTests, options.ci);
|
|
99
99
|
// Update release command status to PASSED
|
|
100
100
|
updateTestStatus("production", "releaseCommand", "PASSED");
|
|
101
101
|
// Mark that release tests have run successfully
|