@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/README.md +238 -75
- package/cspell.json +3 -0
- package/netlify/edge-functions/csp-report.js +151 -0
- package/netlify.toml +3 -3
- package/package.json +6 -6
- package/scripts/generateTest.js +61 -0
- package/src/hooks.server.js +1 -1
- package/src/lib/components/foss/FossItemContent.svelte +28 -30
- package/src/lib/components/layout/HeaderDefault.svelte +1 -1
- package/src/lib/components/layout/HeaderHome.svelte +1 -1
- package/src/lib/pages/AboutContent.svelte +15 -5
- package/src/lib/pages/FossContent.svelte +1 -2
- package/src/lib/pages/LicenseContent.svelte +2 -2
- package/src/lib/styles/css/default.css +11 -2
- package/src/lib/styles/global.min.css +1 -1
- package/src/lib/types/fossTypes.js +23 -0
- package/src/lib/utils/purify.js +74 -0
- package/src/routes/status/+page.server.js +32 -0
- package/tests/internal/auditCoverage.test.js +99 -0
- package/tests/unit/csp-report.test.js +81 -0
- package/tests/unit/lib/utils/purify.test.js +50 -0
- package/vitest.config.client.js +5 -1
- package/vitest.config.server.js +1 -0
- package/netlify-functions/cspReport.js +0 -76
- package/tests/unit/auditScripts.test.js +0 -43
- package/tests/unit/cspReport.test.js +0 -81
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sideEffects": [
|
|
5
5
|
"./.netlify/shims.js"
|
|
6
6
|
],
|
|
7
|
-
"version": "1.
|
|
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
|
|
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:
|
|
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
|
-
"
|
|
80
|
+
"dompurify": "^3.2.6",
|
|
81
81
|
"posthog-js": "^1.249.0",
|
|
82
82
|
"semver": "^7.7.2",
|
|
83
|
-
"svelte": "5.33.
|
|
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.
|
|
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
|
+
}
|
package/src/hooks.server.js
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
<!--
|
|
92
|
-
|
|
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
|
-
<!--
|
|
97
|
-
|
|
92
|
+
<!-- Sanitized input from DOMPurify -->
|
|
93
|
+
<div class="details-description">
|
|
94
|
+
{@html safeDetailsDescription}
|
|
95
|
+
</div>
|
|
98
96
|
|
|
99
|
-
{#each
|
|
97
|
+
{#each safeNotes as note}
|
|
100
98
|
<blockquote class="bquote">
|
|
101
|
-
<!--
|
|
99
|
+
<!-- Sanitized input from DOMPurify -->
|
|
102
100
|
{@html note}
|
|
103
101
|
</blockquote>
|
|
104
102
|
{/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=
|
|
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 <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
|
|
274
|
+
>asc <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
|
|
312
|
+
<strong
|
|
313
|
+
>vcf <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
|
-
: <code>CC-BY-4.0 OR GPL-3.0-or-later</code>
|
|
70
|
+
</a>: <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
|
-
|
|
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
|
-
|
|
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:
|
|
443
|
-
line-height: 1.
|
|
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
|
|
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
|
+
});
|