@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 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("data: reload\n\n");
145
+ client.write(message);
138
146
  });
139
147
  };
140
- const serverPort = await new Promise((res) => {
141
- server.listen(port, () => {
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
- console.info(`Allure is running on http://localhost:${serverPort}`);
164
+ const serverUrl = resolveServerUrl(host, serverPort);
165
+ console.info(`Allure is running on ${serverUrl}`);
155
166
  if (open) {
156
- openUrl(`http://localhost:${serverPort}`);
167
+ openUrl(serverUrl);
157
168
  }
158
169
  return {
159
- url: `http://localhost:${serverPort}`,
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, `http://localhost:${serverPort}`).toString());
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 injectLiveReloadScript: (html: string) => string;
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
- export const injectLiveReloadScript = (html) => {
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
- const eventSource = new EventSource("/__live_reload");
906
-
907
- eventSource.onmessage = () => {
908
- const newHash = Math.random().toString(36).slice(2, 10);
909
- const reload = () => window.location.reload();
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
- if (typeof window.__allureLiveReload !== "function") {
914
- reload();
915
- return;
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
- Promise.resolve(window.__allureLiveReload()).catch(reload);
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.20.1",
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.20.1",
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",