rwsdk 0.2.0-alpha.5 → 0.2.0-alpha.6
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/smokeTests/browser.mjs +13 -6
- package/dist/lib/smokeTests/utils.d.mts +9 -0
- package/dist/lib/smokeTests/utils.mjs +29 -0
- package/dist/runtime/lib/manifest.js +1 -4
- package/dist/runtime/render/stylesheets.d.ts +0 -5
- package/dist/runtime/render/stylesheets.js +1 -11
- package/dist/scripts/smoke-test.mjs +4 -5
- package/dist/vite/manifestPlugin.mjs +0 -71
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import puppeteer from "puppeteer-core";
|
|
|
8
8
|
import { takeScreenshot } from "./artifacts.mjs";
|
|
9
9
|
import { RETRIES } from "./constants.mjs";
|
|
10
10
|
import { $ } from "../$.mjs";
|
|
11
|
-
import { fail } from "./utils.mjs";
|
|
11
|
+
import { fail, withRetries } from "./utils.mjs";
|
|
12
12
|
import { reportSmokeTestResult } from "./reporting.mjs";
|
|
13
13
|
import { updateTestStatus } from "./state.mjs";
|
|
14
14
|
import * as fs from "fs/promises";
|
|
@@ -404,7 +404,7 @@ export async function checkUrlSmoke(page, url, isRealtime, bail = false, skipCli
|
|
|
404
404
|
? "realtimeClientModuleStyles"
|
|
405
405
|
: "initialClientModuleStyles";
|
|
406
406
|
try {
|
|
407
|
-
await checkUrlStyles(page, "red");
|
|
407
|
+
await withRetries(() => checkUrlStyles(page, "red"), "URL styles check");
|
|
408
408
|
updateTestStatus(env, urlStylesKey, "PASSED");
|
|
409
409
|
log(`${phase} URL styles check passed`);
|
|
410
410
|
}
|
|
@@ -420,7 +420,7 @@ export async function checkUrlSmoke(page, url, isRealtime, bail = false, skipCli
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
try {
|
|
423
|
-
await checkClientModuleStyles(page, "blue");
|
|
423
|
+
await withRetries(() => checkClientModuleStyles(page, "blue"), "Client module styles check");
|
|
424
424
|
updateTestStatus(env, clientModuleStylesKey, "PASSED");
|
|
425
425
|
log(`${phase} client module styles check passed`);
|
|
426
426
|
}
|
|
@@ -507,7 +507,14 @@ export async function checkUrlSmoke(page, url, isRealtime, bail = false, skipCli
|
|
|
507
507
|
await testClientComponentHmr(page, targetDir, phase, environment, bail);
|
|
508
508
|
// Test style HMR if style tests aren't skipped
|
|
509
509
|
if (!skipStyleTests) {
|
|
510
|
-
await testStyleHMR(page, targetDir)
|
|
510
|
+
await withRetries(() => testStyleHMR(page, targetDir), "Style HMR test", async () => {
|
|
511
|
+
// This logic runs before each retry of testStyleHMR
|
|
512
|
+
const urlStylePath = join(targetDir, "src", "app", "smokeTestUrlStyles.css");
|
|
513
|
+
const clientStylePath = join(targetDir, "src", "app", "components", "smokeTestClientStyles.module.css");
|
|
514
|
+
// Restore original styles before re-running HMR test
|
|
515
|
+
await fs.writeFile(urlStylePath, urlStylesTemplate);
|
|
516
|
+
await fs.writeFile(clientStylePath, clientStylesTemplate);
|
|
517
|
+
});
|
|
511
518
|
}
|
|
512
519
|
else {
|
|
513
520
|
log("Skipping style HMR test as requested");
|
|
@@ -1182,9 +1189,9 @@ async function testStyleHMR(page, targetDir) {
|
|
|
1182
1189
|
// Allow time for HMR to kick in
|
|
1183
1190
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1184
1191
|
// Check URL-based stylesheet HMR
|
|
1185
|
-
await checkUrlStyles(page, "green");
|
|
1192
|
+
await withRetries(() => checkUrlStyles(page, "green"), "URL styles HMR check");
|
|
1186
1193
|
// Check client-module stylesheet HMR
|
|
1187
|
-
await checkClientModuleStyles(page, "green");
|
|
1194
|
+
await withRetries(() => checkClientModuleStyles(page, "green"), "Client module styles HMR check");
|
|
1188
1195
|
// Restore original styles
|
|
1189
1196
|
await fs.writeFile(urlStylePath, urlStylesTemplate);
|
|
1190
1197
|
await fs.writeFile(clientStylePath, clientStylesTemplate);
|
|
@@ -13,3 +13,12 @@ export declare function teardown(): Promise<void>;
|
|
|
13
13
|
* Formats the path suffix from a custom path
|
|
14
14
|
*/
|
|
15
15
|
export declare function formatPathSuffix(customPath?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Wraps an async function with retry logic.
|
|
18
|
+
* @param fn The async function to execute.
|
|
19
|
+
* @param description A description of the operation for logging.
|
|
20
|
+
* @param beforeRetry A function to run before each retry attempt.
|
|
21
|
+
* @param maxRetries The maximum number of retries.
|
|
22
|
+
* @param delay The delay between retries in milliseconds.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withRetries<T>(fn: () => Promise<T>, description: string, beforeRetry?: () => Promise<void>, maxRetries?: number, delay?: number): Promise<T>;
|
|
@@ -145,3 +145,32 @@ export function formatPathSuffix(customPath) {
|
|
|
145
145
|
log("Formatted path suffix: %s", suffix);
|
|
146
146
|
return suffix;
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Wraps an async function with retry logic.
|
|
150
|
+
* @param fn The async function to execute.
|
|
151
|
+
* @param description A description of the operation for logging.
|
|
152
|
+
* @param beforeRetry A function to run before each retry attempt.
|
|
153
|
+
* @param maxRetries The maximum number of retries.
|
|
154
|
+
* @param delay The delay between retries in milliseconds.
|
|
155
|
+
*/
|
|
156
|
+
export async function withRetries(fn, description, beforeRetry, maxRetries = 5, delay = 2000) {
|
|
157
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
158
|
+
try {
|
|
159
|
+
if (i > 0 && beforeRetry) {
|
|
160
|
+
log(`Running beforeRetry hook for "${description}"`);
|
|
161
|
+
await beforeRetry();
|
|
162
|
+
}
|
|
163
|
+
return await fn();
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
log(`Attempt ${i + 1} of ${maxRetries} failed for "${description}": ${error instanceof Error ? error.message : String(error)}`);
|
|
167
|
+
if (i === maxRetries - 1) {
|
|
168
|
+
log(`All ${maxRetries} retries failed for "${description}".`);
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
log(`Retrying in ${delay}ms...`);
|
|
172
|
+
await setTimeout(delay);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
throw new Error("Retry loop failed unexpectedly.");
|
|
176
|
+
}
|
|
@@ -4,10 +4,7 @@ export const getManifest = async (requestInfo) => {
|
|
|
4
4
|
return manifest;
|
|
5
5
|
}
|
|
6
6
|
if (import.meta.env.VITE_IS_DEV_SERVER) {
|
|
7
|
-
|
|
8
|
-
url.searchParams.set("scripts", JSON.stringify(Array.from(requestInfo.rw.scriptsToBeLoaded)));
|
|
9
|
-
url.pathname = "/__rwsdk_manifest";
|
|
10
|
-
manifest = await fetch(url.toString()).then((res) => res.json());
|
|
7
|
+
manifest = {};
|
|
11
8
|
}
|
|
12
9
|
else {
|
|
13
10
|
const { default: prodManifest } = await import("virtual:rwsdk:manifest.js");
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { type RequestInfo } from "../requestInfo/types.js";
|
|
2
|
-
export type CssEntry = {
|
|
3
|
-
url: string;
|
|
4
|
-
content: string;
|
|
5
|
-
absolutePath: string;
|
|
6
|
-
};
|
|
7
2
|
export declare const Stylesheets: ({ requestInfo }: {
|
|
8
3
|
requestInfo: RequestInfo;
|
|
9
4
|
}) => import("react/jsx-runtime.js").JSX.Element;
|
|
@@ -31,15 +31,5 @@ export const Stylesheets = ({ requestInfo }) => {
|
|
|
31
31
|
allStylesheets.add(entry);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
return (_jsx(_Fragment, { children: Array.from(allStylesheets).map((
|
|
35
|
-
if (typeof entry === "string") {
|
|
36
|
-
return (_jsx("link", { rel: "stylesheet", href: entry, precedence: "first" }, entry));
|
|
37
|
-
}
|
|
38
|
-
if (import.meta.env.VITE_IS_DEV_SERVER) {
|
|
39
|
-
return (_jsx("style", { "data-vite-dev-id": entry.absolutePath, dangerouslySetInnerHTML: { __html: entry.content } }, entry.url));
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
return (_jsx("link", { rel: "stylesheet", href: entry.url, precedence: "first" }, entry.url));
|
|
43
|
-
}
|
|
44
|
-
}) }));
|
|
34
|
+
return (_jsx(_Fragment, { children: Array.from(allStylesheets).map((href) => (_jsx("link", { rel: "stylesheet", href: href, precedence: "first" }, href))) }));
|
|
45
35
|
};
|
|
@@ -30,8 +30,7 @@ if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
30
30
|
copyProject: false, // Default to false - don't copy project to artifacts
|
|
31
31
|
realtime: false, // Default to false - don't just test realtime
|
|
32
32
|
skipHmr: false, // Default to false - run HMR tests
|
|
33
|
-
|
|
34
|
-
skipStyleTests: true, // Default to true - skip style tests
|
|
33
|
+
skipStyleTests: false,
|
|
35
34
|
// sync: will be set below
|
|
36
35
|
};
|
|
37
36
|
// Log if we're in CI
|
|
@@ -55,8 +54,8 @@ if (fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
55
54
|
else if (arg === "--skip-hmr") {
|
|
56
55
|
options.skipHmr = true;
|
|
57
56
|
}
|
|
58
|
-
else if (arg === "--
|
|
59
|
-
options.skipStyleTests =
|
|
57
|
+
else if (arg === "--skip-style-tests") {
|
|
58
|
+
options.skipStyleTests = true;
|
|
60
59
|
}
|
|
61
60
|
else if (arg === "--keep") {
|
|
62
61
|
options.keep = true;
|
|
@@ -94,7 +93,7 @@ Options:
|
|
|
94
93
|
--skip-release Skip testing the release/production deployment
|
|
95
94
|
--skip-client Skip client-side tests, only run server-side checks
|
|
96
95
|
--skip-hmr Skip hot module replacement (HMR) tests
|
|
97
|
-
--
|
|
96
|
+
--skip-style-tests Skip stylesheet-related tests
|
|
98
97
|
--path=PATH Project directory to test
|
|
99
98
|
--artifact-dir=DIR Directory to store test artifacts (default: .artifacts)
|
|
100
99
|
--keep Keep temporary test directory after tests complete
|
|
@@ -4,36 +4,6 @@ import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
|
|
|
4
4
|
const log = debug("rwsdk:vite:manifest-plugin");
|
|
5
5
|
const virtualModuleId = "virtual:rwsdk:manifest.js";
|
|
6
6
|
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
7
|
-
const getCssForModule = (server, moduleId, css) => {
|
|
8
|
-
const stack = [moduleId];
|
|
9
|
-
const visited = new Set();
|
|
10
|
-
while (stack.length > 0) {
|
|
11
|
-
const currentModuleId = stack.pop();
|
|
12
|
-
if (visited.has(currentModuleId)) {
|
|
13
|
-
continue;
|
|
14
|
-
}
|
|
15
|
-
visited.add(currentModuleId);
|
|
16
|
-
const moduleNode = server.environments.client.moduleGraph.getModuleById(currentModuleId);
|
|
17
|
-
if (!moduleNode) {
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
for (const importedModule of moduleNode.importedModules) {
|
|
21
|
-
if (importedModule.url.endsWith(".css")) {
|
|
22
|
-
const absolutePath = importedModule.file;
|
|
23
|
-
css.add({
|
|
24
|
-
url: importedModule.url,
|
|
25
|
-
// The `ssrTransformResult` has the CSS content, because the default
|
|
26
|
-
// transform for CSS is to a string of the CSS content.
|
|
27
|
-
content: importedModule.ssrTransformResult?.code ?? "",
|
|
28
|
-
absolutePath,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if (importedModule.id) {
|
|
32
|
-
stack.push(importedModule.id);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
7
|
export const manifestPlugin = ({ manifestPath, }) => {
|
|
38
8
|
let isBuild = false;
|
|
39
9
|
let root;
|
|
@@ -106,46 +76,5 @@ export const manifestPlugin = ({ manifestPath, }) => {
|
|
|
106
76
|
},
|
|
107
77
|
});
|
|
108
78
|
},
|
|
109
|
-
configureServer(server) {
|
|
110
|
-
log("Configuring server middleware for manifest");
|
|
111
|
-
server.middlewares.use("/__rwsdk_manifest", async (req, res, next) => {
|
|
112
|
-
log("Manifest request received: %s", req.url);
|
|
113
|
-
try {
|
|
114
|
-
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
115
|
-
const scripts = JSON.parse(url.searchParams.get("scripts") || "[]");
|
|
116
|
-
process.env.VERBOSE && log("Transforming scripts: %o", scripts);
|
|
117
|
-
for (const script of scripts) {
|
|
118
|
-
await server.environments.client.transformRequest(script);
|
|
119
|
-
}
|
|
120
|
-
const manifest = {};
|
|
121
|
-
log("Building manifest from module graph");
|
|
122
|
-
for (const file of server.environments.client.moduleGraph.fileToModulesMap.keys()) {
|
|
123
|
-
const modules = server.environments.client.moduleGraph.getModulesByFile(file);
|
|
124
|
-
if (!modules) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
for (const module of modules) {
|
|
128
|
-
if (module.file) {
|
|
129
|
-
const css = new Set();
|
|
130
|
-
getCssForModule(server, module.id, css);
|
|
131
|
-
manifest[normalizeModulePath(module.file, server.config.root)] =
|
|
132
|
-
{
|
|
133
|
-
file: module.url,
|
|
134
|
-
css: Array.from(css),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
log("Manifest built successfully");
|
|
140
|
-
process.env.VERBOSE && log("Manifest: %o", manifest);
|
|
141
|
-
res.setHeader("Content-Type", "application/json");
|
|
142
|
-
res.end(JSON.stringify(manifest));
|
|
143
|
-
}
|
|
144
|
-
catch (e) {
|
|
145
|
-
log("Error building manifest: %o", e);
|
|
146
|
-
next(e);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
79
|
};
|
|
151
80
|
};
|