From 8c46e6fa5e667ff357ccb5fdd5b0d197f0d1395e Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:35:18 +0200 Subject: [PATCH 01/13] fix license copyright holder --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 261eeb9..a6cb839 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 Bundesamt für Sicherheit in der Informationstechnik (BSI) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 5bafa0ce5c7c0e12a6ca7abf146f492a8547be67 Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:38:54 +0200 Subject: [PATCH 02/13] add package.json, tsconfig, and dependencies --- .gitignore | 4 + package-lock.json | 2394 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 53 + tsconfig.json | 26 + 4 files changed, 2477 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3150af0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/node_modules/* +/dist/* +/build +/coverage diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c40a16d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2394 @@ +{ + "name": "@secvisogram/html-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@secvisogram/html-template", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.18.1", + "micromark": "^4.0.2", + "micromark-extension-gfm": "^3.0.0", + "mustache": "^4.2.0" + }, + "devDependencies": { + "@types/lodash": "^4.17.25", + "@types/mustache": "^4.2.6", + "@types/node": "^24.13.5", + "@vitest/coverage-v8": "^5.0.1", + "prettier": "^3.9.8", + "typescript": "^7.0.2", + "vitest": "^5.0.1" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/mustache": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.6.tgz", + "integrity": "sha512-t+8/QWTAhOFlrF1IVZqKnMRJi84EgkIK5Kh0p2JV4OLywUvCwJPFxbJAl7XAow7DVIHsF+xW9f1MVzg0L6Szjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.5.tgz", + "integrity": "sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.1.tgz", + "integrity": "sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "5.0.1", + "vitest": "5.0.1" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz", + "integrity": "sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.1.tgz", + "integrity": "sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/istanbul-lib-coverage": "1.0.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz", + "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.1", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz", + "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", + "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.2.tgz", + "integrity": "sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/picocolors": { + "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", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.8.tgz", + "integrity": "sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rolldown": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.150.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "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" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz", + "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.1", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.1", + "@vitest/browser-preview": "5.0.1", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.1", + "@vitest/coverage-v8": "5.0.1", + "@vitest/ui": "5.0.1", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..31e6a32 --- /dev/null +++ b/package.json @@ -0,0 +1,53 @@ +{ + "name": "@secvisogram/html-template", + "version": "1.0.0", + "description": "HTML/PDF template rendering for CSAF advisories", + "keywords": [ + "CSAF", + "Common Security Advisory Framework", + "Secvisogram" + ], + "license": "Apache-2.0", + "author": "eXXcellent solutions GmbH", + "repository": { + "url": "https://github.com/secvisogram/html-template" + }, + "bugs": { + "url": "https://github.com/secvisogram/html-template/issues" + }, + "homepage": "https://github.com/secvisogram/html-template#readme", + "files": [ + "index.js", + "lib/**/*.js", + "lib/css/**/*.css", + "dist/**/*.d.ts" + ], + "type": "module", + "exports": { + ".": { + "import": "./index.js", + "types": "./dist/index.d.ts" + } + }, + "scripts": { + "prepare": "tsc -b .", + "pretest": "tsc -b .", + "test": "prettier --check . && vitest run", + "test-coverage": "vitest run --coverage" + }, + "dependencies": { + "lodash": "^4.18.1", + "micromark": "^4.0.2", + "micromark-extension-gfm": "^3.0.0", + "mustache": "^4.2.0" + }, + "devDependencies": { + "@types/lodash": "^4.17.25", + "@types/mustache": "^4.2.6", + "@types/node": "^24.13.5", + "@vitest/coverage-v8": "^5.0.1", + "prettier": "^3.9.8", + "typescript": "^7.0.2", + "vitest": "^5.0.1" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e6d038e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "esnext" /* Specify ECMAScript target version. */, + "module": "nodenext" /* ESM package. */, + "allowJs": true /* Allow javascript files to be compiled. */, + "checkJs": true /* Report errors in .js files. */, + "resolveJsonModule": true, + "outDir": "dist" /* Redirect output structure to the directory. */, + "composite": true /* Enable project compilation */, + "emitDeclarationOnly": true /* Do not emit outputs. */, + "types": ["node", "vitest/globals"], + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + + /* Module Resolution Options */ + "esModuleInterop": true /* Enables interop for CJS default imports (e.g. lodash). */, + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "include": ["**/*.js"], + "exclude": ["node_modules", "build", "dist", "coverage"] +} From ee2e8eb478df0c6add900b437e8a39eb29e51af1 Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:39:18 +0200 Subject: [PATCH 03/13] add document enrichment for CSAF 2.0 and 2.1 --- lib/enrichDocument.js | 811 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 811 insertions(+) create mode 100644 lib/enrichDocument.js diff --git a/lib/enrichDocument.js b/lib/enrichDocument.js new file mode 100644 index 0000000..bdfab12 --- /dev/null +++ b/lib/enrichDocument.js @@ -0,0 +1,811 @@ +import lodash from 'lodash' +import { + removeTrailingComma, + replaceUnderscores, + secureHref, + upperCase, +} from './mustacheHelpers.js' + +const { cloneDeep } = lodash + +/** + * @typedef {Object} FullProductName + * @property {string} name + * @property {string} product_id + */ + +/** + * @typedef {Object} Branch + * @property {Array} branches + * @property {FullProductName} product + */ + +// --------------------------------------------------------------------------- +// Shared helpers +// --------------------------------------------------------------------------- + +/** + * @param {Array} branches + * @param {{id: string, name: string, instancePath: string}[]} entries + * @param {string} instancePath + */ +const traverseBranches = (branches, entries, instancePath) => { + for (let i = 0; i < branches.length; ++i) { + const branch = branches[i] + const branchInstancePath = `${instancePath}/${i}` + const fullProductName = branch.product + if (fullProductName) { + if (fullProductName.product_id) { + entries.push({ + id: fullProductName.product_id, + name: fullProductName.name ?? '', + instancePath: `${branchInstancePath}/product/product_id`, + }) + } + } + if (branch.branches) + traverseBranches( + branch.branches, + entries, + `${branchInstancePath}/branches`, + ) + } +} + +/** + * @param {any} document + * @returns {{id: string, name: string, instancePath: string}[]} + */ +const collectProductIds = (document) => { + const entries = + /** @type {{id: string, name: string, instancePath: string}[]} */ ([]) + + const fullProductNames = document.product_tree?.full_product_names + if (fullProductNames) { + for (let i = 0; i < fullProductNames.length; ++i) { + const fullProductName = fullProductNames[i] + if (fullProductName.product_id) { + entries.push({ + id: fullProductName.product_id, + name: fullProductName.name ?? '', + instancePath: `/product_tree/full_product_names/${i}/product_id`, + }) + } + } + } + + const relationships = document.product_tree?.relationships + if (relationships) { + for (let i = 0; i < relationships.length; ++i) { + const relationship = relationships[i] + const fullProductName = relationship.full_product_name + if (fullProductName) { + if (fullProductName.product_id) { + entries.push({ + id: fullProductName.product_id, + name: fullProductName.name ?? '', + instancePath: `/product_tree/relationships/${i}/full_product_name/product_id`, + }) + } + } + } + } + + const branches = document.product_tree?.branches + if (branches) { + traverseBranches(branches, entries, '/product_tree/branches') + } + + return entries +} + +/** + * @param {any} document + * @returns {{id: string, name: string, instancePath: string}[]} + */ +const collectGroupIds = (document) => { + const entries = + /** @type {{id: string, name: string, instancePath: string}[]} */ ([]) + + const productGroups = document.product_tree?.product_groups + if (productGroups) { + for (let i = 0; i < productGroups.length; ++i) { + const productGroup = productGroups[i] + if (productGroup.group_id) { + entries.push({ + id: productGroup.group_id, + name: productGroup.summary ?? '', + instancePath: `/product_tree/product_groups/${i}/group_id`, + }) + } + } + } + + return entries +} + +/** + * @param {any} document + */ +const addDocumentNotesPreviewAttributes = (document) => { + const summary = [] + const details = [] + const general = [] + const description = [] + const other = [] + const faq = [] + const legalDisclaimer = [] + const unknown = [] + + const notes = document.notes + if (notes) { + for (let i = 0; i < notes.length; ++i) { + const note = notes[i] + switch (note.category) { + case 'summary': + summary.push(note) + break + case 'details': + details.push(note) + break + case 'general': + general.push(note) + break + case 'description': + description.push(note) + break + case 'other': + other.push(note) + break + case 'faq': + faq.push(note) + break + case 'legal_disclaimer': + legalDisclaimer.push(note) + break + default: + unknown.push(note) + } + } + } + + document.notes_summary = summary + document.notes_details = details + document.notes_general = general + document.notes_description = description + document.notes_other = other + document.notes_faq = faq + document.notes_legal_disclaimer = legalDisclaimer + document.notes_unknown = unknown +} + +/** + * @param {any} vulnerability + */ +const addVulnerabilityNotesPreviewAttributes = (vulnerability) => { + const summary = [] + const details = [] + const general = [] + const description = [] + const other = [] + const faq = [] + const legalDisclaimer = [] + const unknown = [] + + const notes = vulnerability.notes + if (notes) { + for (let i = 0; i < notes.length; ++i) { + const note = notes[i] + switch (note.category) { + case 'summary': + summary.push(note) + break + case 'details': + details.push(note) + break + case 'general': + general.push(note) + break + case 'description': + description.push(note) + break + case 'other': + other.push(note) + break + case 'faq': + faq.push(note) + break + case 'legal_disclaimer': + legalDisclaimer.push(note) + break + default: + unknown.push(note) + } + } + } + + vulnerability.notes_summary = summary + vulnerability.notes_details = details + vulnerability.notes_general = general + vulnerability.notes_description = description + vulnerability.notes_other = other + vulnerability.notes_faq = faq + vulnerability.notes_legal_disclaimer = legalDisclaimer + vulnerability.notes_unknown = unknown +} + +/** + * @param {{product_groups: []}} productTree + * @param {{id: string, name: string}[]} productIds + */ +const addProductTreePreviewAttributes = (productTree, productIds) => { + const productGroups = productTree.product_groups + if (productGroups) { + for (let i = 0; i < productGroups.length; ++i) { + const productGroup = productGroups[i] + extendProductGroup(productGroup, productIds) + } + } +} + +/** + * @param {{product_ids: any}} productGroup + * @param {{id: string, name: string}[]} extProductIds + */ +const extendProductGroup = (productGroup, extProductIds) => { + if (productGroup) { + const extendedProductIds = [] + let productIds = productGroup.product_ids + if (productIds) { + for (let i = 0; i < productIds.length; ++i) { + let productId = productIds[i] + if (productId) { + extendedProductIds.push({ + id: productId, + name: extProductIds.find((e) => e.id === productId)?.name ?? '', + }) + } + } + } + productGroup.product_ids = extendedProductIds + } +} + +/** + * @param {any} refs + * @param {{id: string}[]} extendedScoreIds + * @param {{id: string; name: string}[]} productIds + */ +const extendProductStatus = (refs, extendedScoreIds, productIds) => { + const extendedProductStatus = [] + if (refs) { + for (let i = 0; i < refs.length; ++i) { + let ref = refs[i] + if (ref) { + extendedProductStatus.push( + extendedScoreIds.find((e) => e.id === ref) ?? { + id: ref, + name: + productIds.find((productId) => productId.id === ref)?.name ?? '', + }, + ) + } + } + } + return extendedProductStatus +} + +/** + * @param {{id: string; name: string}[]} extendedProductStatusList + * @param {{flags?: {label: string; product_ids?: string[]; group_ids?: string[]}[]}} vulnerability + * @param {{group_id: string; product_ids?: string[]}[]} productGroups + */ +const addFlags = (extendedProductStatusList, vulnerability, productGroups) => { + extendedProductStatusList?.forEach((/** @type {any} */ eps) => { + const groups = productGroups + ?.filter((group) => group.product_ids?.includes(eps.id)) + .map((group) => group.group_id) + eps.flags = vulnerability.flags + ?.filter( + (f) => + f.product_ids?.includes(eps.id) || + f.group_ids?.some((id) => groups?.includes(id)), + ) + .map((f) => f.label) + }) +} + +/** + * @param {{product_ids: any, group_ids: any}} remediationOrThreat + * @param {{id: string, name: string}[]} extProductIds + * @param {{id: string, name: string}[]} extGroupIds + */ +const extendRemediationOrThreat = ( + remediationOrThreat, + extProductIds, + extGroupIds, +) => { + if (remediationOrThreat) { + const extendedProductIds = [] + let productIds = remediationOrThreat.product_ids + if (productIds) { + for (let i = 0; i < productIds.length; ++i) { + let productId = productIds[i] + if (productId) { + extendedProductIds.push({ + id: productId, + name: extProductIds.find((e) => e.id === productId)?.name ?? '', + }) + } + } + } + remediationOrThreat.product_ids = extendedProductIds + + const extendedGroupIds = [] + let groupIds = remediationOrThreat.group_ids + if (groupIds) { + for (let i = 0; i < groupIds.length; ++i) { + let groupId = groupIds[i] + if (groupId) { + extendedGroupIds.push({ + id: groupId, + name: extGroupIds.find((e) => e.id === groupId)?.name ?? '', + }) + } + } + } + remediationOrThreat.group_ids = extendedGroupIds + } +} + +/** + * @param {{date: string}} a + * @param {{date: string}} b + */ +const sortByDate = (a, b) => { + if (!a && !b) return 0 + if (!a) return 1 + if (!b) return -1 + return new Date(b.date).getTime() - new Date(a.date).getTime() +} + +/** + * @param {any} vulnerability + * @param {any} productIds + * @param {any} groupIds + */ +const addRemediationsPreviewAttributes = ( + vulnerability, + productIds, + groupIds, +) => { + const vendorFix = [] + const mitigation = [] + const workaround = [] + const noneAvailable = [] + const noFixPlanned = [] + const unknown = [] + const remediations = vulnerability.remediations + if (remediations) { + for (let i = 0; i < remediations.length; ++i) { + const remediation = remediations[i] + extendRemediationOrThreat(remediation, productIds, groupIds) + switch (remediation.category) { + case 'vendor_fix': + vendorFix.push(remediation) + break + case 'mitigation': + mitigation.push(remediation) + break + case 'workaround': + workaround.push(remediation) + break + case 'none_available': + noneAvailable.push(remediation) + break + case 'no_fix_planned': + noFixPlanned.push(remediation) + break + default: + unknown.push(remediation) + } + } + } + + vulnerability.remediations_vendor_fix = vendorFix.sort(sortByDate) + vulnerability.remediations_mitigation = mitigation.sort(sortByDate) + vulnerability.remediations_workaround = workaround.sort(sortByDate) + vulnerability.remediations_none_available = noneAvailable.sort(sortByDate) + vulnerability.remediations_no_fix_planned = noFixPlanned.sort(sortByDate) + vulnerability.remediations_unknown = unknown.sort(sortByDate) +} + +/** + * @param {any} vulnerability + * @param {any} productIds + * @param {any} groupIds + */ +const addThreatsPreviewAttributes = (vulnerability, productIds, groupIds) => { + const exploitStatus = [] + const impact = [] + const targetSet = [] + const unknown = [] + const threats = vulnerability.threats + if (threats) { + for (let i = 0; i < threats.length; ++i) { + const threat = threats[i] + extendRemediationOrThreat(threat, productIds, groupIds) + switch (threat.category) { + case 'exploit_status': + exploitStatus.push(threat) + break + case 'impact': + impact.push(threat) + break + case 'target_set': + targetSet.push(threat) + break + default: + unknown.push(threat) + } + } + } + + vulnerability.threats_exploit_status = exploitStatus.sort(sortByDate) + vulnerability.threats_impact = impact.sort(sortByDate) + vulnerability.threats_target_set = targetSet.sort(sortByDate) + vulnerability.threats_unknown = unknown.sort(sortByDate) +} + +// --------------------------------------------------------------------------- +// V2.0-specific helpers +// --------------------------------------------------------------------------- + +/** + * Retrieve the maximum baseScore from vulnerability.scores[i].cvss_v3 (CSAF 2.0) + * + * @param {{scores: {cvss_v3: {baseScore: string}}[]}[]} vulnerabilities + */ +const retrieveMaxBaseScoreV2_0 = (vulnerabilities) => { + if (!vulnerabilities) return '0' + let maxBaseScore = 0 + for (let i = 0; i < vulnerabilities.length; ++i) { + const vulnerability = vulnerabilities[i] + const scores = vulnerability.scores + if (scores) { + for (let j = 0; j < scores.length; ++j) { + const score = scores[j] + const baseScore = Number(score.cvss_v3?.baseScore) + if (maxBaseScore < baseScore) { + maxBaseScore = baseScore + } + } + } + } + return maxBaseScore.toString() +} + +/** + * Collect all product ids with CVSS data from vulnerability.scores (CSAF 2.0) + * + * @param {any} scores + * @param {{id: string, name: string}[]} productIds + */ +const createExtendedScoreIds = (scores, productIds) => { + const extendedProductIds = [] + if (scores) { + for (let i = 0; i < scores.length; ++i) { + const score = scores[i] + const products = score.products + if (products) { + for (let j = 0; j < products.length; ++j) { + const productId = products[j] + if (productId) { + extendedProductIds.push({ + id: productId, + name: productIds.find((e) => e.id === productId)?.name ?? '', + vectorString: score.cvss_v3?.vectorString ?? '', + baseScore: score.cvss_v3?.baseScore ?? '', + }) + } + } + } + } + } + return extendedProductIds +} + +/** + * @param {{scores: [], product_status: any, flags?: {label: string; product_ids?: string[]}[]}} vulnerability + * @param {any} productIds + * @param {any} productGroups + */ +const addProductStatusPreviewAttributesV2_0 = ( + vulnerability, + productIds, + productGroups, +) => { + const extendedScoreIds = createExtendedScoreIds( + vulnerability.scores, + productIds, + ) + const productStatus = vulnerability.product_status + if (productStatus) { + productStatus.known_affected = extendProductStatus( + productStatus.known_affected, + extendedScoreIds, + productIds, + ) + productStatus.first_affected = extendProductStatus( + productStatus.first_affected, + extendedScoreIds, + productIds, + ) + productStatus.last_affected = extendProductStatus( + productStatus.last_affected, + extendedScoreIds, + productIds, + ) + productStatus.known_not_affected = extendProductStatus( + productStatus.known_not_affected, + extendedScoreIds, + productIds, + ) + addFlags(productStatus.known_not_affected, vulnerability, productGroups) + productStatus.recommended = extendProductStatus( + productStatus.recommended, + extendedScoreIds, + productIds, + ) + productStatus.fixed = extendProductStatus( + productStatus.fixed, + extendedScoreIds, + productIds, + ) + productStatus.first_fixed = extendProductStatus( + productStatus.first_fixed, + extendedScoreIds, + productIds, + ) + productStatus.under_investigation = extendProductStatus( + productStatus.under_investigation, + extendedScoreIds, + productIds, + ) + } +} + +// --------------------------------------------------------------------------- +// V2.1-specific helpers +// --------------------------------------------------------------------------- + +/** + * A single CSAF 2.1 metric's `content` can carry CVSS v2, v3, and/or v4 data + * simultaneously (all three are optional, independent siblings in the CSAF + * 2.1 schema). This picks the highest-version one present, since it's + * generally the most current/authoritative score for a given metric. + * + * @param {{cvss_v2?: {version: string, baseScore: number, vectorString: string}, cvss_v3?: {version: string, baseScore: number, vectorString: string}, cvss_v4?: {version: string, baseScore: number, vectorString: string}} | undefined} content + * @returns {{version: string, baseScore: number, vectorString: string} | undefined} + */ +const selectPreferredCvss = (content) => + content?.cvss_v4 ?? content?.cvss_v3 ?? content?.cvss_v2 + +/** + * Retrieve the maximum baseScore across vulnerability.metrics[i].content + * (CSAF 2.1), preferring CVSS v4, then v3, then v2 when a metric has more + * than one CVSS version present. + * + * @param {{metrics: {content: any}[]}[]} vulnerabilities + */ +const retrieveMaxBaseScoreV2_1 = (vulnerabilities) => { + let maxBaseScore = 0 + if (vulnerabilities) { + for (let i = 0; i < vulnerabilities.length; ++i) { + const vulnerability = vulnerabilities[i] + const metrics = vulnerability.metrics + if (metrics) { + for (let j = 0; j < metrics.length; ++j) { + const metric = metrics[j] + const baseScore = Number( + selectPreferredCvss(metric?.content)?.baseScore, + ) + if (maxBaseScore < baseScore) { + maxBaseScore = baseScore + } + } + } + } + } + return maxBaseScore.toString() +} + +/** + * Collect all product ids with CVSS data from vulnerability.metrics (CSAF + * 2.1), preferring CVSS v4, then v3, then v2 when a metric has more than one + * CVSS version present. + * + * @param {any} metrics + * @param {{id: string, name: string}[]} productIds + */ +const createExtendedMetricsIds = (metrics, productIds) => { + const extendedProductIds = [] + if (metrics) { + for (let i = 0; i < metrics.length; ++i) { + const metric = metrics[i] + const products = metric.products + const cvss = selectPreferredCvss(metric.content) + if (products) { + for (let j = 0; j < products.length; ++j) { + const productId = products[j] + if (productId) { + extendedProductIds.push({ + id: productId, + name: productIds.find((e) => e.id === productId)?.name ?? '', + vectorString: cvss?.vectorString ?? '', + baseScore: cvss?.baseScore ?? '', + cvssVersion: cvss?.version ?? '', + }) + } + } + } + } + } + return extendedProductIds +} + +/** + * @param {{metrics: [], product_status: any, flags?: {label: string; product_ids?: string[]}[]}} vulnerability + * @param {any} productIds + * @param {any} productGroups + */ +const addProductStatusPreviewAttributesV2_1 = ( + vulnerability, + productIds, + productGroups, +) => { + const extendedMetricIds = createExtendedMetricsIds( + vulnerability.metrics, + productIds, + ) + const productStatus = vulnerability.product_status + if (productStatus) { + productStatus.known_affected = extendProductStatus( + productStatus.known_affected, + extendedMetricIds, + productIds, + ) + productStatus.first_affected = extendProductStatus( + productStatus.first_affected, + extendedMetricIds, + productIds, + ) + productStatus.last_affected = extendProductStatus( + productStatus.last_affected, + extendedMetricIds, + productIds, + ) + productStatus.known_not_affected = extendProductStatus( + productStatus.known_not_affected, + extendedMetricIds, + productIds, + ) + addFlags(productStatus.known_not_affected, vulnerability, productGroups) + productStatus.recommended = extendProductStatus( + productStatus.recommended, + extendedMetricIds, + productIds, + ) + productStatus.fixed = extendProductStatus( + productStatus.fixed, + extendedMetricIds, + productIds, + ) + productStatus.first_fixed = extendProductStatus( + productStatus.first_fixed, + extendedMetricIds, + productIds, + ) + productStatus.under_investigation = extendProductStatus( + productStatus.under_investigation, + extendedMetricIds, + productIds, + ) + } +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +/** + * Enriches a CSAF 2.0 document for use with HTMLTemplate2_0. + * Uses `vulnerability.scores[i].cvss_v3` for CVSS data. + * + * @param {any} document + * @returns {{ document: any }} + */ +export function enrichDocumentV2_0(document) { + const templateDoc = cloneDeep(document) + const productIds = collectProductIds(templateDoc) + const groupIds = collectGroupIds(templateDoc) + + if (templateDoc.document) { + templateDoc.document.max_base_score = retrieveMaxBaseScoreV2_0( + templateDoc.vulnerabilities, + ) + addDocumentNotesPreviewAttributes(templateDoc.document) + } + + if (templateDoc.product_tree) { + addProductTreePreviewAttributes(templateDoc.product_tree, productIds) + } + + const vulnerabilities = templateDoc.vulnerabilities + if (vulnerabilities) { + for (let i = 0; i < vulnerabilities.length; ++i) { + const vulnerability = vulnerabilities[i] + addProductStatusPreviewAttributesV2_0( + vulnerability, + productIds, + document.product_tree?.product_groups, + ) + addRemediationsPreviewAttributes(vulnerability, productIds, groupIds) + addThreatsPreviewAttributes(vulnerability, productIds, groupIds) + addVulnerabilityNotesPreviewAttributes(vulnerability) + } + } + + templateDoc.removeTrailingComma = removeTrailingComma + templateDoc.upperCase = upperCase + templateDoc.replaceUnderscores = replaceUnderscores + templateDoc.secureHref = secureHref + + return { document: templateDoc } +} + +/** + * Enriches a CSAF 2.1 document for use with HTMLTemplate2_1. + * Uses `vulnerability.metrics[i].content.cvss_v3` for CVSS data. + * + * @param {any} document + * @returns {{ document: any }} + */ +export function enrichDocumentV2_1(document) { + const templateDoc = cloneDeep(document) + const productIds = collectProductIds(templateDoc) + const groupIds = collectGroupIds(templateDoc) + + if (templateDoc.document) { + templateDoc.document.max_base_score = retrieveMaxBaseScoreV2_1( + templateDoc.vulnerabilities, + ) + addDocumentNotesPreviewAttributes(templateDoc.document) + } + + if (templateDoc.product_tree) { + addProductTreePreviewAttributes(templateDoc.product_tree, productIds) + } + + const vulnerabilities = templateDoc.vulnerabilities + if (vulnerabilities) { + for (let i = 0; i < vulnerabilities.length; ++i) { + const vulnerability = vulnerabilities[i] + addProductStatusPreviewAttributesV2_1( + vulnerability, + productIds, + document.product_tree?.product_groups, + ) + addRemediationsPreviewAttributes(vulnerability, productIds, groupIds) + addThreatsPreviewAttributes(vulnerability, productIds, groupIds) + addVulnerabilityNotesPreviewAttributes(vulnerability) + } + } + + templateDoc.removeTrailingComma = removeTrailingComma + templateDoc.upperCase = upperCase + templateDoc.replaceUnderscores = replaceUnderscores + templateDoc.secureHref = secureHref + + return { document: templateDoc } +} From 8d5676bc28ab73e601e185d47ff05aa1d8d846db Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:39:46 +0200 Subject: [PATCH 04/13] add markdown rendering for CSAF text fields --- lib/markdownParser.js | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 lib/markdownParser.js diff --git a/lib/markdownParser.js b/lib/markdownParser.js new file mode 100644 index 0000000..3c6e234 --- /dev/null +++ b/lib/markdownParser.js @@ -0,0 +1,90 @@ +import lodash from 'lodash' +import { micromark } from 'micromark' +import { gfm, gfmHtml } from 'micromark-extension-gfm' + +const { get, set } = lodash + +/** + * Fields which will be parsed by the markdown parser. + * These fields must not be HTML-escaped in the template — use {{{…}}} instead of {{…}}. + * + * @type {string[]} + */ +const markdownFields = [ + 'document.acknowledgments.*.summary', + 'document.distribution.text', + 'document.notes.*.text', + 'document.publisher.issuing_authority', + 'document.references.*.summary', + 'document.tracking.revision_history.*.summary', + 'product_tree.product_groups.*.summary', + 'vulnerabilities.*.acknowledgments.*.summary', + 'vulnerabilities.*.involvements.*.summary', + 'vulnerabilities.*.notes.*.text', + 'vulnerabilities.*.references.*.summary', + 'vulnerabilities.*.remediations.*.details', + 'vulnerabilities.*.remediations.*.entitlements.*', + 'vulnerabilities.*.remediations.*.restart_required.details', + 'vulnerabilities.*.threats.*.details', +] + +/** + * @param {object} object + * @param {string} key Nested key e.g `a.b.*.c` + * @param {(value: any) => any} modifierFunction + * @returns {void} + */ +const modifyNestedValues = (object, key, modifierFunction) => { + // check if key includes nested array + if (key.indexOf('.*') !== -1) { + const keyParts = key.split('.*') + // get nested key without leading `.` + const newKey = key.substring(keyParts[0].length + 2).replace(/^\./, '') + // get arraylike object + const elem = get(object, keyParts[0]) + + if (newKey) { + // call function recursively for all array entries + for (const x of elem ?? []) { + if (x instanceof Object) { + modifyNestedValues(x, newKey, modifierFunction) + } + } + } else if (elem) { + // apply modifierfunction to all array entries + set( + object, + keyParts[0], + elem.map((/** @type {any} */ v) => modifierFunction(v)), + ) + } + } else { + // apply modifierfunction to nested value + const prevValue = get(object, key) + if (prevValue) { + set(object, key, modifierFunction(prevValue)) + } + } +} + +/** + * Parses selected markdown fields in the document. + * + * @param {object} doc + * @returns {object} + */ +export function renderMarkdown(doc) { + markdownFields.forEach((field) => + modifyNestedValues(doc, field, (x) => { + const parsedX = micromark(x, { + extensions: [gfm()], + htmlExtensions: [gfmHtml()], + }) + + // only return parsed value if markdown was used + return x === parsedX.match(/^

(.*)<\/p>$/)?.[1] ? x : parsedX + }), + ) + + return doc +} From 2d8c82e73a6ddfc2a7c0cf44f3a7b1ed65e34756 Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:40:37 +0200 Subject: [PATCH 05/13] add Mustache templates and rendering for CSAF 2.0 and 2.1 --- index.js | 4 + lib/HTMLTemplate2_0.js | 109 ++++++++++++ lib/HTMLTemplate2_1.js | 111 ++++++++++++ lib/mustacheHelpers.js | 71 ++++++++ lib/templates/csaf_2_0.js | 353 ++++++++++++++++++++++++++++++++++++ lib/templates/csaf_2_1.js | 363 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 1011 insertions(+) create mode 100644 index.js create mode 100644 lib/HTMLTemplate2_0.js create mode 100644 lib/HTMLTemplate2_1.js create mode 100644 lib/mustacheHelpers.js create mode 100644 lib/templates/csaf_2_0.js create mode 100644 lib/templates/csaf_2_1.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..cd01c6b --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +export { enrichDocumentV2_0, enrichDocumentV2_1 } from './lib/enrichDocument.js' +export { default as HTMLTemplate2_0 } from './lib/HTMLTemplate2_0.js' +export { default as HTMLTemplate2_1 } from './lib/HTMLTemplate2_1.js' +export { renderMarkdown } from './lib/markdownParser.js' diff --git a/lib/HTMLTemplate2_0.js b/lib/HTMLTemplate2_0.js new file mode 100644 index 0000000..59bfcf6 --- /dev/null +++ b/lib/HTMLTemplate2_0.js @@ -0,0 +1,109 @@ +import Mustache from 'mustache' +import Template from './templates/csaf_2_0.js' + +const PRODUCT_STATUS_HEADER = ` + + + Product + CVSS-Vector + CVSS Base Score + +` + +const PRODUCT_STATUS_ROW = ` + + {{name}} + {{vectorString}} + {{baseScore}} +` + +const REMEDIATION = ` +

{{#replaceUnderscores}}{{#upperCase}}{{category}}{{/upperCase}}{{/replaceUnderscores}}{{#date}} ({{.}}){{/date}}
+

{{{details}}}

+{{#product_ids.length}} +
For products:
+
    + {{#product_ids}} +
  • {{name}}
  • + {{/product_ids}} +
+{{/product_ids.length}} +{{#group_ids.length}} +
For groups:
+
    + {{#group_ids}} +
  • {{name}}
  • + {{/group_ids}} +
+{{/group_ids.length}} +

{{#url}}{{> url }}{{/url}}

+{{#entitlements}} +

{{{.}}}

+{{/entitlements}} +{{#restart_required}} + Restart required: {{category}} +

{{{details}}}

+{{/restart_required}}` + +const THREAT = ` +
{{#replaceUnderscores}}{{#upperCase}}{{category}}{{/upperCase}}{{/replaceUnderscores}}{{#date}} ({{.}}){{/date}}
+

{{{details}}}

+{{#product_ids.length}} +
For products:
+
    + {{#product_ids}} +
  • {{name}}
  • + {{/product_ids}} +
+{{/product_ids.length}} +{{#group_ids.length}} +
For groups:
+
    + {{#group_ids}} +
  • {{name}}
  • + {{/group_ids}} +
+{{/group_ids.length}}` + +const VULNERABILITY_NOTE = ` +{{#title}}{{.}}{{/title}}{{#audience}} ({{.}}){{/audience}} +{{#text}}

{{{text}}}

{{/text}}` + +const DOCUMENT_NOTE = ` +{{#title}}

{{.}}

{{/title}} +{{#audience}}{{.}}{{/audience}} +{{#text}}

{{{text}}}

{{/text}}` +const ACKNOWLEDGEMENT = ` +{{#.}} +
  • {{#removeTrailingComma}}{{#names}}{{.}}, {{/names}}{{/removeTrailingComma}}{{#organization}}{{#names.length}} from {{/names.length}}{{.}} {{/organization}}{{#summary}} for {{{.}}}{{/summary}}{{#urls.length}} (see: {{#removeTrailingComma}}{{#urls}}{{> url}}, {{/urls}}{{/removeTrailingComma}}){{/urls.length}}
  • +{{/.}}` + +const REFERENCE = ` +{{#.}} +
  • {{{summary}}} {{#category}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/category}}{{#url}}{{> url}}{{/url}}
  • +{{/.}}` + +const URL = ` +{{#.}} + {{.}} +{{/.}} +` + +/** + * Encapsulates the rendering of the mustache template. + * + * @param {{ document: {} }} props + */ +export default function HTMLTemplate2_0({ document }) { + return Mustache.render(Template, document, { + product_status_header: PRODUCT_STATUS_HEADER, + product_status_row: PRODUCT_STATUS_ROW, + remediation: REMEDIATION, + threat: THREAT, + vulnerability_note: VULNERABILITY_NOTE, + document_note: DOCUMENT_NOTE, + acknowledgment: ACKNOWLEDGEMENT, + reference: REFERENCE, + url: URL, + }) +} diff --git a/lib/HTMLTemplate2_1.js b/lib/HTMLTemplate2_1.js new file mode 100644 index 0000000..dd83016 --- /dev/null +++ b/lib/HTMLTemplate2_1.js @@ -0,0 +1,111 @@ +import Mustache from 'mustache' +import Template from './templates/csaf_2_1.js' + +const PRODUCT_STATUS_HEADER = ` + + + Product + CVSS Version + CVSS-Vector + CVSS Base Score + +` + +const PRODUCT_STATUS_ROW = ` + + {{name}} + {{cvssVersion}} + {{vectorString}} + {{baseScore}} +` + +const REMEDIATION = ` +
    {{#replaceUnderscores}}{{#upperCase}}{{category}}{{/upperCase}}{{/replaceUnderscores}}{{#date}} ({{.}}){{/date}}
    +

    {{{details}}}

    +{{#product_ids.length}} +
    For products:
    +
      + {{#product_ids}} +
    • {{name}}
    • + {{/product_ids}} +
    +{{/product_ids.length}} +{{#group_ids.length}} +
    For groups:
    +
      + {{#group_ids}} +
    • {{name}}
    • + {{/group_ids}} +
    +{{/group_ids.length}} +

    {{#url}}{{> url }}{{/url}}

    +{{#entitlements}} +

    {{{.}}}

    +{{/entitlements}} +{{#restart_required}} + Restart required: {{category}} +

    {{{details}}}

    +{{/restart_required}}` + +const THREAT = ` +
    {{#replaceUnderscores}}{{#upperCase}}{{category}}{{/upperCase}}{{/replaceUnderscores}}{{#date}} ({{.}}){{/date}}
    +

    {{{details}}}

    +{{#product_ids.length}} +
    For products:
    +
      + {{#product_ids}} +
    • {{name}}
    • + {{/product_ids}} +
    +{{/product_ids.length}} +{{#group_ids.length}} +
    For groups:
    +
      + {{#group_ids}} +
    • {{name}}
    • + {{/group_ids}} +
    +{{/group_ids.length}}` + +const VULNERABILITY_NOTE = ` +{{#title}}{{.}}{{/title}}{{#audience}} ({{.}}){{/audience}} +{{#text}}

    {{{text}}}

    {{/text}}` + +const DOCUMENT_NOTE = ` +{{#title}}

    {{.}}

    {{/title}} +{{#audience}}{{.}}{{/audience}} +{{#text}}

    {{{text}}}

    {{/text}}` +const ACKNOWLEDGEMENT = ` +{{#.}} +
  • {{#removeTrailingComma}}{{#names}}{{.}}, {{/names}}{{/removeTrailingComma}}{{#organization}}{{#names.length}} from {{/names.length}}{{.}} {{/organization}}{{#summary}} for {{{.}}}{{/summary}}{{#urls.length}} (see: {{#removeTrailingComma}}{{#urls}}{{> url}}, {{/urls}}{{/removeTrailingComma}}){{/urls.length}}
  • +{{/.}}` + +const REFERENCE = ` +{{#.}} +
  • {{{summary}}} {{#category}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/category}}{{#url}}{{> url}}{{/url}}
  • +{{/.}}` + +const URL = ` +{{#.}} + {{.}} +{{/.}} +` + +/** + * Encapsulates the rendering of the mustache template. + * + * @param {{ document: {} }} props + */ +export default function HTMLTemplate2_1({ document }) { + return Mustache.render(Template, document, { + product_status_header: PRODUCT_STATUS_HEADER, + product_status_row: PRODUCT_STATUS_ROW, + remediation: REMEDIATION, + threat: THREAT, + vulnerability_note: VULNERABILITY_NOTE, + document_note: DOCUMENT_NOTE, + acknowledgment: ACKNOWLEDGEMENT, + reference: REFERENCE, + url: URL, + }) +} diff --git a/lib/mustacheHelpers.js b/lib/mustacheHelpers.js new file mode 100644 index 0000000..0d5121b --- /dev/null +++ b/lib/mustacheHelpers.js @@ -0,0 +1,71 @@ +/** + * Returns a Mustache lambda that removes the last trailing comma from rendered text. + * + * @returns {(text: string, render: (text: string) => string) => string} + */ +export function removeTrailingComma() { + return function (/** @type {string} */ text, /** @type {function} */ render) { + var textWithTrailingComma = /** @type {string} */ (render(text)) + const lastIndex = textWithTrailingComma.lastIndexOf(',') + return lastIndex > 0 + ? textWithTrailingComma.substring(0, lastIndex) + : textWithTrailingComma + } +} + +/** + * Returns a Mustache lambda that capitalises the first character of rendered text. + * + * @returns {(text: string, render: (text: string) => string) => string} + */ +export function upperCase() { + return function (/** @type {string} */ text, /** @type {function} */ render) { + var renderedText = /** @type {string} */ (render(text)) + return renderedText.charAt(0).toUpperCase() + renderedText.slice(1) + } +} + +/** + * Returns a Mustache lambda that replaces all underscores with spaces in rendered text. + * + * @returns {(text: string, render: (text: string) => string) => string} + */ +export function replaceUnderscores() { + return function (/** @type {string} */ text, /** @type {function} */ render) { + var renderedText = /** @type {string} */ (render(text)) + return renderedText.replaceAll('_', ' ') + } +} + +/** + * Returns a Mustache lambda that emits a safe `href="…"` attribute for the rendered URL, + * or an empty string when the URL scheme is not on the allow-list. + * + * @returns {(text: string, render: (text: string) => string) => string} + */ +export function secureHref() { + return function (/** @type {string} */ text, /** @type {function} */ render) { + const href = render(text) + let isValid = false + + const validStarts = ['#', 'mailto', 'tel', 'http', 'ftp'] + const validMimeTypes = [ + 'image/png;base64,', + 'image/jpeg;base64,', + 'image/gif;base64,', + ].map((x) => x.replaceAll('/', '/')) + validStarts.forEach((x) => (isValid = isValid || href.startsWith(x))) + const isBase64 = (/** @type {string} */ value) => + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/.test( + value, + ) + validMimeTypes.forEach((mimeType) => { + const isValidDataHref = + href.startsWith(`data:${mimeType}`) && + isBase64(href.split(',')[1]?.replaceAll('=', '=')) + isValid = isValid || isValidDataHref + }) + + return isValid ? `href="${href}"` : '' + } +} diff --git a/lib/templates/csaf_2_0.js b/lib/templates/csaf_2_0.js new file mode 100644 index 0000000..a69de55 --- /dev/null +++ b/lib/templates/csaf_2_0.js @@ -0,0 +1,353 @@ +import gutenbergCss from '../css/gutenberg.js' +import modernCss from '../css/modern.js' +import previewCss from '../css/preview.js' + +export default ` + + + + + + + + + + +

    {{#document.tracking.id}}{{document.tracking.id}}: {{/document.tracking.id}}{{document.title}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Publisher: {{document.publisher.name}}Document category: {{document.category}}
    Initial release date: {{document.tracking.initial_release_date}}Engine: {{#document.tracking.generator.engine}}{{name}}{{#version}} {{.}}{{/version}}{{/document.tracking.generator.engine}}
    Current release date: {{document.tracking.current_release_date}}Build Date: {{document.tracking.generator.date}}
    Current version: {{document.tracking.version}}Status: {{document.tracking.status}}
    CVSSv3.1 Base Score: {{document.max_base_score}}Severity: + {{#document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} + {{^document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} +
    Original language: {{document.source_lang}}Language: {{document.lang}}
    Also referred to: {{#removeTrailingComma}}{{#document.tracking.aliases}}{{.}}, {{/document.tracking.aliases}}{{/removeTrailingComma}}
    + + {{#document.notes_summary}} + {{> document_note}} + {{/document.notes_summary}} + {{#document.notes_details}} + {{> document_note}} + {{/document.notes_details}} + {{#document.notes_general}} + {{> document_note}} + {{/document.notes_general}} + {{#document.notes_description}} + {{> document_note}} + {{/document.notes_description}} + {{#document.notes_faq}} + {{> document_note}} + {{/document.notes_faq}} + {{#document.notes_unknown}} + {{> document_note}} + {{/document.notes_unknown}} + + {{#product_tree.product_groups.length}} +

    Product groups

    + {{#product_tree.product_groups}} + {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{group_id}}{{/summary}} +
      + {{#product_ids}} +
    • {{name}}
    • + {{/product_ids}} +
    + {{/product_tree.product_groups}} + {{/product_tree.product_groups.length}} + +

    Vulnerabilities

    + {{#vulnerabilities}} +

    {{title}}{{#cve}} ({{.}}){{/cve}}

    + + {{#notes_summary}} + {{> vulnerability_note}} + {{/notes_summary}} + {{#notes_details}} + {{> vulnerability_note}} + {{/notes_details}} + {{#notes_general}} + {{> vulnerability_note}} + {{/notes_general}} + {{#notes_description}} + {{> vulnerability_note}} + {{/notes_description}} + {{#notes_other}} + {{> vulnerability_note}} + {{/notes_other}} + {{#notes_faq}} + {{> vulnerability_note}} + {{/notes_faq}} + {{#notes_unknown}} + {{> vulnerability_note}} + {{/notes_unknown}} + + + + + + + {{#id}} + + + + + {{/id}} + {{#discovery_date}} + + + + + {{/discovery_date}} + {{#release_date}} + + + + + {{/release_date}} +
    CWE:{{#cwe}}{{id}}:{{name}}{{/cwe}}
    ID:{{text}}{{#system_name}} ({{.}}){{/system_name}}
    Discovery date:{{discovery_date}}
    Release date:{{release_date}}
    + +

    Product status

    + {{#product_status.known_affected.length}} +
    Known affected
    + + {{> product_status_header}} + + {{#product_status.known_affected}} + {{> product_status_row}} + {{/product_status.known_affected}} + +
    + {{/product_status.known_affected.length}} + + {{#product_status.first_affected.length}} +
    First affected
    + + {{> product_status_header}} + + {{#product_status.first_affected}} + {{> product_status_row}} + {{/product_status.first_affected}} + +
    + {{/product_status.first_affected.length}} + + {{#product_status.last_affected.length}} +
    Last affected
    + + {{> product_status_header}} + + {{#product_status.last_affected}} + {{> product_status_row}} + {{/product_status.last_affected}} + +
    + {{/product_status.last_affected.length}} + + {{#product_status.known_not_affected.length}} +
    Known not affected
    +
      + {{#product_status.known_not_affected}} +
    • + {{name}}{{#flags.length}} ({{#removeTrailingComma}}{{#flags}}{{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}, {{/flags}}{{/removeTrailingComma}}){{/flags.length}} +
    • + {{/product_status.known_not_affected}} +
    + {{/product_status.known_not_affected.length}} + + {{#product_status.recommended.length}} +
    Recommended
    +
      + {{#product_status.recommended}} +
    • {{name}}
    • + {{/product_status.recommended}} +
    + {{/product_status.recommended.length}} + + {{#product_status.fixed.length}} +
    Fixed
    +
      + {{#product_status.fixed}} +
    • {{name}}
    • + {{/product_status.fixed}} +
    + {{/product_status.fixed.length}} + + {{#product_status.first_fixed.length}} +
    First fixed
    +
      + {{#product_status.first_fixed}} +
    • {{name}}
    • + {{/product_status.first_fixed}} +
    + {{/product_status.first_fixed.length}} + + {{#product_status.under_investigation.length}} +
    Under investgation
    +
      + {{#product_status.under_investigation}} +
    • {{name}}
    • + {{/product_status.under_investigation}} +
    + {{/product_status.under_investigation.length}} + + {{#remediations.length}} +

    Remediations

    + {{#remediations_vendor_fix}} + {{> remediation}} + {{/remediations_vendor_fix}} + {{#remediations_mitigation}} + {{> remediation}} + {{/remediations_mitigation}} + {{#remediations_workaround}} + {{> remediation}} + {{/remediations_workaround}} + {{#remediations_none_available}} + {{> remediation}} + {{/remediations_none_available}} + {{#remediations_no_fix_planned}} + {{> remediation}} + {{/remediations_no_fix_planned}} + {{#remediations_unknown}} + {{> remediation}} + {{/remediations_unknown}} + {{/remediations.length}} + + {{#acknowledgments.length}} +

    Acknowledgments

    +
      + {{#acknowledgments}} + {{> acknowledgment}} + {{/acknowledgments}} +
    + {{/acknowledgments.length}} + + {{#involvements.length}} +

    Involvement

    +
      + {{#involvements}} + {{#.}} +
    • {{#date}}{{.}} {{/date}}{{#upperCase}}{{party}}{{/upperCase}}{{#status}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/status}}{{#summary}}: {{{.}}}{{/summary}}
    • + {{/.}} + {{/involvements}} +
    + {{/involvements.length}} + + {{#references.length}} +

    References

    +
      + {{#references}} + {{> reference}} + {{/references}} +
    + {{/references.length}} + + {{#threats.length}} +

    Threats

    + {{#threats_exploit_status}} + {{> threat}} + {{/threats_exploit_status}} + {{#threats_impact}} + {{> threat}} + {{/threats_impact}} + {{#threats_target_set}} + {{> threat}} + {{/threats_target_set}} + {{#threats_unknown}} + {{> threat}} + {{/threats_unknown}} + {{/threats.length}} + + {{/vulnerabilities}} + + {{#document.acknowledgments.length}} +

    Acknowledgments

    + {{document.publisher.name}} thanks the following parties for their efforts: +
      + {{#document.acknowledgments}} + {{> acknowledgment}} + {{/document.acknowledgments}} +
    + {{/document.acknowledgments.length}} + + {{#document.notes_other}} + {{> document_note}} + {{/document.notes_other}} + + {{#document.publisher}} +

    {{name}}

    +

    Namespace: {{namespace}}

    +

    {{contact_details}}

    +

    {{{issuing_authority}}}

    + {{/document.publisher}} + + {{#document.references.length}} +

    References

    +
      + {{#document.references}} + {{> reference}} + {{/document.references}} +
    + {{/document.references.length}} + +

    Revision history

    + + + + + + + + + + {{#document.tracking.revision_history}} + + + + + + {{/document.tracking.revision_history}} + +
    VersionDate of the revisionSummary of the revision
    {{number}}{{date}}{{{summary}}}
    + + {{#document.distribution}} +

    Sharing rules

    +

    + {{#tlp}} + {{#label}}TLP:{{.}}{{/label}}
    + For the TLP version see: {{#url}}{{.}}{{/url}}{{^url}}https://www.first.org/tlp/{{/url}} + {{/tlp}} +

    +

    {{{text}}}

    + {{/document.distribution}} + + {{#document.notes_legal_disclaimer}} + {{> document_note}} + {{/document.notes_legal_disclaimer}} + + + +` diff --git a/lib/templates/csaf_2_1.js b/lib/templates/csaf_2_1.js new file mode 100644 index 0000000..cf47ad9 --- /dev/null +++ b/lib/templates/csaf_2_1.js @@ -0,0 +1,363 @@ +import gutenbergCss from '../css/gutenberg.js' +import modernCss from '../css/modern.js' +import previewCss from '../css/preview.js' + +export default ` + + + + + + + + + + +

    {{$schema}}

    +

    {{#document.tracking.id}}{{document.tracking.id}}: {{/document.tracking.id}}{{document.title}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Publisher: {{document.publisher.name}}Document category: {{document.category}}
    Initial release date: {{document.tracking.initial_release_date}}Engine: {{#document.tracking.generator.engine}}{{name}}{{#version}} {{.}}{{/version}}{{/document.tracking.generator.engine}}
    Current release date: {{document.tracking.current_release_date}}Build Date: {{document.tracking.generator.date}}
    Current version: {{document.tracking.version}}Status: {{document.tracking.status}}
    CVSSv3.1 Base Score: {{document.max_base_score}}Severity: + {{#document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} + {{^document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} +
    Original language: {{document.source_lang}}Language: {{document.lang}}
    Also referred to: {{#removeTrailingComma}}{{#document.tracking.aliases}}{{.}}, {{/document.tracking.aliases}}{{/removeTrailingComma}}
    + + {{#document.notes_summary}} + {{> document_note}} + {{/document.notes_summary}} + {{#document.notes_details}} + {{> document_note}} + {{/document.notes_details}} + {{#document.notes_general}} + {{> document_note}} + {{/document.notes_general}} + {{#document.notes_description}} + {{> document_note}} + {{/document.notes_description}} + {{#document.notes_faq}} + {{> document_note}} + {{/document.notes_faq}} + {{#document.notes_unknown}} + {{> document_note}} + {{/document.notes_unknown}} + + {{#product_tree.product_groups.length}} +

    Product groups

    + {{#product_tree.product_groups}} + {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{group_id}}{{/summary}} +
      + {{#product_ids}} +
    • {{name}}
    • + {{/product_ids}} +
    + {{/product_tree.product_groups}} + {{/product_tree.product_groups.length}} + +

    Vulnerabilities

    + {{#vulnerabilities}} +

    {{title}}{{#cve}} ({{.}}){{/cve}}

    + + {{#notes_summary}} + {{> vulnerability_note}} + {{/notes_summary}} + {{#notes_details}} + {{> vulnerability_note}} + {{/notes_details}} + {{#notes_general}} + {{> vulnerability_note}} + {{/notes_general}} + {{#notes_description}} + {{> vulnerability_note}} + {{/notes_description}} + {{#notes_other}} + {{> vulnerability_note}} + {{/notes_other}} + {{#notes_faq}} + {{> vulnerability_note}} + {{/notes_faq}} + {{#notes_unknown}} + {{> vulnerability_note}} + {{/notes_unknown}} + + +

    CWE's

    + {{#cwes}} + + + + {{/cwes}} + {{#id}} + + + + + {{/id}} + {{#discovery_date}} + + + + + {{/discovery_date}} + {{#disclosure_date}} + + + + + {{/disclosure_date}} +
    {{id}}:{{name}}:{{version}}
    ID:{{text}}{{#system_name}} ({{.}}){{/system_name}}
    Discovery date:{{discovery_date}}
    Disclosure date:{{disclosure_date}}
    + +

    Product status

    + {{#product_status.known_affected.length}} +
    Known affected
    + + {{> product_status_header}} + + {{#product_status.known_affected}} + {{> product_status_row}} + {{/product_status.known_affected}} + +
    + {{/product_status.known_affected.length}} + + {{#product_status.first_affected.length}} +
    First affected
    + + {{> product_status_header}} + + {{#product_status.first_affected}} + {{> product_status_row}} + {{/product_status.first_affected}} + +
    + {{/product_status.first_affected.length}} + + {{#product_status.last_affected.length}} +
    Last affected
    + + {{> product_status_header}} + + {{#product_status.last_affected}} + {{> product_status_row}} + {{/product_status.last_affected}} + +
    + {{/product_status.last_affected.length}} + + {{#product_status.known_not_affected.length}} +
    Known not affected
    +
      + {{#product_status.known_not_affected}} +
    • + {{name}}{{#flags.length}} ({{#removeTrailingComma}}{{#flags}}{{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}, {{/flags}}{{/removeTrailingComma}}){{/flags.length}} +
    • + {{/product_status.known_not_affected}} +
    + {{/product_status.known_not_affected.length}} + + {{#product_status.recommended.length}} +
    Recommended
    +
      + {{#product_status.recommended}} +
    • {{name}}
    • + {{/product_status.recommended}} +
    + {{/product_status.recommended.length}} + + {{#product_status.fixed.length}} +
    Fixed
    +
      + {{#product_status.fixed}} +
    • {{name}}
    • + {{/product_status.fixed}} +
    + {{/product_status.fixed.length}} + + {{#product_status.first_fixed.length}} +
    First fixed
    +
      + {{#product_status.first_fixed}} +
    • {{name}}
    • + {{/product_status.first_fixed}} +
    + {{/product_status.first_fixed.length}} + + {{#product_status.under_investigation.length}} +
    Under investgation
    +
      + {{#product_status.under_investigation}} +
    • {{name}}
    • + {{/product_status.under_investigation}} +
    + {{/product_status.under_investigation.length}} + + {{#remediations.length}} +

    Remediations

    + {{#remediations_vendor_fix}} + {{> remediation}} + {{/remediations_vendor_fix}} + {{#remediations_mitigation}} + {{> remediation}} + {{/remediations_mitigation}} + {{#remediations_workaround}} + {{> remediation}} + {{/remediations_workaround}} + {{#remediations_none_available}} + {{> remediation}} + {{/remediations_none_available}} + {{#remediations_no_fix_planned}} + {{> remediation}} + {{/remediations_no_fix_planned}} + {{#remediations_unknown}} + {{> remediation}} + {{/remediations_unknown}} + {{/remediations.length}} + + {{#acknowledgments.length}} +

    Acknowledgments

    +
      + {{#acknowledgments}} + {{> acknowledgment}} + {{/acknowledgments}} +
    + {{/acknowledgments.length}} + + {{#involvements.length}} +

    Involvement

    +
      + {{#involvements}} + {{#.}} +
    • {{#date}}{{.}} {{/date}}{{#upperCase}}{{party}}{{/upperCase}}{{#status}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/status}}{{#summary}}: {{{.}}}{{/summary}}
    • + {{/.}} + {{/involvements}} +
    + {{/involvements.length}} + + {{#references.length}} +

    References

    +
      + {{#references}} + {{> reference}} + {{/references}} +
    + {{/references.length}} + + {{#threats.length}} +

    Threats

    + {{#threats_exploit_status}} + {{> threat}} + {{/threats_exploit_status}} + {{#threats_impact}} + {{> threat}} + {{/threats_impact}} + {{#threats_target_set}} + {{> threat}} + {{/threats_target_set}} + {{#threats_unknown}} + {{> threat}} + {{/threats_unknown}} + {{/threats.length}} + + {{/vulnerabilities}} + + {{#document.acknowledgments.length}} +

    Acknowledgments

    + {{document.publisher.name}} thanks the following parties for their efforts: +
      + {{#document.acknowledgments}} + {{> acknowledgment}} + {{/document.acknowledgments}} +
    + {{/document.acknowledgments.length}} + + {{#document.notes_other}} + {{> document_note}} + {{/document.notes_other}} + + {{#document.publisher}} +

    {{name}}

    +

    Namespace: {{namespace}}

    +

    {{contact_details}}

    +

    {{{issuing_authority}}}

    + {{/document.publisher}} + + {{#document.references.length}} +

    References

    +
      + {{#document.references}} + {{> reference}} + {{/document.references}} +
    + {{/document.references.length}} + +

    Revision history

    + + + + + + + + + + {{#document.tracking.revision_history}} + + + + + + {{/document.tracking.revision_history}} + +
    VersionDate of the revisionSummary of the revision
    {{number}}{{date}}{{{summary}}}
    + + {{#document.distribution}} +

    Sharing rules

    +

    + {{#sharing_group}} + Sharing Group
    +

    Id: {{id}}

    +

    Name: {{name}}

    + {{/sharing_group}} +

    +

    + {{#tlp}} + {{#label}}TLP:{{.}}{{/label}}
    + For the TLP version see: {{#url}}{{.}}{{/url}}{{^url}}https://www.first.org/tlp/{{/url}} + {{/tlp}} +

    +

    {{{text}}}

    + {{/document.distribution}} + + {{#document.notes_legal_disclaimer}} + {{> document_note}} + {{/document.notes_legal_disclaimer}} + + + +` From ea9292e553f6542662515e002d77bd7dc30799e3 Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:41:08 +0200 Subject: [PATCH 06/13] inline stylesheets into rendered HTML output --- lib/css/gutenberg.js | 11 +++++++ lib/css/gutenberg.min.css | 6 ++++ lib/css/modern.js | 6 ++++ lib/css/modern.min.css | 1 + lib/css/preview.css | 58 +++++++++++++++++++++++++++++++++++ lib/css/preview.js | 64 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 lib/css/gutenberg.js create mode 100644 lib/css/gutenberg.min.css create mode 100644 lib/css/modern.js create mode 100644 lib/css/modern.min.css create mode 100644 lib/css/preview.css create mode 100644 lib/css/preview.js diff --git a/lib/css/gutenberg.js b/lib/css/gutenberg.js new file mode 100644 index 0000000..3eefc25 --- /dev/null +++ b/lib/css/gutenberg.js @@ -0,0 +1,11 @@ +// Generated from ./gutenberg.min.css - keep in sync when updating that file. +// Exported as a JS string (rather than imported as a .css file) so it works +// identically when this package is consumed from Node (this CLI/library) +// and when it is bundled into a browser build (webpack etc.), without +// relying on any bundler-specific CSS-as-string loader. +export default `/*! + * Gutenberg + * + * MIT Fabien Sa + * https://github.com/BafS/Gutenberg + *//*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-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{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}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;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}*{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after,p:first-letter,div:first-letter,blockquote:first-letter,li:first-letter,p:first-line,div:first-line,blockquote:first-line,li:first-line{background:rgba(0,0,0,0) !important;box-shadow:none !important;text-shadow:none !important}html{font-size:16px;margin:0;padding:0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;background:#fff !important;color:#000 !important;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.5;margin:0 auto;text-rendering:optimizeLegibility}p,blockquote,table,ul,ol,dl{margin-bottom:1.5rem;margin-top:0}p:last-child,ul:last-child,ol:last-child{margin-bottom:0}h1,h2,h3,h4,h5,h6{color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;line-height:1.2;margin-bottom:.75rem;margin-top:0}h1{font-size:2.5rem}h2{font-size:2rem}h3{font-size:1.75rem}h4{font-size:1.5rem}h5{font-size:1.25rem}h6{font-size:1rem}a,a:visited{color:#000;text-decoration:underline;word-wrap:break-word}table{border-collapse:collapse}thead{display:table-header-group}table,th,td{border-bottom:1px solid #000}td,th{padding:8px 16px;page-break-inside:avoid}code,pre,kbd{border:1px solid #bbb;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:85%}code,kbd{padding:3px}pre{margin-bottom:1.5rem;padding:10px 12px}pre code,pre kbd{border:0}::-webkit-input-placeholder{color:rgba(0,0,0,0)}:-moz-placeholder{color:rgba(0,0,0,0)}::-moz-placeholder{color:rgba(0,0,0,0)}:-ms-input-placeholder{color:rgba(0,0,0,0)}blockquote{border:0;border-left:5px solid #bbb;margin-left:1px;padding:12px 1.5rem}[dir=rtl] blockquote{border-left:0;border-right:5px solid #bbb;margin-left:0;margin-right:1px}blockquote:first-child{margin-top:0}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer{display:block;font-size:80%}img{border:0;display:block;max-width:100% !important;vertical-align:middle}hr{border:0;border-bottom:2px solid #bbb;height:0;margin:2.25rem 0;padding:0}dt{font-weight:bold}dd{margin:0;margin-bottom:.75rem}abbr[title],acronym[title]{border:0;text-decoration:none}table,blockquote,pre,code,figure,li,hr,ul,ol,a,tr{page-break-inside:avoid}h2,h3,h4,p,a{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid;page-break-inside:avoid}h1+p,h2+p,h3+p{page-break-before:avoid}img{page-break-after:auto;page-break-before:auto;page-break-inside:avoid}pre{white-space:pre-wrap !important;word-wrap:break-word}body{padding-bottom:2.54cm;padding-left:1.8cm;padding-right:1.8cm;padding-top:2.54cm}a[href^=http]:after,a[href^=ftp]:after{content:" (" attr(href) ")";font-size:80%}a[href$=".jpg"]:after,a[href$=".jpeg"]:after,a[href$=".gif"]:after,a[href$=".png"]:after{display:none}abbr[title]:after,acronym[title]:after{content:" (" attr(title) ")"}.page-break,.break-before,.page-break-before{page-break-before:always}.break-after,.page-break-after{page-break-after:always}.avoid-break-inside{page-break-inside:avoid}.no-print{display:none}a.no-reformat:after{content:""}abbr[title].no-reformat:after,acronym[title].no-reformat:after{content:""}.no-reformat abbr:after,.no-reformat acronym:after,.no-reformat a:after{content:""}` diff --git a/lib/css/gutenberg.min.css b/lib/css/gutenberg.min.css new file mode 100644 index 0000000..808c677 --- /dev/null +++ b/lib/css/gutenberg.min.css @@ -0,0 +1,6 @@ +/*! + * Gutenberg + * + * MIT Fabien Sa + * https://github.com/BafS/Gutenberg + *//*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-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{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}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;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}*{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after,p:first-letter,div:first-letter,blockquote:first-letter,li:first-letter,p:first-line,div:first-line,blockquote:first-line,li:first-line{background:rgba(0,0,0,0) !important;box-shadow:none !important;text-shadow:none !important}html{font-size:16px;margin:0;padding:0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;background:#fff !important;color:#000 !important;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:1rem;line-height:1.5;margin:0 auto;text-rendering:optimizeLegibility}p,blockquote,table,ul,ol,dl{margin-bottom:1.5rem;margin-top:0}p:last-child,ul:last-child,ol:last-child{margin-bottom:0}h1,h2,h3,h4,h5,h6{color:#000;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;line-height:1.2;margin-bottom:.75rem;margin-top:0}h1{font-size:2.5rem}h2{font-size:2rem}h3{font-size:1.75rem}h4{font-size:1.5rem}h5{font-size:1.25rem}h6{font-size:1rem}a,a:visited{color:#000;text-decoration:underline;word-wrap:break-word}table{border-collapse:collapse}thead{display:table-header-group}table,th,td{border-bottom:1px solid #000}td,th{padding:8px 16px;page-break-inside:avoid}code,pre,kbd{border:1px solid #bbb;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:85%}code,kbd{padding:3px}pre{margin-bottom:1.5rem;padding:10px 12px}pre code,pre kbd{border:0}::-webkit-input-placeholder{color:rgba(0,0,0,0)}:-moz-placeholder{color:rgba(0,0,0,0)}::-moz-placeholder{color:rgba(0,0,0,0)}:-ms-input-placeholder{color:rgba(0,0,0,0)}blockquote{border:0;border-left:5px solid #bbb;margin-left:1px;padding:12px 1.5rem}[dir=rtl] blockquote{border-left:0;border-right:5px solid #bbb;margin-left:0;margin-right:1px}blockquote:first-child{margin-top:0}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer{display:block;font-size:80%}img{border:0;display:block;max-width:100% !important;vertical-align:middle}hr{border:0;border-bottom:2px solid #bbb;height:0;margin:2.25rem 0;padding:0}dt{font-weight:bold}dd{margin:0;margin-bottom:.75rem}abbr[title],acronym[title]{border:0;text-decoration:none}table,blockquote,pre,code,figure,li,hr,ul,ol,a,tr{page-break-inside:avoid}h2,h3,h4,p,a{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid;page-break-inside:avoid}h1+p,h2+p,h3+p{page-break-before:avoid}img{page-break-after:auto;page-break-before:auto;page-break-inside:avoid}pre{white-space:pre-wrap !important;word-wrap:break-word}body{padding-bottom:2.54cm;padding-left:1.8cm;padding-right:1.8cm;padding-top:2.54cm}a[href^=http]:after,a[href^=ftp]:after{content:" (" attr(href) ")";font-size:80%}a[href$=".jpg"]:after,a[href$=".jpeg"]:after,a[href$=".gif"]:after,a[href$=".png"]:after{display:none}abbr[title]:after,acronym[title]:after{content:" (" attr(title) ")"}.page-break,.break-before,.page-break-before{page-break-before:always}.break-after,.page-break-after{page-break-after:always}.avoid-break-inside{page-break-inside:avoid}.no-print{display:none}a.no-reformat:after{content:""}abbr[title].no-reformat:after,acronym[title].no-reformat:after{content:""}.no-reformat abbr:after,.no-reformat acronym:after,.no-reformat a:after{content:""} \ No newline at end of file diff --git a/lib/css/modern.js b/lib/css/modern.js new file mode 100644 index 0000000..a3fd3fa --- /dev/null +++ b/lib/css/modern.js @@ -0,0 +1,6 @@ +// Generated from ./modern.min.css - keep in sync when updating that file. +// Exported as a JS string (rather than imported as a .css file) so it works +// identically when this package is consumed from Node (this CLI/library) +// and when it is bundled into a browser build (webpack etc.), without +// relying on any bundler-specific CSS-as-string loader. +export default `h1,h2,h3,h4,h5,h6{font-family:"Montserrat","Arial Black","Arial Bold","Helvetica Neue",Helvetica,sans-serif}body{font-family:"Open Sans","Helvetica Neue",Helvetica,arial,sans-serif;text-align:justify}h1{font-weight:700;letter-spacing:-1px;text-align:center}h2{letter-spacing:-1px}h2,h3,h4,h5{color:#262626}pre,code{border:0}pre,code,blockquote{background:#f8f8f9}blockquote{margin-left:1.5rem;margin-right:1.5rem}` diff --git a/lib/css/modern.min.css b/lib/css/modern.min.css new file mode 100644 index 0000000..7b6ad02 --- /dev/null +++ b/lib/css/modern.min.css @@ -0,0 +1 @@ +h1,h2,h3,h4,h5,h6{font-family:"Montserrat","Arial Black","Arial Bold","Helvetica Neue",Helvetica,sans-serif}body{font-family:"Open Sans","Helvetica Neue",Helvetica,arial,sans-serif;text-align:justify}h1{font-weight:700;letter-spacing:-1px;text-align:center}h2{letter-spacing:-1px}h2,h3,h4,h5{color:#262626}pre,code{border:0}pre,code,blockquote{background:#f8f8f9}blockquote{margin-left:1.5rem;margin-right:1.5rem} \ No newline at end of file diff --git a/lib/css/preview.css b/lib/css/preview.css new file mode 100644 index 0000000..136d98c --- /dev/null +++ b/lib/css/preview.css @@ -0,0 +1,58 @@ +pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #1f2328; + background-color: #f6f8fa !important; + border-radius: 6px; +} + +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: rgba(175, 184, 193, 0.2) !important; + border-radius: 6px; +} + +code br { + display: none; +} + +del code { + text-decoration: inherit; +} + +pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent !important; + border: 0; +} + +pre code { + font-size: 100%; + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} diff --git a/lib/css/preview.js b/lib/css/preview.js new file mode 100644 index 0000000..240679f --- /dev/null +++ b/lib/css/preview.js @@ -0,0 +1,64 @@ +// Generated from ./preview.css - keep in sync when updating that file. +// Exported as a JS string (rather than imported as a .css file) so it works +// identically when this package is consumed from Node (this CLI/library) +// and when it is bundled into a browser build (webpack etc.), without +// relying on any bundler-specific CSS-as-string loader. +export default `pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: #1f2328; + background-color: #f6f8fa !important; + border-radius: 6px; +} + +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + padding: 0 0.2em; + font-size: inherit; +} + +code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: rgba(175, 184, 193, 0.2) !important; + border-radius: 6px; +} + +code br { + display: none; +} + +del code { + text-decoration: inherit; +} + +pre > code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent !important; + border: 0; +} + +pre code { + font-size: 100%; + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} +` From 5680d9d75bd9b1e5abbfff9d61e511ee66e9fc0a Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Fri, 18 Sep 2026 12:41:43 +0200 Subject: [PATCH 07/13] add test suite for enrichDocument, markdownParser, mustacheHelpers, and HTMLTemplate --- tests/HTMLTemplate.test.js | 165 +++++++++ tests/enrichDocument.test.js | 608 ++++++++++++++++++++++++++++++++++ tests/markdownParser.test.js | 77 +++++ tests/mustacheHelpers.test.js | 129 ++++++++ vitest.config.js | 20 ++ 5 files changed, 999 insertions(+) create mode 100644 tests/HTMLTemplate.test.js create mode 100644 tests/enrichDocument.test.js create mode 100644 tests/markdownParser.test.js create mode 100644 tests/mustacheHelpers.test.js create mode 100644 vitest.config.js diff --git a/tests/HTMLTemplate.test.js b/tests/HTMLTemplate.test.js new file mode 100644 index 0000000..04f5ce0 --- /dev/null +++ b/tests/HTMLTemplate.test.js @@ -0,0 +1,165 @@ +import { describe, expect, it } from 'vitest' +import { + enrichDocumentV2_0, + enrichDocumentV2_1, +} from '../lib/enrichDocument.js' +import HTMLTemplate2_0 from '../lib/HTMLTemplate2_0.js' +import HTMLTemplate2_1 from '../lib/HTMLTemplate2_1.js' +import { renderMarkdown } from '../lib/markdownParser.js' + +/** + * Minimal but structurally valid CSAF 2.0 document, covering just enough + * fields to exercise the full enrich -> renderMarkdown -> HTMLTemplate + * pipeline. + */ +const minimalV2_0Doc = { + document: { + title: 'Test Advisory', + category: 'csaf_base', + csaf_version: '2.0', + distribution: { tlp: { label: 'WHITE' } }, + lang: 'en', + publisher: { + category: 'vendor', + name: 'Acme Inc', + namespace: 'https://example.com', + }, + tracking: { + id: 'ACME-2024-001', + status: 'final', + version: '1', + initial_release_date: '2024-01-01T00:00:00Z', + current_release_date: '2024-01-01T00:00:00Z', + revision_history: [ + { date: '2024-01-01T00:00:00Z', number: '1', summary: 'Initial' }, + ], + }, + notes: [{ category: 'summary', text: '**Important** summary' }], + }, + vulnerabilities: [ + { + title: 'Example vuln', + notes: [{ category: 'description', text: 'desc' }], + }, + ], +} + +/** + * Minimal but structurally valid CSAF 2.1 document. + */ +const minimalV2_1Doc = { + document: { + title: 'Test Advisory 2.1', + category: 'csaf_base', + csaf_version: '2.1', + distribution: { tlp: { label: 'WHITE' } }, + lang: 'en', + publisher: { + category: 'vendor', + name: 'Acme Inc', + namespace: 'https://example.com', + }, + tracking: { + id: 'ACME-2024-002', + status: 'final', + version: '1', + initial_release_date: '2024-01-01T00:00:00Z', + current_release_date: '2024-01-01T00:00:00Z', + revision_history: [ + { date: '2024-01-01T00:00:00Z', number: '1', summary: 'Initial' }, + ], + }, + }, + vulnerabilities: [], +} + +describe('HTMLTemplate2_0 (end-to-end with enrichDocumentV2_0 + renderMarkdown)', () => { + it('renders a full HTML document containing the advisory content', () => { + const { document: enriched } = enrichDocumentV2_0(minimalV2_0Doc) + const parsed = renderMarkdown(enriched) + const html = HTMLTemplate2_0({ document: parsed }) + + expect(html).toContain('') + expect(html).toContain('Test Advisory') + expect(html).toContain('ACME-2024-001') + }) + + it('inlines the bundled CSS into - - - - - - -

    {{#document.tracking.id}}{{document.tracking.id}}: {{/document.tracking.id}}{{document.title}}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Publisher: {{document.publisher.name}}Document category: {{document.category}}
    Initial release date: {{document.tracking.initial_release_date}}Engine: {{#document.tracking.generator.engine}}{{name}}{{#version}} {{.}}{{/version}}{{/document.tracking.generator.engine}}
    Current release date: {{document.tracking.current_release_date}}Build Date: {{document.tracking.generator.date}}
    Current version: {{document.tracking.version}}Status: {{document.tracking.status}}
    CVSSv3.1 Base Score: {{document.max_base_score}}Severity: - {{#document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} - {{^document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} -
    Original language: {{document.source_lang}}Language: {{document.lang}}
    Also referred to: {{#removeTrailingComma}}{{#document.tracking.aliases}}{{.}}, {{/document.tracking.aliases}}{{/removeTrailingComma}}
    - - {{#document.notes_summary}} - {{> document_note}} - {{/document.notes_summary}} - {{#document.notes_details}} - {{> document_note}} - {{/document.notes_details}} - {{#document.notes_general}} - {{> document_note}} - {{/document.notes_general}} - {{#document.notes_description}} - {{> document_note}} - {{/document.notes_description}} - {{#document.notes_faq}} - {{> document_note}} - {{/document.notes_faq}} - {{#document.notes_unknown}} - {{> document_note}} - {{/document.notes_unknown}} - - {{#product_tree.product_groups.length}} -

    Product groups

    - {{#product_tree.product_groups}} - {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{group_id}}{{/summary}} -
      - {{#product_ids}} -
    • {{name}}
    • - {{/product_ids}} -
    - {{/product_tree.product_groups}} - {{/product_tree.product_groups.length}} - -

    Vulnerabilities

    - {{#vulnerabilities}} -

    {{title}}{{#cve}} ({{.}}){{/cve}}

    - - {{#notes_summary}} - {{> vulnerability_note}} - {{/notes_summary}} - {{#notes_details}} - {{> vulnerability_note}} - {{/notes_details}} - {{#notes_general}} - {{> vulnerability_note}} - {{/notes_general}} - {{#notes_description}} - {{> vulnerability_note}} - {{/notes_description}} - {{#notes_other}} - {{> vulnerability_note}} - {{/notes_other}} - {{#notes_faq}} - {{> vulnerability_note}} - {{/notes_faq}} - {{#notes_unknown}} - {{> vulnerability_note}} - {{/notes_unknown}} - - - +const CWE = ` @@ -127,227 +23,11 @@ export default ` - {{/release_date}} -
    CWE: {{#cwe}}{{id}}:{{name}}{{/cwe}}
    Release date: {{release_date}}
    - -

    Product status

    - {{#product_status.known_affected.length}} -
    Known affected
    - - {{> product_status_header}} - - {{#product_status.known_affected}} - {{> product_status_row}} - {{/product_status.known_affected}} - -
    - {{/product_status.known_affected.length}} - - {{#product_status.first_affected.length}} -
    First affected
    - - {{> product_status_header}} - - {{#product_status.first_affected}} - {{> product_status_row}} - {{/product_status.first_affected}} - -
    - {{/product_status.first_affected.length}} - - {{#product_status.last_affected.length}} -
    Last affected
    - - {{> product_status_header}} - - {{#product_status.last_affected}} - {{> product_status_row}} - {{/product_status.last_affected}} - -
    - {{/product_status.last_affected.length}} - - {{#product_status.known_not_affected.length}} -
    Known not affected
    -
      - {{#product_status.known_not_affected}} -
    • - {{name}}{{#flags.length}} ({{#removeTrailingComma}}{{#flags}}{{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}, {{/flags}}{{/removeTrailingComma}}){{/flags.length}} -
    • - {{/product_status.known_not_affected}} -
    - {{/product_status.known_not_affected.length}} - - {{#product_status.recommended.length}} -
    Recommended
    -
      - {{#product_status.recommended}} -
    • {{name}}
    • - {{/product_status.recommended}} -
    - {{/product_status.recommended.length}} - - {{#product_status.fixed.length}} -
    Fixed
    -
      - {{#product_status.fixed}} -
    • {{name}}
    • - {{/product_status.fixed}} -
    - {{/product_status.fixed.length}} - - {{#product_status.first_fixed.length}} -
    First fixed
    -
      - {{#product_status.first_fixed}} -
    • {{name}}
    • - {{/product_status.first_fixed}} -
    - {{/product_status.first_fixed.length}} - - {{#product_status.under_investigation.length}} -
    Under investgation
    -
      - {{#product_status.under_investigation}} -
    • {{name}}
    • - {{/product_status.under_investigation}} -
    - {{/product_status.under_investigation.length}} - - {{#remediations.length}} -

    Remediations

    - {{#remediations_vendor_fix}} - {{> remediation}} - {{/remediations_vendor_fix}} - {{#remediations_mitigation}} - {{> remediation}} - {{/remediations_mitigation}} - {{#remediations_workaround}} - {{> remediation}} - {{/remediations_workaround}} - {{#remediations_none_available}} - {{> remediation}} - {{/remediations_none_available}} - {{#remediations_no_fix_planned}} - {{> remediation}} - {{/remediations_no_fix_planned}} - {{#remediations_unknown}} - {{> remediation}} - {{/remediations_unknown}} - {{/remediations.length}} - - {{#acknowledgments.length}} -

    Acknowledgments

    -
      - {{#acknowledgments}} - {{> acknowledgment}} - {{/acknowledgments}} -
    - {{/acknowledgments.length}} - - {{#involvements.length}} -

    Involvement

    -
      - {{#involvements}} - {{#.}} -
    • {{#date}}{{.}} {{/date}}{{#upperCase}}{{party}}{{/upperCase}}{{#status}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/status}}{{#summary}}: {{{.}}}{{/summary}}
    • - {{/.}} - {{/involvements}} -
    - {{/involvements.length}} - - {{#references.length}} -

    References

    -
      - {{#references}} - {{> reference}} - {{/references}} -
    - {{/references.length}} - - {{#threats.length}} -

    Threats

    - {{#threats_exploit_status}} - {{> threat}} - {{/threats_exploit_status}} - {{#threats_impact}} - {{> threat}} - {{/threats_impact}} - {{#threats_target_set}} - {{> threat}} - {{/threats_target_set}} - {{#threats_unknown}} - {{> threat}} - {{/threats_unknown}} - {{/threats.length}} - - {{/vulnerabilities}} - - {{#document.acknowledgments.length}} -

    Acknowledgments

    - {{document.publisher.name}} thanks the following parties for their efforts: -
      - {{#document.acknowledgments}} - {{> acknowledgment}} - {{/document.acknowledgments}} -
    - {{/document.acknowledgments.length}} - - {{#document.notes_other}} - {{> document_note}} - {{/document.notes_other}} - - {{#document.publisher}} -

    {{name}}

    -

    Namespace: {{namespace}}

    -

    {{contact_details}}

    -

    {{{issuing_authority}}}

    - {{/document.publisher}} - - {{#document.references.length}} -

    References

    -
      - {{#document.references}} - {{> reference}} - {{/document.references}} -
    - {{/document.references.length}} - -

    Revision history

    - - - - - - - - - - {{#document.tracking.revision_history}} - - - - - - {{/document.tracking.revision_history}} - -
    VersionDate of the revisionSummary of the revision
    {{number}}{{date}}{{{summary}}}
    - - {{#document.distribution}} -

    Sharing rules

    -

    - {{#tlp}} - {{#label}}TLP:{{.}}{{/label}}
    - For the TLP version see: {{#url}}{{.}}{{/url}}{{^url}}https://www.first.org/tlp/{{/url}} - {{/tlp}} -

    -

    {{{text}}}

    - {{/document.distribution}} - - {{#document.notes_legal_disclaimer}} - {{> document_note}} - {{/document.notes_legal_disclaimer}} - - - -` + {{/release_date}}` + +export default `${HEAD} +${CVSS} +${MID} +${CWE} +${BIG} +${TAIL}` diff --git a/lib/templates/csaf_2_1.js b/lib/templates/csaf_2_1.js index cf47ad9..2ca7d17 100644 --- a/lib/templates/csaf_2_1.js +++ b/lib/templates/csaf_2_1.js @@ -1,113 +1,8 @@ -import gutenbergCss from '../css/gutenberg.js' -import modernCss from '../css/modern.js' -import previewCss from '../css/preview.js' +import { BIG, HEAD, MID, TAIL } from './sharedTemplateParts.js' -export default ` - +const CVSS = ` {{document.max_base_score_cvss_label}} Base Score: {{document.max_base_score}}` - - - - - - - - -

    {{$schema}}

    -

    {{#document.tracking.id}}{{document.tracking.id}}: {{/document.tracking.id}}{{document.title}}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Publisher: {{document.publisher.name}}Document category: {{document.category}}
    Initial release date: {{document.tracking.initial_release_date}}Engine: {{#document.tracking.generator.engine}}{{name}}{{#version}} {{.}}{{/version}}{{/document.tracking.generator.engine}}
    Current release date: {{document.tracking.current_release_date}}Build Date: {{document.tracking.generator.date}}
    Current version: {{document.tracking.version}}Status: {{document.tracking.status}}
    CVSSv3.1 Base Score: {{document.max_base_score}}Severity: - {{#document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} - {{^document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} -
    Original language: {{document.source_lang}}Language: {{document.lang}}
    Also referred to: {{#removeTrailingComma}}{{#document.tracking.aliases}}{{.}}, {{/document.tracking.aliases}}{{/removeTrailingComma}}
    - - {{#document.notes_summary}} - {{> document_note}} - {{/document.notes_summary}} - {{#document.notes_details}} - {{> document_note}} - {{/document.notes_details}} - {{#document.notes_general}} - {{> document_note}} - {{/document.notes_general}} - {{#document.notes_description}} - {{> document_note}} - {{/document.notes_description}} - {{#document.notes_faq}} - {{> document_note}} - {{/document.notes_faq}} - {{#document.notes_unknown}} - {{> document_note}} - {{/document.notes_unknown}} - - {{#product_tree.product_groups.length}} -

    Product groups

    - {{#product_tree.product_groups}} - {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{group_id}}{{/summary}} -
      - {{#product_ids}} -
    • {{name}}
    • - {{/product_ids}} -
    - {{/product_tree.product_groups}} - {{/product_tree.product_groups.length}} - -

    Vulnerabilities

    - {{#vulnerabilities}} -

    {{title}}{{#cve}} ({{.}}){{/cve}}

    - - {{#notes_summary}} - {{> vulnerability_note}} - {{/notes_summary}} - {{#notes_details}} - {{> vulnerability_note}} - {{/notes_details}} - {{#notes_general}} - {{> vulnerability_note}} - {{/notes_general}} - {{#notes_description}} - {{> vulnerability_note}} - {{/notes_description}} - {{#notes_other}} - {{> vulnerability_note}} - {{/notes_other}} - {{#notes_faq}} - {{> vulnerability_note}} - {{/notes_faq}} - {{#notes_unknown}} - {{> vulnerability_note}} - {{/notes_unknown}} - - -

    CWE's

    +const CWE = `

    CWE's

    {{#cwes}} @@ -130,234 +25,20 @@ export default ` - {{/disclosure_date}} -
    {{id}}:{{name}}:{{version}} Disclosure date: {{disclosure_date}}
    - -

    Product status

    - {{#product_status.known_affected.length}} -
    Known affected
    - - {{> product_status_header}} - - {{#product_status.known_affected}} - {{> product_status_row}} - {{/product_status.known_affected}} - -
    - {{/product_status.known_affected.length}} - - {{#product_status.first_affected.length}} -
    First affected
    - - {{> product_status_header}} - - {{#product_status.first_affected}} - {{> product_status_row}} - {{/product_status.first_affected}} - -
    - {{/product_status.first_affected.length}} - - {{#product_status.last_affected.length}} -
    Last affected
    - - {{> product_status_header}} - - {{#product_status.last_affected}} - {{> product_status_row}} - {{/product_status.last_affected}} - -
    - {{/product_status.last_affected.length}} - - {{#product_status.known_not_affected.length}} -
    Known not affected
    -
      - {{#product_status.known_not_affected}} -
    • - {{name}}{{#flags.length}} ({{#removeTrailingComma}}{{#flags}}{{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}, {{/flags}}{{/removeTrailingComma}}){{/flags.length}} -
    • - {{/product_status.known_not_affected}} -
    - {{/product_status.known_not_affected.length}} - - {{#product_status.recommended.length}} -
    Recommended
    -
      - {{#product_status.recommended}} -
    • {{name}}
    • - {{/product_status.recommended}} -
    - {{/product_status.recommended.length}} - - {{#product_status.fixed.length}} -
    Fixed
    -
      - {{#product_status.fixed}} -
    • {{name}}
    • - {{/product_status.fixed}} -
    - {{/product_status.fixed.length}} - - {{#product_status.first_fixed.length}} -
    First fixed
    -
      - {{#product_status.first_fixed}} -
    • {{name}}
    • - {{/product_status.first_fixed}} -
    - {{/product_status.first_fixed.length}} - - {{#product_status.under_investigation.length}} -
    Under investgation
    -
      - {{#product_status.under_investigation}} -
    • {{name}}
    • - {{/product_status.under_investigation}} -
    - {{/product_status.under_investigation.length}} - - {{#remediations.length}} -

    Remediations

    - {{#remediations_vendor_fix}} - {{> remediation}} - {{/remediations_vendor_fix}} - {{#remediations_mitigation}} - {{> remediation}} - {{/remediations_mitigation}} - {{#remediations_workaround}} - {{> remediation}} - {{/remediations_workaround}} - {{#remediations_none_available}} - {{> remediation}} - {{/remediations_none_available}} - {{#remediations_no_fix_planned}} - {{> remediation}} - {{/remediations_no_fix_planned}} - {{#remediations_unknown}} - {{> remediation}} - {{/remediations_unknown}} - {{/remediations.length}} - - {{#acknowledgments.length}} -

    Acknowledgments

    -
      - {{#acknowledgments}} - {{> acknowledgment}} - {{/acknowledgments}} -
    - {{/acknowledgments.length}} - - {{#involvements.length}} -

    Involvement

    -
      - {{#involvements}} - {{#.}} -
    • {{#date}}{{.}} {{/date}}{{#upperCase}}{{party}}{{/upperCase}}{{#status}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/status}}{{#summary}}: {{{.}}}{{/summary}}
    • - {{/.}} - {{/involvements}} -
    - {{/involvements.length}} - - {{#references.length}} -

    References

    -
      - {{#references}} - {{> reference}} - {{/references}} -
    - {{/references.length}} - - {{#threats.length}} -

    Threats

    - {{#threats_exploit_status}} - {{> threat}} - {{/threats_exploit_status}} - {{#threats_impact}} - {{> threat}} - {{/threats_impact}} - {{#threats_target_set}} - {{> threat}} - {{/threats_target_set}} - {{#threats_unknown}} - {{> threat}} - {{/threats_unknown}} - {{/threats.length}} - - {{/vulnerabilities}} - - {{#document.acknowledgments.length}} -

    Acknowledgments

    - {{document.publisher.name}} thanks the following parties for their efforts: -
      - {{#document.acknowledgments}} - {{> acknowledgment}} - {{/document.acknowledgments}} -
    - {{/document.acknowledgments.length}} - - {{#document.notes_other}} - {{> document_note}} - {{/document.notes_other}} - - {{#document.publisher}} -

    {{name}}

    -

    Namespace: {{namespace}}

    -

    {{contact_details}}

    -

    {{{issuing_authority}}}

    - {{/document.publisher}} - - {{#document.references.length}} -

    References

    -
      - {{#document.references}} - {{> reference}} - {{/document.references}} -
    - {{/document.references.length}} - -

    Revision history

    - - - - - - - - - - {{#document.tracking.revision_history}} - - - - - - {{/document.tracking.revision_history}} - -
    VersionDate of the revisionSummary of the revision
    {{number}}{{date}}{{{summary}}}
    + {{/disclosure_date}}` - {{#document.distribution}} -

    Sharing rules

    -

    - {{#sharing_group}} +const SHARING_GROUP = ` {{#sharing_group}} Sharing Group

    Id: {{id}}

    Name: {{name}}

    {{/sharing_group}}

    -

    - {{#tlp}} - {{#label}}TLP:{{.}}{{/label}}
    - For the TLP version see: {{#url}}{{.}}{{/url}}{{^url}}https://www.first.org/tlp/{{/url}} - {{/tlp}} -

    -

    {{{text}}}

    - {{/document.distribution}} - - {{#document.notes_legal_disclaimer}} - {{> document_note}} - {{/document.notes_legal_disclaimer}} - - - -` +

    ` + +export default `${HEAD} +${CVSS} +${MID} +${CWE} +${BIG} +${SHARING_GROUP} +${TAIL}` diff --git a/lib/templates/sharedTemplateParts.js b/lib/templates/sharedTemplateParts.js new file mode 100644 index 0000000..9fddd23 --- /dev/null +++ b/lib/templates/sharedTemplateParts.js @@ -0,0 +1,342 @@ +import gutenbergCss from '../css/gutenberg.js' +import modernCss from '../css/modern.js' +import previewCss from '../css/preview.js' + +// Shared, version-independent building blocks of the csaf_2_0.js and +// csaf_2_1.js top-level HTML templates. These two CSAF versions render an +// almost identical document; the only real differences are the CVSS label +// row, the CWE/date fields, and the CSAF 2.1-only sharing_group block, which +// stay defined separately in each templates/csaf_2_*.js file. + +export const HEAD = ` + + + + + + + + + + +

    {{#document.tracking.id}}{{document.tracking.id}}: {{/document.tracking.id}}{{document.title}}

    + + + + + + + + + + + + + + + + + + + ` + +export const MID = ` + + + + + + + + +
    Publisher: {{document.publisher.name}}Document category: {{document.category}}
    Initial release date: {{document.tracking.initial_release_date}}Engine: {{#document.tracking.generator.engine}}{{name}}{{#version}} {{.}}{{/version}}{{/document.tracking.generator.engine}}
    Current release date: {{document.tracking.current_release_date}}Build Date: {{document.tracking.generator.date}}
    Current version: {{document.tracking.version}}Status: {{document.tracking.status}}
    Severity: + {{#document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} + {{^document.aggregate_severity.namespace}} {{document.aggregate_severity.text}}{{/document.aggregate_severity.namespace}} +
    Original language: {{document.source_lang}}Language: {{document.lang}}
    Also referred to: {{#removeTrailingComma}}{{#document.tracking.aliases}}{{.}}, {{/document.tracking.aliases}}{{/removeTrailingComma}}
    + + {{#document.notes_summary}} + {{> document_note}} + {{/document.notes_summary}} + {{#document.notes_details}} + {{> document_note}} + {{/document.notes_details}} + {{#document.notes_general}} + {{> document_note}} + {{/document.notes_general}} + {{#document.notes_description}} + {{> document_note}} + {{/document.notes_description}} + {{#document.notes_faq}} + {{> document_note}} + {{/document.notes_faq}} + {{#document.notes_unknown}} + {{> document_note}} + {{/document.notes_unknown}} + + {{#product_tree.product_groups.length}} +

    Product groups

    + {{#product_tree.product_groups}} + {{#summary}}{{{.}}}{{/summary}}{{^summary}}{{group_id}}{{/summary}} +
      + {{#product_ids}} +
    • {{name}}
    • + {{/product_ids}} +
    + {{/product_tree.product_groups}} + {{/product_tree.product_groups.length}} + +

    Vulnerabilities

    + {{#vulnerabilities}} +

    {{title}}{{#cve}} ({{.}}){{/cve}}

    + + {{#notes_summary}} + {{> vulnerability_note}} + {{/notes_summary}} + {{#notes_details}} + {{> vulnerability_note}} + {{/notes_details}} + {{#notes_general}} + {{> vulnerability_note}} + {{/notes_general}} + {{#notes_description}} + {{> vulnerability_note}} + {{/notes_description}} + {{#notes_other}} + {{> vulnerability_note}} + {{/notes_other}} + {{#notes_faq}} + {{> vulnerability_note}} + {{/notes_faq}} + {{#notes_legal_disclaimer}} + {{> vulnerability_note}} + {{/notes_legal_disclaimer}} + {{#notes_unknown}} + {{> vulnerability_note}} + {{/notes_unknown}} + + ` + +export const BIG = `
    + +

    Product status

    + {{#product_status.known_affected.length}} +
    Known affected
    + + {{> product_status_header}} + + {{#product_status.known_affected}} + {{> product_status_row}} + {{/product_status.known_affected}} + +
    + {{/product_status.known_affected.length}} + + {{#product_status.first_affected.length}} +
    First affected
    + + {{> product_status_header}} + + {{#product_status.first_affected}} + {{> product_status_row}} + {{/product_status.first_affected}} + +
    + {{/product_status.first_affected.length}} + + {{#product_status.last_affected.length}} +
    Last affected
    + + {{> product_status_header}} + + {{#product_status.last_affected}} + {{> product_status_row}} + {{/product_status.last_affected}} + +
    + {{/product_status.last_affected.length}} + + {{#product_status.known_not_affected.length}} +
    Known not affected
    +
      + {{#product_status.known_not_affected}} +
    • + {{name}}{{#flags.length}} ({{#removeTrailingComma}}{{#flags}}{{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}, {{/flags}}{{/removeTrailingComma}}){{/flags.length}} +
    • + {{/product_status.known_not_affected}} +
    + {{/product_status.known_not_affected.length}} + + {{#product_status.recommended.length}} +
    Recommended
    +
      + {{#product_status.recommended}} +
    • {{name}}
    • + {{/product_status.recommended}} +
    + {{/product_status.recommended.length}} + + {{#product_status.fixed.length}} +
    Fixed
    +
      + {{#product_status.fixed}} +
    • {{name}}
    • + {{/product_status.fixed}} +
    + {{/product_status.fixed.length}} + + {{#product_status.first_fixed.length}} +
    First fixed
    +
      + {{#product_status.first_fixed}} +
    • {{name}}
    • + {{/product_status.first_fixed}} +
    + {{/product_status.first_fixed.length}} + + {{#product_status.under_investigation.length}} +
    Under investgation
    +
      + {{#product_status.under_investigation}} +
    • {{name}}
    • + {{/product_status.under_investigation}} +
    + {{/product_status.under_investigation.length}} + + {{#remediations.length}} +

    Remediations

    + {{#remediations_vendor_fix}} + {{> remediation}} + {{/remediations_vendor_fix}} + {{#remediations_mitigation}} + {{> remediation}} + {{/remediations_mitigation}} + {{#remediations_workaround}} + {{> remediation}} + {{/remediations_workaround}} + {{#remediations_none_available}} + {{> remediation}} + {{/remediations_none_available}} + {{#remediations_no_fix_planned}} + {{> remediation}} + {{/remediations_no_fix_planned}} + {{#remediations_unknown}} + {{> remediation}} + {{/remediations_unknown}} + {{/remediations.length}} + + {{#acknowledgments.length}} +

    Acknowledgments

    +
      + {{#acknowledgments}} + {{> acknowledgment}} + {{/acknowledgments}} +
    + {{/acknowledgments.length}} + + {{#involvements.length}} +

    Involvement

    +
      + {{#involvements}} + {{#.}} +
    • {{#date}}{{.}} {{/date}}{{#upperCase}}{{party}}{{/upperCase}}{{#status}} ({{#replaceUnderscores}}{{.}}{{/replaceUnderscores}}){{/status}}{{#summary}}: {{{.}}}{{/summary}}
    • + {{/.}} + {{/involvements}} +
    + {{/involvements.length}} + + {{#references.length}} +

    References

    +
      + {{#references}} + {{> reference}} + {{/references}} +
    + {{/references.length}} + + {{#threats.length}} +

    Threats

    + {{#threats_exploit_status}} + {{> threat}} + {{/threats_exploit_status}} + {{#threats_impact}} + {{> threat}} + {{/threats_impact}} + {{#threats_target_set}} + {{> threat}} + {{/threats_target_set}} + {{#threats_unknown}} + {{> threat}} + {{/threats_unknown}} + {{/threats.length}} + + {{/vulnerabilities}} + + {{#document.acknowledgments.length}} +

    Acknowledgments

    + {{document.publisher.name}} thanks the following parties for their efforts: +
      + {{#document.acknowledgments}} + {{> acknowledgment}} + {{/document.acknowledgments}} +
    + {{/document.acknowledgments.length}} + + {{#document.notes_other}} + {{> document_note}} + {{/document.notes_other}} + + {{#document.publisher}} +

    {{name}}

    +

    Namespace: {{namespace}}

    +

    {{contact_details}}

    +

    {{{issuing_authority}}}

    + {{/document.publisher}} + + {{#document.references.length}} +

    References

    +
      + {{#document.references}} + {{> reference}} + {{/document.references}} +
    + {{/document.references.length}} + +

    Revision history

    + + + + + + + + + + {{#document.tracking.revision_history}} + + + + + + {{/document.tracking.revision_history}} + +
    VersionDate of the revisionSummary of the revision
    {{number}}{{date}}{{{summary}}}
    + + {{#document.distribution}} +

    Sharing rules

    +

    ` + +export const TAIL = ` {{#tlp}} + {{#label}}TLP:{{.}}{{/label}}
    + For the TLP version see: {{#url}}{{.}}{{/url}}{{^url}}https://www.first.org/tlp/{{/url}} + {{/tlp}} +

    +

    {{{text}}}

    + {{/document.distribution}} + + {{#document.notes_legal_disclaimer}} + {{> document_note}} + {{/document.notes_legal_disclaimer}} + + + +` From 390a66b739362c8cfa48ccb1fa51e56ab4b2031a Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Wed, 23 Sep 2026 14:37:21 +0200 Subject: [PATCH 12/13] add regression tests for CVSS labeling, markdown and rendering fixes --- tests/HTMLTemplate.test.js | 116 ++++++++++++++++++++++++++++++++++ tests/enrichDocument.test.js | 103 ++++++++++++++++++++++++++++++ tests/markdownParser.test.js | 40 ++++++++++++ tests/mustacheHelpers.test.js | 23 ++++++- 4 files changed, 280 insertions(+), 2 deletions(-) diff --git a/tests/HTMLTemplate.test.js b/tests/HTMLTemplate.test.js index 04f5ce0..c84a321 100644 --- a/tests/HTMLTemplate.test.js +++ b/tests/HTMLTemplate.test.js @@ -118,6 +118,65 @@ describe('HTMLTemplate2_0 (end-to-end with enrichDocumentV2_0 + renderMarkdown)' expect(html).not.toContain('') expect(html).toContain('<script>') }) + + it('renders vulnerability-level legal_disclaimer notes', () => { + // Regression test: the template computed notes_legal_disclaimer for + // each vulnerability (via addVulnerabilityNotesPreviewAttributes) but + // never actually rendered it anywhere, unlike every other note + // category, which silently dropped these notes from the output. + const doc = /** @type {any} */ (structuredClone(minimalV2_0Doc)) + doc.vulnerabilities = [ + { + title: 'Example vuln', + notes: [ + { + category: 'legal_disclaimer', + title: 'Disclaimer', + text: 'Vulnerability-level legal disclaimer text', + }, + ], + }, + ] + const { document: enriched } = enrichDocumentV2_0(doc) + const parsed = renderMarkdown(enriched) + const html = HTMLTemplate2_0({ document: parsed }) + + expect(html).toContain('Vulnerability-level legal disclaimer text') + }) + + it("renders a product group's markdown summary consistently, whether shown directly or referenced via group_ids", () => { + // Regression test: product_tree.product_groups[].summary is a markdown + // field, and enrichDocument copies it into `group_ids[].name` on every + // remediation/threat that references that group - but that's a plain + // string copy, not a shared reference, so it used to need its own + // markdown field entry (it didn't have one) and its own {{{triple-brace}}} + // template interpolation (it was double-brace, which would otherwise + // double-escape the now-rendered HTML into literal `<p>...` text). + const doc = /** @type {any} */ (structuredClone(minimalV2_0Doc)) + doc.product_tree = { + product_groups: [ + { group_id: 'G1', summary: '**Bold Group**', product_ids: [] }, + ], + } + doc.vulnerabilities = [ + { + title: 'Example vuln', + remediations: [{ category: 'vendor_fix', group_ids: ['G1'] }], + threats: [{ category: 'impact', group_ids: ['G1'] }], + }, + ] + const { document: enriched } = enrichDocumentV2_0(doc) + const parsed = renderMarkdown(enriched) + const html = HTMLTemplate2_0({ document: parsed }) + + // Rendered directly (product_tree.product_groups[].summary). + expect(html).toContain('

    Bold Group

    ') + // Rendered via remediation.group_ids[].name. + expect(html).toContain('
  • Bold Group

  • ') + // Never appears as raw, unrendered markdown or double-escaped HTML. + expect(html).not.toContain('**Bold Group**') + expect(html).not.toContain('<strong>') + }) }) describe('HTMLTemplate2_1 (end-to-end with enrichDocumentV2_1)', () => { @@ -130,6 +189,17 @@ describe('HTMLTemplate2_1 (end-to-end with enrichDocumentV2_1)', () => { expect(html).toContain('ACME-2024-002') }) + it('does not render a stray, always-empty {{$schema}} placeholder', () => { + // Regression test: an unlabelled

    {{$schema}}

    with no + // corresponding field ever set anywhere in enrichDocument - and no + // 2.0 equivalent - used to render an empty

    in every document. + const enriched = enrichDocumentV2_1(minimalV2_1Doc) + const html = HTMLTemplate2_1(enriched) + + expect(html).not.toContain('$schema') + expect(html).not.toContain('

    \n

    ') + }) + it('shows the CVSS version alongside the vector string/base score in the product status table', () => { const doc = /** @type {any} */ (structuredClone(minimalV2_1Doc)) doc.product_tree = { @@ -162,4 +232,50 @@ describe('HTMLTemplate2_1 (end-to-end with enrichDocumentV2_1)', () => { expect(html).toContain('9.8') expect(html).toContain('Affected Product') }) + + it('shows the document-level max base score header labelled with the CVSS version that actually produced it', () => { + // Regression test: the header used to always say "CVSSv3.1 Base Score" + // regardless of which CVSS version the score actually came from. Using + // cvss_v2 here (neither v3 nor v4) proves the label is genuinely + // computed from the winning score, not just switched between two + // hardcoded options. + const doc = /** @type {any} */ (structuredClone(minimalV2_1Doc)) + doc.vulnerabilities = [ + { + metrics: [ + { + products: ['P1'], + content: { + cvss_v2: { version: '2.0', baseScore: 4.3, vectorString: 'V2' }, + }, + }, + ], + }, + ] + const enriched = enrichDocumentV2_1(doc) + const html = HTMLTemplate2_1(enriched) + + expect(html).toContain('CVSSv2.0 Base Score: 4.3') + expect(html).not.toContain('CVSSv3.1 Base Score') + }) + + it('renders vulnerability-level legal_disclaimer notes', () => { + const doc = /** @type {any} */ (structuredClone(minimalV2_1Doc)) + doc.vulnerabilities = [ + { + title: 'Example vuln', + notes: [ + { + category: 'legal_disclaimer', + title: 'Disclaimer', + text: 'Vulnerability-level legal disclaimer text', + }, + ], + }, + ] + const enriched = enrichDocumentV2_1(doc) + const html = HTMLTemplate2_1(enriched) + + expect(html).toContain('Vulnerability-level legal disclaimer text') + }) }) diff --git a/tests/enrichDocument.test.js b/tests/enrichDocument.test.js index 2610744..c8cbc62 100644 --- a/tests/enrichDocument.test.js +++ b/tests/enrichDocument.test.js @@ -116,6 +116,22 @@ describe('enrichDocumentV2_0', () => { { id: 'P1', name: 'Related Product' }, ]) }) + + it('uses the first matching entry when a product_id is defined more than once (invalid but possible CSAF document)', () => { + const { document } = enrichDocumentV2_0({ + document: {}, + product_tree: { + full_product_names: [ + { product_id: 'P1', name: 'First Definition' }, + { product_id: 'P1', name: 'Second Definition' }, + ], + product_groups: [{ group_id: 'G1', product_ids: ['P1'] }], + }, + }) + expect(document.product_tree.product_groups[0].product_ids).toEqual([ + { id: 'P1', name: 'First Definition' }, + ]) + }) }) describe('vulnerability product_status enrichment', () => { @@ -269,6 +285,33 @@ describe('enrichDocumentV2_0', () => { ).toEqual(['2024-01-01', undefined]) }) + it('keeps dated remediations correctly sorted even when multiple undated ones are also present', () => { + // Regression test: comparing two dateless entries used to compute + // new Date(undefined).getTime() - new Date(undefined).getTime(), + // i.e. NaN - NaN = NaN, an invalid sort comparator result that could + // produce an unstable/incorrect overall order (verified: it did, + // e.g. moving the most recent dated entry to the end instead of the + // start), rather than just leaving the two dateless entries adjacent. + const { document } = enrichDocumentV2_0({ + document: {}, + vulnerabilities: [ + { + remediations: [ + { category: 'vendor_fix' }, + { category: 'vendor_fix', date: '2024-01-01' }, + { category: 'vendor_fix' }, + { category: 'vendor_fix', date: '2025-06-01' }, + ], + }, + ], + }) + expect( + document.vulnerabilities[0].remediations_vendor_fix.map( + (/** @type {any} */ r) => r.date, + ), + ).toEqual(['2025-06-01', '2024-01-01', undefined, undefined]) + }) + it('resolves product_ids and group_ids to id/name pairs on each remediation', () => { const { document } = enrichDocumentV2_0({ document: {}, @@ -603,6 +646,66 @@ describe('enrichDocumentV2_1', () => { // Highest baseScore across metrics wins, regardless of which CVSS // version each individual metric happens to use. expect(document.document.max_base_score).toBe('10') + // The label must reflect whichever CVSS version actually produced the + // winning score (v2 here), not always "CVSSv3.1" as it used to be + // hardcoded regardless of the real underlying CVSS version. + expect(document.document.max_base_score_cvss_label).toBe('CVSSv2.0') + }) + }) + + describe('max_base_score_cvss_label', () => { + it('is "CVSS" (no version suffix) when there are no scores at all', () => { + const { document } = enrichDocumentV2_1({ + document: {}, + vulnerabilities: [], + }) + expect(document.document.max_base_score_cvss_label).toBe('CVSS') + }) + + it('reflects CVSS v3 when that is the winning score', () => { + const { document } = enrichDocumentV2_1({ + document: {}, + vulnerabilities: [ + { + metrics: [ + { + products: ['P1'], + content: { + cvss_v3: { + version: '3.1', + baseScore: 5.0, + vectorString: 'VEC3', + }, + }, + }, + ], + }, + ], + }) + expect(document.document.max_base_score_cvss_label).toBe('CVSSv3.1') + }) + + it('reflects CVSS v4 when that is the winning score', () => { + const { document } = enrichDocumentV2_1({ + document: {}, + vulnerabilities: [ + { + metrics: [ + { + products: ['P1'], + content: { + cvss_v4: { + version: '4.0', + baseScore: 9.8, + vectorString: 'VEC4', + }, + }, + }, + ], + }, + ], + }) + expect(document.document.max_base_score_cvss_label).toBe('CVSSv4.0') }) }) }) diff --git a/tests/markdownParser.test.js b/tests/markdownParser.test.js index 3d783e8..139bd57 100644 --- a/tests/markdownParser.test.js +++ b/tests/markdownParser.test.js @@ -8,6 +8,18 @@ describe('renderMarkdown', () => { expect(doc.document.distribution.text).toBe('plain text') }) + it('leaves multi-line plain text (no markdown syntax) unwrapped and unchanged', () => { + // Regression test: without the `s` (dotAll) flag, `.` in the + // "was markdown actually used" detection regex doesn't match newlines, + // so this never matched at all for multi-line text - even though + // micromark, like any other plain text, still wraps it in a single + //

    tag - and the wrapped version was incorrectly returned instead + // of the original plain text. + const doc = { document: { distribution: { text: 'line1\nline2' } } } + renderMarkdown(doc) + expect(doc.document.distribution.text).toBe('line1\nline2') + }) + it('renders bold markdown syntax to HTML', () => { const doc = { document: { distribution: { text: '**bold** text' } } } renderMarkdown(doc) @@ -70,6 +82,34 @@ describe('renderMarkdown', () => { ]) }) + it('applies markdown rendering to a triple-nested wildcard array field (vulnerabilities.*.remediations.*.group_ids.*.name)', () => { + const doc = { + vulnerabilities: [ + { + remediations: [{ group_ids: [{ id: 'G1', name: '**bold**' }] }], + }, + ], + } + renderMarkdown(doc) + expect(doc.vulnerabilities[0].remediations[0].group_ids[0].name).toBe( + '

    bold

    ', + ) + }) + + it('applies markdown rendering to vulnerabilities.*.threats.*.group_ids.*.name', () => { + const doc = { + vulnerabilities: [ + { + threats: [{ group_ids: [{ id: 'G1', name: '**bold**' }] }], + }, + ], + } + renderMarkdown(doc) + expect(doc.vulnerabilities[0].threats[0].group_ids[0].name).toBe( + '

    bold

    ', + ) + }) + it('returns the same document instance it was given (mutates in place)', () => { const doc = { document: { distribution: { text: 'plain' } } } expect(renderMarkdown(doc)).toBe(doc) diff --git a/tests/mustacheHelpers.test.js b/tests/mustacheHelpers.test.js index 9d36fce..cd9d6de 100644 --- a/tests/mustacheHelpers.test.js +++ b/tests/mustacheHelpers.test.js @@ -45,9 +45,13 @@ describe('removeTrailingComma', () => { expect(lambda('a single value', identityRender)).toBe('a single value') }) - it('does not strip a comma that is the very first character', () => { + it('strips a comma that is the very first character, leaving an empty string', () => { + // Regression test: `lastIndexOf(',') > 0` used to skip stripping + // whenever the (only) comma was at index 0 (e.g. a single empty-string + // list entry rendered as ", "), leaving a stray leading comma in the + // output instead of the intended empty result. const lambda = removeTrailingComma() - expect(lambda(',leading comma', identityRender)).toBe(',leading comma') + expect(lambda(', ', identityRender)).toBe('') }) }) @@ -115,6 +119,21 @@ describe('secureHref', () => { ) }) + it('accepts a valid base64-encoded data URI whose payload contains a "/" character', () => { + // Regression test: `/` in the base64 payload is HTML-escaped to + // `/` by `render()` (same as the URL scheme itself), but the code + // used to only un-escape `=` (`=`) before validating the base64 + // payload - never `/` - so any otherwise-valid base64 payload + // that happened to contain a `/` was incorrectly rejected outright. + const lambda = secureHref() + // Base64 for three 0xFF bytes, chosen because it's guaranteed to + // encode using only "/" characters (no letters/digits/"+"/"="). + const href = 'data:image/png;base64,////' + expect(lambda(href, identityRender)).toBe( + 'href="data:image/png;base64,////"', + ) + }) + it('rejects a data URI with a disallowed mime type', () => { const lambda = secureHref() expect(lambda('data:application/pdf;base64,QUJD', identityRender)).toBe('') From 8c0257dd4f9050af464796e1e4d4ce680abf0c79 Mon Sep 17 00:00:00 2001 From: Philipp Backes Date: Wed, 23 Sep 2026 14:37:48 +0200 Subject: [PATCH 13/13] fix pipeline step count and mention modern.css theme in README --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f2de391..83959ad 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,9 @@ npm install @secvisogram/html-template ## Usage -Rendering a document is a two- (2.0) or three-step (2.1, if you also want -markdown fields converted to HTML) pipeline: enrich the raw CSAF document -with the data the template needs, optionally render markdown fields, then -render the HTML template. +Rendering a document is a three-step pipeline: enrich the raw CSAF document +with the data the template needs, render markdown fields, then render the +HTML template. All three steps apply to both CSAF versions. ### CSAF 2.0 @@ -140,9 +139,10 @@ returned document, which the bundled templates invoke as e.g. ## Rendered HTML output The bundled `lib/css` stylesheets (a vendored copy of -[gutenberg-css](https://github.com/BafS/Gutenberg), plus Secvisogram's own -`preview.css`) are inlined directly into `