@networkpro/web 1.7.9 → 1.9.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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "sideEffects": [
5
5
  "./.netlify/shims.js"
6
6
  ],
7
- "version": "1.7.9",
7
+ "version": "1.9.0",
8
8
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
9
9
  "keywords": [
10
10
  "advisory",
@@ -49,7 +49,7 @@
49
49
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
50
50
  "check:env": "node scripts/checkEnv.js",
51
51
  "check:node": "node scripts/checkNode.js",
52
- "checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check && npm run audit:scripts",
52
+ "checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check",
53
53
  "verify": "npm run checkout",
54
54
  "delete": "rm -rf build .svelte-kit node_modules package-lock.json",
55
55
  "clean": "npm run delete && npm cache clean --force && npm install",
@@ -71,16 +71,16 @@
71
71
  "format:fix": "prettier --write .",
72
72
  "lhci": "lhci",
73
73
  "lhci:run": "lhci autorun --config=.lighthouserc.cjs",
74
- "audit:scripts": "node scripts/auditScripts.js",
74
+ "audit:coverage": "vitest run tests/internal/auditCoverage.test.js",
75
75
  "head:flatten": "node scripts/flattenHeaders.js",
76
76
  "head:validate": "node scripts/validateHeaders.js",
77
77
  "postinstall": "npm run check:node"
78
78
  },
79
79
  "dependencies": {
80
- "nodemailer": "^7.0.3",
80
+ "dompurify": "^3.2.6",
81
81
  "posthog-js": "^1.249.0",
82
82
  "semver": "^7.7.2",
83
- "svelte": "5.33.10"
83
+ "svelte": "5.33.11"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@eslint/compat": "^1.2.9",
@@ -97,7 +97,7 @@
97
97
  "browserslist": "^4.25.0",
98
98
  "eslint": "^9.28.0",
99
99
  "eslint-config-prettier": "^10.1.5",
100
- "eslint-plugin-jsdoc": "^50.6.17",
100
+ "eslint-plugin-jsdoc": "^50.7.0",
101
101
  "eslint-plugin-svelte": "^3.9.0",
102
102
  "globals": "^16.2.0",
103
103
  "jsdom": "^26.1.0",
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* ==========================================================================
4
+ scripts/generateTest.js
5
+
6
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
7
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
8
+ This file is part of Network Pro.
9
+ ========================================================================== */
10
+
11
+ /**
12
+ * @file generateTest.js
13
+ * @description Auto-generates a *.test.js scaffold for utilities and
14
+ * components.
15
+ * @module scripts
16
+ * @author SunDevil311
17
+ * @updated 2025-06-01
18
+ */
19
+
20
+ import fs from "fs";
21
+ import path from "path";
22
+
23
+ const [, , targetFile] = process.argv;
24
+
25
+ if (!targetFile) {
26
+ console.error("Usage: node generateTest.js <path/to/yourFile.js>");
27
+ process.exit(1);
28
+ }
29
+
30
+ const absolutePath = path.resolve(targetFile);
31
+ const parsed = path.parse(absolutePath);
32
+
33
+ const testFileName = `${parsed.name}.test.js`;
34
+ const testFilePath = path.join(
35
+ parsed.dir.replace("src", "tests/unit"),
36
+ testFileName,
37
+ );
38
+
39
+ // Example scaffold content
40
+ const scaffold = `/**
41
+ * Unit tests for ${parsed.base}
42
+ */
43
+
44
+ import { describe, it, expect } from "vitest";
45
+ import * as Module from "${path.relative(path.dirname(testFilePath), absolutePath).replace(/\\/g, "/")}";
46
+
47
+ describe("${parsed.name}", () => {
48
+ it("should have tests", () => {
49
+ expect(true).toBe(true);
50
+ });
51
+ });
52
+ `;
53
+
54
+ fs.mkdirSync(path.dirname(testFilePath), { recursive: true });
55
+
56
+ if (fs.existsSync(testFilePath)) {
57
+ console.warn(`Test file already exists at: ${testFilePath}`);
58
+ } else {
59
+ fs.writeFileSync(testFilePath, scaffold);
60
+ console.log(`✅ Test scaffold created: ${testFilePath}`);
61
+ }
@@ -25,7 +25,7 @@ export async function handle({ event, resolve }) {
25
25
  console.log("[CSP Debug] ENV_MODE:", process.env.ENV_MODE);
26
26
 
27
27
  // Determine report URI
28
- const reportUri = isProdEnvironment ? "/api/cspReport" : "/api/mock-csp";
28
+ const reportUri = isProdEnvironment ? "/api/csp-report" : "/api/mock-csp";
29
29
 
30
30
  // Construct base policy
31
31
  const cspDirectives = [
@@ -7,8 +7,11 @@ This file is part of Network Pro.
7
7
  ========================================================================== -->
8
8
 
9
9
  <script>
10
+ /* at-html is sanitized by DOMPurify */
10
11
  /* eslint-disable svelte/no-at-html-tags */
11
12
 
13
+ import { onMount } from "svelte";
14
+ import { sanitizeHtml } from "$lib/utils/purify.js";
12
15
  import FossFeatures from "$lib/components/foss/FossFeatures.svelte";
13
16
  // Import directly from $lib by way of image utility
14
17
  import { obtainiumPng, obtainiumWbp } from "$lib";
@@ -26,30 +29,9 @@ This file is part of Network Pro.
26
29
  /** @type {"lazy"} */
27
30
  const loading = "lazy";
28
31
 
29
- /**
30
- * @type {{
31
- * id: string,
32
- * title: string,
33
- * images: {
34
- * webp: string,
35
- * png: string
36
- * },
37
- * imgAlt: string,
38
- * headline: string,
39
- * headlineDescription: string,
40
- * detailsDescription: string,
41
- * features: any[],
42
- * notes: string[],
43
- * links: Array<{
44
- * label?: string,
45
- * href?: string,
46
- * imgAlt?: string,
47
- * downloadText?: string,
48
- * downloadHref?: string,
49
- * hideLabels?: boolean
50
- * }>
51
- * }}
52
- */
32
+ /// <reference path="$lib/types/fossTypes.js" />
33
+
34
+ /** @type {FossItem} */
53
35
  export let fossItem;
54
36
 
55
37
  /**
@@ -58,6 +40,18 @@ This file is part of Network Pro.
58
40
  * @type {boolean}
59
41
  */
60
42
  export let isFirst = false;
43
+
44
+ let safeHeadlineDescription = "";
45
+ let safeDetailsDescription = "";
46
+ /** @type {string[]} */
47
+ let safeNotes = [];
48
+
49
+ // Sanitize everything on mount
50
+ onMount(async () => {
51
+ safeHeadlineDescription = await sanitizeHtml(fossItem.headlineDescription);
52
+ safeDetailsDescription = await sanitizeHtml(fossItem.detailsDescription);
53
+ safeNotes = await Promise.all((fossItem.notes ?? []).map(sanitizeHtml));
54
+ });
61
55
  </script>
62
56
 
63
57
  <!-- BEGIN FOSS ITEMS -->
@@ -88,17 +82,21 @@ This file is part of Network Pro.
88
82
 
89
83
  <h3>{fossItem.headline}</h3>
90
84
 
91
- <!-- Trusted input, from internal CMS -->
92
- {@html fossItem.headlineDescription}
85
+ <!-- Sanitized input from DOMPurify -->
86
+ <div class="headline-description">
87
+ {@html safeHeadlineDescription}
88
+ </div>
93
89
 
94
90
  <FossFeatures features={fossItem.features} />
95
91
 
96
- <!-- Trusted input, from internal CMS -->
97
- {@html fossItem.detailsDescription}
92
+ <!-- Sanitized input from DOMPurify -->
93
+ <div class="details-description">
94
+ {@html safeDetailsDescription}
95
+ </div>
98
96
 
99
- {#each fossItem.notes as note}
97
+ {#each safeNotes as note}
100
98
  <blockquote class="bquote">
101
- <!-- Trusted input, from internal CMS -->
99
+ <!-- Sanitized input from DOMPurify -->
102
100
  {@html note}
103
101
  </blockquote>
104
102
  {/each}
@@ -79,7 +79,7 @@ This file is part of Network Pro.
79
79
  {#if label === "blog"}
80
80
  <br />
81
81
  {:else}
82
- <span class="separator">|</span>
82
+ <span class="goldseparator">|</span>
83
83
  {/if}
84
84
  {/if}
85
85
  {/each}
@@ -77,7 +77,7 @@ This file is part of Network Pro.
77
77
  {#if label === "blog"}
78
78
  <br />
79
79
  {:else}
80
- <span class="separator">|</span>
80
+ <span class="goldseparator">|</span>
81
81
  {/if}
82
82
  {/if}
83
83
  {/each}
@@ -8,6 +8,16 @@ This file is part of Network Pro.
8
8
 
9
9
  <script>
10
10
  import { pgpContact, pgpSupport, vcfSrc } from "$lib";
11
+ import { base } from "$app/paths";
12
+
13
+ // Log the base path to verify its value
14
+ //console.log("Base path:", base);
15
+
16
+ /**
17
+ * URL to the Contact Form route, using the base path
18
+ * @type {string}
19
+ */
20
+ const contactLink = `${base}/contact`;
11
21
 
12
22
  /**
13
23
  * Security attribute for external links
@@ -196,7 +206,7 @@ This file is part of Network Pro.
196
206
  <div class="spacer"></div>
197
207
 
198
208
  <p>
199
- <a href="https://netwk.pro/contact" target="_self">Let's connect</a>
209
+ <a href={contactLink} target="_self">Let's connect</a>
200
210
  to discuss how we can help secure and strengthen your business today.
201
211
  </p>
202
212
 
@@ -236,8 +246,7 @@ This file is part of Network Pro.
236
246
  type="application/pgp-keys"
237
247
  download
238
248
  target={tgtBlank}
239
- >asc
240
- <span class="fas fa-file-arrow-down"></span></a
249
+ >asc &nbsp;<span class="fas fa-file-arrow-down"></span></a
241
250
  ></strong>
242
251
  </p>
243
252
  <p
@@ -262,7 +271,7 @@ This file is part of Network Pro.
262
271
  type="application/pgp-keys"
263
272
  download
264
273
  target={tgtBlank}
265
- >asc <span class="fas fa-file-arrow-down"></span></a
274
+ >asc &nbsp;<span class="fas fa-file-arrow-down"></span></a
266
275
  ></strong>
267
276
  </p>
268
277
  <p
@@ -300,7 +309,8 @@ This file is part of Network Pro.
300
309
  type="text/vcard"
301
310
  download
302
311
  target={tgtBlank}>
303
- <strong>vcf</strong>
312
+ <strong
313
+ >vcf &nbsp;<span class="fas fa-file-arrow-down"></span></strong>
304
314
  </a>
305
315
  </p>
306
316
  </td>
@@ -67,8 +67,7 @@ This file is part of Network Pro.
67
67
  href="https://spdx.dev/learn/handling-license-info"
68
68
  target={constants.targetBlank}>
69
69
  SPDX License Identifier
70
- </a>
71
- : &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
70
+ </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
72
71
  </span>
73
72
  </section>
74
73
 
@@ -218,7 +218,7 @@ This file is part of Network Pro.
218
218
  </ul>
219
219
  {:else if link.id === "cc-by"}
220
220
  <p class={constants.classSmall}>
221
- Formats:
221
+ Download:
222
222
  <a
223
223
  href="/assets/license/CC-BY-4.0.html"
224
224
  download
@@ -321,7 +321,7 @@ This file is part of Network Pro.
321
321
  </code>
322
322
  {:else if link.id === "gnu-gpl"}
323
323
  <p class={constants.classSmall}>
324
- Formats:
324
+ Download:
325
325
  <a
326
326
  href="/assets/license/COPYING.html"
327
327
  download
@@ -439,8 +439,8 @@ footer .container {
439
439
 
440
440
  .center-nav {
441
441
  padding: 5px;
442
- font-size: 0.875rem;
443
- line-height: 1.125rem;
442
+ font-size: 1rem;
443
+ line-height: 1.5rem;
444
444
  text-align: center;
445
445
  }
446
446
 
@@ -497,3 +497,12 @@ footer .container {
497
497
  font-family: inherit; /* Ensure it uses the same font-family as normal text */
498
498
  font-style: normal; /* Remove italic for the description */
499
499
  }
500
+
501
+ .goldseparator {
502
+ margin: 0 0.5rem;
503
+ color: #FFC627;
504
+ }
505
+
506
+ .gold {
507
+ color: #FFC627;
508
+ }
@@ -3,4 +3,4 @@ Copyright © 2025 Network Pro Strategies (Network Pro™)
3
3
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
4
4
  This file is part of Network Pro.
5
5
  ========================================================================== */
6
- html{-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{border-bottom:none;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted buttontext}fieldset{padding:.35em .75em .625em}legend{color:inherit;box-sizing:border-box;white-space:normal;max-width:100%;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}html{color:#222;scroll-behavior:smooth;font-size:1em;line-height:1.4}::-moz-selection{text-shadow:none;background:#191919}::selection{text-shadow:none;background:#191919}hr{border:0;border-top:1px solid #ccc;height:1px;margin:1em 0;padding:0;display:block;overflow:visible}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}body{color:#fafafa;background-color:#191919;margin:10px;font-family:Arial,Helvetica,sans-serif}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.hidden,[hidden]{display:none!important}.visually-hidden{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visually-hidden.focusable:active,.visually-hidden.focusable:focus{clip:auto;width:auto;height:auto;white-space:inherit;margin:0;position:static;overflow:visible}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}@media print{*,:before,:after{color:#000!important;box-shadow:none!important;text-shadow:none!important;background:#fff!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^=\#]:after,a[href^=javascript\:]:after{content:""}pre{white-space:pre-wrap!important}pre,blockquote{page-break-inside:avoid;border:1px solid #999}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.full-width-section{background-position:50%;background-size:cover;width:100%;max-width:1920px;margin:0 auto}.container{max-width:1200px;margin:0 auto;padding:0 12px}.readable{max-width:900px;margin:0 auto}header,footer{width:100%}header .container,footer .container{max-width:1200px;margin:0 auto;padding:20px 12px}.gh{border-collapse:collapse;border-spacing:0;margin:0 auto}.gh td,.gh th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.gh .gh-tcell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.gh,.gh col{width:auto!important}.gh-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.soc{border-collapse:collapse;border-spacing:0;margin:0 auto}.soc td,.soc th{border-collapse:collapse;word-break:normal;padding:8px;overflow:hidden}.soc .soc-fa{text-align:center;vertical-align:middle}@media screen and (width<=767px){.soc,.soc col{width:auto!important}.soc-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.foss{border-collapse:collapse;border-spacing:0}.foss td,.foss th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.foss .foss-cell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.foss,.foss col{width:auto!important}.foss-wrap{-webkit-overflow-scrolling:touch;overflow-x:auto}}.bnav{text-align:center;border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav td,.bnav th{text-align:center;vertical-align:middle;word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav .bnav-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav,.bnav col{width:auto!important}.bnav-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.bnav2{border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav2 td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav2 th{word-break:normal;border-style:none;padding:12px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.bnav2 .bnav2-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav2,.bnav2 col{width:auto!important}.bnav2-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.pgp{border-collapse:collapse;border-spacing:0;margin:0 auto}.pgp td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp th{word-break:normal;border:1px solid #000;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp .pgp-col1{text-align:right;vertical-align:middle;padding-right:1rem}.pgp .pgp-col2{text-align:left;vertical-align:middle;padding-left:1rem}@media screen and (width<=767px){.pgp,.pgp col{width:auto!important}.pgp-wrap{-webkit-overflow-scrolling:touch;margin:2rem 0 auto;overflow-x:auto}}.logo{margin-left:auto;margin-right:auto;display:block}.index-title1{text-align:center;font-style:italic;font-weight:700}.index-title2{letter-spacing:-.015em;text-align:center;font-variant:small-caps;font-size:1.25rem;line-height:1.625rem}.index1{letter-spacing:-.035em;text-align:center;font-style:italic;font-weight:700;line-height:2.125rem}.index2{letter-spacing:-.035em;text-align:center;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.index3{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem}.index4{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem;text-decoration:underline}.subhead{letter-spacing:-.035em;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.bold{font-weight:700}.emphasis{font-style:italic}.uline{text-decoration:underline}.bolditalic{font-style:italic;font-weight:700}.bquote{border-left:3px solid #9e9e9e;margin-left:30px;padding-left:10px;font-style:italic}.small-text{font-size:.75rem;line-height:1.125rem}.large-text-center{text-align:center;font-size:1.25rem;line-height:1.75rem}.prewrap{white-space:pre-wrap;display:block}.hr-styled{width:75%;margin:auto}.center-text{text-align:center}.copyright{text-align:center;font-size:.75rem;line-height:1.125rem}.visited{color:#7f6227}.center-nav{text-align:center;padding:5px;font-size:.875rem;line-height:1.125rem}.block{resize:none;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-size:.75rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:150px;height:150px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}.emoji{margin-right:8px}.headline{margin-bottom:4px;font-style:italic;font-weight:700;display:block}.label{font-family:inherit;font-weight:700}.description{font-family:inherit;font-style:normal;font-weight:400;display:inline}
6
+ html{-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{border-bottom:none;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted buttontext}fieldset{padding:.35em .75em .625em}legend{color:inherit;box-sizing:border-box;white-space:normal;max-width:100%;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}html{color:#222;scroll-behavior:smooth;font-size:1em;line-height:1.4}::-moz-selection{text-shadow:none;background:#191919}::selection{text-shadow:none;background:#191919}hr{border:0;border-top:1px solid #ccc;height:1px;margin:1em 0;padding:0;display:block;overflow:visible}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}body{color:#fafafa;background-color:#191919;margin:10px;font-family:Arial,Helvetica,sans-serif}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.hidden,[hidden]{display:none!important}.visually-hidden{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visually-hidden.focusable:active,.visually-hidden.focusable:focus{clip:auto;width:auto;height:auto;white-space:inherit;margin:0;position:static;overflow:visible}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}@media print{*,:before,:after{color:#000!important;box-shadow:none!important;text-shadow:none!important;background:#fff!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^=\#]:after,a[href^=javascript\:]:after{content:""}pre{white-space:pre-wrap!important}pre,blockquote{page-break-inside:avoid;border:1px solid #999}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.full-width-section{background-position:50%;background-size:cover;width:100%;max-width:1920px;margin:0 auto}.container{max-width:1200px;margin:0 auto;padding:0 12px}.readable{max-width:900px;margin:0 auto}header,footer{width:100%}header .container,footer .container{max-width:1200px;margin:0 auto;padding:20px 12px}.gh{border-collapse:collapse;border-spacing:0;margin:0 auto}.gh td,.gh th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.gh .gh-tcell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.gh,.gh col{width:auto!important}.gh-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.soc{border-collapse:collapse;border-spacing:0;margin:0 auto}.soc td,.soc th{border-collapse:collapse;word-break:normal;padding:8px;overflow:hidden}.soc .soc-fa{text-align:center;vertical-align:middle}@media screen and (width<=767px){.soc,.soc col{width:auto!important}.soc-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.foss{border-collapse:collapse;border-spacing:0}.foss td,.foss th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.foss .foss-cell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.foss,.foss col{width:auto!important}.foss-wrap{-webkit-overflow-scrolling:touch;overflow-x:auto}}.bnav{text-align:center;border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav td,.bnav th{text-align:center;vertical-align:middle;word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav .bnav-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav,.bnav col{width:auto!important}.bnav-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.bnav2{border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav2 td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav2 th{word-break:normal;border-style:none;padding:12px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.bnav2 .bnav2-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav2,.bnav2 col{width:auto!important}.bnav2-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.pgp{border-collapse:collapse;border-spacing:0;margin:0 auto}.pgp td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp th{word-break:normal;border:1px solid #000;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp .pgp-col1{text-align:right;vertical-align:middle;padding-right:1rem}.pgp .pgp-col2{text-align:left;vertical-align:middle;padding-left:1rem}@media screen and (width<=767px){.pgp,.pgp col{width:auto!important}.pgp-wrap{-webkit-overflow-scrolling:touch;margin:2rem 0 auto;overflow-x:auto}}.logo{margin-left:auto;margin-right:auto;display:block}.index-title1{text-align:center;font-style:italic;font-weight:700}.index-title2{letter-spacing:-.015em;text-align:center;font-variant:small-caps;font-size:1.25rem;line-height:1.625rem}.index1{letter-spacing:-.035em;text-align:center;font-style:italic;font-weight:700;line-height:2.125rem}.index2{letter-spacing:-.035em;text-align:center;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.index3{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem}.index4{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem;text-decoration:underline}.subhead{letter-spacing:-.035em;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.bold{font-weight:700}.emphasis{font-style:italic}.uline{text-decoration:underline}.bolditalic{font-style:italic;font-weight:700}.bquote{border-left:3px solid #9e9e9e;margin-left:30px;padding-left:10px;font-style:italic}.small-text{font-size:.75rem;line-height:1.125rem}.large-text-center{text-align:center;font-size:1.25rem;line-height:1.75rem}.prewrap{white-space:pre-wrap;display:block}.hr-styled{width:75%;margin:auto}.center-text{text-align:center}.copyright{text-align:center;font-size:.75rem;line-height:1.125rem}.visited{color:#7f6227}.center-nav{text-align:center;padding:5px;font-size:1rem;line-height:1.5rem}.block{resize:none;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-size:.75rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:150px;height:150px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}.emoji{margin-right:8px}.headline{margin-bottom:4px;font-style:italic;font-weight:700;display:block}.label{font-family:inherit;font-weight:700}.description{font-family:inherit;font-style:normal;font-weight:400;display:inline}.goldseparator{color:#ffc627;margin:0 .5rem}.gold{color:#ffc627}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @typedef {object} FossLink
3
+ * @property {string} [label]
4
+ * @property {string} [href]
5
+ * @property {string} [imgAlt]
6
+ * @property {string} [downloadText]
7
+ * @property {string} [downloadHref]
8
+ * @property {boolean} [hideLabels]
9
+ */
10
+
11
+ /**
12
+ * @typedef {object} FossItem
13
+ * @property {string} id
14
+ * @property {string} title
15
+ * @property {{ webp: string, png: string }} images
16
+ * @property {string} imgAlt
17
+ * @property {string} headline
18
+ * @property {string} headlineDescription
19
+ * @property {string} detailsDescription
20
+ * @property {Array<any>} features
21
+ * @property {Array<string>} notes
22
+ * @property {Array<FossLink>} links
23
+ */
@@ -0,0 +1,74 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/purify.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file purify.js
11
+ * @description Universal DOMPurify instance for SSR + client with safe build support.
12
+ * Secures untrusted HTML before injecting it into the DOM.
13
+ * @module src/lib/utils
14
+ * @author SunDevil311
15
+ * @updated 2025-06-01
16
+ */
17
+
18
+ import createDOMPurify from "dompurify";
19
+
20
+ /**
21
+ * @typedef {ReturnType<import('dompurify').default>} DOMPurifyInstance
22
+ */
23
+
24
+ /** @type {import('dompurify').DOMPurify | null} */
25
+ let DOMPurifyInstance = null;
26
+
27
+ /** @type {import('jsdom').JSDOM['window'] | null} */
28
+ let jsdomWindow = null;
29
+
30
+ /**
31
+ * SSR-safe + Vite-compatible init of DOMPurify.
32
+ *
33
+ * Caches DOMPurify across multiple calls to improve performance in tests or SSR environments.
34
+ *
35
+ * @returns {Promise<DOMPurifyInstance>}
36
+ */
37
+ export async function getDOMPurify() {
38
+ if (DOMPurifyInstance) return DOMPurifyInstance;
39
+
40
+ if (typeof window !== "undefined") {
41
+ // ✅ Client-side: use native window
42
+ DOMPurifyInstance = createDOMPurify(window);
43
+ } else {
44
+ // ✅ SSR: dynamically import jsdom to avoid bundling
45
+ const { JSDOM } = await import("jsdom");
46
+ jsdomWindow = jsdomWindow || new JSDOM("").window;
47
+ DOMPurifyInstance = createDOMPurify(jsdomWindow);
48
+ }
49
+
50
+ return DOMPurifyInstance;
51
+ }
52
+
53
+ /**
54
+ * Sanitizes HTML content to prevent XSS.
55
+ *
56
+ * @param {string} dirtyHtml
57
+ * @returns {Promise<string>} - A Promise resolving to sanitized HTML
58
+ */
59
+ export async function sanitizeHtml(dirtyHtml) {
60
+ const DOMPurify = await getDOMPurify();
61
+ return DOMPurify.sanitize(dirtyHtml, {
62
+ USE_PROFILES: { html: true },
63
+ ALLOW_DATA_ATTR: false,
64
+ ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto):)/i,
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Optional helper to reset cache (for test isolation).
70
+ */
71
+ export function resetDOMPurifyCache() {
72
+ DOMPurifyInstance = null;
73
+ jsdomWindow = null;
74
+ }
@@ -0,0 +1,32 @@
1
+ /* ==========================================================================
2
+ src/routes/status/+page.server.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * Server-side load function for /status route.
11
+ *
12
+ * Logs runtime environment variables for debugging purposes.
13
+ * This function executes on the server during SSR and is used to verify
14
+ * that environment variables (e.g., ENV_MODE and NODE_ENV) are properly
15
+ * injected and available during runtime in Netlify's SSR context.
16
+ *
17
+ * @module src/routes/status
18
+ * @author SunDevil311
19
+ * @updated 2025-05-31
20
+ */
21
+
22
+ export const prerender = false;
23
+
24
+ /**
25
+ * @function load
26
+ * @returns {{ ok: boolean }} Response indicating successful execution
27
+ */
28
+ export function load() {
29
+ console.log("[Status Debug] ENV_MODE:", process.env.ENV_MODE);
30
+ console.log("[Status Debug] NODE_ENV:", process.env.NODE_ENV);
31
+ return { ok: true };
32
+ }
@@ -0,0 +1,99 @@
1
+ /* ==========================================================================
2
+ tests/internal/auditCoverage.test.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file auditCoverage.test.js
11
+ * @description Scans all .js files in src/ and scripts/ for matching unit test
12
+ * @module tests/internal
13
+ * @author SunDevil311
14
+ * @updated 2025-06-01
15
+ */
16
+
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { describe, expect, it } from "vitest";
20
+
21
+ /**
22
+ * Recursively get all .js files in a directory
23
+ * @param {string} dir
24
+ * @param {object} [opts]
25
+ * @param {boolean} [opts.includeTests=false]
26
+ * @returns {string[]}
27
+ */
28
+ function getAllJsFiles(dir, { includeTests = false } = {}) {
29
+ let results = [];
30
+ const list = fs.readdirSync(dir);
31
+ for (const file of list) {
32
+ const fullPath = path.join(dir, file);
33
+ const stat = fs.statSync(fullPath);
34
+ if (stat.isDirectory()) {
35
+ results = results.concat(getAllJsFiles(fullPath, { includeTests }));
36
+ } else if (file.endsWith(".js")) {
37
+ if (
38
+ !includeTests &&
39
+ (file.endsWith(".test.js") || file.endsWith(".spec.js"))
40
+ ) {
41
+ continue;
42
+ }
43
+ results.push(fullPath);
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+
49
+ describe("auditCoverage", () => {
50
+ it("should have corresponding test files for all JS modules", () => {
51
+ const allowList = new Set([
52
+ "checkNode.js",
53
+ "auditScripts.js",
54
+ "vite.config.js",
55
+ "svelte.config.js",
56
+ ]);
57
+
58
+ const srcFiles = getAllJsFiles(path.resolve("src"));
59
+ const scriptsFiles = getAllJsFiles(path.resolve("scripts"));
60
+ const allFiles = [...srcFiles, ...scriptsFiles].map((f) =>
61
+ path
62
+ .relative(process.cwd(), f)
63
+ .replace(/\\/g, "/") // Normalize Windows slashes
64
+ .replace(/^src\//, "")
65
+ .replace(/^scripts\//, "")
66
+ .replace(/\.js$/, ""),
67
+ );
68
+
69
+ const testFiles = getAllJsFiles(path.resolve("tests/unit"), {
70
+ includeTests: true,
71
+ });
72
+
73
+ const testFilesNormalized = testFiles.map((f) =>
74
+ path
75
+ .relative(path.resolve("tests/unit"), f)
76
+ .replace(/\\/g, "/")
77
+ .replace(/\.test\.js$|\.spec\.js$/, ""),
78
+ );
79
+
80
+ const testedNames = new Set(testFilesNormalized);
81
+
82
+ const untested = allFiles.filter((file) => {
83
+ if (file.startsWith("tests/")) return false;
84
+ if ([...allowList].some((allowed) => file.endsWith(allowed)))
85
+ return false;
86
+ return !testedNames.has(file);
87
+ });
88
+
89
+ if (untested.length > 0) {
90
+ console.warn(
91
+ "\n[WARN] The following JS modules do not have corresponding tests:\n" +
92
+ untested.map((f) => ` - ${f}`).join("\n"),
93
+ );
94
+ }
95
+
96
+ // ✅ Always pass — warn only
97
+ expect(true).toBe(true);
98
+ });
99
+ });