diff --git a/.gitignore b/.gitignore index 1170717..8c92b77 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,7 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Ignore all files in /blog except base templates +/blog/* +!/blog/*base-template.html* diff --git a/articles/JS-X-Ray-6.0.md b/articles/JS-X-Ray-6.0.md index 19082da..86ba00a 100644 --- a/articles/JS-X-Ray-6.0.md +++ b/articles/JS-X-Ray-6.0.md @@ -1,6 +1,7 @@ --- title: JS-X-Ray 6.0 -author: Thomas.G +description: Discover what’s new in JS-X-Ray 6.0! Explore the latest features of this open source JavaScript security analyzer and see how it helps you write safer, cleaner code. +author: fraxken date: 16/01/2023 --- diff --git a/articles/announcing-nodesecure-vuln-era.md b/articles/announcing-nodesecure-vuln-era.md index 05847c3..3b9f136 100644 --- a/articles/announcing-nodesecure-vuln-era.md +++ b/articles/announcing-nodesecure-vuln-era.md @@ -1,6 +1,7 @@ --- title: NodeSecure Vuln-era -author: Thomas.G +description: Dive into the rebranding of Vulnera, NodeSecure’s powerful tool for uncovering Node.js vulnerabilities from multiple sources. Discover its evolution and how it’s shaping the future of open source security! +author: fraxken date: 21/07/2022 --- diff --git a/articles/everything-you-need-to-know-about-package-managers.md b/articles/everything-you-need-to-know-about-package-managers.md index 5834ec4..99df8dd 100644 --- a/articles/everything-you-need-to-know-about-package-managers.md +++ b/articles/everything-you-need-to-know-about-package-managers.md @@ -1,6 +1,7 @@ --- title: Everything you need to know: package managers -author: Antoine.C +description: Curious about package managers? Explore their essential role in modern software, from Linux to npm, and learn why they’re the backbone of every developer’s workflow! +author: antoine-coulon date: 18/10/2022 --- diff --git a/articles/securizing-your-github-org.md b/articles/securizing-your-github-org.md index e11497a..c9f3d29 100644 --- a/articles/securizing-your-github-org.md +++ b/articles/securizing-your-github-org.md @@ -1,6 +1,7 @@ --- title: Securizing your GitHub org -author: Thomas.G +description: Learn hands-on strategies and real-world tips to boost the security of your GitHub organization. Perfect for open source maintainers and anyone serious about code safety! +author: fraxken date: 19/02/2023 --- diff --git a/blog/article-base-template.html b/blog/article-base-template.html new file mode 100644 index 0000000..dcecaad --- /dev/null +++ b/blog/article-base-template.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + NodeSecure - Blog + + + + + +
+
+
+ NodeSecure Logo +

+ NodeSecure Blog +

+

+ Building a safer Node.js and JavaScript ecosystem +

+

We are a community of developers building free open source tools to secure the Node.js & JavaScript ecosystem. Our area of expertise is SCA (Software Composition Analysis).

+ +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/blog/index-base-template.html b/blog/index-base-template.html new file mode 100644 index 0000000..b70dbbd --- /dev/null +++ b/blog/index-base-template.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + NodeSecure - Blog + + + + + +
+
+
+ NodeSecure Logo +

+ NodeSecure Blog +

+

+ Building a safer Node.js and JavaScript ecosystem +

+

We are a community of developers building free open source tools to secure the Node.js & JavaScript ecosystem. Our area of expertise is SCA (Software Composition Analysis).

+ +
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/index.html b/index.html index a53ccd9..c01af4d 100644 --- a/index.html +++ b/index.html @@ -10,15 +10,15 @@ href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> - - + + NodeSecure - Securing the Node.js Ecosystem -
+
NodeSecure Logo style="width:28px;height:28px;filter:invert(1) brightness(2);"> Join Discord + + Blog + Visit Blog +
@@ -81,7 +85,7 @@

🐤 New to NodeSecure? Check out our beginner guides to start contributing.

- + @@ -133,7 +137,7 @@

🔬 JS-X-Ray

- +

Vulnera

Programmatically fetch security vulnerabilities with one or many strategies (NPM Audit, Sonatype, Snyk, OSV...).

diff --git a/package-lock.json b/package-lock.json index 20c5d59..0b278fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "dompurify": "^3.2.7", + "jsdom": "^27.0.0", + "marked": "^16.3.0", "zup": "^0.0.2" }, "devDependencies": { @@ -16,6 +19,169 @@ "vite": "^7.0.2" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.4.tgz", + "integrity": "sha512-cKjSKvWGmAziQWbCouOsFwb14mp1betm8Y7Fn+yglDMUUu3r9DCbJ9iJbeFDenLMqFbIMC0pQP8K+B8LAxX3OQ==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.1.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.5.tgz", + "integrity": "sha512-kI2MX9pmImjxWT8nxDZY+MuN6r1jJGe7WxizEbsAEPB/zxfW5wYLIiPG1v3UKgEOOP8EsDkp0ZL99oRFAdPM8g==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", @@ -1091,6 +1257,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.35.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz", @@ -1371,6 +1544,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1418,6 +1600,15 @@ "dev": true, "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1510,11 +1701,50 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", + "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1528,6 +1758,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1535,6 +1771,27 @@ "dev": true, "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -1989,6 +2246,56 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2075,6 +2382,12 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2095,6 +2408,45 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.5.4", + "cssstyle": "^5.3.0", + "data-urls": "^6.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.3.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0", + "ws": "^8.18.2", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2163,6 +2515,33 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/marked": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", + "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2220,14 +2599,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2312,6 +2689,18 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2336,7 +2725,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2356,7 +2744,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2395,7 +2782,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2422,6 +2808,15 @@ ], "license": "MIT" }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2483,6 +2878,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2507,6 +2908,24 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -2547,7 +2966,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2579,6 +2997,12 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -2596,6 +3020,24 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tldts": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.15.tgz", + "integrity": "sha512-heYRCiGLhtI+U/D0V8YM3QRwPfsLJiP+HX+YwiHZTnWzjIKC+ZCxQRYlzvOoTEc6KIP62B1VeAN63diGCng2hg==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.15" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.15.tgz", + "integrity": "sha512-YBkp2VfS9VTRMPNL2PA6PMESmxV1JEVoAr5iBlZnB5JG3KUrWzNCB3yNNkRa2FZkqClaBgfNYCp8PgpYmpjkZw==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2609,6 +3051,30 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -2758,6 +3224,61 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2784,6 +3305,42 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index cd737b0..7ff4a5b 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "landing_git", "version": "1.0.0", - "description": "A simple HTML/CSS landing page hosted on [GitHub Pages](https://pages.github.com/). Just open `index.html` and you're all set!", + "description": "NodeSecure Landing Page", "main": "index.js", "type": "module", "scripts": { "dev": "vite", - "build": "vite build", - "preview": "vite preview" + "build": "npm run build:articles && vite build", + "preview": "vite preview", + "build:articles": "node src/build-articles.js" }, "repository": { "type": "git", @@ -25,6 +26,9 @@ "vite": "^7.0.2" }, "dependencies": { + "dompurify": "^3.2.7", + "jsdom": "^27.0.0", + "marked": "^16.3.0", "zup": "^0.0.2" } } diff --git a/public/css/blog.css b/public/css/blog.css new file mode 100644 index 0000000..12557d4 --- /dev/null +++ b/public/css/blog.css @@ -0,0 +1,263 @@ +.articleHeader { + z-index: 2; +} + +.articles-list { + padding: 30px; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-gap: 40px; + z-index: 2; + margin: auto; + max-width: 70%; +} + +@media (max-width: 1600px) { + .articles-list { + grid-template-columns: repeat(2, 1fr); + max-width: 90%; + } +} + +@media (max-width: 1000px) { + .articles-list { + display: flex; + flex-direction: column; + align-items: center; + } +} + + +.article-link { + font-size: 30px; + z-index: 2; + font-weight: 600; +} + +.article-link:hover { + text-decoration: underline; +} + +article { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; +} + +.article-content { + margin: 40px 0; + max-width: 60%; + padding: 40px; + background: linear-gradient(135deg, rgba(90, 68, 218, 0.5) 0%, rgba(55, 34, 175, 0.5) 100%); + z-index: 2; +} + +.article-content > h1, +.article-content > h2, +.article-content >h3 { + margin: 30px 0 20px 0; + font-weight: 600; +} + +.article-content > h1 { + font-size: 40px +} + +.article-content > h2 { + font-size: 28px; +} + +.article-content > h3 { + font-size: 24px; +} + +img { + max-width: 100%; +} + +h1.article-title { + display: block; + text-align: center; + margin: 50px 0; +} + +.article-content > p { + font-size: 20px; + margin: 12px 0; +} + +.article-content > p:has(img) { + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 20px; + max-width: 100%; +} + +.article-content > pre { + position: relative; + background-color: #1e1e1e; + border-radius: 8px; + overflow: hidden; + margin: 1.5rem 0; +} + +.article-content > pre code[class^="language-"]::before { + display: block; + color: grey; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + font-family: 'JetBrains Mono', monospace; + letter-spacing: 0.5px; + margin-bottom: 10px; +} + +.article-content > pre code.language-js::before, +.article-content > pre code.language-javascript::before { + content: "JavaScript"; +} + +.article-content > pre code.language-ts::before, +.article-content > pre code.language-typescript::before { + content: "TypeScript"; +} + +.article-content > pre code.language-css::before { + content: "CSS"; +} + +.article-content > pre code.language-html::before { + content: "HTML"; +} + +.article-content > pre code.language-json::before { + content: "JSON"; +} + +.article-content > pre code.language-yml::before { + content: "YAML" +} + +.article-content > pre code.language-bash::before { + content: "BASH" +} + +.article-content code { + background: #23272f; + color: #ffecb3; + padding: 1px 6px; + border-radius: 4px; + font-size: 0.95em; + font-family: 'JetBrains Mono', monospace; + font-weight: 500; + box-shadow: 0 1px 2px #0002; + white-space: break-spaces; +} + +.article-content li { + list-style: disc inside; + margin-left: 1.5em; + margin-bottom: 0.5em; +} + +.article-content > table { + border: 1px solid #ccc; + border-collapse: collapse; + width: 100%; +} + +.article-content > table th, +.article-content > table td { + border: 1px solid #ccc; + padding: 10px 16px; +} + +.article-content > pre code { + display: block; + padding: 1rem; + color: #f8f8f2; + font-family: 'JetBrains Mono', monospace; + background: none; + border: none; +} + +.article-card { + position: relative; + padding: 0.5rem; + color: #fff; + border-radius: 12px; + box-shadow: 0 6px 32px #0003; + background: var(--bg-img) center/cover no-repeat, #3722AF; + display: flex; + transition: transform 0.2s; + z-index: 2; +} + +.article-card:hover { + transform: scale(1.04); + box-shadow: 0 12px 40px #0005; +} + +.article-card-content { + padding: 2rem; + width: 100%; + background: linear-gradient(0deg, #3722AFee 80%, #00D1FF44 100%); + border-radius: 12px; + display: flex; + flex-direction: column; + justify-content: start; + align-items: start; + gap: 10px; +} + +.article-card-header { + display: flex; + justify-content: start; + align-items: start; + gap: 1rem; + margin-bottom: 1rem; +} + +.article-card-header .authorImg { + width: 60px; + height: 60px; + border-radius: 50%; + box-shadow: + 0 0 0 6px rgba(0, 209, 255, 0.18), + 0 0 24px 8px #00d1ff55, + 0 0 48px 8px #3722af33; + transition: transform 0.3s, box-shadow 0.3s; + background: linear-gradient(120deg, #261877 0%, #3722AF 100%); + object-fit: cover; +} + +.article-card-header .authorImg:hover { + box-shadow: + 0 0 0 8px #00d1ff88, + 0 0 32px 12px #00d1ff77, + 0 0 64px 16px #3722af55; +} + +.article-card-header-infos { + display: flex; + flex-direction: column; + justify-content: start; + align-items: start; + gap: 0.6rem; +} + +.article-card-header-infos > span:first-child { + font-size: 20px; +} + +.article-card-header-infos > span:last-child { + font-size: 12px; +} + +.external-article-link:hover { + text-decoration: underline; +} diff --git a/public/css/index.css b/public/css/index.css index cceaf91..a282ba6 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -28,6 +28,10 @@ body { height: auto; } +a { + color: var(--primary-theme-color); +} + #network-bg { position: fixed; top: 0; @@ -83,6 +87,23 @@ header { margin-bottom: 2rem; } +.blogHeader { + margin-bottom: 0; +} + +.articleHeader { + width: 100%; + display: flex; + flex-direction: center; + justify-content: center; + height: 70px; +} + +.articleHeader > a { + align-self: center; + font-size: 30px; +} + @media only screen and (max-width: 600px) { header>.header-content > img { display: none; @@ -528,6 +549,10 @@ header>.header-content>.description b { text-align: left; } +.project-content h1 { + font-size: 2rem; +} + .project-content .logo { height: 28px; width: auto; @@ -585,3 +610,11 @@ header>.header-content>.description b { color: #fff; letter-spacing: 0.01em; } + + + + + + + + diff --git a/public/css/reset.css b/public/css/reset.css index aa8f3ba..44c3421 100644 --- a/public/css/reset.css +++ b/public/css/reset.css @@ -129,10 +129,12 @@ q:after { a { text-decoration: none; + cursor: pointer; } @keyframes pulse { 0% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.2); opacity: 0.8; } 100% { transform: scale(1); opacity: 0.5; } -} \ No newline at end of file +} + diff --git a/src/build-articles.js b/src/build-articles.js new file mode 100644 index 0000000..dc0fd5a --- /dev/null +++ b/src/build-articles.js @@ -0,0 +1,255 @@ +// Import Node.js Dependencies +import fs from "node:fs"; +import path from "node:path"; + +// Import Third-party Dependencies +import { marked } from "marked"; +import createDOMPurify from "dompurify"; +import { JSDOM } from "jsdom"; + +const inputDir = "./articles"; +const outputDir = "./blog"; + +// processes all markdown files in articles directory +export function convertAllMarkdownArticles(authors) { + if (!fs.existsSync(inputDir)) { + throw new Error(`${inputDir} directory does not exist`); + } + + const files = fs.readdirSync(inputDir).filter((file) => file.endsWith(".md")); + + const articles = files.map((file) => { + const filePath = path.join(inputDir, file); + + return generateBlogPost(filePath); + }); + + // Generate index page + generateBlogIndex(articles, authors); +} + +function generateBlogPost(markdownFile) { + const markdownContent = fs.readFileSync(markdownFile, "utf8"); + const fileName = path.basename(markdownFile, ".md"); + + // retrieve metadata and dirty html from yaml file + const { metadata, content } = parseYamlFile(markdownContent); + + // replace external links tags + const contentWithLinks = content + // 1. dev.to articles + .replace( + /{%\s*link\s+(https?:\/\/[^\s%]+)\s*%}/g, + '
↪ Read article' + ) + // 2. youtube videos + .replace( + /{%\s*youtube\s+([a-zA-Z0-9_-]{11})\s*%}/g, + ` + ▶ Watch on YouTube + ` + // 2. github links + ).replace( + /{%\s*github\s+([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)\s*%}/g, + '🔗 GitHub: $1' + ); + + const htmlContent = marked.parse(contentWithLinks); + + // sanitize html + const window = new JSDOM("").window; + const DOMPurify = createDOMPurify(window); + const sanitizedHtmlContent = DOMPurify.sanitize(htmlContent, { + ADD_ATTR: ["target", "rel", "class"] + }); + + metadata.readTime = getReadTimeEstimation(sanitizedHtmlContent); + + const baseArticleTemplate = fs.readFileSync( + `${outputDir}/article-base-template.html`, + "utf-8" + ); + + const articleTemplate = baseArticleTemplate.replace( + "
", + ` +
+
+

${metadata.title}

+ ${sanitizedHtmlContent} +
+
` + ); + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const outputPath = path.join(outputDir, `${fileName}.html`); + fs.writeFileSync(outputPath, articleTemplate); + + return { ...metadata, path: `${fileName}.html` }; +} + +function parseYamlFile(content) { + if (!content.startsWith("---")) { + return { metadata: {}, content }; + } + + const lines = content.split("\n"); + let endLineIndex = -1; + + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === "---") { + endLineIndex = i; + break; + } + } + + if (endLineIndex === -1) { + return { metadata: {}, content }; + } + + // extract frontmatter + const frontmatterLines = lines.slice(1, endLineIndex); + const markdownLines = lines.slice(endLineIndex + 1); + + const markdownContent = markdownLines.join("\n").trim(); + + const metadata = {}; + + for (const line of frontmatterLines) { + const trimmedLine = line.trim(); + if (trimmedLine && trimmedLine.includes(":")) { + const [key, ...valueParts] = trimmedLine.split(":"); + + const dataKey = key.trim(); + const dataValue = valueParts.join(":").trim(); + + if (dataKey === "date") { + const [day, month, year] = dataValue.split("/").map(Number); + const date = new Date(year, month - 1, day); + metadata[dataKey] = date; + } + else { + metadata[dataKey] = dataValue; + } + } + } + + return { metadata, content: markdownContent }; +} + +function generateBlogIndex(articles, authors) { + const articlesList = articles + .sort((articleA, articleB) => articleB.date - articleA.date) + .map((article) => { + const coreContributor = authors.filter( + (c) => c.github === article.author + )?.[0]; + + const imgSource = coreContributor?.github + ? `https://github.com/${coreContributor.github}.png` + : "https://img.icons8.com/ios-glyphs/30/test-account.png"; + + return ` +
+
+
+ + Thomas + +
+ ${coreContributor?.name || article.author} + + + calendar + ${formatDate(article.date)} + + + clock + ${article.readTime} min read + + +
+
+ + ${article.title} + +

${article.description}

+
+
+ `; + }) + .join("\n "); + + const baseTemplate = fs.readFileSync( + `${outputDir}/index-base-template.html`, + "utf-8" + ); + + const indexTemplate = baseTemplate.replace( + '
', + ` +
+ ${articlesList} +
` + ); + + const indexPath = path.join(outputDir, "index.html"); + fs.writeFileSync(indexPath, indexTemplate); +} + +function formatDate(date) { + if (!(date instanceof Date)) { + return date; + } + + const day = date.getDate(); + const month = date.toLocaleString("en-US", { month: "short" }); + const year = date.getFullYear(); + + return `${month} ${day}, ${year}`; +} + +function getReadTimeEstimation(content) { + // remove HTML tags to get plain text, regroup white spaces + const text = content.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim(); + + // get words count, filtering empty elements out + const wordCount = text.split(" ").filter(Boolean).length; + + // calculate minutes, round up to nearest integer - one minute min + return Math.max(1, Math.ceil(wordCount / 200)); +} + +(async() => { + const response = await fetch( + "https://raw.githubusercontent.com/NodeSecure/Governance/main/contributors.json" + ); + + if (!response.ok) { + throw new Error( + `Error while fetching contributors list: ${response.status}` + ); + } + + const contributors = await response.json(); + const authors = contributors.core; + + convertAllMarkdownArticles(authors); +})(); diff --git a/vite.config.js b/vite.config.js index 1230797..f779856 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,11 +1,40 @@ +// Import Node.js Dependencies +import fs from "node:fs"; +import path from "node:path"; + // Import Third-party Dependencies import { defineConfig } from "vite"; // Import Internal Dependencies import zupTransformer from "./plugins/zupTransformer.js"; +function getBlogEntries() { + const entries = { + main: "index.html" + }; + + if (fs.existsSync("./blog")) { + const files = fs.readdirSync("./blog").filter((file) => file.endsWith(".html")); + + files.forEach((file) => { + const name = path.basename(file, ".html"); + + if (!name.includes("base-template")) { + entries[`blog-${name}`] = `blog/${file}`; + } + }); + } + + return entries; +} + export default defineConfig({ plugins: [ zupTransformer({}) - ] + ], + build: { + rollupOptions: { + input: getBlogEntries() + } + } });