@allurereport/static-server 3.20.1 → 3.21.0
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/README.md +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +21 -10
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +61 -14
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -34,6 +34,8 @@ import { serve } from "@allurereport/static-server";
|
|
|
34
34
|
const server = await serve({
|
|
35
35
|
// by default uses a random available port
|
|
36
36
|
port: 8080,
|
|
37
|
+
// by default listens on all interfaces
|
|
38
|
+
host: "127.0.0.1",
|
|
37
39
|
// path to the directory with files should be served
|
|
38
40
|
servePath: "/path/to/your/static/files",
|
|
39
41
|
// enable live reload
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type AllureStaticServer = {
|
|
|
8
8
|
export declare const renderDirectory: (entries: string[], dirPath?: boolean) => Promise<string>;
|
|
9
9
|
export declare const serve: (options?: {
|
|
10
10
|
port?: number;
|
|
11
|
+
host?: string;
|
|
11
12
|
live?: boolean;
|
|
12
13
|
servePath?: string;
|
|
13
14
|
open?: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as console from "node:console";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { createReadStream } from "node:fs";
|
|
3
4
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
5
|
import { createServer } from "node:http";
|
|
@@ -6,7 +7,7 @@ import { basename, extname, join, resolve } from "node:path";
|
|
|
6
7
|
import { cwd } from "node:process";
|
|
7
8
|
import watchDirectory from "@allurereport/directory-watcher";
|
|
8
9
|
import openUrl from "open";
|
|
9
|
-
import { TYPES_BY_EXTENSION, identity, injectLiveReloadScript, resolveUrlPathnameUnderServeRoot } from "./utils.js";
|
|
10
|
+
import { TYPES_BY_EXTENSION, identity, injectLiveReloadScript, normalizeHost, resolveServerUrl, resolveUrlPathnameUnderServeRoot, } from "./utils.js";
|
|
10
11
|
export const renderDirectory = async (entries, dirPath) => {
|
|
11
12
|
const links = [];
|
|
12
13
|
const files = [];
|
|
@@ -49,8 +50,12 @@ export const renderDirectory = async (entries, dirPath) => {
|
|
|
49
50
|
};
|
|
50
51
|
export const serve = async (options) => {
|
|
51
52
|
const { port, live = false, servePath, open = false } = options ?? {};
|
|
53
|
+
const host = normalizeHost(options?.host);
|
|
52
54
|
const pathToServe = servePath ? resolve(cwd(), servePath) : cwd();
|
|
53
55
|
const clients = new Set();
|
|
56
|
+
const instanceId = randomUUID();
|
|
57
|
+
let reloadsCount = 0;
|
|
58
|
+
const currentReloadId = () => `${instanceId}:${reloadsCount}`;
|
|
54
59
|
const server = createServer(async (req, res) => {
|
|
55
60
|
const hostHeaderIdx = req.rawHeaders.findIndex((header) => header === "Host") + 1;
|
|
56
61
|
const host = req.rawHeaders[hostHeaderIdx];
|
|
@@ -66,6 +71,7 @@ export const serve = async (options) => {
|
|
|
66
71
|
req.on("close", () => {
|
|
67
72
|
clients.delete(res);
|
|
68
73
|
});
|
|
74
|
+
res.write(`event: sync\ndata: ${currentReloadId()}\n\n`);
|
|
69
75
|
return;
|
|
70
76
|
}
|
|
71
77
|
const resolvedPublic = resolveUrlPathnameUnderServeRoot(pathToServe, pathname);
|
|
@@ -101,7 +107,7 @@ export const serve = async (options) => {
|
|
|
101
107
|
}
|
|
102
108
|
else {
|
|
103
109
|
const html = await renderDirectory(files.map((file) => resolve(fsPath, file)), pathname !== "/");
|
|
104
|
-
const htmlContent = injectLiveReloadScript(html);
|
|
110
|
+
const htmlContent = injectLiveReloadScript(html, currentReloadId());
|
|
105
111
|
const byteLength = Buffer.byteLength(htmlContent);
|
|
106
112
|
res.writeHead(200, {
|
|
107
113
|
"Content-Type": "text/html",
|
|
@@ -115,7 +121,7 @@ export const serve = async (options) => {
|
|
|
115
121
|
const contentType = TYPES_BY_EXTENSION[fileExtension] ?? "application/octet-stream";
|
|
116
122
|
if (contentType === "text/html" && !query.has("attachment")) {
|
|
117
123
|
const html = await readFile(fsPath, "utf-8");
|
|
118
|
-
const htmlContent = injectLiveReloadScript(html);
|
|
124
|
+
const htmlContent = injectLiveReloadScript(html, currentReloadId());
|
|
119
125
|
const byteLength = Buffer.byteLength(htmlContent);
|
|
120
126
|
res.writeHead(200, {
|
|
121
127
|
"Content-Type": contentType,
|
|
@@ -133,12 +139,16 @@ export const serve = async (options) => {
|
|
|
133
139
|
.on("close", () => res.end());
|
|
134
140
|
});
|
|
135
141
|
const triggerReload = () => {
|
|
142
|
+
reloadsCount += 1;
|
|
143
|
+
const message = `id: ${currentReloadId()}\ndata: reload\n\n`;
|
|
136
144
|
clients.forEach((client) => {
|
|
137
|
-
client.write(
|
|
145
|
+
client.write(message);
|
|
138
146
|
});
|
|
139
147
|
};
|
|
140
|
-
const serverPort = await new Promise((res) => {
|
|
141
|
-
server.
|
|
148
|
+
const serverPort = await new Promise((res, rej) => {
|
|
149
|
+
server.once("error", rej);
|
|
150
|
+
server.listen(port, host, () => {
|
|
151
|
+
server.off("error", rej);
|
|
142
152
|
const address = server.address();
|
|
143
153
|
if (!address || typeof address === "string") {
|
|
144
154
|
throw new Error("could not start a server: invalid server address is returned");
|
|
@@ -151,19 +161,20 @@ export const serve = async (options) => {
|
|
|
151
161
|
ignoreInitial: true,
|
|
152
162
|
})
|
|
153
163
|
: identity;
|
|
154
|
-
|
|
164
|
+
const serverUrl = resolveServerUrl(host, serverPort);
|
|
165
|
+
console.info(`Allure is running on ${serverUrl}`);
|
|
155
166
|
if (open) {
|
|
156
|
-
openUrl(
|
|
167
|
+
openUrl(serverUrl);
|
|
157
168
|
}
|
|
158
169
|
return {
|
|
159
|
-
url:
|
|
170
|
+
url: serverUrl,
|
|
160
171
|
port: serverPort,
|
|
161
172
|
reload: async () => {
|
|
162
173
|
triggerReload();
|
|
163
174
|
},
|
|
164
175
|
open: async (url) => {
|
|
165
176
|
if (url.startsWith("/")) {
|
|
166
|
-
await openUrl(new URL(url,
|
|
177
|
+
await openUrl(new URL(url, serverUrl).toString());
|
|
167
178
|
return;
|
|
168
179
|
}
|
|
169
180
|
await openUrl(url);
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const EXTENSIONS_BY_TYPE: Record<string, string>;
|
|
2
2
|
export declare const TYPES_BY_EXTENSION: Record<string, string>;
|
|
3
3
|
export declare const identity: <T>(value?: T) => T | undefined;
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const normalizeHost: (host: string | undefined) => string | undefined;
|
|
5
|
+
export declare const resolveServerUrl: (host: string | undefined, port: number) => string;
|
|
6
|
+
export declare const injectLiveReloadScript: (html: string, reloadId: string) => string;
|
|
5
7
|
export declare function resolveUrlPathnameUnderServeRoot(rootDir: string, urlPathname: string): string | null;
|
package/dist/utils.js
CHANGED
|
@@ -899,24 +899,71 @@ export const EXTENSIONS_BY_TYPE = {
|
|
|
899
899
|
};
|
|
900
900
|
export const TYPES_BY_EXTENSION = Object.fromEntries(Object.entries(EXTENSIONS_BY_TYPE).map(([type, extension]) => [extension, type]));
|
|
901
901
|
export const identity = (value) => value;
|
|
902
|
-
|
|
902
|
+
const WILDCARD_HOSTS = new Set(["0.0.0.0", "::"]);
|
|
903
|
+
export const normalizeHost = (host) => {
|
|
904
|
+
const trimmed = host?.trim().replace(/^\[(.*)\]$/, "$1");
|
|
905
|
+
return trimmed || undefined;
|
|
906
|
+
};
|
|
907
|
+
export const resolveServerUrl = (host, port) => {
|
|
908
|
+
const normalizedHost = normalizeHost(host);
|
|
909
|
+
const linkHost = !normalizedHost || WILDCARD_HOSTS.has(normalizedHost) ? "localhost" : normalizedHost;
|
|
910
|
+
return `http://${linkHost.includes(":") ? `[${linkHost}]` : linkHost}:${port}`;
|
|
911
|
+
};
|
|
912
|
+
export const injectLiveReloadScript = (html, reloadId) => {
|
|
903
913
|
const liveReloadScript = `
|
|
904
914
|
<script>
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
window.localStorage.setItem("${ALLURE_LIVE_RELOAD_HASH_STORAGE_KEY}", newHash);
|
|
915
|
+
(() => {
|
|
916
|
+
// every opened page would keep a permanent connection to the server and browsers limit the number of them per host,
|
|
917
|
+
// so the connection is closed while the page is hidden and the reloads missed in the meantime are caught up on return
|
|
918
|
+
let eventSource;
|
|
919
|
+
let lastReloadId = ${JSON.stringify(reloadId)};
|
|
912
920
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
921
|
+
const applyReload = () => {
|
|
922
|
+
const newHash = Math.random().toString(36).slice(2, 10);
|
|
923
|
+
const reload = () => window.location.reload();
|
|
924
|
+
|
|
925
|
+
window.localStorage.setItem("${ALLURE_LIVE_RELOAD_HASH_STORAGE_KEY}", newHash);
|
|
926
|
+
|
|
927
|
+
if (typeof window.__allureLiveReload !== "function") {
|
|
928
|
+
reload();
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
917
931
|
|
|
918
|
-
|
|
919
|
-
|
|
932
|
+
Promise.resolve(window.__allureLiveReload()).catch(reload);
|
|
933
|
+
};
|
|
934
|
+
const connect = () => {
|
|
935
|
+
eventSource = new EventSource("/__live_reload");
|
|
936
|
+
|
|
937
|
+
eventSource.onmessage = (event) => {
|
|
938
|
+
lastReloadId = event.lastEventId;
|
|
939
|
+
applyReload();
|
|
940
|
+
};
|
|
941
|
+
eventSource.addEventListener("sync", (event) => {
|
|
942
|
+
if (lastReloadId === event.data) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
lastReloadId = event.data;
|
|
947
|
+
applyReload();
|
|
948
|
+
});
|
|
949
|
+
};
|
|
950
|
+
const disconnect = () => {
|
|
951
|
+
eventSource?.close();
|
|
952
|
+
eventSource = undefined;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
document.addEventListener("visibilitychange", () => {
|
|
956
|
+
if (document.hidden) {
|
|
957
|
+
disconnect();
|
|
958
|
+
} else if (!eventSource) {
|
|
959
|
+
connect();
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
if (!document.hidden) {
|
|
964
|
+
connect();
|
|
965
|
+
}
|
|
966
|
+
})();
|
|
920
967
|
</script>
|
|
921
968
|
`;
|
|
922
969
|
return html.replace("</body>", `${liveReloadScript}</body>`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allurereport/static-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.0",
|
|
4
4
|
"description": "Minimalistic web-server for serving static files",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -32,12 +32,14 @@
|
|
|
32
32
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@allurereport/directory-watcher": "3.
|
|
35
|
+
"@allurereport/directory-watcher": "3.21.0",
|
|
36
36
|
"open": "^11.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@allurereport/test-config": "3.21.0",
|
|
39
40
|
"@playwright/test": "^1.62.1",
|
|
40
41
|
"@types/node": "^20",
|
|
42
|
+
"@vitest/coverage-istanbul": "^4.1.0",
|
|
41
43
|
"@vitest/runner": "^2",
|
|
42
44
|
"@vitest/snapshot": "^5.0.1",
|
|
43
45
|
"allure-js-commons": "^3",
|