@lexho111/plainblog 0.6.1 → 0.6.3

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.
Files changed (37) hide show
  1. package/Blog.js +2 -8
  2. package/blog_test_empty.db +0 -0
  3. package/blog_test_load.db +0 -0
  4. package/coverage/clover.xml +485 -485
  5. package/coverage/coverage-final.json +10 -10
  6. package/coverage/lcov-report/index.html +1 -1
  7. package/coverage/lcov-report/package/Article.js.html +5 -5
  8. package/coverage/lcov-report/package/Blog.js.html +171 -171
  9. package/coverage/lcov-report/package/Formatter.js.html +1 -1
  10. package/coverage/lcov-report/package/build-scripts.js.html +1 -1
  11. package/coverage/lcov-report/package/build-styles.js.html +1 -1
  12. package/coverage/lcov-report/package/index.html +1 -1
  13. package/coverage/lcov-report/package/model/APIModel.js.html +1 -1
  14. package/coverage/lcov-report/package/model/DataModel.js.html +7 -4
  15. package/coverage/lcov-report/package/model/DatabaseModel.js.html +6 -3
  16. package/coverage/lcov-report/package/model/FileAdapter.js.html +10 -7
  17. package/coverage/lcov-report/package/model/FileModel.js.html +9 -6
  18. package/coverage/lcov-report/package/model/SequelizeAdapter.js.html +1 -1
  19. package/coverage/lcov-report/package/model/SqliteAdapter.js.html +1 -1
  20. package/coverage/lcov-report/package/model/datastructures/ArrayList.js.html +2 -2
  21. package/coverage/lcov-report/package/model/datastructures/ArrayListHashMap.js.html +1 -1
  22. package/coverage/lcov-report/package/model/datastructures/BinarySearchTree.js.html +42 -39
  23. package/coverage/lcov-report/package/model/datastructures/BinarySearchTreeHashMap.js.html +8 -5
  24. package/coverage/lcov-report/package/model/datastructures/FileList.js.html +1 -1
  25. package/coverage/lcov-report/package/model/datastructures/index.html +1 -1
  26. package/coverage/lcov-report/package/model/index.html +1 -1
  27. package/coverage/lcov-report/package/utilities.js.html +19 -19
  28. package/coverage/lcov.info +916 -916
  29. package/debug-loader.js +26 -0
  30. package/model/DataModel.js +1 -2
  31. package/model/DatabaseModel.js +1 -2
  32. package/model/FileAdapter.js +1 -2
  33. package/model/FileModel.js +2 -3
  34. package/model/datastructures/BinarySearchTree.js +1 -2
  35. package/model/datastructures/BinarySearchTreeHashMap.js +1 -2
  36. package/package.json +3 -3
  37. package/public/styles.min.css +2 -2
@@ -951,6 +951,7 @@
951
951
  <span class="cline-any cline-neutral">&nbsp;</span>
952
952
  <span class="cline-any cline-neutral">&nbsp;</span>
953
953
  <span class="cline-any cline-neutral">&nbsp;</span>
954
+ <span class="cline-any cline-neutral">&nbsp;</span>
954
955
  <span class="cline-any cline-yes">5x</span>
955
956
  <span class="cline-any cline-neutral">&nbsp;</span>
956
957
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1089,7 +1090,7 @@
1089
1090
  <span class="cline-any cline-neutral">&nbsp;</span>
1090
1091
  <span class="cline-any cline-neutral">&nbsp;</span>
1091
1092
  <span class="cline-any cline-neutral">&nbsp;</span>
1092
- <span class="cline-any cline-yes">164647x</span>
1093
+ <span class="cline-any cline-yes">164663x</span>
1093
1094
  <span class="cline-any cline-neutral">&nbsp;</span>
1094
1095
  <span class="cline-any cline-neutral">&nbsp;</span>
1095
1096
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1178,12 +1179,12 @@
1178
1179
  <span class="cline-any cline-neutral">&nbsp;</span>
1179
1180
  <span class="cline-any cline-neutral">&nbsp;</span>
1180
1181
  <span class="cline-any cline-neutral">&nbsp;</span>
1181
- <span class="cline-any cline-yes">19x</span>
1182
+ <span class="cline-any cline-yes">18x</span>
1182
1183
  <span class="cline-any cline-neutral">&nbsp;</span>
1183
1184
  <span class="cline-any cline-neutral">&nbsp;</span>
1184
- <span class="cline-any cline-yes">19x</span>
1185
+ <span class="cline-any cline-yes">18x</span>
1185
1186
  <span class="cline-any cline-neutral">&nbsp;</span>
1186
- <span class="cline-any cline-yes">3x</span>
1187
+ <span class="cline-any cline-yes">4x</span>
1187
1188
  <span class="cline-any cline-neutral">&nbsp;</span>
1188
1189
  <span class="cline-any cline-neutral">&nbsp;</span>
1189
1190
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1483,18 +1484,18 @@
1483
1484
  <span class="cline-any cline-yes">25x</span>
1484
1485
  <span class="cline-any cline-yes">24x</span>
1485
1486
  <span class="cline-any cline-yes">24x</span>
1486
- <span class="cline-any cline-yes">311056x</span>
1487
- <span class="cline-any cline-yes">155516x</span>
1488
- <span class="cline-any cline-yes">155516x</span>
1489
- <span class="cline-any cline-yes">155516x</span>
1487
+ <span class="cline-any cline-yes">311088x</span>
1488
+ <span class="cline-any cline-yes">155532x</span>
1489
+ <span class="cline-any cline-yes">155532x</span>
1490
+ <span class="cline-any cline-yes">155532x</span>
1490
1491
  <span class="cline-any cline-neutral">&nbsp;</span>
1491
1492
  <span class="cline-any cline-neutral">&nbsp;</span>
1492
1493
  <span class="cline-any cline-neutral">&nbsp;</span>
1493
1494
  <span class="cline-any cline-neutral">&nbsp;</span>
1494
1495
  <span class="cline-any cline-neutral">&nbsp;</span>
1495
- <span class="cline-any cline-yes">155516x</span>
1496
- <span class="cline-any cline-yes">155516x</span>
1497
- <span class="cline-any cline-yes">155516x</span>
1496
+ <span class="cline-any cline-yes">155532x</span>
1497
+ <span class="cline-any cline-yes">155532x</span>
1498
+ <span class="cline-any cline-yes">155532x</span>
1498
1499
  <span class="cline-any cline-neutral">&nbsp;</span>
1499
1500
  <span class="cline-any cline-yes">24x</span>
1500
1501
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -1788,7 +1789,6 @@
1788
1789
  <span class="cline-any cline-neutral">&nbsp;</span>
1789
1790
  <span class="cline-any cline-neutral">&nbsp;</span>
1790
1791
  <span class="cline-any cline-neutral">&nbsp;</span>
1791
- <span class="cline-any cline-neutral">&nbsp;</span>
1792
1792
  <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import http from "http";
1793
1793
  import crypto from "node:crypto";
1794
1794
  import fs from "fs";
@@ -1797,7 +1797,7 @@ import process from "node:process";
1797
1797
  import path from "path";
1798
1798
  import { fileURLToPath } from "url";
1799
1799
  import pkg from "./package.json" with { type: "json" };
1800
- import createDebug from "debug";
1800
+ import { debuglog as createDebug } from "node:util";
1801
1801
  import Article from "./Article.js";
1802
1802
  import DatabaseModel from "./model/DatabaseModel.js";
1803
1803
  import { fetchData, postData } from "./model/APIModel.js";
@@ -1893,9 +1893,9 @@ export default class Blog {
1893
1893
  this.#title = title;
1894
1894
  }
1895
1895
  &nbsp;
1896
- <span class="fstat-no" title="function not covered" > se</span>tPassword(password) {
1897
- <span class="cstat-no" title="statement not covered" > this.#password = password;</span>
1898
- }
1896
+ setPassword(password) {
1897
+ <span class="fstat-no" title="function not covered" ></span> this.#password = password;
1898
+ }<span class="cstat-no" title="statement not covered" ></span>
1899
1899
  &nbsp;
1900
1900
  /**
1901
1901
  * Appends CSS rules to the \&lt;style\&gt;-tag.
@@ -1923,9 +1923,9 @@ export default class Blog {
1923
1923
  * @param {*} adapter a database adapter like PostgresAdapter or SqliteAdapter
1924
1924
  */
1925
1925
  setDatabaseAdapter(adapter) {
1926
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!this.#databaseModel) {
1927
- <span class="missing-if-branch" title="else path not taken" >E</span>if (this.database.type === "file") {
1928
- const adapter = new FileAdapter(this.database);
1926
+ if (!this.#databaseModel) {
1927
+ <span class="missing-if-branch" title="else path not taken" >E</span> if (this.database.type === "file") {
1928
+ <span class="missing-if-branch" title="else path not taken" >E</span> const adapter = new FileAdapter(this.database);
1929
1929
  this.#databaseModel = new DatabaseModel(adapter);
1930
1930
  }
1931
1931
  }
@@ -1979,10 +1979,10 @@ export default class Blog {
1979
1979
  Location: "/",
1980
1980
  });
1981
1981
  res.end();
1982
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
1983
- <span class="cstat-no" title="statement not covered" > res.writeHead(401, { "Content-Type": "text/html" });</span>
1982
+ } else {
1983
+ res.w<span class="missing-if-branch" title="else path not taken" >E</span>riteHead(401, { "Content-Type": "text/html" });
1984
1984
  <span class="cstat-no" title="statement not covered" > res.end(`${header("My Blog")}</span>
1985
- &lt;body&gt;
1985
+ &lt;body&gt;<span class="cstat-no" title="statement not covered" ></span>
1986
1986
  &lt;h1&gt;Unauthorized&lt;/h1&gt;&lt;div class="box"&gt;&lt;p&gt;Please enter the password.&lt;form method="POST"&gt;
1987
1987
  &lt;input type="password" name="password" placeholder="Password" /&gt;
1988
1988
  &lt;button style="margin: 2px;"&gt;Login&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;
@@ -1991,22 +1991,22 @@ export default class Blog {
1991
1991
  }
1992
1992
  &nbsp;
1993
1993
  #handleLogout(req, res) {
1994
- <span class="cstat-no" title="statement not covered" > debug("handle logout");</span>
1995
- <span class="cstat-no" title="statement not covered" > if (req.headers.cookie) {</span>
1996
- const params = <span class="cstat-no" title="statement not covered" >new URLSearchParams(</span>
1997
- req.headers.cookie.replace(/; /g, "&amp;"),
1994
+ debug("handle logout");
1995
+ <span class="cstat-no" title="statement not covered" > if (req.headers.cookie) </span>{
1996
+ <span class="cstat-no" title="statement not covered" > const params = new URLSearchParams(</span>
1997
+ req.headers.c<span class="cstat-no" title="statement not covered" >ookie.replace(/; /g, "&amp;"),</span>
1998
1998
  );
1999
- const sessionId = <span class="cstat-no" title="statement not covered" >params.get("session");</span>
2000
- <span class="cstat-no" title="statement not covered" > if (this.sessions.has(sessionId)) {</span>
1999
+ const sessionId = params.get("session");
2000
+ if (this.sessions.<span class="cstat-no" title="statement not covered" >has(sessionId)) {</span>
2001
2001
  <span class="cstat-no" title="statement not covered" > this.sessions.delete(sessionId);</span>
2002
- }
2002
+ }<span class="cstat-no" title="statement not covered" ></span>
2003
2003
  }
2004
- <span class="cstat-no" title="statement not covered" > res.writeHead(303, {</span>
2005
- "Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",
2004
+ res.writeHead(303, {
2005
+ <span class="cstat-no" title="statement not covered" > "Set-Cookie": "session=; HttpOnly; Path=/; Max-Age=0",</span>
2006
2006
  Location: "/",
2007
2007
  });
2008
- <span class="cstat-no" title="statement not covered" > res.end();</span>
2009
- }
2008
+ res.end();
2009
+ }<span class="cstat-no" title="statement not covered" ></span>
2010
2010
  &nbsp;
2011
2011
  /** initializes database */
2012
2012
  async init() {
@@ -2044,17 +2044,17 @@ export default class Blog {
2044
2044
  const match = publicCSS.match(
2045
2045
  /\/\* source-hash: ([a-f0-9]{64}) \*\//,
2046
2046
  );
2047
- <span class="missing-if-branch" title="else path not taken" >E</span>if (match) publicHash = match[1];
2048
- })
2047
+ if (match) publicHash = match[1];
2048
+ })<span class="missing-if-branch" title="else path not taken" >E</span>
2049
2049
  .catch((err) =&gt; console.error(err)), // public/styles.min.css doesn't exist, will be created.
2050
2050
  fs.promises
2051
2051
  .readFile(srcStylePath, "utf8")
2052
2052
  .then((content) =&gt; {
2053
2053
  srcStyles = content;
2054
2054
  })
2055
- .catch(<span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; {
2056
- <span class="cstat-no" title="statement not covered" > if (err.code !== "ENOENT") <span class="cstat-no" title="statement not covered" >console.error(err);</span></span>
2057
- }), // ignore if src/styles.css doesn't exist
2055
+ .catch((err) =&gt; {
2056
+ if (e<span class="fstat-no" title="function not covered" >rr</span>.code !== "ENOENT") console.error(err);
2057
+ })<span class="cstat-no" title="statement not covered" >, // ignore if src/styles.c<span class="cstat-no" title="statement not covered" >ss doesn't exist</span></span>
2058
2058
  ]);
2059
2059
  &nbsp;
2060
2060
  const combinedStyles = this.#styles + srcStyles;
@@ -2075,8 +2075,8 @@ export default class Blog {
2075
2075
  finalStyles + `\n/* source-hash: ${srcHash} */`,
2076
2076
  );
2077
2077
  } catch (err) {
2078
- <span class="cstat-no" title="statement not covered" > console.error("Failed to write styles to public folder:", err);</span>
2079
- }
2078
+ console.error("Failed to write styles to public folder:", err);
2079
+ }<span class="cstat-no" title="statement not covered" ></span>
2080
2080
  }
2081
2081
  }
2082
2082
  &nbsp;
@@ -2092,8 +2092,8 @@ export default class Blog {
2092
2092
  } else {
2093
2093
  console.log(`database: ${this.database.type}`);
2094
2094
  if (!this.#databaseModel) {
2095
- <span class="missing-if-branch" title="else path not taken" >E</span>if (this.database.type === "file") {
2096
- const adapter = new FileAdapter(this.database);
2095
+ if (this.database.type === "file") {
2096
+ <span class="missing-if-branch" title="else path not taken" >E</span> const adapter = new FileAdapter(this.database);
2097
2097
  this.#databaseModel = new DatabaseModel(adapter);
2098
2098
  }
2099
2099
  }
@@ -2131,9 +2131,9 @@ export default class Blog {
2131
2131
  ),
2132
2132
  );
2133
2133
  }
2134
- <span class="missing-if-branch" title="if path not taken" >I</span>if (this.reloadStylesOnGET)
2135
- <span class="cstat-no" title="statement not covered" > console.log("reload scripts and styles on GET-Request");</span>
2136
- let title = "";
2134
+ if (this.reloadStylesOnGET)
2135
+ <span class="missing-if-branch" title="if path not taken" >I</span> console.log("reload scripts and styles on GET-Request");
2136
+ le<span class="cstat-no" title="statement not covered" >t title = "";</span>
2137
2137
  if (this.#title != null &amp;&amp; this.#title.length &gt; 0)
2138
2138
  title = this.#title; // use blog title if set
2139
2139
  else title = dbTitle; // use title from the database
@@ -2158,8 +2158,8 @@ export default class Blog {
2158
2158
  const title = params.get("title");
2159
2159
  const content = params.get("content");
2160
2160
  &nbsp;
2161
- <span class="missing-if-branch" title="else path not taken" >E</span>if (title &amp;&amp; content) {
2162
- const newArticleData = { title, content };
2161
+ if (title &amp;&amp; content) {
2162
+ <span class="missing-if-branch" title="else path not taken" >E</span> const newArticleData = { title, content };
2163
2163
  try {
2164
2164
  // Save the new article to the database via the ApiServer
2165
2165
  const promises = [];
@@ -2174,8 +2174,8 @@ export default class Blog {
2174
2174
  this.#articles.remove(1); // "Sample Entry #1"
2175
2175
  this.#articles.remove(2); // "Sample Entry #2"
2176
2176
  } catch (error) {
2177
- <span class="cstat-no" title="statement not covered" > console.error("Failed to post new article to API:", error);</span>
2178
- }
2177
+ console.error("Failed to post new article to API:", error);
2178
+ }<span class="cstat-no" title="statement not covered" ></span>
2179
2179
  }
2180
2180
  &nbsp;
2181
2181
  res.writeHead(303, { Location: "/" });
@@ -2184,8 +2184,8 @@ export default class Blog {
2184
2184
  }
2185
2185
  &nbsp;
2186
2186
  /** start a http server with default port 8080 */
2187
- async startServer(port = <span class="branch-0 cbranch-no" title="branch not covered" >8080)</span> {
2188
- await this.init();
2187
+ async startServer(port = 8080) {
2188
+ await this.init();<span class="branch-0 cbranch-no" title="branch not covered" ></span>
2189
2189
  &nbsp;
2190
2190
  const server = http.createServer(async (req, res) =&gt; {
2191
2191
  //debug("query %s", req.url);
@@ -2197,82 +2197,82 @@ export default class Blog {
2197
2197
  &nbsp;
2198
2198
  // Web Page Routes
2199
2199
  if (req.url === "/login") {
2200
- <span class="missing-if-branch" title="if path not taken" >I</span>if (req.method === "GET") {
2201
- <span class="cstat-no" title="statement not covered" > res.writeHead(200, { "Content-Type": "text/html" });</span>
2200
+ if (req.method === "GET") {
2201
+ <span class="missing-if-branch" title="if path not taken" >I</span> res.writeHead(200, { "Content-Type": "text/html" });
2202
2202
  <span class="cstat-no" title="statement not covered" > res.end(`${header("My Blog")}</span>
2203
- &lt;body&gt;
2203
+ &lt;body&gt;<span class="cstat-no" title="statement not covered" ></span>
2204
2204
  &lt;h1&gt;Login&lt;/h1&gt;&lt;div class="box"&gt;&lt;form method="POST"&gt;
2205
2205
  &lt;input type="password" name="password" placeholder="Password" /&gt;
2206
2206
  &lt;button style="margin: 2px;"&gt;Login&lt;/button&gt;&lt;/form&gt;&lt;/div&gt;
2207
2207
  &lt;/body&gt;&lt;/html&gt;`);
2208
- <span class="cstat-no" title="statement not covered" > return;</span>
2209
- } else <span class="missing-if-branch" title="else path not taken" >E</span>if (req.method === "POST") {
2210
- await this.#handleLogin(req, res);
2208
+ return;
2209
+ } <span class="cstat-no" title="statement not covered" >else if </span>(req.method === "POST") {
2210
+ await<span class="missing-if-branch" title="else path not taken" >E</span> this.#handleLogin(req, res);
2211
2211
  return;
2212
2212
  }
2213
2213
  }
2214
2214
  &nbsp;
2215
- <span class="missing-if-branch" title="if path not taken" >I</span>if (req.url === "/logout") {
2216
- <span class="cstat-no" title="statement not covered" > this.#handleLogout(req, res);</span>
2215
+ if (req.url === "/logout") {
2216
+ <span class="missing-if-branch" title="if path not taken" >I</span> this.#handleLogout(req, res);
2217
2217
  <span class="cstat-no" title="statement not covered" > return;</span>
2218
- }
2218
+ }<span class="cstat-no" title="statement not covered" ></span>
2219
2219
  // POST new article
2220
- <span class="missing-if-branch" title="if path not taken" >I</span>if (req.method === "POST" &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >req.url === "/")</span> {
2221
- <span class="cstat-no" title="statement not covered" > if (!this.#isAuthenticated(req)) {</span>
2220
+ if (req.method === "POST" &amp;&amp; req.url === "/") {
2221
+ <span class="missing-if-branch" title="if path not taken" >I</span> if (!this.#isAuthenticated(<span class="branch-1 cbranch-no" title="branch not covered" >req)) {</span>
2222
2222
  <span class="cstat-no" title="statement not covered" > res.writeHead(403, { "Content-Type": "text/plain" });</span>
2223
2223
  <span class="cstat-no" title="statement not covered" > res.end("Forbidden");</span>
2224
2224
  <span class="cstat-no" title="statement not covered" > return;</span>
2225
- }
2226
- <span class="cstat-no" title="statement not covered" > await this.postArticle(req, res);</span>
2227
- // GET artciles
2225
+ }<span class="cstat-no" title="statement not covered" ></span>
2226
+ await this.postArticle(req, res);
2227
+ <span class="cstat-no" title="statement not covered" > // GET artciles</span>
2228
2228
  } else if (req.method === "GET" &amp;&amp; req.url === "/") {
2229
2229
  // load articles
2230
2230
  &nbsp;
2231
2231
  // reload styles and scripts on (every) request
2232
- <span class="missing-if-branch" title="if path not taken" >I</span>if (this.reloadStylesOnGET) {
2233
- <span class="cstat-no" title="statement not covered" > if (this.#stylesheetPath != null &amp;&amp; this.compilestyle) {</span>
2232
+ if (this.reloadStylesOnGET) {
2233
+ <span class="missing-if-branch" title="if path not taken" >I</span> if (this.#stylesheetPath != null &amp;&amp; this.compilestyle) {
2234
2234
  <span class="cstat-no" title="statement not covered" > await this.#processStylesheets(this.#stylesheetPath);</span>
2235
- }
2236
- const __filename = <span class="cstat-no" title="statement not covered" >fileURLToPath(import.meta.url);</span>
2237
- const __dirname = <span class="cstat-no" title="statement not covered" >path.dirname(__filename);</span>
2238
- const srcScriptPath = <span class="cstat-no" title="statement not covered" >path.join(__dirname, "src", "fetchData.js");</span>
2239
- <span class="cstat-no" title="statement not covered" > await this.#processScripts(srcScriptPath);</span>
2240
- }
2235
+ }<span class="cstat-no" title="statement not covered" ></span>
2236
+ const __filename = fileURLToPath(import.meta.url);
2237
+ const __dirname = p<span class="cstat-no" title="statement not covered" >ath.dirname(__filename);</span>
2238
+ const srcScriptPat<span class="cstat-no" title="statement not covered" >h = path.join(__dirname, </span>"src", "fetchData.js");
2239
+ await this.#processScr<span class="cstat-no" title="statement not covered" >ipts(srcScriptPath);</span>
2240
+ }<span class="cstat-no" title="statement not covered" ></span>
2241
2241
  &nbsp;
2242
2242
  let loggedin = false;
2243
2243
  if (!this.#isAuthenticated(req)) {
2244
2244
  // login
2245
2245
  loggedin = false;
2246
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
2247
- // logout
2248
- <span class="cstat-no" title="statement not covered" > loggedin = true;</span>
2249
- }
2246
+ } else {
2247
+ // lo<span class="missing-if-branch" title="else path not taken" >E</span>gout
2248
+ loggedin = true;
2249
+ }<span class="cstat-no" title="statement not covered" ></span>
2250
2250
  &nbsp;
2251
2251
  try {
2252
2252
  const html = await this.toHTML(loggedin); // render this blog to HTML
2253
2253
  res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8" });
2254
2254
  res.end(html);
2255
2255
  } catch (err) {
2256
- <span class="cstat-no" title="statement not covered" > console.error(err);</span>
2257
- <span class="cstat-no" title="statement not covered" > res.writeHead(500, { "Content-Type": "text/plain" });</span>
2256
+ console.error(err);
2257
+ <span class="cstat-no" title="statement not covered" > res.writeHead(500, {</span> "Content-Type": "text/plain" });
2258
2258
  <span class="cstat-no" title="statement not covered" > res.end("Internal Server Error");</span>
2259
- }
2260
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
2261
- // Try to serve static files from public folder
2262
- <span class="cstat-no" title="statement not covered" > try {</span>
2263
- const publicDir = <span class="cstat-no" title="statement not covered" >path.join(process.cwd(), "public");</span>
2259
+ }<span class="cstat-no" title="statement not covered" ></span>
2260
+ } else {
2261
+ // Tr<span class="missing-if-branch" title="else path not taken" >E</span>y to serve static files from public folder
2262
+ try {
2263
+ <span class="cstat-no" title="statement not covered" > const publicDir = path.join(process.cwd(), "public");</span>
2264
2264
  const parsedUrl = <span class="cstat-no" title="statement not covered" >new URL(</span>
2265
- req.url,
2265
+ req.url,<span class="cstat-no" title="statement not covered" ></span>
2266
2266
  `http://${req.headers.host || "localhost"}`,
2267
2267
  );
2268
- const filePath = <span class="cstat-no" title="statement not covered" >path.join(publicDir, parsedUrl.pathname);</span>
2269
- &nbsp;
2270
- <span class="cstat-no" title="statement not covered" > if (filePath.startsWith(publicDir)) {</span>
2271
- const stats = <span class="cstat-no" title="statement not covered" >await fs.promises.stat(filePath);</span>
2272
- <span class="cstat-no" title="statement not covered" > if (stats.isFile()) {</span>
2273
- const ext = <span class="cstat-no" title="statement not covered" >path.extname(filePath).toLowerCase();</span>
2274
- const mimeTypes = <span class="cstat-no" title="statement not covered" >{</span>
2275
- ".html": "text/html",
2268
+ const filePath = path.join(publicDir, parsedUrl.pathname);
2269
+ <span class="cstat-no" title="statement not covered" ></span>
2270
+ if (filePath.startsWith(publicDir)) {
2271
+ <span class="cstat-no" title="statement not covered" > const stats = await fs.promises.stat(filePath);</span>
2272
+ if (stats.isFi<span class="cstat-no" title="statement not covered" >le()) {</span>
2273
+ <span class="cstat-no" title="statement not covered" > const ext = path.extname(filePath).toLowerCase();</span>
2274
+ const mimeTy<span class="cstat-no" title="statement not covered" >pes = {</span>
2275
+ ".html": "text/h<span class="cstat-no" title="statement not covered" >tml",</span>
2276
2276
  ".js": "text/javascript",
2277
2277
  ".css": "text/css",
2278
2278
  ".json": "application/json",
@@ -2282,31 +2282,31 @@ export default class Blog {
2282
2282
  ".svg": "image/svg+xml",
2283
2283
  ".ico": "image/x-icon",
2284
2284
  };
2285
- const contentType = <span class="cstat-no" title="statement not covered" >mimeTypes[ext] || "application/octet-stream";</span>
2286
- <span class="cstat-no" title="statement not covered" > res.writeHead(200, { "Content-Type": contentType });</span>
2285
+ const contentType = mimeTypes[ext] || "application/octet-stream";
2286
+ res.writeHead(200, {<span class="cstat-no" title="statement not covered" > "Content-Type": contentType });</span>
2287
2287
  <span class="cstat-no" title="statement not covered" > fs.createReadStream(filePath).pipe(res);</span>
2288
2288
  <span class="cstat-no" title="statement not covered" > return;</span>
2289
- }
2289
+ }<span class="cstat-no" title="statement not covered" ></span>
2290
2290
  }
2291
2291
  } catch (err) {
2292
2292
  // ENOENT (file not found) is expected, leading to a 404.
2293
2293
  // Only log other, unexpected errors.
2294
- <span class="cstat-no" title="statement not covered" > if (err.code !== "ENOENT") {</span>
2294
+ if (err.code !== "ENOENT") {
2295
2295
  <span class="cstat-no" title="statement not covered" > console.error(err);</span>
2296
- }
2296
+ }<span class="cstat-no" title="statement not covered" ></span>
2297
2297
  }
2298
2298
  &nbsp;
2299
2299
  // Error 404
2300
- <span class="cstat-no" title="statement not covered" > res.writeHead(404, { "Content-Type": "application/json" });</span>
2300
+ res.writeHead(404, { "Content-Type": "application/json" });
2301
2301
  <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ message: "Not Found" }));</span>
2302
- }
2302
+ }<span class="cstat-no" title="statement not covered" ></span>
2303
2303
  });
2304
2304
  &nbsp;
2305
2305
  this.#server = server;
2306
2306
  &nbsp;
2307
2307
  return new Promise((resolve, reject) =&gt; {
2308
- const errorHandler = <span class="fstat-no" title="function not covered" >(e</span>rr) =&gt; <span class="cstat-no" title="statement not covered" >reject(err);</span>
2309
- this.#server.once("error", errorHandler);
2308
+ const errorHandler = (err) =&gt; reject(err);
2309
+ this.#server.once("er<span class="fstat-no" title="function not covered" >ro</span>r", err<span class="cstat-no" title="statement not covered" >orHandler);</span>
2310
2310
  this.#server.listen(port, "0.0.0.0", () =&gt; {
2311
2311
  // &lt;-- for docker 0.0.0.0, localhost 127.0.0.1
2312
2312
  this.#server.removeListener("error", errorHandler);
@@ -2321,8 +2321,8 @@ export default class Blog {
2321
2321
  if (this.#server) {
2322
2322
  // if server is running
2323
2323
  this.#server.close((err) =&gt; {
2324
- <span class="missing-if-branch" title="if path not taken" >I</span>if (err &amp;&amp; <span class="branch-1 cbranch-no" title="branch not covered" >err.code !== "ERR_SERVER_NOT_RUNNING")</span> <span class="cstat-no" title="statement not covered" >return reject(err);</span>
2325
- console.log("Server closed.");
2324
+ if (err &amp;&amp; err.code !== "ERR_SERVER_NOT_RUNNING") return reject(err);
2325
+ <span class="missing-if-branch" title="if path not taken" >I</span>console.log<span class="branch-1 cbranch-no" title="branch not covered" >("Server closed.");<span class="cstat-no" title="statement not covered" ></span></span>
2326
2326
  resolve();
2327
2327
  });
2328
2328
  } else {
@@ -2337,13 +2337,13 @@ export default class Blog {
2337
2337
  debug("applyBlogData");
2338
2338
  if (this.#articles.storage.clear) {
2339
2339
  this.#articles.storage.clear();
2340
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
2341
- //this.#articles.setDataModel(this.#makeDataModel()); // Fallback if clear() isn't implemented
2340
+ } else {
2341
+ //thi<span class="missing-if-branch" title="else path not taken" >E</span>s.#articles.setDataModel(this.#makeDataModel()); // Fallback if clear() isn't implemented
2342
2342
  }
2343
2343
  this.#title = data.title;
2344
2344
  // Assuming data contains a title and an array of articles with title and content
2345
- <span class="missing-if-branch" title="else path not taken" >E</span>if (this.#articles &amp;&amp; data.articles &amp;&amp; Array.isArray(data.articles)) {
2346
- if (this.#articles.getStorageName().includes("BinarySearchTree")) {
2345
+ if (this.#articles &amp;&amp; data.articles &amp;&amp; Array.isArray(data.articles)) {
2346
+ <span class="missing-if-branch" title="else path not taken" >E</span> if (this.#articles.getStorageName().includes("BinarySearchTree")) {
2347
2347
  debug("using special insert method for BST");
2348
2348
  const insertBalanced = (start, end) =&gt; {
2349
2349
  if (start &gt; end) return;
@@ -2376,16 +2376,16 @@ export default class Blog {
2376
2376
  &nbsp;
2377
2377
  async #loadFromAPI() {
2378
2378
  const data = await fetchData(this.#apiUrl);
2379
- <span class="missing-if-branch" title="else path not taken" >E</span>if (data) {
2380
- this.#applyBlogData(data);
2379
+ if (data) {
2380
+ <span class="missing-if-branch" title="else path not taken" >E</span> this.#applyBlogData(data);
2381
2381
  }
2382
2382
  }
2383
2383
  &nbsp;
2384
2384
  // controller
2385
2385
  /** everything that happens in /api */
2386
2386
  async #jsonAPI(req, res) {
2387
- const url = new URL(req.url, `http://${req.headers.host || <span class="branch-1 cbranch-no" title="branch not covered" >"localhost"}</span>`);
2388
- const pathname = url.pathname;
2387
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
2388
+ const pathname = url.pathname;<span class="branch-1 cbranch-no" title="branch not covered" ></span>
2389
2389
  &nbsp;
2390
2390
  if (req.method === "GET") {
2391
2391
  if (pathname === "/api" || pathname === "/api/") {
@@ -2396,19 +2396,19 @@ export default class Blog {
2396
2396
  res.end(JSON.stringify(data));
2397
2397
  }
2398
2398
  // Search
2399
- <span class="missing-if-branch" title="if path not taken" >I</span>if (url.searchParams.has("q")) {
2400
- const query = <span class="cstat-no" title="statement not covered" >url.searchParams.get("q");</span>
2401
- <span class="cstat-no" title="statement not covered" > res.writeHead(200, { "Content-Type": "application/json" });</span>
2402
- const results = <span class="cstat-no" title="statement not covered" >this.#articles.search(query);</span>
2403
- <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ articles: results }));</span>
2399
+ if (url.searchParams.has("q")) {
2400
+ <span class="missing-if-branch" title="if path not taken" >I</span> const query = url.searchParams.get("q");
2401
+ res.writeHead(<span class="cstat-no" title="statement not covered" >200, { "Content-Type": "ap</span>plication/json" });
2402
+ <span class="cstat-no" title="statement not covered" > const results = this.#articles.search(query);</span>
2403
+ res.end(JSON.str<span class="cstat-no" title="statement not covered" >ingify({ articles: results })</span>);
2404
2404
  <span class="cstat-no" title="statement not covered" > return;</span>
2405
- }
2405
+ }<span class="cstat-no" title="statement not covered" ></span>
2406
2406
  // GET all blog data
2407
2407
  if (pathname === "/api/articles") {
2408
2408
  // Use 'offset' param as startId (filter) to get items starting at ID
2409
2409
  const pLimit = parseInt(url.searchParams.get("limit"));
2410
- const limit = !isNaN(pLimit) ? <span class="branch-0 cbranch-no" title="branch not covered" >pLimit </span>: null;
2411
- &nbsp;
2410
+ const limit = !isNaN(pLimit) ? pLimit : null;
2411
+ <span class="branch-0 cbranch-no" title="branch not covered" ></span>
2412
2412
  const start = url.searchParams.get("startdate");
2413
2413
  const end = url.searchParams.get("enddate");
2414
2414
  //const parsedStart = parseDateParam(qStartdate);
@@ -2446,41 +2446,41 @@ export default class Blog {
2446
2446
  // extern
2447
2447
  res.writeHead(201, { "Content-Type": "application/json" });
2448
2448
  res.end(JSON.stringify(newArticle));
2449
- } else <span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>if (req.method === "DELETE" &amp;&amp; pathname === "/api/articles") {</span>
2450
- <span class="cstat-no" title="statement not covered" > if (!this.#isAuthenticated(req)) {</span>
2449
+ } else if (req.method === "DELETE" &amp;&amp; pathname === "/api/articles") {
2450
+ if (!<span class="cstat-no" title="statement not covered" ><span class="missing-if-branch" title="else path not taken" >E</span>this.#isAuthenticated(req)) {</span>
2451
2451
  <span class="cstat-no" title="statement not covered" > res.writeHead(403, { "Content-Type": "application/json" });</span>
2452
2452
  <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ error: "Forbidden" }));</span>
2453
2453
  <span class="cstat-no" title="statement not covered" > return;</span>
2454
- }
2455
- const id = <span class="cstat-no" title="statement not covered" >url.searchParams.get("id");</span>
2456
- <span class="cstat-no" title="statement not covered" > if (id) {</span>
2454
+ }<span class="cstat-no" title="statement not covered" ></span>
2455
+ const id = url.searchParams.get("id");
2456
+ if (id) {<span class="cstat-no" title="statement not covered" ></span>
2457
2457
  <span class="cstat-no" title="statement not covered" > this.#articles.remove(parseInt(id));</span>
2458
2458
  <span class="cstat-no" title="statement not covered" > if (this.#databaseModel) {</span>
2459
2459
  <span class="cstat-no" title="statement not covered" > await this.#databaseModel.remove(parseInt(id));</span>
2460
- }
2461
- <span class="cstat-no" title="statement not covered" > res.writeHead(200, { "Content-Type": "application/json" });</span>
2460
+ }<span class="cstat-no" title="statement not covered" ></span>
2461
+ res.writeHead(200, { "Content-Type": "application/json" });
2462
2462
  <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ status: "deleted", id }));</span>
2463
- }
2464
- } else <span class="cstat-no" title="statement not covered" >if (req.method === "PUT" &amp;&amp; pathname === "/api/articles") {</span>
2465
- <span class="cstat-no" title="statement not covered" > if (!this.#isAuthenticated(req)) {</span>
2463
+ }<span class="cstat-no" title="statement not covered" ></span>
2464
+ } else if (req.method === "PUT" &amp;&amp; pathname === "/api/articles") {
2465
+ if (!<span class="cstat-no" title="statement not covered" >this.#isAuthenticated(req)) {</span>
2466
2466
  <span class="cstat-no" title="statement not covered" > res.writeHead(403, { "Content-Type": "application/json" });</span>
2467
2467
  <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ error: "Forbidden" }));</span>
2468
2468
  <span class="cstat-no" title="statement not covered" > return;</span>
2469
- }
2470
- const id = <span class="cstat-no" title="statement not covered" >url.searchParams.get("id");</span>
2471
- const body = <span class="cstat-no" title="statement not covered" >await new Promise(<span class="fstat-no" title="function not covered" >(r</span>esolve) =&gt; {</span>
2472
- let data = <span class="cstat-no" title="statement not covered" >"";</span>
2473
- <span class="cstat-no" title="statement not covered" > req.on("data", <span class="fstat-no" title="function not covered" >(c</span>hunk) =&gt; (<span class="cstat-no" title="statement not covered" >data += chunk)</span>);</span>
2474
- <span class="cstat-no" title="statement not covered" > req.on("end", <span class="fstat-no" title="function not covered" >()</span> =&gt; <span class="cstat-no" title="statement not covered" >resolve(data))</span>;</span>
2475
- });
2476
- const { title, content } = <span class="cstat-no" title="statement not covered" >JSON.parse(body);</span>
2477
- <span class="cstat-no" title="statement not covered" > this.#articles.update(parseInt(id), title, content);</span>
2469
+ }<span class="cstat-no" title="statement not covered" ></span>
2470
+ const id = url.searchParams.get("id");
2471
+ const body <span class="cstat-no" title="statement not covered" >= await new Promise((resolv</span>e) =&gt; {
2472
+ let data = <span class="cstat-no" title="statement not covered" >"";<span class="fstat-no" title="function not covered" ></span></span>
2473
+ req.on("dat<span class="cstat-no" title="statement not covered" >a",</span> (chunk) =&gt; (data += chunk));
2474
+ <span class="cstat-no" title="statement not covered" > req.on("end", (<span class="fstat-no" title="function not covered" >) </span>=&gt; resolve<span class="cstat-no" title="statement not covered" >(data));</span></span>
2475
+ })<span class="cstat-no" title="statement not covered" >;<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" ></span></span></span>
2476
+ const { title, content } = JSON.parse(body);
2477
+ this.#articles.update(parse<span class="cstat-no" title="statement not covered" >Int(id), title, c</span>ontent);
2478
2478
  <span class="cstat-no" title="statement not covered" > if (this.#databaseModel) {</span>
2479
2479
  <span class="cstat-no" title="statement not covered" > await this.#databaseModel.update(parseInt(id), { title, content });</span>
2480
- }
2481
- <span class="cstat-no" title="statement not covered" > res.writeHead(200, { "Content-Type": "application/json" });</span>
2480
+ }<span class="cstat-no" title="statement not covered" ></span>
2481
+ res.writeHead(200, { "Content-Type": "application/json" });
2482
2482
  <span class="cstat-no" title="statement not covered" > res.end(JSON.stringify({ status: "updated", id }));</span>
2483
- }
2483
+ }<span class="cstat-no" title="statement not covered" ></span>
2484
2484
  }
2485
2485
  &nbsp;
2486
2486
  /** set external API */
@@ -2512,8 +2512,8 @@ export default class Blog {
2512
2512
  login: "",
2513
2513
  };
2514
2514
  &nbsp;
2515
- <span class="missing-if-branch" title="if path not taken" >I</span>if (loggedin) <span class="cstat-no" title="statement not covered" >data.login = `&lt;a href="/logout"&gt;logout&lt;/a&gt;`;</span>
2516
- else data.login = `&lt;a href="/login"&gt;login&lt;/a&gt;`;
2515
+ if (loggedin) data.login = `&lt;a href="/logout"&gt;logout&lt;/a&gt;`;
2516
+ <span class="missing-if-branch" title="if path not taken" >I</span>else data.logi<span class="cstat-no" title="statement not covered" >n = `&lt;a href="/login"&gt;login&lt;/a&gt;`;</span>
2517
2517
  &nbsp;
2518
2518
  //debug("typeof data: %o", typeof data);
2519
2519
  //debug("typeof data.articles: %o", typeof data.articles);
@@ -2522,9 +2522,9 @@ export default class Blog {
2522
2522
  debug("first article: ");
2523
2523
  debug("%d %s %s", article.id, article.title, article.getContentVeryShort());
2524
2524
  const html = formatHTML(data);
2525
- <span class="missing-if-branch" title="else path not taken" >E</span>if (validate(html)) return html;
2526
- <span class="cstat-no" title="statement not covered" > throw new Error("Error. Invalid HTML!");</span>
2527
- }
2525
+ if (validate(html)) return html;
2526
+ <span class="missing-if-branch" title="else path not taken" >E</span>throw new Error("Error. Invalid HTML!");
2527
+ }<span class="cstat-no" title="statement not covered" ></span>
2528
2528
  &nbsp;
2529
2529
  /**
2530
2530
  * read files, compare checksums, compile and write to public/styles.min.css
@@ -2551,8 +2551,8 @@ export default class Blog {
2551
2551
  const fileData = await Promise.all(
2552
2552
  styleFiles.sort().map(async (f) =&gt; {
2553
2553
  const content = await fs.promises.readFile(f, "utf-8");
2554
- <span class="missing-if-branch" title="if path not taken" >I</span>if (content == "") <span class="cstat-no" title="statement not covered" >throw new Error("Invalid Filepath or empty file!");</span>
2555
- return { path: f, content };
2554
+ if (content == "") throw new Error("Invalid Filepath or empty file!");
2555
+ <span class="missing-if-branch" title="if path not taken" >I</span>return { path: f, c<span class="cstat-no" title="statement not covered" >ontent };</span>
2556
2556
  }),
2557
2557
  );
2558
2558
  &nbsp;
@@ -2584,9 +2584,9 @@ export default class Blog {
2584
2584
  path.join(publicDir, "styles.min.css"),
2585
2585
  this.compiledStyles + `\n/* source-hash: ${currentHash} */`,
2586
2586
  );
2587
- } else <span class="missing-if-branch" title="else path not taken" >E</span>{
2588
- <span class="cstat-no" title="statement not covered" > console.log("styles are up-to-date");</span>
2589
- }
2587
+ } else {
2588
+ conso<span class="missing-if-branch" title="else path not taken" >E</span>le.log("styles are up-to-date");
2589
+ }<span class="cstat-no" title="statement not covered" ></span>
2590
2590
  }
2591
2591
  }
2592
2592
  &nbsp;
@@ -2596,18 +2596,18 @@ export default class Blog {
2596
2596
  */
2597
2597
  async #processScripts(files) {
2598
2598
  // Normalize input to array
2599
- const fileList = Array.isArray(files) ? <span class="branch-0 cbranch-no" title="branch not covered" >files </span>: [files];
2600
- const scriptFiles = fileList.filter(
2599
+ const fileList = Array.isArray(files) ? files : [files];
2600
+ const scriptFiles = fileList.filter(<span class="branch-0 cbranch-no" title="branch not covered" ></span>
2601
2601
  (f) =&gt;
2602
2602
  typeof f === "string" &amp;&amp; f.endsWith(".js") &amp;&amp; !f.endsWith(".min.js"),
2603
2603
  );
2604
2604
  &nbsp;
2605
- <span class="missing-if-branch" title="else path not taken" >E</span>if (scriptFiles.length &gt; 0) {
2606
- const fileData = await Promise.all(
2605
+ if (scriptFiles.length &gt; 0) {
2606
+ <span class="missing-if-branch" title="else path not taken" >E</span> const fileData = await Promise.all(
2607
2607
  scriptFiles.map(async (f) =&gt; {
2608
2608
  const content = await fs.promises.readFile(f, "utf-8");
2609
- <span class="missing-if-branch" title="if path not taken" >I</span>if (content == "") <span class="cstat-no" title="statement not covered" >throw new Error("Invalid Filepath or empty file!");</span>
2610
- return { path: f, content };
2609
+ if (content == "") throw new Error("Invalid Filepath or empty file!");
2610
+ <span class="missing-if-branch" title="if path not taken" >I</span>return { path: f, c<span class="cstat-no" title="statement not covered" >ontent };</span>
2611
2611
  }),
2612
2612
  );
2613
2613
  &nbsp;
@@ -2622,30 +2622,30 @@ export default class Blog {
2622
2622
  )
2623
2623
  .digest("hex");
2624
2624
  &nbsp;
2625
- <span class="missing-if-branch" title="else path not taken" >E</span>if (!this.#scriptsHash) {
2626
- try {
2625
+ if (!this.#scriptsHash) {
2626
+ <span class="missing-if-branch" title="else path not taken" >E</span> try {
2627
2627
  const publicDir = path.join(process.cwd(), "public");
2628
2628
  const existing = await fs.promises.readFile(
2629
2629
  path.join(publicDir, "scripts.min.js"),
2630
2630
  "utf-8",
2631
2631
  );
2632
2632
  const match = existing.match(/\/\* source-hash: ([a-f0-9]{64}) \*\//);
2633
- <span class="missing-if-branch" title="else path not taken" >E</span>if (match) this.#scriptsHash = match[1];
2634
- } catch (err) {
2633
+ if (match) this.#scriptsHash = match[1];
2634
+ } <span class="missing-if-branch" title="else path not taken" >E</span>catch (err) {
2635
2635
  // ignore
2636
2636
  }
2637
2637
  }
2638
2638
  &nbsp;
2639
- <span class="missing-if-branch" title="if path not taken" >I</span>if (currentHash !== this.#scriptsHash) {
2640
- <span class="cstat-no" title="statement not covered" > console.log("Script assets have changed. Recompiling...");</span>
2639
+ if (currentHash !== this.#scriptsHash) {
2640
+ <span class="missing-if-branch" title="if path not taken" >I</span> console.log("Script assets have changed. Recompiling...");
2641
2641
  <span class="cstat-no" title="statement not covered" > this.#scriptsHash = currentHash;</span>
2642
- &nbsp;
2643
- const compiledScripts = <span class="cstat-no" title="statement not covered" >await compileScripts(fileData);</span>
2644
- &nbsp;
2645
- const publicDir = <span class="cstat-no" title="statement not covered" >path.join(process.cwd(), "public");</span>
2646
- <span class="cstat-no" title="statement not covered" > await fs.promises.mkdir(publicDir, { recursive: true });</span>
2642
+ <span class="cstat-no" title="statement not covered" ></span>
2643
+ const compiledScripts = await compileScripts(fileData);
2644
+ <span class="cstat-no" title="statement not covered" ></span>
2645
+ const publicDir = path.join(process.cwd(), "public");
2646
+ await fs.promises.<span class="cstat-no" title="statement not covered" >mkdir(publicDir, { recursive: true </span>});
2647
2647
  <span class="cstat-no" title="statement not covered" > await fs.promises.writeFile(</span>
2648
- path.join(publicDir, "scripts.min.js"),
2648
+ <span class="cstat-no" title="statement not covered" > path.join(publicDir, "scripts.min.js"),</span>
2649
2649
  compiledScripts + `\n/* source-hash: ${currentHash} */`,
2650
2650
  );
2651
2651
  }
@@ -2659,7 +2659,7 @@ export default class Blog {
2659
2659
  <div class='footer quiet pad2 space-top1 center small'>
2660
2660
  Code coverage generated by
2661
2661
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2662
- at 2026-01-18T09:21:18.383Z
2662
+ at 2026-01-18T09:48:57.520Z
2663
2663
  </div>
2664
2664
  <script src="../prettify.js"></script>
2665
2665
  <script>