@networkpro/web 1.1.3 → 1.4.2
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/_redirects +1 -0
- package/eslint.config.mjs +11 -0
- package/package.json +61 -38
- package/playwright.config.js +9 -1
- package/scripts/auditScripts.js +94 -0
- package/scripts/checkEnv.js +66 -0
- package/scripts/checkNode.js +54 -0
- package/scripts/checkVersions.js +58 -0
- package/src/global.d.ts +15 -0
- package/src/lib/components/PWAInstallButton.svelte +93 -0
- package/src/lib/components/foss/FossItemContent.svelte +6 -1
- package/src/lib/registerServiceWorker.js +22 -8
- package/src/routes/+layout.svelte +3 -2
- package/src/service-worker.js +57 -9
- package/static/manifest.json +9 -5
- package/static/robots.txt +19 -4
- package/tests/{app.spec.js → e2e/app.spec.js} +1 -1
- package/tests/{mobile.spec.js → e2e/mobile.spec.js} +1 -1
- package/tests/unit/auditScripts.test.js +42 -0
- package/tests/unit/checkEnv.test.js +48 -0
- package/tests/unit/checkVersions.test.js +24 -0
- package/{src/demo.spec.js → tests/unit/demo.test.js} +1 -1
- package/{src → tests/unit}/routes/page.svelte.test.js +2 -2
- package/vite.config.js +7 -0
- package/vitest.config.client.js +9 -2
- package/vitest.config.server.js +9 -2
- package/tests-examples/demo-todo-app.spec.js +0 -504
package/_redirects
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://www.netwk.pro/* https://netwk.pro/:splat 301
|
package/eslint.config.mjs
CHANGED
|
@@ -75,6 +75,17 @@ export default [
|
|
|
75
75
|
"jsdoc/check-types": "warn", // Checks if types in JSDoc are defined correctly
|
|
76
76
|
"jsdoc/require-param": "warn", // Requires @param in JSDoc
|
|
77
77
|
"jsdoc/require-returns": "warn", // Requires @returns in JSDoc
|
|
78
|
+
"jsdoc/require-jsdoc": [
|
|
79
|
+
"warn",
|
|
80
|
+
{
|
|
81
|
+
publicOnly: true,
|
|
82
|
+
require: {
|
|
83
|
+
FunctionDeclaration: true,
|
|
84
|
+
MethodDefinition: true,
|
|
85
|
+
ClassDeclaration: true,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
78
89
|
},
|
|
79
90
|
},
|
|
80
91
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@networkpro/web",
|
|
3
3
|
"private": false,
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.4.2",
|
|
6
6
|
"description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"security",
|
|
@@ -30,37 +30,61 @@
|
|
|
30
30
|
},
|
|
31
31
|
"type": "module",
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=22"
|
|
33
|
+
"node": ">=22.0.0 <25",
|
|
34
|
+
"npm": ">=11.0.0 <12"
|
|
34
35
|
},
|
|
35
|
-
"main": "src/app.html",
|
|
36
36
|
"style": "src/lib/styles/global.min.css",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite dev",
|
|
39
|
+
"preview": "vite preview",
|
|
40
|
+
"start": "npm run dev",
|
|
41
|
+
|
|
42
|
+
"build": "vite build",
|
|
43
|
+
"build:netlify": "netlify build",
|
|
44
|
+
"css:bundle": "node scripts/bundleCss.js",
|
|
45
|
+
|
|
46
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
47
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
48
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
49
|
+
"check:env": "node scripts/checkEnv.js",
|
|
50
|
+
"check:node": "node scripts/checkNode.js",
|
|
51
|
+
|
|
52
|
+
"checkout": "npm run check:node && npm run test:all && npm run lint:all && npm run check && npm run audit:scripts",
|
|
53
|
+
"verify": "npm run checkout",
|
|
54
|
+
|
|
55
|
+
"delete": "rm -rf build .svelte-kit node_modules package-lock.json",
|
|
56
|
+
"clean": "npm run delete && npm cache clean --force && npm install",
|
|
57
|
+
"upgrade": "npx npm-check-updates -u",
|
|
58
|
+
|
|
59
|
+
"test": "npm run test:all",
|
|
60
|
+
"test:all": "npm run test:client -- --run && npm run test:server -- --run",
|
|
61
|
+
"test:client": "vitest --config vitest.config.client.js",
|
|
62
|
+
"test:server": "vitest --config vitest.config.server.js",
|
|
63
|
+
"test:watch": "vitest --config vitest.config.client.js --watch",
|
|
64
|
+
"test:coverage": "npm run test:client -- --run --coverage && npm run test:server -- --run --coverage",
|
|
65
|
+
|
|
66
|
+
"lint": "eslint . --ext .mjs,.js,.svelte",
|
|
67
|
+
"lint:fix": "eslint . --ext .mjs,.js,.svelte --fix",
|
|
68
|
+
"lint:jsdoc": "eslint . --ext .js,.mjs,.svelte --max-warnings=0",
|
|
69
|
+
"lint:css": "stylelint \"**/*.{css,svelte}\" --ignore-path .stylelintignore",
|
|
70
|
+
"lint:md": "npx markdownlint-cli2 \"**/*.{md,markdown}\" \"#node_modules/**\" \"#build/**\"",
|
|
71
|
+
"lint:all": "npm run lint && npm run lint:md && npm run lint:css && npm run format",
|
|
72
|
+
"format": "prettier --check .",
|
|
73
|
+
"format:fix": "prettier --write .",
|
|
74
|
+
|
|
75
|
+
"lhci": "lhci",
|
|
76
|
+
"lighthouse": "npm run lighthouse:local",
|
|
77
|
+
"lighthouse:local": "npm run build && npm run preview & wait-on http://localhost:4173 && npm run lhci:run",
|
|
78
|
+
"lhci:run": "lhci autorun --config=.lighthouserc.cjs",
|
|
79
|
+
|
|
80
|
+
"audit:scripts": "node scripts/auditScripts.js",
|
|
81
|
+
"head:flatten": "node scripts/flattenHeaders.js",
|
|
82
|
+
"head:validate": "node scripts/validateHeaders.js",
|
|
83
|
+
|
|
84
|
+
"postinstall": "npm run check:node"
|
|
85
|
+
},
|
|
86
|
+
"dependencies": {
|
|
87
|
+
"svelte": "5.31.1"
|
|
64
88
|
},
|
|
65
89
|
"devDependencies": {
|
|
66
90
|
"@eslint/compat": "^1.2.9",
|
|
@@ -68,21 +92,20 @@
|
|
|
68
92
|
"@netlify/plugin-sitemap": "^0.8.1",
|
|
69
93
|
"@playwright/test": "^1.52.0",
|
|
70
94
|
"@sveltejs/adapter-netlify": "^5.0.2",
|
|
71
|
-
"@sveltejs/kit": "
|
|
72
|
-
"@sveltejs/vite-plugin-svelte": "
|
|
95
|
+
"@sveltejs/kit": "2.21.1",
|
|
96
|
+
"@sveltejs/vite-plugin-svelte": "5.0.3",
|
|
73
97
|
"@testing-library/jest-dom": "^6.6.3",
|
|
74
|
-
"@testing-library/svelte": "^5.2.
|
|
75
|
-
"@vitest/coverage-v8": "^3.1.
|
|
98
|
+
"@testing-library/svelte": "^5.2.8",
|
|
99
|
+
"@vitest/coverage-v8": "^3.1.4",
|
|
76
100
|
"autoprefixer": "^10.4.21",
|
|
77
101
|
"browserslist": "^4.24.5",
|
|
78
102
|
"eslint": "^9.27.0",
|
|
79
103
|
"eslint-config-prettier": "^10.1.5",
|
|
80
104
|
"eslint-plugin-jsdoc": "^50.6.17",
|
|
81
|
-
"eslint-plugin-svelte": "^3.8.
|
|
105
|
+
"eslint-plugin-svelte": "^3.8.2",
|
|
82
106
|
"globals": "^16.1.0",
|
|
83
107
|
"jsdom": "^26.1.0",
|
|
84
108
|
"lightningcss": "^1.30.1",
|
|
85
|
-
"lightningcss-cli": "^1.30.1",
|
|
86
109
|
"markdownlint": "^0.38.0",
|
|
87
110
|
"markdownlint-cli2": "^0.18.1",
|
|
88
111
|
"mdsvex": "^0.12.6",
|
|
@@ -91,18 +114,18 @@
|
|
|
91
114
|
"postcss-html": "^1.8.0",
|
|
92
115
|
"prettier": "^3.5.3",
|
|
93
116
|
"prettier-plugin-svelte": "^3.4.0",
|
|
117
|
+
"semver": "^7.7.2",
|
|
94
118
|
"stylelint": "^16.19.1",
|
|
95
119
|
"stylelint-config-html": "^1.1.0",
|
|
96
120
|
"stylelint-config-recommended": "^16.0.0",
|
|
97
121
|
"stylelint-order": "^7.0.0",
|
|
98
|
-
"svelte": "^5.30.2",
|
|
99
122
|
"svelte-check": "^4.2.1",
|
|
100
123
|
"svelte-eslint-parser": "^1.2.0",
|
|
101
124
|
"svelte-preprocess": "^6.0.3",
|
|
102
125
|
"typescript": "^5.8.3",
|
|
103
126
|
"vite": "^6.3.5",
|
|
104
127
|
"vite-plugin-lightningcss": "^0.0.5",
|
|
105
|
-
"vitest": "^3.1.
|
|
128
|
+
"vitest": "^3.1.4"
|
|
106
129
|
},
|
|
107
130
|
"overrides": {
|
|
108
131
|
"@sveltejs/kit": {
|
package/playwright.config.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
playwright.config.js
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
1
8
|
// @ts-check
|
|
2
9
|
import { defineConfig, devices } from "@playwright/test";
|
|
3
10
|
|
|
@@ -5,7 +12,8 @@ import { defineConfig, devices } from "@playwright/test";
|
|
|
5
12
|
* @see https://playwright.dev/docs/test-configuration
|
|
6
13
|
*/
|
|
7
14
|
export default defineConfig({
|
|
8
|
-
testDir: "./tests",
|
|
15
|
+
testDir: "./tests/e2e",
|
|
16
|
+
testMatch: "*.spec.js",
|
|
9
17
|
|
|
10
18
|
/* Run tests in files in parallel */
|
|
11
19
|
fullyParallel: true,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
scripts/auditScripts.js
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Audit for untested script modules in scripts/
|
|
10
|
+
* Looks for any .js/.mjs files without a matching test in tests/
|
|
11
|
+
*
|
|
12
|
+
* @module scripts/
|
|
13
|
+
* @author SunDevil311
|
|
14
|
+
* @updated 2025-05-21
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
|
|
20
|
+
const scriptsDir = path.resolve("./scripts");
|
|
21
|
+
const testsDir = path.resolve("./tests");
|
|
22
|
+
|
|
23
|
+
// Scripts intentionally excluded from test coverage
|
|
24
|
+
const allowList = new Set([
|
|
25
|
+
"checkNode.js",
|
|
26
|
+
"auditScripts.js", // itself
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Recursively gather all test files
|
|
30
|
+
function getAllTestFiles(dir) {
|
|
31
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
32
|
+
const files = [];
|
|
33
|
+
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const fullPath = path.join(dir, entry.name);
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
files.push(...getAllTestFiles(fullPath));
|
|
38
|
+
} else if (
|
|
39
|
+
entry.isFile() &&
|
|
40
|
+
(entry.name.endsWith(".test.js") ||
|
|
41
|
+
entry.name.endsWith(".spec.js") ||
|
|
42
|
+
entry.name.endsWith(".test.mjs") ||
|
|
43
|
+
entry.name.endsWith(".spec.mjs"))
|
|
44
|
+
) {
|
|
45
|
+
files.push(fullPath);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return files;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get base names of tested modules (no extension or test specifier)
|
|
53
|
+
const testFiles = getAllTestFiles(testsDir);
|
|
54
|
+
const testedModules = new Set(
|
|
55
|
+
testFiles.map((filePath) =>
|
|
56
|
+
path
|
|
57
|
+
.basename(filePath)
|
|
58
|
+
.replace(/\.test\.js$|\.spec\.js$|\.test\.mjs$|\.spec\.mjs$/, ""),
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Gather all scripts (.js and .mjs)
|
|
63
|
+
const scriptFiles = fs
|
|
64
|
+
.readdirSync(scriptsDir)
|
|
65
|
+
.filter((file) => file.endsWith(".js") || file.endsWith(".mjs"));
|
|
66
|
+
|
|
67
|
+
const untested = scriptFiles.filter((file) => {
|
|
68
|
+
const base = file.replace(/\.(js|mjs)$/, "");
|
|
69
|
+
return !allowList.has(file) && !testedModules.has(base);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const pathRelative = (file) =>
|
|
73
|
+
path.relative(process.cwd(), path.join(scriptsDir, file));
|
|
74
|
+
|
|
75
|
+
// Output results
|
|
76
|
+
if (untested.length) {
|
|
77
|
+
console.warn("\n⚠ Untested script files detected:\n");
|
|
78
|
+
|
|
79
|
+
untested.forEach((file) => {
|
|
80
|
+
const filePath = pathRelative(file);
|
|
81
|
+
console.warn(
|
|
82
|
+
`::warning file=${filePath},line=1,col=1::Missing test file for ${file}`,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.warn(
|
|
87
|
+
`\nAdd a corresponding test file in /tests (e.g., ${untested[0].replace(
|
|
88
|
+
/\.(js|mjs)$/,
|
|
89
|
+
".test.js",
|
|
90
|
+
)})`,
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
console.log("✅ All script files have corresponding tests.");
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
scripts/checkEnv.js
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility to validate execution environment
|
|
10
|
+
* Ensures `ENV_MODE` is defined and matches allowed environments
|
|
11
|
+
*
|
|
12
|
+
* @module scripts/
|
|
13
|
+
* @author SunDevil311
|
|
14
|
+
* @updated 2025-05-21
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { basename } from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
|
|
20
|
+
const validEnvs = new Set(["dev", "test", "ci", "prod", "preview"]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks and returns validation for ENV_MODE
|
|
24
|
+
* @returns {{
|
|
25
|
+
* mode: string,
|
|
26
|
+
* valid: boolean,
|
|
27
|
+
* wasDefaulted: boolean,
|
|
28
|
+
* allowed: string[]
|
|
29
|
+
* }}
|
|
30
|
+
*/
|
|
31
|
+
export function checkEnv() {
|
|
32
|
+
const current = process.env.ENV_MODE;
|
|
33
|
+
let mode = current;
|
|
34
|
+
let valid = false;
|
|
35
|
+
let wasDefaulted = false;
|
|
36
|
+
|
|
37
|
+
if (!mode) {
|
|
38
|
+
mode = "dev";
|
|
39
|
+
process.env.ENV_MODE = mode;
|
|
40
|
+
wasDefaulted = true;
|
|
41
|
+
console.warn("⚠️ ENV_MODE not set. Defaulting to 'dev'.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
valid = validEnvs.has(mode);
|
|
45
|
+
|
|
46
|
+
if (valid) {
|
|
47
|
+
const tag = wasDefaulted ? "[info]" : "[ok]";
|
|
48
|
+
console.log(`${tag} ENV_MODE is set to: "${mode}"`);
|
|
49
|
+
} else {
|
|
50
|
+
console.error(
|
|
51
|
+
`❌ Invalid ENV_MODE "${mode}". Must be one of: ${[...validEnvs].join(", ")}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
mode,
|
|
57
|
+
valid,
|
|
58
|
+
wasDefaulted,
|
|
59
|
+
allowed: [...validEnvs].sort(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ✅ Run only if called directly via CLI
|
|
64
|
+
if (basename(fileURLToPath(import.meta.url)) === basename(process.argv[1])) {
|
|
65
|
+
checkEnv();
|
|
66
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
scripts/checkNode.js
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility to check Node.js and NPM version
|
|
10
|
+
* Ensures the current environment matches the required versions from package.json
|
|
11
|
+
*
|
|
12
|
+
* @module scripts/
|
|
13
|
+
* @author SunDevil311
|
|
14
|
+
* @updated 2025-05-20
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import semver from "semver";
|
|
21
|
+
|
|
22
|
+
// Load engines from package.json
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve("./package.json"), "utf8"));
|
|
24
|
+
const { node: nodeRange, npm: npmRange } = pkg.engines;
|
|
25
|
+
|
|
26
|
+
// Check Node version
|
|
27
|
+
if (!semver.satisfies(process.version, nodeRange)) {
|
|
28
|
+
console.error(
|
|
29
|
+
`⚠️ Node version ${process.version} does not match required range: ${nodeRange}`,
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check NPM version
|
|
35
|
+
let npmVersion;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
npmVersion = execSync("npm --version").toString().trim();
|
|
39
|
+
if (!semver.satisfies(npmVersion, npmRange)) {
|
|
40
|
+
console.error(
|
|
41
|
+
`⚠️ NPM version ${npmVersion} does not match required range: ${npmRange}`,
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("❌ Failed to check NPM version:", err.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!process.env.CI || process.env.VERBOSE === "true") {
|
|
51
|
+
console.log(
|
|
52
|
+
`✅ Node (${process.version}) matches ${nodeRange}, and NPM (${npmVersion}) matches ${npmRange}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
scripts/checkVersions.js
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility to check Node.js and NPM version (non-blocking)
|
|
10
|
+
* Returns version info, can be unit tested, and doesn't kill process during
|
|
11
|
+
* test runs
|
|
12
|
+
*
|
|
13
|
+
* @module scripts/
|
|
14
|
+
* @author SunDevil311
|
|
15
|
+
* @updated 2025-05-20
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { execSync } from "child_process";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import semver from "semver";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} VersionCheckResult
|
|
25
|
+
* @property {string} nodeVersion
|
|
26
|
+
* @property {string} npmVersion
|
|
27
|
+
* @property {string} nodeRange
|
|
28
|
+
* @property {string} npmRange
|
|
29
|
+
* @property {boolean} nodeValid
|
|
30
|
+
* @property {boolean} npmValid
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns an object with validation results for Node and NPM versions.
|
|
35
|
+
*
|
|
36
|
+
* @returns {VersionCheckResult}
|
|
37
|
+
*/
|
|
38
|
+
export function checkVersions() {
|
|
39
|
+
const pkg = JSON.parse(
|
|
40
|
+
fs.readFileSync(path.resolve("./package.json"), "utf8"),
|
|
41
|
+
);
|
|
42
|
+
const { node: nodeRange, npm: npmRange } = pkg.engines;
|
|
43
|
+
|
|
44
|
+
const nodeVersion = process.version;
|
|
45
|
+
const npmVersion = execSync("npm --version").toString().trim();
|
|
46
|
+
|
|
47
|
+
const nodeValid = semver.satisfies(nodeVersion, nodeRange);
|
|
48
|
+
const npmValid = semver.satisfies(npmVersion, npmRange);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
nodeVersion,
|
|
52
|
+
npmVersion,
|
|
53
|
+
nodeRange,
|
|
54
|
+
npmRange,
|
|
55
|
+
nodeValid,
|
|
56
|
+
npmValid,
|
|
57
|
+
};
|
|
58
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
src/global.d.ts
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== */
|
|
7
|
+
|
|
8
|
+
interface BeforeInstallPromptEvent extends Event {
|
|
9
|
+
readonly platforms: string[];
|
|
10
|
+
readonly userChoice: Promise<{
|
|
11
|
+
outcome: 'accepted' | 'dismissed';
|
|
12
|
+
platform: string;
|
|
13
|
+
}>;
|
|
14
|
+
prompt(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!-- ==========================================================================
|
|
2
|
+
src/lib/components/PWAInstallButton.svelte
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
5
|
+
This file is part of Network Pro.
|
|
6
|
+
========================================================================== -->
|
|
7
|
+
|
|
8
|
+
<script>
|
|
9
|
+
import { onMount } from "svelte";
|
|
10
|
+
import { fade } from "svelte/transition";
|
|
11
|
+
|
|
12
|
+
let show = false;
|
|
13
|
+
|
|
14
|
+
/** @type {BeforeInstallPromptEvent | null} */
|
|
15
|
+
let deferredPrompt = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {CustomEvent<BeforeInstallPromptEvent>} PWAInstallAvailableEvent
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
onMount(() => {
|
|
22
|
+
/**
|
|
23
|
+
* Listen for the custom event fired by registerServiceWorker.js
|
|
24
|
+
* to enable a custom install experience.
|
|
25
|
+
*
|
|
26
|
+
* TypeScript / svelte-check does not recognize custom events by default,
|
|
27
|
+
* so we cast the base Event to CustomEvent manually.
|
|
28
|
+
*/
|
|
29
|
+
window.addEventListener(
|
|
30
|
+
"pwa-install-available",
|
|
31
|
+
(/** @type {Event} */ e) => {
|
|
32
|
+
const customEvent = /** @type {PWAInstallAvailableEvent} */ (e);
|
|
33
|
+
deferredPrompt = customEvent.detail;
|
|
34
|
+
show = true;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Trigger the native install prompt and handle user response
|
|
41
|
+
*/
|
|
42
|
+
async function promptInstall() {
|
|
43
|
+
if (!deferredPrompt) return;
|
|
44
|
+
|
|
45
|
+
deferredPrompt.prompt();
|
|
46
|
+
|
|
47
|
+
const { outcome } = await deferredPrompt.userChoice;
|
|
48
|
+
console.log(`User response to PWA install prompt: ${outcome}`);
|
|
49
|
+
|
|
50
|
+
// Always hide the button after interaction
|
|
51
|
+
show = false;
|
|
52
|
+
deferredPrompt = null;
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
{#if show}
|
|
57
|
+
<button
|
|
58
|
+
id="pwa-install"
|
|
59
|
+
class="install-button"
|
|
60
|
+
on:click={promptInstall}
|
|
61
|
+
transition:fade={{ duration: 600 }}>
|
|
62
|
+
Install App
|
|
63
|
+
</button>
|
|
64
|
+
{/if}
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.install-button {
|
|
68
|
+
display: block;
|
|
69
|
+
padding: 0.5rem 1rem;
|
|
70
|
+
border: none;
|
|
71
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
72
|
+
font-size: 1rem;
|
|
73
|
+
font-weight: bold;
|
|
74
|
+
color: #000;
|
|
75
|
+
background-color: #ffc627;
|
|
76
|
+
transition: background-color 0.2s ease-in-out;
|
|
77
|
+
border-radius: 6px;
|
|
78
|
+
cursor: pointer;
|
|
79
|
+
font-family: inherit;
|
|
80
|
+
margin-left: auto;
|
|
81
|
+
margin-right: auto;
|
|
82
|
+
margin-top: 1rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.install-button:hover {
|
|
86
|
+
background-color: #e6b300;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.install-button:focus {
|
|
90
|
+
outline: 2px solid #000;
|
|
91
|
+
outline-offset: 2px;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -6,6 +6,8 @@ This file is part of Network Pro.
|
|
|
6
6
|
========================================================================== -->
|
|
7
7
|
|
|
8
8
|
<script>
|
|
9
|
+
/* eslint-disable svelte/no-at-html-tags */
|
|
10
|
+
|
|
9
11
|
import FossFeatures from "$lib/components/foss/FossFeatures.svelte";
|
|
10
12
|
// Import directly from $lib by way of image utility
|
|
11
13
|
import { obtainiumPng, obtainiumWbp } from "$lib";
|
|
@@ -85,14 +87,17 @@ This file is part of Network Pro.
|
|
|
85
87
|
|
|
86
88
|
<h3>{fossItem.headline}</h3>
|
|
87
89
|
|
|
88
|
-
|
|
90
|
+
<!-- Trusted input, from internal CMS -->
|
|
91
|
+
{@html fossItem.detailsDescription}
|
|
89
92
|
|
|
90
93
|
<FossFeatures features={fossItem.features} />
|
|
91
94
|
|
|
95
|
+
<!-- Trusted input, from internal CMS -->
|
|
92
96
|
{@html fossItem.detailsDescription}
|
|
93
97
|
|
|
94
98
|
{#each fossItem.notes as note}
|
|
95
99
|
<blockquote class="bquote">
|
|
100
|
+
<!-- Trusted input, from internal CMS -->
|
|
96
101
|
{@html note}
|
|
97
102
|
</blockquote>
|
|
98
103
|
{/each}
|
|
@@ -5,25 +5,31 @@ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
|
|
|
5
5
|
This file is part of Network Pro.
|
|
6
6
|
========================================================================== */
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Registers the service worker and handles update lifecycle, install prompt, and
|
|
10
|
+
* browser/environment compatibility checks. This supports offline usage and PWA behavior.
|
|
11
|
+
*/
|
|
8
12
|
export function registerServiceWorker() {
|
|
9
13
|
if ('serviceWorker' in navigator) {
|
|
10
14
|
// Skip registration in Firefox during development
|
|
11
|
-
const isFirefox = navigator.userAgent.
|
|
12
|
-
const isDevelopment =
|
|
13
|
-
|
|
15
|
+
const isFirefox = navigator.userAgent.includes('Firefox');
|
|
16
|
+
const isDevelopment =
|
|
17
|
+
window.location.hostname === 'localhost' ||
|
|
18
|
+
window.location.hostname === '127.0.0.1';
|
|
14
19
|
|
|
15
20
|
if (isFirefox && isDevelopment) {
|
|
16
21
|
console.log('Service Worker registration skipped in Firefox development mode');
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
// Wait until after
|
|
25
|
+
// Wait until after full page load for performance optimization
|
|
21
26
|
window.addEventListener('load', () => {
|
|
22
|
-
navigator.serviceWorker
|
|
27
|
+
navigator.serviceWorker
|
|
28
|
+
.register('service-worker.js')
|
|
23
29
|
.then((registration) => {
|
|
24
30
|
console.log('Service Worker registered with scope:', registration.scope);
|
|
25
31
|
|
|
26
|
-
// Track installation of new service worker
|
|
32
|
+
// Track installation of a new service worker
|
|
27
33
|
registration.addEventListener('updatefound', () => {
|
|
28
34
|
const newWorker = registration.installing;
|
|
29
35
|
console.log('New service worker installing...');
|
|
@@ -40,7 +46,7 @@ export function registerServiceWorker() {
|
|
|
40
46
|
) {
|
|
41
47
|
updatePrompted = true;
|
|
42
48
|
|
|
43
|
-
// Custom prompt:
|
|
49
|
+
// Custom prompt: ask user to reload for latest content
|
|
44
50
|
if (confirm('New content is available. Reload to update?')) {
|
|
45
51
|
window.location.reload();
|
|
46
52
|
}
|
|
@@ -52,7 +58,7 @@ export function registerServiceWorker() {
|
|
|
52
58
|
console.error('Service Worker registration failed:', error);
|
|
53
59
|
});
|
|
54
60
|
|
|
55
|
-
// Ensure page reloads when new service worker takes control
|
|
61
|
+
// Ensure the page reloads automatically when the new service worker takes control
|
|
56
62
|
let refreshing = false;
|
|
57
63
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
58
64
|
if (!refreshing) {
|
|
@@ -60,6 +66,14 @@ export function registerServiceWorker() {
|
|
|
60
66
|
window.location.reload();
|
|
61
67
|
}
|
|
62
68
|
});
|
|
69
|
+
|
|
70
|
+
// Optional PWA install prompt logic
|
|
71
|
+
window.addEventListener('beforeinstallprompt', (e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
window.dispatchEvent(new CustomEvent('pwa-install-available', {
|
|
74
|
+
detail: /** @type {BeforeInstallPromptEvent} */ (e)
|
|
75
|
+
}));
|
|
76
|
+
});
|
|
63
77
|
});
|
|
64
78
|
}
|
|
65
79
|
}
|