From 05dd1c9663f113931d35195004d3ef6bd9768df0 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:11:21 +0100 Subject: [PATCH 01/45] Setup environment for decaffeination --- .github/workflows/check.yml | 34 + .github/workflows/test.yml | 28 + .gitignore | 1 + .prettierignore | 1 + .prettierrc.json | 8 + .vscode/extensions.json | 3 + .vscode/settings.json | 7 + .zed/settings.json | 7 + eslint.config.js | 24 + package-lock.json | 1462 ++++++++++++++++++++++++++++++----- package.json | 4 + 11 files changed, 1400 insertions(+), 179 deletions(-) create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/test.yml create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .zed/settings.json create mode 100644 eslint.config.js diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 00000000..13013b7f --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,34 @@ +name: Prettier Check + +on: [pull_request] + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Run Prettier check + run: npm run prettier:check + + # - name: Annotate Pull Request with Results + # if: failure() + # uses: actions/github-script@v7 + # with: + # script: | + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: '⚠️ Prettier found formatting issues. Please fix them.' + # }) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..776a3260 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + # Releases https://github.com/nodejs/release#release-schedule + node-version: + - 20.x # Maintenance + - 22.x # LTS + - 24.x # Current + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test diff --git a/.gitignore b/.gitignore index 3908c983..19c53c96 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ config.json /default-data/status/sitemap.json node_modules npm-debug.log +temp diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..4ebc8aea --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +coverage diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..4aa98acb --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "bracketSpacing": true, + "bracketSameLine": true, + "arrowParens": "avoid", + "printWidth": 120 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d7df89c9 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..02db64b7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..8cbcf3cc --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,7 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "format_on_save": "on" +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..0877defc --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,24 @@ +import globals from 'globals' +import pluginJs from '@eslint/js' + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + pluginJs.configs.recommended, + { + rules: { + 'no-unused-vars': 'warn', + }, + }, + { ignores: ['client/*'] }, + { + languageOptions: { + sourceType: 'commonjs', + globals: { + wiki: 'readonly', + ...globals.node, + ...globals.nodeBuiltin, + ...globals.mocha, + }, + }, + }, +] diff --git a/package-lock.json b/package-lock.json index d758cca9..b2674f29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,11 +29,15 @@ "xml2js": "^0.6.2" }, "devDependencies": { + "@eslint/js": "^9.27.0", + "eslint": "^9.27.0", + "globals": "^16.1.0", "grunt": "^1.6.1", "grunt-contrib-watch": "^1.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", "mocha": "^11.1.0", + "prettier": "^3.5.3", "should": "^13.2.3", "supertest": "^7.0.0", "wiki-client": "^0.31", @@ -44,6 +48,19 @@ "node": ">=18.x" } }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -118,9 +135,9 @@ } }, "node_modules/body-parser/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -479,220 +496,1278 @@ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/client-sessions": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/client-sessions/-/client-sessions-0.8.0.tgz", + "integrity": "sha512-XERL6B5cJYGEaAigTADRr8NrUhkGmIUdrlHBzRM62uZEtFben5QYbaOxgWX79wFbCIvABhgZCWch1glw2fcyiQ==", + "license": "MPL-2.0", + "dependencies": { + "cookies": "^0.7.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/client-sessions/node_modules/cookies": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", + "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "keygrip": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/client-sessions/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/client-sessions/node_modules/keygrip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", + "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/coffeescript": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", + "integrity": "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==", + "license": "MIT", + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dompurify/node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/errorhandler/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/errorhandler/node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/errorhandler/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/errorhandler/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/errorhandler/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/eslint/node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/eslint/node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/eslint/node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/eslint/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/eslint/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/eslint/node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/eslint/node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/eslint/node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/eslint/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/toidentifier": { + "node_modules/eslint/node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/body-parser/node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" - } - }, - "node_modules/client-sessions": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/client-sessions/-/client-sessions-0.8.0.tgz", - "integrity": "sha512-XERL6B5cJYGEaAigTADRr8NrUhkGmIUdrlHBzRM62uZEtFben5QYbaOxgWX79wFbCIvABhgZCWch1glw2fcyiQ==", - "license": "MPL-2.0", - "dependencies": { - "cookies": "^0.7.0" - }, - "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/client-sessions/node_modules/cookies": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", - "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", + "node_modules/eslint/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "keygrip": "~1.0.3" - }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/client-sessions/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8.0" } }, - "node_modules/client-sessions/node_modules/keygrip": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", - "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "node_modules/eslint/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/coffeescript": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz", - "integrity": "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==", + "node_modules/eslint/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", - "bin": { - "cake": "bin/cake", - "coffee": "bin/coffee" - }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "node_modules/eslint/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "node_modules/eslint/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/dompurify": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", - "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "node": ">=8" } }, - "node_modules/dompurify/node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "node_modules/eslint/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/errorhandler/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8.0" } }, - "node_modules/errorhandler/node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "node_modules/eslint/node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } }, - "node_modules/errorhandler/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", + "node_modules/eslint/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, "engines": { - "node": ">= 0.6" + "node": ">= 8" } }, - "node_modules/errorhandler/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/eslint/node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/errorhandler/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/express": { @@ -1215,9 +2290,9 @@ } }, "node_modules/express-hbs/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "optional": true, "bin": { @@ -2390,6 +3465,19 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/globals": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", + "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/grunt": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", @@ -4320,9 +5408,9 @@ } }, "node_modules/jsdom/node_modules/@asamuzakjp/css-color": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz", - "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "license": "MIT", "dependencies": { "@csstools/css-calc": "^2.1.3", @@ -4478,9 +5566,9 @@ } }, "node_modules/jsdom/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4834,16 +5922,16 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.2.2.tgz", - "integrity": "sha512-VlSBxrPYHK4YNOEbFdkCxHQbZMoNzBkoPprqtZRW6311EUF/DlSxoycE2e/2NtRk4WKkIXzyrXDTrlikJMWgbw==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.4.0.tgz", + "integrity": "sha512-O6oi5Y9G6uu8f9iqXR6iKNLWHLRex3PKbmHynfpmUnMJJGrdgXh8ZmS85Ei5KR2Gnl+/gQ9s+Ktv5CqKybNw4A==", "dev": true, "license": "MIT", "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", @@ -5131,9 +6219,9 @@ } }, "node_modules/mocha/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5162,9 +6250,9 @@ } }, "node_modules/mocha/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6182,6 +7270,22 @@ "node": ">= 8" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", @@ -6243,14 +7347,14 @@ "license": "MIT" }, "node_modules/supertest": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.0.tgz", - "integrity": "sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz", + "integrity": "sha512-aI59HBTlG9e2wTjxGJV+DygfNLgnWbGdZxiA/sgrnNNikIW8lbDvCtF6RnhZoJ82nU7qv7ZLjrvWqCEm52fAmw==", "dev": true, "license": "MIT", "dependencies": { "methods": "^1.1.2", - "superagent": "^9.0.1" + "superagent": "^10.2.1" }, "engines": { "node": ">=14.18.0" @@ -6355,9 +7459,9 @@ "license": "MIT" }, "node_modules/supertest/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6781,9 +7885,9 @@ } }, "node_modules/supertest/node_modules/superagent": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", - "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.1.tgz", + "integrity": "sha512-O+PCv11lgTNJUzy49teNAWLjBZfc+A1enOwTpLlH6/rsvKcTwcdTT8m9azGkVqM7HBl5jpyZ7KTPhHweokBcdg==", "dev": true, "license": "MIT", "dependencies": { @@ -6792,7 +7896,7 @@ "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.0", - "formidable": "^3.5.1", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.11.0" @@ -6809,9 +7913,9 @@ "license": "ISC" }, "node_modules/wiki-client": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/wiki-client/-/wiki-client-0.31.0.tgz", - "integrity": "sha512-zwRcS5rLyVypiGfrHPnKhh0ngcmetaqNlySLt9nYNtRnMkMAKD3M/j4jteBVHNooqK5kJBmCRGPKWkBgcFuzlA==", + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/wiki-client/-/wiki-client-0.31.1.tgz", + "integrity": "sha512-xeLC/ya0GxiMaCPHavW+FPrFkN4kl8hmJZpvCGNNla6o4O1t+RU+mCmhjraGbkSjjAOyoivToylT9fOqwKTYuw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1b64c07e..d0b067df 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,15 @@ "test": "grunt mochaTest" }, "devDependencies": { + "@eslint/js": "^9.27.0", + "eslint": "^9.27.0", + "globals": "^16.1.0", "grunt": "^1.6.1", "grunt-contrib-watch": "^1.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", "mocha": "^11.1.0", + "prettier": "^3.5.3", "should": "^13.2.3", "supertest": "^7.0.0", "wiki-client": "^0.31", From 10846c61599c9a83b7375a1aa76b0114dc349037 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:28:45 +0100 Subject: [PATCH 02/45] rename the tests, from .coffee to .js --- test/{defaultargs.coffee => defaultargs.js} | 0 test/{page.coffee => page.js} | 0 test/{random.coffee => random.js} | 0 test/{server.coffee => server.js} | 0 test/{sitemap.coffee => sitemap.js} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename test/{defaultargs.coffee => defaultargs.js} (100%) rename test/{page.coffee => page.js} (100%) rename test/{random.coffee => random.js} (100%) rename test/{server.coffee => server.js} (100%) rename test/{sitemap.coffee => sitemap.js} (100%) diff --git a/test/defaultargs.coffee b/test/defaultargs.js similarity index 100% rename from test/defaultargs.coffee rename to test/defaultargs.js diff --git a/test/page.coffee b/test/page.js similarity index 100% rename from test/page.coffee rename to test/page.js diff --git a/test/random.coffee b/test/random.js similarity index 100% rename from test/random.coffee rename to test/random.js diff --git a/test/server.coffee b/test/server.js similarity index 100% rename from test/server.coffee rename to test/server.js diff --git a/test/sitemap.coffee b/test/sitemap.js similarity index 100% rename from test/sitemap.coffee rename to test/sitemap.js From b2b699fda77343a17fce44f2f792c332b89f85c7 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:40:51 +0100 Subject: [PATCH 03/45] convert test to JavaScript As we did this previously, all in one commit rather than individually. --- Gruntfile.js | 59 ++++---- test/defaultargs.js | 21 +-- test/page.js | 123 +++++++++------- test/random.js | 18 ++- test/server.js | 339 ++++++++++++++++++++++++-------------------- test/sitemap.js | 175 ++++++++++++----------- 6 files changed, 401 insertions(+), 334 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 6671c27e..9a121345 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,10 +1,9 @@ -module.exports = function( grunt ) { +module.exports = function (grunt) { + 'use strict' - "use strict"; - - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-mocha-test'); - grunt.loadNpmTasks('grunt-git-authors'); + grunt.loadNpmTasks('grunt-contrib-watch') + grunt.loadNpmTasks('grunt-mocha-test') + grunt.loadNpmTasks('grunt-git-authors') grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), @@ -13,43 +12,33 @@ module.exports = function( grunt ) { test: { options: { reporter: 'spec', - require: [ - 'coffeescript/register', - 'should' - ] + require: ['coffeescript/register', 'should'], }, - src: [ - 'test/defaultargs.coffee', - 'test/page.coffee', - 'test/random.coffee', - 'test/server.coffee', - 'test/sitemap.coffee' - ] - } + src: ['test/defaultargs.js', 'test/page.js', 'test/random.js', 'test/server.js', 'test/sitemap.js'], + }, }, authors: { prior: [ - "Ward Cunningham ", - "Nick Niemeir ", - "Patrick Mueller ", - "Erkan Yilmaz ", - "Tom Lee ", - "Nicholas Hallahan ", - "Paul Rodwell ", - "Austin King " - ] + 'Ward Cunningham ', + 'Nick Niemeir ', + 'Patrick Mueller ', + 'Erkan Yilmaz ', + 'Tom Lee ', + 'Nicholas Hallahan ', + 'Paul Rodwell ', + 'Austin King ', + ], }, watch: { all: { - files: ['lib/*.coffee', 'test/*.coffee'], - tasks: ['mochaTest'] - } - } - }); - - grunt.registerTask('default', ['mochaTest']); - grunt.registerTask('check', ['retire']); + files: ['lib/*.js', 'test/*.js'], + tasks: ['mochaTest'], + }, + }, + }) + grunt.registerTask('default', ['mochaTest']) + grunt.registerTask('check', ['retire']) } diff --git a/test/defaultargs.js b/test/defaultargs.js index d975e051..363ce1ef 100644 --- a/test/defaultargs.js +++ b/test/defaultargs.js @@ -1,10 +1,15 @@ -defaultargs = require '../lib/defaultargs' +const defaultargs = require('../lib/defaultargs') -describe 'defaultargs', -> - describe '#defaultargs()', -> - it 'should not write over give args', -> - defaultargs({port: 1234}).port.should.equal(1234) - it 'should write non give args', -> +describe('defaultargs', () => { + describe('#defaultargs()', () => { + it('should not write over give args', () => { + defaultargs({ port: 1234 }).port.should.equal(1234) + }) + it('should write non give args', () => { defaultargs().port.should.equal(3000) - it 'should modify dependant args', -> - defaultargs({data: '/tmp/asdf/'}).db.should.equal('/tmp/asdf/pages') + }) + it('should modify dependant args', () => { + defaultargs({ data: '/tmp/asdf/' }).db.should.equal('/tmp/asdf/pages') + }) + }) +}) diff --git a/test/page.js b/test/page.js index c9ffc68f..da79ec96 100644 --- a/test/page.js +++ b/test/page.js @@ -1,61 +1,82 @@ -path = require('path') -random = require('../lib/random_id') -testid = random() -argv = require('../lib/defaultargs.coffee')({data: path.join('/tmp', 'sfwtests', testid), root: path.join(__dirname, '..'), packageDir: path.join(__dirname, '..', 'node_modules'), security_legacy: true}) -page = require('../lib/page.coffee')(argv) -fs = require('fs') +const path = require('node:path') +const random = require('../lib/random_id') +const testid = random() +const argv = require('../lib/defaultargs')({ + data: path.join('/tmp', 'sfwtests', testid), + root: path.join(__dirname, '..'), + packageDir: path.join(__dirname, '..', 'node_modules'), + security_legacy: true, +}) +const page = require('../lib/page')(argv) +const fs = require('node:fs') -testpage = {title: 'Asdf'} +const testpage = { title: 'Asdf' } -describe 'page', -> - describe '#page.put()', -> - it 'should save a page', (done) -> - page.put('asdf', testpage, (e) -> +console.log('testid', testid) + +describe('page', () => { + describe('#page.put()', () => { + it('should save a page', done => { + page.put('asdf', testpage, e => { done(e) - ) - describe '#page.get()', -> - it 'should get a page if it exists', (done) -> - page.get('asdf', (e, got) -> - if e then throw e - got.title.should.equal 'Asdf' + }) + }) + }) + describe('#page.get()', () => { + it('should get a page if it exists', done => { + page.get('asdf', (e, got) => { + if (e) throw e + got.title.should.equal('Asdf') done() - ) - it 'should copy a page from default if nonexistant in db', (done) -> - page.get('welcome-visitors', (e, got) -> - if e then throw e - got.title.should.equal 'Welcome Visitors' + }) + }) + it('should copy a page from default if nonexistant in db', done => { + page.get('welcome-visitors', (e, got) => { + if (e) throw e + got.title.should.equal('Welcome Visitors') done() - ) - # note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it 'should copy a page from plugins if nonexistant in db', (done) -> - page.get('recent-changes', (e, got) -> - if e then throw e - got.title.should.equal 'Recent Changes' + }) + }) + // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory + it('should copy a page from plugins if nonexistant in db', done => { + page.get('recent-changes', (e, got) => { + if (e) throw e + got.title.should.equal('Recent Changes') done() - ) - # note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it 'should mark a page from plugins with the plugin name', (done) -> - page.get('recent-changes', (e, got) -> - if e then throw e - got.plugin.should.equal 'activity' + }) + }) + // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory + it('should mark a page from plugins with the plugin name', done => { + page.get('recent-changes', (e, got) => { + if (e) throw e + got.plugin.should.equal('activity') done() - ) - it 'should create a page if it exists nowhere', (done) -> - page.get(random(), (e, got) -> - if e then throw e + }) + }) + it('should create a page if it exists nowhere', done => { + page.get(random(), (e, got) => { + if (e) throw e got.should.equal('Page not found') done() - ) - it 'should eventually write the page to disk', (done) -> - test = -> - fs.readFile(path.join(argv.db, 'asdf'), (err, data) -> - if err then throw err - readPage = JSON.parse(data) - page.get('asdf', (e, got) -> - readPage.title.should.equal got.title + }) + }) + it('should eventually write the page to disk', done => { + const test = () => { + console.log('should write', argv) + fs.readFile(path.join(argv.db, 'asdf'), (err, data) => { + if (err) throw err + const readPage = JSON.parse(data) + page.get('asdf', (e, got) => { + readPage.title.should.equal(got.title) done() - ) - ) - if page.isWorking() - page.on('finished', -> test()) - else test() + }) + }) + } + if (page.isWorking()) { + page.on('finished', () => test()) + } else { + test() + } + }) + }) +}) diff --git a/test/random.js b/test/random.js index a7dc5700..e685fea5 100644 --- a/test/random.js +++ b/test/random.js @@ -1,8 +1,12 @@ -random = require '../lib/random_id' +const random = require('../lib/random_id') -describe 'random', -> - describe '#random_id', -> - it 'should not be the same twice', -> - random().should.not.equal random() - it 'should be 16 digits', -> - random().length.should.equal 16 +describe('random', () => { + describe('#random_id', () => { + it('should not be the same twice', () => { + random().should.not.equal(random()) + }) + it('should be 16 digits', () => { + random().length.should.equal(16) + }) + }) +}) diff --git a/test/server.js b/test/server.js index 485b0165..a9b08e46 100644 --- a/test/server.js +++ b/test/server.js @@ -1,228 +1,263 @@ -request = require 'supertest' -fs = require 'fs' -server = require '..' -path = require 'path' -random = require '../lib/random_id' -testid = random() -argv = require('../lib/defaultargs.coffee')({data: path.join('/tmp', 'sfwtests', testid), packageDir: path.join(__dirname, '..', 'node_modules'), port: 55557, security_legacy: true}) - -describe 'server', -> - app = {} - before((done) -> - # as starting the server this was does not create a sitemap file, create an empty one - sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') - fs.mkdirSync path.join('/tmp', 'sfwtests', testid) - fs.mkdirSync path.join('/tmp', 'sfwtests', testid, 'status') - fs.writeFileSync sitemapLoc, JSON.stringify([]) +const supertest = require('supertest') +const fs = require('node:fs') +const server = require('..') +const path = require('node:path') +const random = require('../lib/random_id') +const testid = random() +const argv = require('../lib/defaultargs')({ + data: path.join('/tmp', 'sfwtests', testid), + packageDir: path.join(__dirname, '..', 'node_modules'), + port: 55557, + security_legacy: true, +}) - app = server(argv) - app.once("owner-set", -> - app.listen app.startOpts.port, app.startOpts.host, done - )) +describe('server', () => { + var app = {} + before(done => { + // as starting the server this was does not create a sitemap file, create an empty one + const sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') + fs.mkdirSync(path.join('/tmp', 'sfwtests', testid)) + fs.mkdirSync(path.join('/tmp', 'sfwtests', testid, 'status')) + fs.writeFileSync(sitemapLoc, JSON.stringify([])) + app = server(argv) + app.once('owner-set', () => { + app.listen(app.startOpts.port, app.startOpts.host, done) + }) + }) - request = request('http://localhost:55557') + const request = supertest('http://localhost:55557') - # location of the test page - loc = path.join('/tmp', 'sfwtests', testid, 'pages', 'adsf-test-page') + // location of the test page + const loc = path.join('/tmp', 'sfwtests', testid, 'pages', 'adsf-test-page') - it 'factories should return a list of plugin', () -> - await request + it('factories should return a list of plugin', () => { + request .get('/system/factories.json') .expect(200) .expect('Content-Type', /json/) - .then (res) -> + .then(res => { res.body[1].name.should.equal('Video') res.body[1].category.should.equal('format') + }) + }) - it 'new site should have an empty list of pages', () -> - await request + it('new site should have an empty list of pages', () => { + request .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) - .then (res) -> - res.body.should.be.empty - , (error) -> - throw error - .catch (error) -> + .then( + res => res.body.should.be.empty, + error => { + throw error + }, + ) + .catch(error => { throw error + }) + }) - it 'should create a page', () -> - body = JSON.stringify({ - type: 'create' + it('should create a page', () => { + const body = JSON.stringify({ + type: 'create', item: { - title: "Asdf Test Page" + title: 'Asdf Test Page', story: [ - {id: "a1", type: "paragraph", text: "this is the first paragraph"} - {id: "a2", type: "paragraph", text: "this is the second paragraph"} - {id: "a3", type: "paragraph", text: "this is the third paragraph"} - {id: "a4", type: "paragraph", text: "this is the fourth paragraph"} - ] - } - date: 1234567890123 - }) + { id: 'a1', type: 'paragraph', text: 'this is the first paragraph' }, + { id: 'a2', type: 'paragraph', text: 'this is the second paragraph' }, + { id: 'a3', type: 'paragraph', text: 'this is the third paragraph' }, + { id: 'a4', type: 'paragraph', text: 'this is the fourth paragraph' }, + ], + }, + date: 1234567890123, + }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .catch (err) -> + .catch(err => { throw err + }) + }) - it 'should move the paragraphs to the order given ', () -> - body = '{ "type": "move", "order": [ "a1", "a3", "a2", "a4"] }' + it('should move the paragraphs to the order given ', () => { + const body = '{ "type": "move", "order": [ "a1", "a3", "a2", "a4"] }' request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - if err - throw err - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.story[1].id.should.equal('a3') + page.story[2].id.should.equal('a2') + page.journal[1].type.should.equal('move') + }, + err => { throw err - page.story[1].id.should.equal('a3') - page.story[2].id.should.equal('a2') - page.journal[1].type.should.equal('move') - , (err) -> - throw err - .catch (err) -> + }, + ) + .catch(err => { throw err + }) + }) - it 'should add a paragraph', () -> - body = JSON.stringify({ - type: 'add' - after: 'a2' - item: {id: 'a5', type: 'paragraph', text: 'this is the NEW paragrpah'} + it('should add a paragraph', () => { + const body = JSON.stringify({ + type: 'add', + after: 'a2', + item: { id: 'a5', type: 'paragraph', text: 'this is the NEW paragrpah' }, }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.story.length.should.equal(5) + page.story[3].id.should.equal('a5') + page.journal[2].type.should.equal('add') + }, + err => { throw err - page.story.length.should.equal(5) - page.story[3].id.should.equal('a5') - page.journal[2].type.should.equal('add') - , (err) -> - throw err - .catch (err) -> + }, + ) + .catch(err => { throw err + }) + }) - it 'should remove a paragraph with given id', () -> - body = JSON.stringify({ - type: 'remove' - id: 'a2' + it('should remove a paragraph with given id', () => { + const body = JSON.stringify({ + type: 'remove', + id: 'a2', }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.story.length.should.equal(4) + page.story[1].id.should.equal('a3') + page.story[2].id.should.not.equal('a2') + page.story[2].id.should.equal('a5') + page.journal[3].type.should.equal('remove') + }, + err => { throw err - page.story.length.should.equal(4) - page.story[1].id.should.equal('a3') - page.story[2].id.should.not.equal('a2') - page.story[2].id.should.equal('a5') - page.journal[3].type.should.equal('remove') - , (err) -> - throw err - .catch (err) -> + }, + ) + .catch(err => { throw err + }) + }) - it 'should edit a paragraph in place', () -> - body = JSON.stringify({ - type: 'edit' - item: {id: 'a3', type: 'paragraph', text: 'edited'} - id: 'a3' + it('should edit a paragraph in place', () => { + const body = JSON.stringify({ + type: 'edit', + item: { id: 'a3', type: 'paragraph', text: 'edited' }, + id: 'a3', }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.story[1].text.should.equal('edited') + page.journal[4].type.should.equal('edit') + }, + err => { throw err - page.story[1].text.should.equal('edited') - page.journal[4].type.should.equal('edit') - , (err) -> + }, + ) + .catch(err => { throw err - .catch (err) -> - throw err - - it 'should default to no change', () -> - body = JSON.stringify({ - type: 'asdf' }) + }) + + it('should default to no change', () => { + const body = JSON.stringify({ + type: 'asdf', + }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(500) - .then (res) -> - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.story.length.should.equal(4) + page.journal.length.should.equal(5) + page.story[0].id.should.equal('a1') + page.story[3].text.should.equal('this is the fourth paragraph') + page.journal[4].type.should.equal('edit') + }, + err => { throw err - page.story.length.should.equal(4) - page.journal.length.should.equal(5) - page.story[0].id.should.equal('a1') - page.story[3].text.should.equal('this is the fourth paragraph') - page.journal[4].type.should.equal('edit') - , (err) -> - throw err - .catch (err) -> + }, + ) + .catch(err => { throw err + }) + }) - it 'should refuse to create over a page', () -> - body = JSON.stringify({ - type: 'create' - item: { title: 'Doh'} - id: 'c1' + it('should refuse to create over a page', () => { + const body = JSON.stringify({ + type: 'create', + item: { title: 'Doh' }, + id: 'c1', }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(409) - .then (res) -> - try - page = JSON.parse(fs.readFileSync(loc)) - catch err + .then( + () => { + const page = JSON.parse(fs.readFileSync(loc)) + page.title.should.not.equal('Doh') + }, + err => { throw err - page.title.should.not.equal('Doh') - , (err) -> - throw err - .catch (err) -> + }, + ) + .catch(err => { throw err + }) + }) - it 'site should now have one page', () -> + it('site should now have one page', () => { request .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) - .then (res) -> - res.body.length.should.equal[1] - res.body[0].should.equal['adsf-test-page'] - , (err) -> - throw err - .catch (err) -> + .then( + res => { + res.body.length.should.equal[1] + res.body[0].should.equal['adsf-test-page'] + }, + err => { + throw err + }, + ) + .catch(err => { throw err + }) + }) - - - after( -> - app.close() if app.close) + after(() => { + if (app.close) app.close() + }) +}) diff --git a/test/sitemap.js b/test/sitemap.js index cfc69449..c65bef87 100644 --- a/test/sitemap.js +++ b/test/sitemap.js @@ -1,110 +1,123 @@ -request = require 'supertest' -fs = require 'fs' -server = require '..' -path = require 'path' -random = require '../lib/random_id' -testid = random() -argv = require('../lib/defaultargs.coffee')({data: path.join('/tmp', 'sfwtests', testid), port: 55556, security_legacy: true}) +const supertest = require('supertest') +const fs = require('node:fs') +const server = require('..') +const path = require('node:path') +const random = require('../lib/random_id') +const testid = random() +const argv = require('../lib/defaultargs')({ + data: path.join('/tmp', 'sfwtests', testid), + port: 55556, + security_legacy: true, +}) -describe 'sitemap', -> - app = {} - runningServer = null - beforeEach((done) -> +describe('sitemap', () => { + let app = {} + let runningServer = null + beforeEach(done => { app = server(argv) - app.once("owner-set", -> - runningServer = app.listen app.startOpts.port, app.startOpts.host, done - )) - afterEach () -> + app.once('owner-set', () => { + runningServer = app.listen(app.startOpts.port, app.startOpts.host, done) + }) + }) + afterEach(() => { runningServer.close() + }) + const request = supertest('http://localhost:55556') + fs.mkdirSync(path.join('/tmp', 'sfwtests', testid, 'pages'), { recursive: true }) - request = request('http://localhost:55556') - fs.mkdirSync path.join('/tmp', 'sfwtests', testid, 'pages'), {recursive: true} - - # location of the sitemap - sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') + // location of the sitemap + const sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') - it 'new site should have an empty sitemap', () -> + it('new site should have an empty sitemap', () => { request - .get('/system/sitemap.json') - .expect(200) - .expect('Content-Type', /json/) - .then (res) -> - res.body.should.be.empty - , (err) -> - throw err - + .get('/system/sitemap.json') + .expect(200) + .expect('Content-Type', /json/) + .then( + res => { + res.body.should.be.empty + }, + err => { + throw err + }, + ) + }) - it 'creating a page should add it to the sitemap', () -> - body = JSON.stringify({ - type: 'create' + it('creating a page should add it to the sitemap', () => { + const body = JSON.stringify({ + type: 'create', item: { - title: "Asdf Test Page" + title: 'Asdf Test Page', story: [ - {id: "a1", type: "paragraph", text: "this is the first paragraph"} - {id: "a2", type: "paragraph", text: "this is the second paragraph"} - {id: "a3", type: "paragraph", text: "this is the third paragraph"} - {id: "a4", type: "paragraph", text: "this is the fourth paragraph"} - ] - } - date: 1234567890123 - }) + { id: 'a1', type: 'paragraph', text: 'this is the first paragraph' }, + { id: 'a2', type: 'paragraph', text: 'this is the second paragraph' }, + { id: 'a3', type: 'paragraph', text: 'this is the third paragraph' }, + { id: 'a4', type: 'paragraph', text: 'this is the fourth paragraph' }, + ], + }, + date: 1234567890123, + }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - # sitemap update does not happen until after the put has returned, so wait for it to finish - app.sitemaphandler.once 'finished', -> - try - sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - catch err - throw err - sitemap[0].slug.should.equal['adsf-test-page'] - sitemap[0].synopsis.should.equal['this is the first paragraph'] - , (err) -> - throw err + .then( + () => { + // sitemap update does not happen until after the put has returned, so wait for it to finish + app.sitemaphandler.once('finished', () => { + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) + sitemap[0].slug.should.equal['adsf-test-page'] + sitemap[0].synopsis.should.equal['this is the first paragraph'] + }) + }, + err => { + throw err + }, + ) + }) - it 'synopsis should reflect edit to first paragraph', () -> - body = JSON.stringify({ - type: 'edit' - item: {id: 'a1', type: 'paragraph', text: 'edited'} - id: 'a1' + it('synopsis should reflect edit to first paragraph', () => { + const body = JSON.stringify({ + type: 'edit', + item: { id: 'a1', type: 'paragraph', text: 'edited' }, + id: 'a1', }) request .put('/page/adsf-test-page/action') - .send("action=" + body) + .send('action=' + body) .expect(200) - .then (res) -> - app.sitemaphandler.once 'finished', -> - try - sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - catch err - throw err + .then(() => { + app.sitemaphandler.once('finished', () => { + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) sitemap[0].slug.should.equal['adsf-test-page'] sitemap[0].synopsis.should.equal['edited'] - , (err) -> - throw err - - it 'deleting a page should remove it from the sitemap', () -> + }), + err => { + throw err + } + }) + }) + it('deleting a page should remove it from the sitemap', () => { request .delete('/adsf-test-page.json') .send() .expect(200) - .then (res) -> - app.sitemaphandler.once 'finished', -> - try - sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - catch error - throw err + .then(() => { + app.sitemaphandler.once('finished', () => { + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) sitemap.should.be.empty - , (err) -> - throw err - - + }), + err => { + throw err + } + }) + }) - after( -> - app.close() if app.close) + after(() => { + if (app.close) app.close() + }) +}) From f89145f07c133e37142e5fd24b714befedca2854 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:46:21 +0100 Subject: [PATCH 04/45] Add forgotten prettier scripts also apply to lib/forward.js --- lib/forward.js | 20 ++++++++++---------- package.json | 2 ++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/forward.js b/lib/forward.js index d5ddd30e..8c608364 100644 --- a/lib/forward.js +++ b/lib/forward.js @@ -1,12 +1,12 @@ const init = (app, emitter) => { let sockets = [] - app.io.on('connection', (socket) => { + app.io.on('connection', socket => { let listeners = [] sockets.push(socket) console.log('client connected:', socket.handshake.address) - socket.on('disconnect', (reason) => { + socket.on('disconnect', reason => { console.log('client disconnected:', socket.handshake.address, reason) - for (let {sProducer, listener} of listeners) { + for (let { sProducer, listener } of listeners) { console.log('removing listener:', sProducer) emitter.removeListener(sProducer, listener) } @@ -14,9 +14,9 @@ const init = (app, emitter) => { let i = sockets.indexOf(socket) sockets.splice(i, 1) }) - socket.on('unsubscribe', (sProducer) => { + socket.on('unsubscribe', sProducer => { console.log('unsubscribing listener:', socket.handshake.address, sProducer) - for (let [i, {slugItem, listener}] of listeners.entries()) { + for (let [i, { slugItem, listener }] of listeners.entries()) { if (slugItem == sProducer) { console.log('removing listener:', sProducer) emitter.removeListener(sProducer, listener) @@ -24,15 +24,15 @@ const init = (app, emitter) => { } } }) - socket.on('subscribe', (sProducer) => { - let listener = (result) => { + socket.on('subscribe', sProducer => { + let listener = result => { console.log('forwarding:', socket.handshake.address, result) - socket.emit(sProducer, {slugItem: sProducer, result}) + socket.emit(sProducer, { slugItem: sProducer, result }) } console.log(`registering listener:`, socket.handshake.address, sProducer) emitter.on(sProducer, listener) - listeners.push({sProducer, listener}) + listeners.push({ sProducer, listener }) }) }) } -module.exports = {init} +module.exports = { init } diff --git a/package.json b/package.json index d0b067df..c8d3de65 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "xml2js": "^0.6.2" }, "scripts": { + "prettier:format": "prettier --write './**/*.js'", + "prettier:check": "prettier --check ./**/*.js", "test": "grunt mochaTest" }, "devDependencies": { From c5183a723e80e04bba7bd1f470b6a834cd389c89 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:47:57 +0100 Subject: [PATCH 05/45] remove old version of action --- .github/workflows/node.js.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/node.js.yml diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 093a9544..00000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs - -name: Node.js CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run build --if-present - - run: npm test From 0f0ae692e2329e18bf7de9eabe3f7fedd4aa87b7 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:56:00 +0100 Subject: [PATCH 06/45] rename defaultargs `git mv lib/defaultargs.coffee lib/defaultargs.js` --- lib/{defaultargs.coffee => defaultargs.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{defaultargs.coffee => defaultargs.js} (100%) diff --git a/lib/defaultargs.coffee b/lib/defaultargs.js similarity index 100% rename from lib/defaultargs.coffee rename to lib/defaultargs.js From e1fe305ff94c06df4682f34c4e3051bb1510d467 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 10:56:47 +0100 Subject: [PATCH 07/45] convert defaultargs to JavaScript --- lib/defaultargs.js | 109 ++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/lib/defaultargs.js b/lib/defaultargs.js index 3fac264f..042aa1e8 100644 --- a/lib/defaultargs.js +++ b/lib/defaultargs.js @@ -1,62 +1,66 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### + */ +// **defaultargs.coffee** when called on the argv object this +// module will create reasonable defaults for options not supplied, +// based on what information is provided. +const path = require('node:path') -# **defaultargs.coffee** when called on the argv object this -# module will create reasonable defaults for options not supplied, -# based on what information is provided. -path = require 'path' +const getUserHome = () => { + return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE +} -getUserHome = -> - process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE +module.exports = argv => { + argv = argv || {} + argv.root ||= __dirname + // the directory that contains all the packages that makeup the wiki + argv.packageDir ||= path.join(argv.root, '..') + argv.port ||= 3000 + argv.home ||= 'welcome-visitors' + argv.data ||= path.join(getUserHome(), '.wiki') // see also cli + argv.client ||= path.join(argv.packageDir, 'wiki-client', 'client') + argv.db ||= path.join(argv.data, 'pages') + argv.status ||= path.join(argv.data, 'status') + argv.assets ||= path.join(argv.data, 'assets') + argv.recycler ||= path.join(argv.data, 'recycle') + argv.commons ||= path.join(argv.data, 'commons') + argv.url ||= `http://localhost${argv.port === 80 ? '' : ':' + argv.port}` + argv.id ||= path.join(argv.status, 'owner.json') + argv.uploadLimit ||= '5mb' + argv.cookieSecret ||= require('crypto').randomBytes(64).toString('hex') + argv.secure_cookie ||= false + argv.session_duration ||= 7 + argv.neighbors ||= '' + argv.debug ||= false -module.exports = (argv) -> - argv or= {} - argv.root or= __dirname - # the directory that contains all the packages that makeup the wiki - argv.packageDir or= path.join(argv.root, "..") - argv.port or= 3000 - argv.home or= 'welcome-visitors' - argv.data or= path.join(getUserHome(), '.wiki') # see also cli - argv.client or= path.join(argv.packageDir, 'wiki-client', 'client') - argv.db or= path.join(argv.data, 'pages') - argv.status or= path.join(argv.data, 'status') - argv.assets or= path.join(argv.data, 'assets') - argv.recycler or= path.join(argv.data, 'recycle') - argv.commons or= path.join(argv.data, 'commons') - argv.url or= 'http://localhost' + (if argv.port is 80 then '' else ':' + argv.port) - argv.id or= path.join(argv.status, 'owner.json') - argv.uploadLimit or= '5mb' - argv.cookieSecret or= require('crypto').randomBytes(64).toString('hex') - argv.secure_cookie or= false - argv.session_duration or= 7 - argv.neighbors or= '' - argv.debug or= false - - if typeof(argv.database) is 'string' + if (typeof argv.database === 'string') { argv.database = JSON.parse(argv.database) - argv.database or= {} - argv.database.type or= './page' - if argv.database.type.charAt(0) is '.' - if argv.database.type != './page' - console.log "\n\nWARNING: This storage option is depeciated." - console.log " See ReadMe for details of the changes required.\n\n" - else + } + argv.database ||= {} + argv.database.type ||= './page' + if (argv.database.type.charAt(0) === '.') { + if (argv.database.type != './page') { + console.log('\n\nWARNING: This storage option is depeciated.') + console.log(' See ReadMe for details of the changes required.\n\n') + } + } else { argv.database.type = 'wiki-storage-' + argv.database.type + } - argv.security_type or= './security' - if argv.security_type is './security' - console.log "\n\nINFORMATION: Using default security module." - else + argv.security_type ||= './security' + if (argv.security_type === './security') { + console.log('\n\nINFORMATION: Using default security module.') + } else { argv.security_type = 'wiki-security-' + argv.security_type - argv.security_legacy or= false + } + argv.security_legacy ||= false - #resolve all relative paths + // resolve all relative paths argv.root = path.resolve(argv.root) argv.packageDir = path.resolve(argv.packageDir) argv.data = path.resolve(argv.data) @@ -68,10 +72,11 @@ module.exports = (argv) -> argv.commons = path.resolve(argv.commons) argv.id = path.resolve(argv.id) - if /node_modules/.test(argv.data) - console.log "\n\nWARNING : The dafault data path is not a safe place." - console.log " : by using ", argv.data, " your pages will be lost when packages are updated." - console.log " : You are strongly advised to use an alternative directory." - console.log " : See the wiki package ReadMe for how to do this.\n\n" - - argv + if (/node_modules/.test(argv.data)) { + console.log('\n\nWARNING : The dafault data path is not a safe place.') + console.log(' : by using ', argv.data, ' your pages will be lost when packages are updated.') + console.log(' : You are strongly advised to use an alternative directory.') + console.log(' : See the wiki package ReadMe for how to do this.\n\n') + } + return argv +} From e318a83d08fcb1d842dcd166c5020d02d300a3a4 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:23:33 +0100 Subject: [PATCH 08/45] rename page --- lib/{page.coffee => page.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{page.coffee => page.js} (100%) diff --git a/lib/page.coffee b/lib/page.js similarity index 100% rename from lib/page.coffee rename to lib/page.js From 8a1d6aa728c6f27cd465dd95693f9562aeaeca7c Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:27:14 +0100 Subject: [PATCH 09/45] convert page to JavaScript --- lib/page.js | 715 +++++++++++++++++++++++++++++----------------------- 1 file changed, 396 insertions(+), 319 deletions(-) diff --git a/lib/page.js b/lib/page.js index 44f138f5..68e55187 100644 --- a/lib/page.js +++ b/lib/page.js @@ -1,371 +1,448 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### -# **page.coffee** -# Module for interacting with pages persisted on the server. -# Everything is stored using json flat files. - -#### Requires #### -fs = require 'fs' -path = require 'path' -events = require 'events' -glob = require 'glob' - -async = require 'async' - -random_id = require './random_id' -synopsis = require 'wiki-client/lib/synopsis' - -asSlug = (name) -> - name.replace(/\s/g, '-').replace(/[^A-Za-z0-9-]/g, '').toLowerCase() - - -# Export a function that generates a page handler -# when called with options object. -module.exports = exports = (argv) -> - - wikiName = new URL(argv.url).hostname - - fs.mkdir argv.db, { recursive: true }, (e) -> - if e then throw e - - #### Private utility methods. #### - load_parse = (loc, cb, annotations={}) -> - fs.readFile(loc, (err, data) -> - return cb(err) if err - try + */ +// **page.coffee** +// Module for interacting with pages persisted on the server. +// Everything is stored using json flat files. + +// #### Requires #### +const fs = require('fs') +const path = require('path') +const events = require('events') +const glob = require('glob') + +const async = require('async') + +const random_id = require('./random_id') +const synopsis = require('wiki-client/lib/synopsis') + +const asSlug = name => + name + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9-]/g, '') + .toLowerCase() + +// Export a function that generates a page handler +// when called with options object. +module.exports = exports = argv => { + const wikiName = new URL(argv.url).hostname + + fs.mkdir(argv.db, { recursive: true }, e => { + if (e) throw e + }) + + // #### Private utility methods. #### + const load_parse = (loc, cb, annotations = {}) => { + let page + fs.readFile(loc, (err, data) => { + if (err) return cb(err) + try { page = JSON.parse(data) - catch e - errorPage = path.basename(loc) - errorPagePath = path.dirname(loc) - recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage) - fs.exists(path.dirname(recyclePage), (exists) -> - if exists - fs.rename(loc, recyclePage, (err) -> - if err - console.log "ERROR: moving problem page #{loc} to recycler", err - else - console.log "ERROR: problem page #{loc} moved to recycler" - ) - else - fs.mkdir(path.dirname(recyclePage), { recursive: true }, (err) -> - if err - console.log "ERROR: creating recycler", err - else - fs.rename(loc, recyclePage, (err) -> - if err - console.log "ERROR: moving problem page #{loc} to recycler", err - else - console.log "ERROR: problem page #{loc} moved to recycler" - ) - ) - ) + } catch { + const errorPage = path.basename(loc) + const errorPagePath = path.dirname(loc) + const recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage) + // TODO: fs.exists depreciated + fs.exists(path.dirname(recyclePage), exists => { + if (exists) { + fs.rename(loc, recyclePage, err => { + if (err) { + console.log('ERROR: moving problem page #{loc} to recycler', err) + } else { + console.log('ERROR: problem page #{loc} moved to recycler') + } + }) + } else { + fs.mkdir(path.dirname(recyclePage), { recursive: true }, err => { + if (err) { + console.log('ERROR: creating recycler', err) + } else { + fs.rename(loc, recyclePage, err => { + if (err) { + console.log('ERROR: moving problem page #{loc} to recycler', err) + } else { + console.log('ERROR: problem page #{loc} moved to recycler') + } + }) + } + }) + } + }) + return cb(null, 'Error Parsing Page', 404) - for key, val of annotations + } + for (const [key, val] of Object.entries(annotations)) { page[key] = val + } cb(null, page) - ) - - load_parse_copy = (defloc, file, cb) -> - fs.readFile(defloc, (err, data) -> - if err then cb(err) - try + }) + } + + const load_parse_copy = (defloc, file, cb) => { + fs.readFile(defloc, (err, data) => { + if (err) cb(err) + let page + try { page = JSON.parse(data) - catch e + } catch (e) { return cb(e) + } cb(null, page) - itself.put(file, page, (err) -> - if err then cb(err) - ) - ) - - # Reads and writes are async, but serially queued to avoid race conditions. - queue = [] - - tryDefaults = (file, cb) -> - lastDefault = (cb) -> - defloc = path.join(argv.root, 'default-data', 'pages', file) - fs.exists defloc, (exists) -> - if exists + // TODO: what is happening here?! put will never be reached??? + itself.put(file, page, err => { + if (err) cb(err) + }) + }) + } + // Reads and writes are async, but serially queued to avoid race conditions. + const queue = [] + + const tryDefaults = (file, cb) => { + const lastDefault = cb => { + const defloc = path.join(argv.root, 'default-data', 'pages', file) + // TODO: fs.exists depreciated + fs.exists(defloc, exists => { + if (exists) { cb(defloc) - else + } else { cb(null) - if argv.defaults - defloc = path.join(argv.data, '..', argv.defaults, 'pages', file) - console.log 'firstDefault', defloc - fs.exists defloc, (exists) -> - if exists + } + }) + } + if (argv.defaults) { + const defloc = path.join(argv.data, '..', argv.defaults, 'pages', file) + console.log('firstDefault', defloc) + // TODO: fs.exists depreciated + fs.exists(defloc, exists => { + if (exists) { cb(defloc) - else + } else { lastDefault(cb) - else + } + }) + } else { lastDefault(cb) - - # Main file io function, when called without page it reads, - # when called with page it writes. - fileio = (action, file, page, cb) -> - if file.startsWith 'recycler/' - loc = path.join(argv.recycler, file.split('/')[1]) - else - loc = path.join(argv.db, file) - switch action - when 'delete' - if file.startsWith 'recycler/' - # delete from recycler - fs.exists(loc, (exists) -> - if exists - fs.unlink(loc, (err) -> + } + } + + // Main file io function, when called without page it reads, + // when called with page it writes. + const fileio = (action, file, page, cb) => { + const loc = file.startsWith('recycler/') ? path.join(argv.recycler, file.split('/')[1]) : path.join(argv.db, file) + + switch (action) { + case 'delete': + if (file.startsWith('recycler/')) { + // delete from recycler + // TODO: fs.exists depreciated + fs.exists(loc, exists => { + if (exists) + fs.unlink(loc, err => { cb(err) - ) - ) - else - # move page to recycler - fs.exists(loc, (exists) -> - if exists - recycleLoc = path.join(argv.recycler, file) - fs.exists(path.dirname(recycleLoc), (exists) -> - if exists - fs.rename(loc, recycleLoc, (err) -> + }) + }) + } else { + // move page to recycler + // TODO: fs.exists depreciated + fs.exists(loc, exists => { + if (exists) { + const recycleLoc = path.join(argv.recycler, file) + // TODO: fs.exists depreciated + fs.exists(path.dirname(recycleLoc), exists => { + if (exists) { + fs.rename(loc, recycleLoc, err => { cb(err) - ) - else - fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) -> - if err then cb(err) - fs.rename(loc, recycleLoc, (err) -> + }) + } else { + fs.mkdir(path.dirname(recycleLoc), { recursive: true }, err => { + if (err) cb(err) + fs.rename(loc, recycleLoc, err => { cb(err) - ) - ) - ) - else + }) + }) + } + }) + } else { cb('page does not exist') - ) - when 'recycle' - copyFile = (source, target, cb) -> - - done = (err) -> - if !cbCalled - cb err + } + }) + } + break + case 'recycle': { + const copyFile = (source, target, cb) => { + const done = err => { + if (!cbCalled) { + cb(err) cbCalled = true + } return + } - cbCalled = false + let cbCalled = false - rd = fs.createReadStream(source) - rd.on 'error', (err) -> - done err + const rd = fs.createReadStream(source) + rd.on('error', err => { + done(err) return + }) - wr = fs.createWriteStream(target) - wr.on 'error', (err) -> - done err + const wr = fs.createWriteStream(target) + wr.on('error', err => { + done(err) return - wr.on 'close', (ex) -> + }) + wr.on('close', () => { done() return - rd.pipe wr + }) + rd.pipe(wr) return - - fs.exists(loc, (exists) -> - if exists - recycleLoc = path.join(argv.recycler, file) - fs.exists(path.dirname(recycleLoc), (exists) -> - if exists - copyFile(loc, recycleLoc, (err) -> + } + + // TODO: fs.exists depreciated + fs.exists(loc, exists => { + if (exists) { + const recycleLoc = path.join(argv.recycler, file) + // TODO: fs.exists depreciated + fs.exists(path.dirname(recycleLoc), exists => { + if (exists) { + copyFile(loc, recycleLoc, err => { cb(err) - ) - else - fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) -> - if err then cb(err) - copyFile(loc, recycleLoc, (err) -> + }) + } else { + fs.mkdir(path.dirname(recycleLoc), { recursive: true }, err => { + if (err) cb(err) + copyFile(loc, recycleLoc, err => { cb(err) - ) - ) - ) - else + }) + }) + } + }) + } else { cb('page does not exist') - ) - when 'get' - fs.exists(loc, (exists) -> - if exists - load_parse(loc, cb, {plugin: undefined}) - else - tryDefaults(file, (defloc) -> - if defloc + } + }) + break + } + case 'get': + // TODO: fs.exists depreciated + fs.exists(loc, exists => { + if (exists) { + load_parse(loc, cb, { plugin: undefined }) + } else { + tryDefaults(file, defloc => { + if (defloc) { load_parse(defloc, cb) - else - glob "wiki-plugin-*/pages", {cwd: argv.packageDir}, (e, plugins) -> - if e then return cb(e) + } else { + glob('wiki-plugin-*/pages', { cwd: argv.packageDir }, (e, plugins) => { + if (e) return cb(e) - # if no plugins found - if plugins.length is 0 + // if no plugins found + if (plugins.length === 0) { cb(null, 'Page not found', 404) - - giveUp = do -> - count = plugins.length - return -> - count -= 1 - if count is 0 - cb(null, 'Page not found', 404) - - for plugin in plugins - do -> - pluginName = plugin.slice(12, -6) - pluginloc = path.join(argv.packageDir, plugin, file) - fs.exists(pluginloc, (exists) -> - if exists - load_parse(pluginloc, cb, {plugin: pluginName}) - else - giveUp() - ) - ) - ) - when 'put' + } + + let count = plugins.length + const giveUp = () => { + count -= 1 + if (count === 0) { + cb(null, 'Page not found', 404) + } + } + plugins.forEach(plugin => { + const pluginName = plugin.slice(12, -6) + const pluginloc = path.join(argv.packageDir, plugin, file) + fs.exists(pluginloc, exists => { + if (exists) { + load_parse(pluginloc, cb, { plugin: pluginName }) + } else { + giveUp() + } + }) + }) + }) + } + }) + } + }) + break + case 'put': page = JSON.stringify(page, null, 2) - fs.exists(path.dirname(loc), (exists) -> - if exists - fs.writeFile(loc, page, (err) -> - if err - console.log "ERROR: write file #{loc} ", err + fs.exists(path.dirname(loc), exists => { + if (exists) { + fs.writeFile(loc, page, err => { + if (err) { + console.log('ERROR: write file #{loc} ', err) + } cb(err) - ) - else - fs.mkdir(path.dirname(loc), { recursive: true }, (err) -> - if err then cb(err) - fs.writeFile(loc, page, (err) -> - if err - console.log "ERROR: write file #{loc} ", err + }) + } else { + fs.mkdir(path.dirname(loc), { recursive: true }, err => { + if (err) cb(err) + fs.writeFile(loc, page, err => { + if (err) { + console.log('ERROR: write file #{loc} ', err) + } cb(err) - ) - ) - ) - else - console.log "pagehandler: unrecognized action #{action}" - - # Control variable that tells if the serial queue is currently working. - # Set back to false when all jobs are complete. - working = false - - # Keep file io working on queued jobs, but don't block the main thread. - serial = (item) -> - if item + }) + }) + } + }) + break + default: + console.log('pagehandler: unrecognized action #{action}') + } + } + + // Control variable that tells if the serial queue is currently working. + // Set back to false when all jobs are complete. + let working = false + + // Keep file io working on queued jobs, but don't block the main thread. + const serial = item => { + if (item) { itself.start() - fileio(item.action, item.file, item.page, (err, data, status) -> - process.nextTick( -> + fileio(item.action, item.file, item.page, (err, data, status) => { + process.nextTick(() => { serial(queue.shift()) - ) + }) item.cb(err, data, status) - ) - else + }) + } else { itself.stop() + } + } - #### Public stuff #### - # Make the exported object an instance of EventEmitter - # so other modules can tell if it is working or not. - itself = new events.EventEmitter - itself.start = -> + // #### Public stuff #### + // Make the exported object an instance of EventEmitter + // so other modules can tell if it is working or not. + const itself = new events.EventEmitter() + + itself.start = () => { working = true - @emit 'working' - itself.stop = -> + itself.emit('working') + } + + itself.stop = () => { working = false - @emit 'finished' - - itself.isWorking = -> - working - - # get method takes a slug and a callback, adding them to the queue, - # starting serial if it isn't already working. - itself.get = (file, cb) -> - queue.push({action: 'get', file, page: null, cb}) - serial(queue.shift()) unless working - - # put takes a slugged name, the page as a json object, and a callback. - # adds them to the queue, and starts it unless it is working. - itself.put = (file, page, cb) -> - queue.push({action: 'put', file, page, cb}) - serial(queue.shift()) unless working - - itself.delete = (file, cb) -> - queue.push({action: 'delete', file, page: null, cb}) - serial(queue.shift()) unless working - - itself.saveToRecycler = (file, cb) -> - queue.push({action: 'recycle', file, page: null, cb}) - serial(queue.shift()) unless working - - editDate = (journal) -> - for action in (journal || []) by -1 - return action.date if action.date and action.type != 'fork' - undefined - - itself.pages = (cb) -> - - extractPageLinks = (collaborativeLinks, currentItem, currentIndex, array) -> - # extract collaborative links - # - this will need extending if we also extract the id of the item containing the link - try - linkRe = /\[\[([^\]]+)\]\]/g - match = undefined - while (match = linkRe.exec(currentItem.text)) != null - if not collaborativeLinks.has(asSlug(match[1])) + itself.emit('finished') + } + + itself.isWorking = () => working + + // get method takes a slug and a callback, adding them to the queue, + // starting serial if it isn't already working. + itself.get = (file, cb) => { + queue.push({ action: 'get', file, page: null, cb }) + if (!working) serial(queue.shift()) + } + + // put takes a slugged name, the page as a json object, and a callback. + // adds them to the queue, and starts it unless it is working. + itself.put = (file, page, cb) => { + queue.push({ action: 'put', file, page, cb }) + if (!working) serial(queue.shift()) + } + + itself.delete = (file, cb) => { + queue.push({ action: 'delete', file, page: null, cb }) + if (!working) serial(queue.shift()) + } + + itself.saveToRecycler = (file, cb) => { + queue.push({ action: 'recycle', file, page: null, cb }) + if (!working) serial(queue.shift()) + } + + const editDate = journal => { + if (!journal) return undefined + // find the last journal entry, that is not a fork, with a date. + const last = journal.findLast(action => { + return action.date && action.type != 'fork' + }) + return last ? last.date : undefined + } + + itself.pages = cb => { + const extractPageLinks = (collaborativeLinks, currentItem, currentIndex, array) => { + // extract collaborative links + // - this will need extending if we also extract the id of the item containing the link + try { + const linkRe = /\[\[([^\]]+)\]\]/g + let match = undefined + while ((match = linkRe.exec(currentItem.text)) != null) { + if (!collaborativeLinks.has(asSlug(match[1]))) { collaborativeLinks.set(asSlug(match[1]), currentItem.id) - if 'reference' == currentItem.type - if not collaborativeLinks.has(currentItem.slug) + } + } + if ('reference' == currentItem.type) { + if (!collaborativeLinks.has(currentItem.slug)) { collaborativeLinks.set(currentItem.slug, currentItem.id) - catch err - console.log "METADATA *** #{wikiName} Error extracting links from #{currentIndex} of #{JSON.stringify(array)}", err.message - collaborativeLinks - - fs.readdir argv.db, (e, files) -> - return cb(e) if e - # used to make sure all of the files are read - # and processesed in the site map before responding - doSitemap = (file, cb) -> - itself.get file, (e, page, status) -> - return cb() if file.match /^\./ - if e or status is 404 - console.log 'Problem building sitemap:', file, 'e: ', e, 'status:', status - return cb() # Ignore errors in the pagehandler get. - - try - pageLinksMap = page.story.reduce( extractPageLinks, new Map()) - catch err - console.log "METADATA *** #{wikiName} reduce to extract links on #{file} failed", err.message + } + } + } catch (err) { + console.log( + `METADATA *** ${wikiName} Error extracting links from ${currentIndex} of ${JSON.stringify(array)}`, + err.message, + ) + } + return collaborativeLinks + } + fs.readdir(argv.db, (e, files) => { + if (e) return cb(e) + // used to make sure all of the files are read + // and processesed in the site map before responding + const doSitemap = (file, cb) => { + itself.get(file, (e, page, status) => { + if (file.match(/^\./)) return cb() + if (e || status === 404) { + console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status) + return cb() // Ignore errors in the pagehandler get. + } + let pageLinksMap + try { + pageLinksMap = page.story.reduce(extractPageLinks, new Map()) + } catch (err) { + console.log('METADATA *** #{wikiName} reduce to extract links on #{file} failed', err.message) pageLinksMap = [] - # - if pageLinksMap.size > 0 - pageLinks = Object.fromEntries(pageLinksMap) - else - pageLinks = undefined - - cb null, { - slug : file - title : page.title - date : editDate(page.journal) - synopsis : synopsis(page) - links : pageLinks } + // + const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined + + cb(null, { + slug: file, + title: page.title, + date: editDate(page.journal), + synopsis: synopsis(page), + links: pageLinks, + }) + }) + } + async.map(files, doSitemap, (e, sitemap) => { + if (e) return cb(e) + cb( + null, + sitemap.filter(item => item != null), + ) + }) + }) + } + + itself.slugs = cb => { + fs.readdir(argv.db, { withFileTypes: true }, (e, files) => { + if (e) { + console.log('Problem reading pages directory', e) + return cb(e) + } + + const onlyFiles = files.map(i => (i.isFile() ? i.name : null)).filter(i => i != null && !i?.startsWith('.')) + cb(null, onlyFiles) + }) + } - async.map files, doSitemap, (e, sitemap) -> - return cb(e) if e - cb null, sitemap.filter (item) -> if item? then true - - itself.slugs = (cb) -> - fs.readdir argv.db, {withFileTypes: true}, (e, files) -> - if e - console.log 'Problem reading pages directory', e - cb(e) - else - onlyFiles = files.map((i) -> - if i.isFile() - return i.name - else - return null - ).filter((i) -> - i != null && !i?.startsWith('.')) - cb(null, onlyFiles) - - itself + return itself +} From f637db468d2453eb361bf2d64f07c9c4bef6185d Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:30:08 +0100 Subject: [PATCH 10/45] rename plugin --- lib/{plugins.coffee => plugins.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{plugins.coffee => plugins.js} (100%) diff --git a/lib/plugins.coffee b/lib/plugins.js similarity index 100% rename from lib/plugins.coffee rename to lib/plugins.js From d944ea3a6f4b4b4af78bb79b4cb5c6f1a964bc69 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:31:45 +0100 Subject: [PATCH 11/45] convert plugin to JavaScript --- lib/plugins.js | 92 +++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 43 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 3b646dfb..b6c40b4f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -1,48 +1,54 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### - -# support server-side plugins - -fs = require 'fs' -path = require 'path' -glob = require 'glob' -events = require 'events' -{ pathToFileURL } = require 'node:url' -# forward = require './forward' - -module.exports = exports = (argv) -> - -# NOTE: plugins are now in their own package directories alongside this one... -# Plugins are in directories of the form wiki-package-* -# those with a server component will have a server directory - - plugins = {} - - # http://stackoverflow.com/questions/10914751/loading-node-js-modules-dynamically-based-on-route - - startServer = (params, plugin) -> - server = "#{argv.packageDir}/#{plugin}/server/server.js" - fs.exists server, (exists) -> - if exists - console.log 'starting plugin', plugin - import(pathToFileURL(server)).then((exported) -> - plugins[plugin] = exported - plugins[plugin].startServer?(params) - ).catch((e) -> - console.log 'failed to start plugin', plugin, e?.stack or e - ) - - startServers = (params) -> - # emitter = new events.EventEmitter() - # forward.init params.app, emitter - # params.emitter = emitter - glob "wiki-plugin-*", {cwd: argv.packageDir}, (e, plugins) -> - startServer params, plugin for plugin in plugins - - - {startServers} + */ + +// support server-side plugins + +const fs = require('node:fs') +const glob = require('glob') +const { pathToFileURL } = require('node:url') +// forward = require './forward' + +module.exports = exports = argv => { + // NOTE: plugins are now in their own package directories alongside this one... + // Plugins are in directories of the form wiki-package-* + // those with a server component will have a server directory + + const plugins = {} + + // http://stackoverflow.com/questions/10914751/loading-node-js-modules-dynamically-based-on-route + + const startServer = (params, plugin) => { + const server = `${argv.packageDir}/${plugin}/server/server.js` + fs.exists(server, exists => { + if (exists) { + console.log('starting plugin', plugin) + import(pathToFileURL(server)) + .then(exported => { + plugins[plugin] = exported + plugins[plugin].startServer?.(params) + }) + .catch(e => { + console.log('failed to start plugin', plugin, e?.stack || e) + }) + } + }) + } + + const startServers = params => { + // emitter = new events.EventEmitter() + // forward.init params.app, emitter + // params.emitter = emitter + glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, plugins) => { + plugins.forEach(plugin => { + startServer(params, plugin) + }) + }) + } + + return { startServers } +} From 9dee9908fa5d0fa2334a9d8b1737713e86314287 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:33:22 +0100 Subject: [PATCH 12/45] rename random --- lib/{random_id.coffee => random_id.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{random_id.coffee => random_id.js} (100%) diff --git a/lib/random_id.coffee b/lib/random_id.js similarity index 100% rename from lib/random_id.coffee rename to lib/random_id.js From d9d3b6a8cdfd6fcff0ab72a4f4e6c4fc1c146332 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:34:23 +0100 Subject: [PATCH 13/45] convert random to JavaScript --- lib/random_id.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/random_id.js b/lib/random_id.js index 3ad9fd02..52476aab 100644 --- a/lib/random_id.js +++ b/lib/random_id.js @@ -1,18 +1,15 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### + */ -# **random_id.coffee** -# Simple random hex generator, takes an optional number of -# chars that defaults to 16 and returns a random id. +// **random_id.coffee** +// Simple random hex generator, takes an optional number of +// chars that defaults to 16 and returns a random id. -random_id = (chars = 16) -> - [0...chars].map( -> - Math.floor(Math.random() * 16).toString(16) - ).join('') +const random_id = (chars = 16) => [...Array(chars)].map(() => Math.floor(Math.random() * 16).toString(16)).join('') module.exports = random_id.random_id = random_id From 65c9c425d3fb0b821668c0e6f96071bcabdb21fc Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:36:38 +0100 Subject: [PATCH 14/45] rename security --- lib/{security.coffee => security.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{security.coffee => security.js} (100%) diff --git a/lib/security.coffee b/lib/security.js similarity index 100% rename from lib/security.coffee rename to lib/security.js From 4172ac7dd3b32bbc9d4a9e24027ee21121635883 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 22 May 2025 11:59:28 +0100 Subject: [PATCH 15/45] convert security to JavaScript --- lib/security.js | 146 +++++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/lib/security.js b/lib/security.js index 6b7fc8e3..7efe0a8d 100644 --- a/lib/security.js +++ b/lib/security.js @@ -1,86 +1,94 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-node-server/blob/master/LICENSE.txt -### -# **security.coffee** -# Module for default site security. -# -# This module is not intented for use, but is here to catch a problem with -# configuration of security. It does not provide any authentication, but will -# allow the server to run read-only. - -#### Requires #### -fs = require 'fs' - - -# Export a function that generates security handler -# when called with options object. -module.exports = exports = (log, loga, argv) -> - security={} - - #### Private utility methods. #### - - user = '' - - owner = '' - - admin = argv.admin - - # save the location of the identity file - idFile = argv.id - - #### Public stuff #### - - security.authenticate_session = -> - (req, res, next) -> - # not possible to login, so always false - req.isAuthenticated = -> - return false - next() - - # Retrieve owner infomation from identity file in status directory - security.retrieveOwner = (cb) -> - fs.exists idFile, (exists) -> - if exists - fs.readFile(idFile, (err, data) -> - if err then return cb err + */ +// **security.js** +// Module for default site security. +// +// This module is not intented for use, but is here to catch a problem with +// configuration of security. It does not provide any authentication, but will +// allow the server to run read-only. + +// #### Requires #### +const fs = require('node:fs') + +// Export a function that generates security handler +// when called with options object. +module.exports = exports = (log, loga, argv) => { + const security = {} + + // #### Private utility methods. #### + + const user = '' + + let owner = '' + + // save the admin user, and location of the identity file + const { admin, id: idFile } = argv + + // #### Public stuff #### + + security.authenticate_session = () => { + ;(req, res, next) => { + // not possible to login, so always false + req.isAuthenticated = () => false + return next() + } + } + + // Retrieve owner infomation from identity file in status directory + security.retrieveOwner = cb => { + fs.exists(idFile, exists => { + if (exists) { + fs.readFile(idFile, (err, data) => { + if (err) return cb(err) owner += data - cb()) - else + cb() + }) + } else { owner = '' cb() - - # Return the owners name - security.getOwner = -> - if !owner.name? + } + }) + } + + // Return the owners name + security.getOwner = () => { + let ownerName + if (!owner.name) { ownerName = '' - else + } else { ownerName = owner.name - ownerName - - security.getUser = (req) -> + } + return ownerName + } + security.getUser = req => { return '' + } - security.isAuthorized = (req) -> - # nobody is authorized - everything is read-only - # unless legacy support, when unclaimed sites can be editted. - if owner == '' - if argv.security_legacy + security.isAuthorized = req => { + // nobody is authorized - everything is read-only + // unless legacy support, when unclaimed sites can be editted. + if (owner == '') { + if (argv.security_legacy) { return true - else + } else { return false - else + } + } else { return false - - # Wiki server admin - security.isAdmin = -> + } + } + // Wiki server admin + security.isAdmin = () => { return false + } + security.defineRoutes = (app, cors, updateOwner) => { + // default security does not have any routes + } - security.defineRoutes = (app, cors, updateOwner) -> - # default security does not have any routes - - - security + return security +} From f5a0fdef8fa0d2091b492f65b3f7fd52f1b7f482 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Fri, 23 May 2025 17:42:19 +0100 Subject: [PATCH 16/45] rename search --- lib/{search.coffee => search.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{search.coffee => search.js} (100%) diff --git a/lib/search.coffee b/lib/search.js similarity index 100% rename from lib/search.coffee rename to lib/search.js From 555f4d35c74eb4f7f170c18fee609395f624fb4c Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Sun, 25 May 2025 11:24:06 +0100 Subject: [PATCH 17/45] convert search to JavaScript --- lib/search.js | 578 +++++++++++++++++++++++++++++--------------------- 1 file changed, 332 insertions(+), 246 deletions(-) diff --git a/lib/search.js b/lib/search.js index 86eee63b..ccaa9bf9 100644 --- a/lib/search.js +++ b/lib/search.js @@ -1,304 +1,390 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### + */ -# **search.coffee** +// **search.js** -fs = require 'fs' -path = require 'path' -events = require 'events' -url = require 'node:url' -writeFileAtomic = require 'write-file-atomic' +const fs = require('node:fs') +const path = require('node:path') +const events = require('node:events') +const url = require('node:url') +const writeFileAtomic = require('write-file-atomic') -miniSearch = require 'minisearch' +const miniSearch = require('minisearch') -module.exports = exports = (argv) -> - - wikiName = new URL(argv.url).hostname +module.exports = exports = argv => { + const wikiName = new URL(argv.url).hostname + let siteIndex = [] + const queue = [] - siteIndex = [] + let searchPageHandler = null - queue = [] + // ms since last update we will remove index from memory + // orig - searchTimeoutMs = 1200000 + const searchTimeoutMs = 120000 // temp reduce to 2 minutes + let searchTimeoutHandler = null - searchPageHandler = null + const siteIndexLoc = path.join(argv.status, 'site-index.json') + const indexUpdateFlag = path.join(argv.status, 'index-updated') - # ms since last update we will remove index from memory - # orig - searchTimeoutMs = 1200000 - searchTimeoutMs = 120000 # temp reduce to 2 minutes - searchTimeoutHandler = null + let working = false - siteIndexLoc = path.join(argv.status, 'site-index.json') - indexUpdateFlag = path.join(argv.status, 'index-updated') - - working = false - - touch = (file, cb) -> - fs.stat file, (err, stats) -> - return cb() if err is null - fs.open file, 'w', (err,fd) -> - cb(err) if err - fs.close fd, (err) -> + const touch = (file, cb) => { + fs.stat(file, (err, stats) => { + if (err === null) return cb() + fs.open(file, 'w', (err, fd) => { + if (err) cb(err) + fs.close(fd, err => { cb(err) - - searchPageUpdate = (slug, page, cb) -> - # to update we have to remove the page first, and then readd it - try - pageText = page.story.reduce( extractPageText, '') - catch err - console.log "SITE INDEX *** #{wikiName} reduce to extract the text on #{slug} failed", err.message - pageText = "" - if siteIndex.has slug - siteIndex.replace { - 'id': slug - 'title': page.title - 'content': pageText - } - else - siteIndex.add { - 'id': slug - 'title': page.title - 'content': pageText - } + }) + }) + }) + } + + const searchPageUpdate = (slug, page, cb) => { + // to update we have to remove the page first, and then readd it + let pageText + try { + pageText = page.story.reduce(extractPageText, '') + } catch (err) { + console.log(`SITE INDEX *** ${wikiName} reduce to extract the text on ${slug} failed`, err.message) + pageText = '' + } + if (siteIndex.has(slug)) { + siteIndex.replace({ + id: slug, + title: page.title, + content: pageText, + }) + } else { + siteIndex.add({ + id: slug, + title: page.title, + content: pageText, + }) + } cb() - - searchPageRemove = (slug, cb) -> - # remove page from index - timeLabel = "SITE INDEX page remove #{slug} - #{wikiName}" - try - siteIndex.discard slug - catch err - # swallow error, if the page was not in index - console.log "removing #{slug} from index #{wikiName} failed", err unless err.message.includes('not in the index') + } + + const searchPageRemove = (slug, cb) => { + // remove page from index + const timeLabel = 'SITE INDEX page remove #{slug} - #{wikiName}' + try { + siteIndex.discard(slug) + } catch (err) { + // swallow error, if the page was not in index + if (!err.message.includes('not in the index')) { + console.log(`removing ${slug} from index ${wikiName} failed`, err) + } + } cb() - - searchSave = (siteIndex, cb) -> - # save index to file - fs.exists argv.status, (exists) -> - if exists - writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) -> - return cb(e) if e - touch indexUpdateFlag, (err) -> + } + + const searchSave = (siteIndex, cb) => { + // save index to file + fs.exists(argv.status, exists => { + if (exists) { + writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => { + if (e) return cb(e) + touch(indexUpdateFlag, err => { cb() - else - fs.mkdir argv.status, { recursive: true }, -> - writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) -> - return cb(e) if e - touch indexUpdateFlag, (err) -> + }) + }) + } else { + fs.mkdir(argv.status, { recursive: true }, () => { + writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => { + if (e) return cb(e) + touch(indexUpdateFlag, err => { cb() - - - searchRestore = (cb) -> - # restore index, or create if it doesn't already exist - fs.exists siteIndexLoc, (exists) -> - if exists - fs.readFile(siteIndexLoc, (err, data) -> - return cb(err) if err - try - siteIndex = miniSearch.loadJSON data, - fields: ['title', 'content'] - catch e + }) + }) + }) + } + }) + } + + const searchRestore = cb => { + // restore index, or create if it doesn't already exist + fs.exists(siteIndexLoc, exists => { + if (exists) { + fs.readFile(siteIndexLoc, (err, data) => { + if (err) return cb(err) + try { + siteIndex = miniSearch.loadJSON(data, { + fields: ['title', 'content'], + }) + } catch (e) { return cb(e) - process.nextTick( -> - serial(queue.shift()))) + } + process.nextTick(() => { + serial(queue.shift()) + }) + }) + } + }) + } - serial = (item) -> - if item - switch item.action - when "update" + const serial = item => { + if (item) { + switch (item.action) { + case 'update': itself.start() - searchPageUpdate(item.slug, item.page, (e) -> - process.nextTick( -> + searchPageUpdate(item.slug, item.page, e => { + process.nextTick(() => { serial(queue.shift()) - ) - ) - when "remove" + }) + }) + break + case 'remove': itself.start() - searchPageRemove(item.slug, (e) -> - process.nextTick( -> + searchPageRemove(item.slug, e => { + process.nextTick(() => { serial(queue.shift()) - ) - ) - else - console.log "SITE INDEX *** unexpected action #{item.action} for #{item.page}" - process.nextTick( -> - serial(queue.shift)) - else - searchSave siteIndex, (e) -> - console.log "SITE INDEX *** save failed: " + e if e + }) + }) + break + default: + console.log(`SITE INDEX *** unexpected action ${item.action} for ${item.page}`) + process.nextTick(() => { + serial(queue.shift) + }) + } + } else { + searchSave(siteIndex, e => { + if (e) console.log('SITE INDEX *** save failed: ' + e) itself.stop() + }) + } + } - extractItemText = (text) -> - return text.replace(/\[([^\]]*?)\][\[\(].*?[\]\)]/g, " $1 ") + const extractItemText = text => { + return text + .replace(/\[([^\]]*?)\][[(].*?[\])]/g, ' $1 ') .replace(/\[{2}|\[(?:[\S]+)|\]{1,2}/g, ' ') .replace(/\n/g, ' ') .replace(//g, ' ') .replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ') .replace(/<(?:[^>])+>/g, ' ') - .replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, (match) -> - myUrl = url.parse(match) - return myUrl.hostname + ' ' + myUrl.pathname) + .replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, match => { + const myUrl = url.parse(match) + return myUrl.hostname + ' ' + myUrl.pathname + }) .replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ') - .replace /[\p{White_Space}\n\t]+/gu, ' ' - - - extractPageText = (pageText, currentItem, currentIndex, array) -> - # console.log('extractPageText', pageText, currentItem, currentIndex, array) - try - if currentItem.text? - switch currentItem.type - when 'paragraph', 'markdown', 'html', 'reference', 'image', 'pagefold', 'math', 'mathjax', 'code' - pageText += ' ' + extractItemText currentItem.text - when 'audio', 'video', 'frame' - pageText += ' ' + extractItemText(currentItem.text.split(/\r\n?|\n/) - .map((line) -> - firstWord = line.split(/\p{White_Space}/u)[0] - if firstWord.startsWith('http') or firstWord.toUpperCase() is firstWord or firstWord.startsWith('//') - # line is markup - return '' - else - return line - ).join(' ')) - catch err - throw new Error("Error extracting text from #{currentIndex}, #{JSON.stringify(currentItem)} #{err}, #{err.stack}") - pageText - + .replace(/[\p{White_Space}\n\t]+/gu, ' ') + } + + const extractPageText = (pageText, currentItem, currentIndex, array) => { + // console.log('extractPageText', pageText, currentItem, currentIndex, array) + try { + if (currentItem.text) { + switch (currentItem.type) { + case 'paragraph': + case 'markdown': + case 'html': + case 'reference': + case 'image': + case 'pagefold': + case 'math': + case 'mathjax': + case 'code': + pageText += ' ' + extractItemText(currentItem.text) + break + case 'audio': + case 'video': + case 'frame': + pageText += + ' ' + + extractItemText( + currentItem.text + .split(/\r\n?|\n/) + .map(line => { + const firstWord = line.split(/\p{White_Space}/u)[0] + if ( + firstWord.startsWith('http') || + firstWord.toUpperCase() === firstWord || + firstWord.startsWith('//') + ) { + // line is markup + return '' + } else { + return line + } + }) + .join(' '), + ) + } + } + } catch (err) { + throw new Error(`Error extracting text from ${currentIndex}, ${JSON.stringify(currentItem)} ${err}, ${err.stack}`) + } + return pageText + } - #### Public stuff #### + // #### Public stuff #### - itself = new events.EventEmitter - itself.start = -> + var itself = new events.EventEmitter() + itself.start = () => { clearTimeout(searchTimeoutHandler) working = true - @emit 'indexing' - itself.stop = -> - clearsearch = -> - console.log "SITE INDEX #{wikiName} : removed from memory" + return itself.emit('indexing') + } + itself.stop = () => { + const clearsearch = () => { + console.log(`SITE INDEX ${wikiName} : removed from memory`) siteIndex = [] clearTimeout(searchTimeoutHandler) - searchTimeoutHandler = setTimeout clearsearch, searchTimeoutMs + } + searchTimeoutHandler = setTimeout(clearsearch, searchTimeoutMs) working = false - @emit 'indexed' - - itself.isWorking = -> - working - - itself.createIndex = (pagehandler) -> - + return itself.emit('indexed') + } + itself.isWorking = () => { + return working + } + itself.createIndex = pagehandler => { itself.start() - # we save the pagehandler, so we can recreate the site index if it is removed - searchPageHandler = pagehandler if !searchPageHandler? + // we save the pagehandler, so we can recreate the site index if it is removed + searchPageHandler = searchPageHandler ?? pagehandler - #timeLabel = "SITE INDEX #{wikiName} : Created" - #console.time timeLabel + //timeLabel = "SITE INDEX #{wikiName} : Created" + //console.time timeLabel - pagehandler.slugs (e, slugs) -> - if e - console.log "SITE INDEX *** createIndex #{wikiName} error:", e + pagehandler.slugs((e, slugs) => { + if (e) { + console.log(`SITE INDEX *** createIndex ${wikiName} error:`, e) itself.stop() return e - + } siteIndex = new miniSearch({ - fields: ['title', 'content'] + fields: ['title', 'content'], }) - indexPromises = slugs.map (slug) -> - return new Promise (resolve) -> - pagehandler.get slug, (err, page) -> - if err - console.log "SITE INDEX *** #{wikiName}: error reading page", slug + const indexPromises = slugs.map(slug => { + return new Promise(resolve => { + pagehandler.get(slug, (err, page) => { + if (err) { + console.log(`SITE INDEX *** ${wikiName}: error reading page`, slug) return - # page - try - pageText = page.story.reduce( extractPageText, '') - catch err - console.log "SITE INDEX *** #{wikiName} reduce to extract text on #{slug} failed", err.message - # console.log "page", page - pageText = "" - siteIndex.add { - 'id': slug - 'title': page.title - 'content': pageText } + // page + let pageText + try { + pageText = page.story.reduce(extractPageText, '') + } catch (err) { + console.log(`SITE INDEX *** ${wikiName} reduce to extract text on ${slug} failed`, err.message) + // console.log "page", page + pageText = '' + } + siteIndex.add({ + id: slug, + title: page.title, + content: pageText, + }) resolve() - - Promise.all(indexPromises) - .then () -> - # console.timeEnd timeLabel - process.nextTick ( -> - serial(queue.shift())) - - itself.removePage = (slug) -> - action = "remove" - queue.push({action, slug }) - if Array.isArray(siteIndex) and !working + }) + }) + }) + Promise.all(indexPromises).then(() => { + // console.timeEnd timeLabel + process.nextTick(() => { + serial(queue.shift()) + }) + }) + }) + } + + itself.removePage = slug => { + const action = 'remove' + queue.push({ action, slug }) + if (Array.isArray(siteIndex) && !working) { itself.start() - searchRestore (e) -> - console.log "SITE INDEX *** Problems restoring search index #{wikiName}:" + e if e + searchRestore(e => { + if (e) console.log(`SITE INDEX *** Problems restoring search index ${wikiName}:` + e) itself.createIndex(searchPageHandler) - else - serial(queue.shift()) unless working - - itself.update = (slug, page) -> - action = "update" - queue.push({action, slug, page}) - if Array.isArray(siteIndex) and !working + }) + } else { + if (!working) serial(queue.shift()) + } + } + + itself.update = (slug, page) => { + const action = 'update' + queue.push({ action, slug, page }) + if (Array.isArray(siteIndex) && !working) { itself.start() - searchRestore( (e) -> - console.log "SITE INDEX *** Problems restoring search index #{wikiName}:" + e if e - itself.createIndex(searchPageHandler)) - else - serial(queue.shift()) unless working - - itself.startUp = (pagehandler) -> - # called on server startup, here we check if wiki already is index - # we only create an index if there is either no index or there have been updates since last startup - console.log "SITE INDEX #{wikiName} : StartUp" - fs.stat siteIndexLoc, (err, stats) -> - if err is null - # site index exists, but has it been updated? - fs.stat indexUpdateFlag, (err, stats) -> - if !err - # index has been updated, so recreate it. - itself.createIndex pagehandler - # remove the update flag once the index has been created - itself.once 'indexed', -> - fs.unlink indexUpdateFlag, (err) -> - console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err - else - # not been updated, but is it the correct version? - fs.readFile siteIndexLoc, (err, data) -> - if !err - try + searchRestore(e => { + if (e) console.log(`SITE INDEX *** Problems restoring search index ${wikiName}:` + e) + itself.createIndex(searchPageHandler) + }) + } else { + if (!working) serial(queue.shift()) + } + } + itself.startUp = pagehandler => { + // called on server startup, here we check if wiki already is index + // we only create an index if there is either no index or there have been updates since last startup + console.log(`SITE INDEX ${wikiName} : StartUp`) + fs.stat(siteIndexLoc, (err, stats) => { + if (err === null) { + // site index exists, but has it been updated? + fs.stat(indexUpdateFlag, (err, stats) => { + if (!err) { + // index has been updated, so recreate it. + itself.createIndex(pagehandler) + // remove the update flag once the index has been created + itself.once('indexed', () => { + fs.unlink(indexUpdateFlag, err => { + if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + }) + }) + } else { + // not been updated, but is it the correct version? + fs.readFile(siteIndexLoc, (err, data) => { + if (!err) { + let testIndex + try { testIndex = JSON.parse(data) - catch err + } catch (err) { testIndex = {} - if testIndex.serializationVersion != 2 - console.log "+++ SITE INDEX #{wikiName} : updating to latest version." - itself.createIndex pagehandler - # remove the update flag once the index has been created - itself.once 'indexed', -> - fs.unlink indexUpdateFlag, (err) -> - console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err - else - console.log "+++ SITE INDEX #{wikiName} : error reading index - attempting creating" - itself.createIndex pagehandler - # remove the update flag once the index has been created - itself.once 'indexed', -> - fs.unlink indexUpdateFlag, (err) -> - console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err - else - # index does not exist, so create it - itself.createIndex pagehandler - # remove the update flag once the index has been created - itself.once 'indexed', -> - fs.unlink indexUpdateFlag, (err) -> - console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err - + } + if (testIndex.serializationVersion != 2) + console.log(`+++ SITE INDEX ${wikiName} : updating to latest version.`) + itself.createIndex(pagehandler) + // remove the update flag once the index has been created + itself.once('indexed', () => { + fs.unlink(indexUpdateFlag, err => { + if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + }) + }) + } else { + console.log(`+++ SITE INDEX ${wikiName} : error reading index - attempting creating`) + itself.createIndex(pagehandler) + // remove the update flag once the index has been created + itself.once('indexed', () => { + fs.unlink(indexUpdateFlag, err => { + if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + }) + }) + } + }) + } + }) + } else + // index does not exist, so create it + itself.createIndex(pagehandler) + // remove the update flag once the index has been created + itself.once('indexed', () => { + fs.unlink(indexUpdateFlag, err => { + if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + }) + }) + }) + } - - itself + return itself +} From d3d72b9978efb6fb62e9e57a813e2e3761cf88b1 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Sun, 25 May 2025 11:57:43 +0100 Subject: [PATCH 18/45] rename sitemap --- lib/{sitemap.coffee => sitemap.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{sitemap.coffee => sitemap.js} (100%) diff --git a/lib/sitemap.coffee b/lib/sitemap.js similarity index 100% rename from lib/sitemap.coffee rename to lib/sitemap.js From 2a7473b62e07d5d1296c13d30004f9013131f6d5 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 26 May 2025 11:04:22 +0100 Subject: [PATCH 19/45] convert sitemap to JavaScript --- lib/sitemap.js | 430 +++++++++++++++++++++++++++---------------------- 1 file changed, 236 insertions(+), 194 deletions(-) diff --git a/lib/sitemap.js b/lib/sitemap.js index 234841c3..3fee3285 100644 --- a/lib/sitemap.js +++ b/lib/sitemap.js @@ -1,246 +1,288 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### - -# **sitemap.coffee** - -fs = require 'fs' -path = require 'path' -events = require 'events' -writeFileAtomic = require 'write-file-atomic' -xml2js = require 'xml2js' - -synopsis = require 'wiki-client/lib/synopsis' - -asSlug = (name) -> - name.replace(/\s/g, '-').replace(/[^A-Za-z0-9-]/g, '').toLowerCase() - - -module.exports = exports = (argv) -> - - wikiName = new URL(argv.url).hostname - - sitemap = [] - - queue = [] - - sitemapPageHandler = null - - # ms since last update we will remove sitemap from memory - sitemapTimeoutMs = 120000 - sitemapTimeoutHandler = null - - sitemapLoc = path.join(argv.status, 'sitemap.json') - xmlSitemapLoc = path.join(argv.status, 'sitemap.xml') - - working = false - - lastEdit = (journal) -> - for action in (journal || []) by -1 - return action.date if action.date and action.type != 'fork' - undefined - - sitemapUpdate = (file, page, cb) -> - - extractPageLinks = (collaborativeLinks, currentItem, currentIndex, array) -> - # extract collaborative links - # - this will need extending if we also extract the id of the item containing the link - try - linkRe = /\[\[([^\]]+)\]\]/g - match = undefined - while (match = linkRe.exec(currentItem.text)) != null - if not collaborativeLinks.has(asSlug(match[1])) + */ + +// **sitemap.coffee** + +const fs = require('fs') +const path = require('path') +const events = require('events') +const writeFileAtomic = require('write-file-atomic') +const xml2js = require('xml2js') + +const synopsis = require('wiki-client/lib/synopsis') + +const asSlug = name => { + name + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9-]/g, '') + .toLowerCase() +} + +module.exports = exports = argv => { + const wikiName = new URL(argv.url).hostname + + let sitemap = [] + + const queue = [] + + let sitemapPageHandler = null + + // ms since last update we will remove sitemap from memory + const sitemapTimeoutMs = 120000 + let sitemapTimeoutHandler = null + + const sitemapLoc = path.join(argv.status, 'sitemap.json') + const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml') + + let working = false + + const lastEdit = journal => { + if (!journal) return undefined + // find the last journal entry, that is not a fork, with a date. + const last = journal.findLast(action => { + return action.date && action.type != 'fork' + }) + return last ? last.date : undefined + } + + const sitemapUpdate = (file, page, cb) => { + let pageLinks, pageLinksMap + const extractPageLinks = (collaborativeLinks, currentItem, currentIndex, array) => { + // extract collaborative links + // - this will need extending if we also extract the id of the item containing the link + try { + const linkRe = /\[\[([^\]]+)\]\]/g + let match = undefined + while ((match = linkRe.exec(currentItem.text)) != null) { + if (!collaborativeLinks.has(asSlug(match[1]))) { collaborativeLinks.set(asSlug(match[1]), currentItem.id) - if 'reference' == currentItem.type - if not collaborativeLinks.has(currentItem.slug) + } + } + if ('reference' == currentItem.type) { + if (!collaborativeLinks.has(currentItem.slug)) { collaborativeLinks.set(currentItem.slug, currentItem.id) - catch err - console.log "METADATA *** #{wikiName} Error extracting links from #{currentIndex} of #{JSON.stringify(array)}", err.message - collaborativeLinks - - try - pageLinksMap = page.story.reduce( extractPageLinks, new Map()) - catch err - console.log "METADATA *** #{wikiName} reduce to extract links on #{file} failed", err.message + } + } + } catch (err) { + console.log( + `METADATA *** ${wikiName} Error extracting links from ${currentIndex} of ${JSON.stringify(array)}`, + err.message, + ) + } + return collaborativeLinks + } + try { + pageLinksMap = page.story.reduce(extractPageLinks, new Map()) + } catch (err) { + console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message) pageLinksMap = [] - # - if pageLinksMap.size > 0 + } + // + if (pageLinksMap.size > 0) { pageLinks = Object.fromEntries(pageLinksMap) - else + } else { pageLinks = undefined + } - entry = { - 'slug': file - 'title': page.title - 'date': lastEdit(page.journal) - 'synopsis': synopsis(page) - 'links': pageLinks + const entry = { + slug: file, + title: page.title, + date: lastEdit(page.journal), + synopsis: synopsis(page), + links: pageLinks, } - slugs = sitemap.map (page) -> page.slug + const slugs = sitemap.map(page => page.slug) - idx = slugs.indexOf(file) + const idx = slugs.indexOf(file) - if ~idx + if (~idx) { sitemap[idx] = entry - else - sitemap.push entry - + } else { + sitemap.push(entry) + } cb() + } - sitemapRemovePage = (file, cb) -> - slugs = sitemap.map (page) -> page.slug - idx = slugs.indexOf(file) - - if ~idx - sitemap.splice(idx,1) + const sitemapRemovePage = (file, cb) => { + const slugs = sitemap.map(page => page.slug) + const idx = slugs.indexOf(file) + if (~idx) { + sitemap.splice(idx, 1) + } cb() + } - sitemapSave = (sitemap, cb) -> - fs.exists argv.status, (exists) -> - if exists - writeFileAtomic sitemapLoc, JSON.stringify(sitemap), (e) -> - return cb(e) if e + const sitemapSave = (sitemap, cb) => { + fs.exists(argv.status, exists => { + if (exists) { + writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => { + if (e) return cb(e) cb() - else - fs.mkdir argv.status, { recursive: true }, -> - writeFileAtomic sitemapLoc, JSON.stringify(sitemap), (e) -> - return cb(e) if e + }) + } else + fs.mkdir(argv.status, { recursive: true }, () => { + writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => { + if (e) return cb(e) cb() - - sitemapRestore = (cb) -> - fs.exists sitemapLoc, (exists) -> - if exists - fs.readFile(sitemapLoc, (err, data) -> - return cb(err) if err - try + }) + }) + }) + } + + const sitemapRestore = cb => { + fs.exists(sitemapLoc, exists => { + if (exists) { + fs.readFile(sitemapLoc, (err, data) => { + if (err) return cb(err) + try { sitemap = JSON.parse(data) - catch e + } catch (e) { return cb(e) - process.nextTick( -> - serial(queue.shift())) - ) - else - # sitemap file does not exist, so needs creating + } + process.nextTick(() => { + serial(queue.shift()) + }) + }) + } else { + // sitemap file does not exist, so needs creating itself.createSitemap(sitemapPageHandler) - - xmlSitemapSave = (sitemap, cb) -> - xmlmap = [] - sitemap.forEach (page) -> - result = {} - result["loc"] = argv.url + "/" + page.slug + ".html" - if page.date? - date = new Date(page.date) - if !(isNaN(date.valueOf())) - result["lastmod"] = date.toISOString().substring(0,10) - xmlmap.push result - xmlmap = {'urlset': {"$": {"xmlns": "http://www.sitemaps.org/schemas/sitemap/0.9"},'url': xmlmap}} - builder = new xml2js.Builder() - xml = builder.buildObject(xmlmap) - fs.exists argv.status, (exists) -> - if exists - writeFileAtomic xmlSitemapLoc, xml, (e) -> - return cb(e) if e + } + }) + } + + const xmlSitemapSave = (sitemap, cb) => { + const xmlmapPages = [] + sitemap.forEach(page => { + const result = {} + result['loc'] = argv.url + '/' + page.slug + '.html' + if (page.date) { + const date = new Date(page.date) + if (!isNaN(date.valueOf())) { + result['lastmod'] = date.toISOString().substring(0, 10) + } + } + xmlmapPages.push(result) + }) + const xmlmap = { urlset: { $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' }, url: xmlmapPages } } + const builder = new xml2js.Builder() + const xml = builder.buildObject(xmlmap) + fs.exists(argv.status, exists => { + if (exists) { + writeFileAtomic(xmlSitemapLoc, xml, e => { + if (e) return cb(e) cb() - else - fs.mkdir argv.status, { recursive: true }, -> - writeFileAtomic xmlSitemapLoc, xml, (e) -> - return cb(e) if e + }) + } else { + fs.mkdir(argv.status, { recursive: true }, () => { + writeFileAtomic(xmlSitemapLoc, xml, e => { + if (e) return cb(e) cb() - - serial = (item) -> - if item - switch item.action - when "update" + }) + }) + } + }) + } + + const serial = item => { + if (item) { + switch (item.action) { + case 'update': itself.start() - sitemapUpdate(item.file, item.page, (e) -> - process.nextTick( -> - serial(queue.shift()) - ) - ) - when "remove" + sitemapUpdate(item.file, item.page, e => process.nextTick(() => serial(queue.shift()))) + break + case 'remove': itself.start() - sitemapRemovePage(item.file, (e) -> - process.nextTick( -> - serial(queue.shift()) - ) - ) - else - console.log "Sitemap unexpected action #{item.action} for #{item.page} in #{wikiName}" - process.nextTick( -> - serial(queue.shift)) - else - sitemapSave sitemap, (e) -> - console.log "Problems saving sitemap #{wikiName}: "+ e if e + sitemapRemovePage(item.file, e => process.nextTick(() => serial(queue.shift()))) + break + default: + console.log(`Sitemap unexpected action ${item.action} for ${item.page} in ${wikiName}`) + process.nextTick(() => serial(queue.shift)) + } + } else + sitemapSave(sitemap, e => { + if (e) console.log(`Problems saving sitemap ${wikiName}: ` + e) itself.stop() - xmlSitemapSave sitemap, (e) -> - console.log "Problems saving sitemap(xml) #{wikiName}"+ e if e - + }) + xmlSitemapSave(sitemap, e => { + if (e) console.log(`Problems saving sitemap(xml) ${wikiName}`) + e + }) + } - #### Public stuff #### + // #### Public stuff #### - itself = new events.EventEmitter - itself.start = -> + const itself = new events.EventEmitter() + itself.start = () => { clearTimeout(sitemapTimeoutHandler) working = true - @emit 'working' - itself.stop = -> - clearsitemap = -> - console.log "removing sitemap #{wikiName} from memory" - sitemap = [] + itself.emit('working') + } + itself.stop = () => { + const clearsitemap = () => { + console.log(`removing sitemap ${wikiName} from memory`) + sitemap.length = 0 clearTimeout(sitemapTimeoutHandler) - sitemapTimeoutHandler = setTimeout clearsitemap, sitemapTimeoutMs + } + sitemapTimeoutHandler = setTimeout(clearsitemap, sitemapTimeoutMs) working = false - @emit 'finished' - - itself.isWorking = -> + itself.emit('finished') + } + itself.isWorking = () => { working + } - itself.createSitemap = (pagehandler) -> - + itself.createSitemap = pagehandler => { itself.start() + // we save the pagehandler, so we can recreate the sitemap if it is removed + if (!sitemapPageHandler) sitemapPageHandler = pagehandler - # we save the pagehandler, so we can recreate the sitemap if it is removed - sitemapPageHandler = pagehandler if !sitemapPageHandler? - - pagehandler.pages (e, newsitemap) -> - if e - console.log "createSitemap #{wikiName} : error " + e + pagehandler.pages((e, newsitemap) => { + if (e) { + console.log(`createSitemap ${wikiName} : error ` + e) itself.stop() return e + } sitemap = newsitemap - process.nextTick ( -> - serial(queue.shift())) + process.nextTick(() => { + serial(queue.shift()) + }) + }) + } - itself.removePage = (file) -> - action = "remove" - queue.push({action, file, ""}) - if sitemap.length is 0 and !working + itself.removePage = file => { + const action = 'remove' + queue.push({ action, file }) + if (sitemap.length === 0 && !working) { itself.start() - sitemapRestore (e) -> - console.log "Problems restoring sitemap #{wikiName} : " + e if e + sitemapRestore(e => { + if (e) console.log(`Problems restoring sitemap ${wikiName} : ` + e) itself.createSitemap(sitemapPageHandler) - else - serial(queue.shift()) unless working - + }) + } else { + if (!working) serial(queue.shift()) + } + } - itself.update = (file, page) -> - action = "update" - queue.push({action, file, page}) - if sitemap.length is 0 and !working + itself.update = (file, page) => { + const action = 'update' + queue.push({ action, file, page }) + if (sitemap.length === 0 && !working) { itself.start() - sitemapRestore (e) -> - console.log "Problems restoring sitemap #{wikiName} : " + e if e + sitemapRestore(e => { + if (e) console.log(`Problems restoring sitemap ${wikiName} : ` + e) itself.createSitemap(sitemapPageHandler) - else - serial(queue.shift()) unless working - - + }) + } else { + if (!working) serial(queue.shift()) + } + } - itself + return itself +} From 3822d17fde6b94a3da1a307225f5aac4467926b1 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 26 May 2025 17:19:07 +0100 Subject: [PATCH 20/45] rename server --- lib/{server.coffee => server.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{server.coffee => server.js} (100%) diff --git a/lib/server.coffee b/lib/server.js similarity index 100% rename from lib/server.coffee rename to lib/server.js From 14a07024699b914684e713db3925017c27014bf8 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Tue, 27 May 2025 19:01:08 +0100 Subject: [PATCH 21/45] convert server to JavaScript --- lib/server.js | 1417 +++++++++++++++++++++++++++---------------------- 1 file changed, 784 insertions(+), 633 deletions(-) diff --git a/lib/server.js b/lib/server.js index 4e82cd3a..d131976b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,782 +1,933 @@ -### +/* * Federated Wiki : Node Server * * Copyright Ward Cunningham and other contributors * Licensed under the MIT license. * https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt -### - -# **server.coffee** is the main guts of the express version -# of (Smallest Federated Wiki)[https://github.com/WardCunningham/Smallest-Federated-Wiki]. -# The CLI and Farm are just front ends -# for setting arguments, and spawning servers. In a complex system -# you would probably want to replace the CLI/Farm with your own code, -# and use server.coffee directly. -# -#### Dependencies #### -# anything not in the standard library is included in the repo, or -# can be installed with an: -# npm install - -# Standard lib -fs = require 'fs' -path = require 'path' -http = require 'http' -url = require 'url' -{ pipeline } = require 'node:stream/promises' - -# From npm -express = require 'express' -hbs = require 'express-hbs' -glob = require 'glob' -async = require 'async' -f = require('flates') - -createDOMPurify = require('dompurify') -{ JSDOM } = require('jsdom') - -window = new JSDOM('').window -DOMPurify = createDOMPurify(window) - -# node-fetch is now ESM only -fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); - -# Express 4 middleware -logger = require 'morgan' -cookieParser = require 'cookie-parser' -methodOverride = require 'method-override' -## session = require 'express-session' -sessions = require 'client-sessions' -bodyParser = require 'body-parser' -errorHandler = require 'errorhandler' - - - -# Local files -random = require './random_id' -defargs = require './defaultargs' -resolveClient = require 'wiki-client/lib/resolve' -pluginsFactory = require './plugins' -sitemapFactory = require './sitemap' -searchFactory = require './search' - -render = (page) -> - return f.div({class: "twins"}, f.p('')) + '\n' + - f.div({class: "header"}, f.h1( - f.a({href: '/', style: 'text-decoration: none'}, - f.img({height: '32px', src: '/favicon.png'})) + - ' ' + (page.title))) + '\n' + - f.div {class: "story"}, - page.story.map((story) -> - return '' unless story - if story.type is 'paragraph' - f.div {class: "item paragraph"}, f.p(resolveClient.resolveLinks(story.text)) - else if story.type is 'image' - f.div {class: "item image"}, - f.img({class: "thumbnail", src: story.url}), - f.p(resolveClient.resolveLinks(story.text or story.caption or 'uploaded image')) - else if story.type is 'html' - f.div {class: "item html"}, - f.p(resolveClient.resolveLinks(story.text or '', DOMPurify.sanitize)) - else f.div {class: "item"}, f.p(resolveClient.resolveLinks(story.text or '')) - ).join('\n') - -# Set export objects for node and coffee to a function that generates a sfw server. -module.exports = exports = (argv) -> - # Create the main application object, app. - app = express() - - # remove x-powered-by header + */ + +// **server.coffee** is the main guts of the express version +// of (Smallest Federated Wiki)[https://github.com/WardCunningham/Smallest-Federated-Wiki]. +// The CLI and Farm are just front ends +// for setting arguments, and spawning servers. In a complex system +// you would probably want to replace the CLI/Farm with your own code, +// and use server.coffee directly. +// +// #### Dependencies #### +// anything not in the standard library is included in the repo, or +// can be installed with an: +// npm install + +// Standard lib +const fs = require('fs') +const path = require('path') +const http = require('http') +const url = require('url') +const { pipeline } = require('node:stream/promises') + +// From npm +const express = require('express') +const hbs = require('express-hbs') +const glob = require('glob') +const async = require('async') +const f = require('flates') + +const createDOMPurify = require('dompurify') +const { JSDOM } = require('jsdom') + +const window = new JSDOM('').window +const DOMPurify = createDOMPurify(window) + +// node-fetch is now ESM only +const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)) + +// Express 4 middleware +const logger = require('morgan') +const cookieParser = require('cookie-parser') +const methodOverride = require('method-override') +// session = require 'express-session' +const sessions = require('client-sessions') +const bodyParser = require('body-parser') +const errorHandler = require('errorhandler') + +// Local files +const random = require('./random_id') +const defargs = require('./defaultargs') +const resolveClient = require('wiki-client/lib/resolve') +const pluginsFactory = require('./plugins') +const sitemapFactory = require('./sitemap') +const searchFactory = require('./search') + +const render = page => { + return ( + f.div({ class: 'twins' }, f.p('')) + + '\n' + + f.div( + { class: 'header' }, + f.h1( + f.a({ href: '/', style: 'text-decoration: none' }, f.img({ height: '32px', src: '/favicon.png' })) + + ' ' + + page.title, + ), + ) + + '\n' + + f.div( + { class: 'story' }, + page.story + .map(story => { + if (!story) return '' + if (story.type === 'paragraph') { + f.div({ class: 'item paragraph' }, f.p(resolveClient.resolveLinks(story.text))) + } else if (story.type === 'image') { + f.div( + { class: 'item image' }, + f.img({ class: 'thumbnail', src: story.url }), + f.p(resolveClient.resolveLinks(story.text || story.caption || 'uploaded image')), + ) + } else if (story.type === 'html') { + f.div({ class: 'item html' }, f.p(resolveClient.resolveLinks(story.text || '', DOMPurify.sanitize))) + } else f.div({ class: 'item' }, f.p(resolveClient.resolveLinks(story.text || ''))) + }) + .join('\n'), + ) + ) +} +// Set export objects for node and coffee to a function that generates a sfw server. +module.exports = exports = argv => { + // Create the main application object, app. + const app = express() + + // remove x-powered-by header app.disable('x-powered-by') - # defaultargs.coffee exports a function that takes the argv object - # that is passed in and then does its - # best to supply sane defaults for any arguments that are missing. + // defaultargs.coffee exports a function that takes the argv object + // that is passed in and then does its + // best to supply sane defaults for any arguments that are missing. argv = defargs(argv) app.startOpts = argv - log = (stuff...) -> - console.log stuff if argv.debug - - loga = (stuff...) -> - console.log stuff - + const log = (...stuff) => { + if (argv.debug) console.log(stuff) + } + const loga = (...stuff) => { + console.log(stuff) + } - ourErrorHandler = (req, res, next) -> - fired = false - res.e = (error, status) -> - if !fired + const ourErrorHandler = (req, res, next) => { + let fired = false + res.e = (error, status) => { + if (!fired) { fired = true - res.statusCode = status or 500 - res.end 'Server ' + error - log "Res sent:", res.statusCode, error - else - log "Already fired", error + res.statusCode = status || 500 + res.end('Server ' + error) + log('Res sent:', res.statusCode, error) + } else { + log('Already fired', error) + } + } next() - - # Require the database adapter and initialize it with options. + } + let pagehandler, sitemaphandler, searchhandler, securityhandler + // Require the database adapter and initialize it with options. app.pagehandler = pagehandler = require(argv.database.type)(argv) - # Require the sitemap adapter and initialize it with options. + // Require the sitemap adapter and initialize it with options. app.sitemaphandler = sitemaphandler = sitemapFactory(argv) - # Require the site indexer and initialize it with options + // Require the site indexer and initialize it with options app.searchhandler = searchhandler = searchFactory(argv) - # Require the security adapter and initialize it with options. + // Require the security adapter and initialize it with options. app.securityhandler = securityhandler = require(argv.security_type)(log, loga, argv) - # If the site is owned, owner will contain the name of the owner - owner = '' + // If the site is owned, owner will contain the name of the owner + let owner = '' - # If the user is logged in, user will contain their identity - user = '' + // If the user is logged in, user will contain their identity + let user = '' - # Called from authentication when the site is claimed, - # to update the name of the owner held here. - updateOwner = (id) -> + // Called from authentication when the site is claimed, + // to update the name of the owner held here. + const updateOwner = id => { owner = id + } - - #### Middleware #### - # - # Allow json to be got cross origin. - cors = (req, res, next) -> - res.header 'Access-Control-Allow-Origin', req.get('origin')||'*' + // #### Middleware #### + // + // Allow json to be got cross origin. + const cors = (req, res, next) => { + res.header('Access-Control-Allow-Origin', req.get('origin') || '*') next() + } + const remoteGet = (remote, slug, cb) => { + // assume http, as we know no better at this point and we need to specify a protocol. + const remoteURL = new URL('http://#{remote}/#{slug}.json').toString() + // set a two second timeout + fetch(remoteURL, { signal: AbortSignal.timeout(2000) }) + .then(res => { + if (res.ok) { + return res + } + throw new Error(res.statusText) + }) + .then(res => { + return res.json() + }) + .then(json => { + cb(null, json, 200) + }) + .catch(err => { + console.error('Unable to fetch remote resource', remote, slug, err) + cb(err, 'Page not found', 404) + }) + } - remoteGet = (remote, slug, cb) -> - # assume http, as we know no better at this point and we need to specify a protocol. - remoteURL = new URL("http://#{remote}/#{slug}.json").toString() - # set a two second timeout - fetch(remoteURL, {signal: AbortSignal.timeout(2000)}) - .then (res) -> - if res.ok - return res - throw new Error(res.statusText) - .then (res) -> - return res.json() - .then (json) -> - cb(null, json, 200) - .catch (err) -> - console.error('Unable to fetch remote resource', remote, slug, err) - cb(err, 'Page not found', 404) - - - - #### Express configuration #### - # Set up all the standard express server options, - # including hbs to use handlebars/mustache templates - # saved with a .html extension, and no layout. - - # - staticPathOptions = { - dotfiles: 'ignore' - etag: true - immutable: false - lastModified: false - maxAge: '1h' + // #### Express configuration #### + // Set up all the standard express server options, + // including hbs to use handlebars/mustache templates + // saved with a .html extension, and no layout. + + // + const staticPathOptions = { + dotfiles: 'ignore', + etag: true, + immutable: false, + lastModified: false, + maxAge: '1h', } - app.set('views', - path.join(require.resolve('wiki-client/package.json'), '..', 'views')) + app.set('views', path.join(require.resolve('wiki-client/package.json'), '..', 'views')) app.set('view engine', 'html') app.engine('html', hbs.express4()) - app.set('view options', layout: false) + app.set('view options', { layout: false }) - # return deterministically colored strings - colorString = (str) -> - colorReset = '\x1b[0m' - hash = 0; - str.split('').forEach (char) -> + // return deterministically colored strings + const colorString = str => { + const colorReset = '\x1b[0m' + let hash = 0 + str.split('').forEach(char => { hash = char.charCodeAt(0) + ((hash << 5) - hash) - color = '\x1b[38;2' - for i in [0..2] - do (i) -> - value = (hash >> (i * 8)) & 0xff - color += ';' + value.toString() + }) + let color = '\x1b[38;2' + ;[...Array(3).keys()].forEach(i => { + const value = (hash >> (i * 8)) & 0xff + color += ':' + value.toString() + }) color += 'm' return color + str + colorReset + } - # use logger, at least in development, probably needs a param to configure (or turn off). - # use stream to direct to somewhere other than stdout. - logger.token('vhost', (req, res) -> - return colorString(req.hostname)) + // use logger, at least in development, probably needs a param to configure (or turn off). + // use stream to direct to somewhere other than stdout. + logger.token('vhost', (req, res) => { + return colorString(req.hostname) + }) app.use(logger(':vhost :method :url :status :res[content-length] - :response-time ms')) app.use(cookieParser()) - app.use(bodyParser.json({ limit: argv.uploadLimit})) - app.use(bodyParser.urlencoded({ extended: true, limit: argv.uploadLimit})) + app.use(bodyParser.json({ limit: argv.uploadLimit })) + app.use(bodyParser.urlencoded({ extended: true, limit: argv.uploadLimit })) app.use(methodOverride()) - cookieValue = { - httpOnly: true - sameSite: 'lax' + const cookieValue = { + httpOnly: true, + sameSite: 'lax', } - if argv.wiki_domain - if !argv.wiki_domain.endsWith('localhost') + if (argv.wiki_domain) { + if (!argv.wiki_domain.endsWith('localhost')) { cookieValue['domain'] = argv.wiki_domain - # use secureProxy as TLS is terminated in outside the node process - if argv.secure_cookie + } + } + // use secureProxy as TLS is terminated in outside the node process + let cookieName + if (argv.secure_cookie) { cookieName = 'wikiTlsSession' cookieValue['secureProxy'] = true - else - cookieName = "wikiSession" - app.use(sessions({ - cookieName: cookieName, - requestKey: 'session', - secret: argv.cookieSecret, - # make the session session_duration days long - duration: argv.session_duration * 24 * 60 * 60 * 1000, - # add 12 hours to session if less than 12 hours to expiry - activeDuration: 24 * 60 * 60 * 1000, - cookie: cookieValue - })) + } else { + cookieName = 'wikiSession' + } + app.use( + sessions({ + cookieName: cookieName, + requestKey: 'session', + secret: argv.cookieSecret, + // make the session session_duration days long + duration: argv.session_duration * 24 * 60 * 60 * 1000, + // add 12 hours to session if less than 12 hours to expiry + activeDuration: 24 * 60 * 60 * 1000, + cookie: cookieValue, + }), + ) app.use(ourErrorHandler) - # Add static route to the client + // Add static route to the client app.use(express.static(argv.client, staticPathOptions)) - ##### Define security routes ##### - securityhandler.defineRoutes app, cors, updateOwner + // ##### Define security routes ##### + securityhandler.defineRoutes(app, cors, updateOwner) - # Add static route to assets + // Add static route to assets app.use('/assets', cors, express.static(argv.assets)) - # Add static routes to the plugins client. - glob "wiki-plugin-*/client", {cwd: argv.packageDir}, (e, plugins) -> - plugins.map (plugin) -> - pluginName = plugin.slice(12, -7) - pluginPath = '/plugins/' + pluginName + // Add static routes to the plugins client. + glob('wiki-plugin-*/client', { cwd: argv.packageDir }, (e, plugins) => { + plugins.map(plugin => { + const pluginName = plugin.slice(12, -7) + const pluginPath = '/plugins/' + pluginName app.use(pluginPath, cors, express.static(path.join(argv.packageDir, plugin), staticPathOptions)) + }) + }) - # Add static routes to the security client. - if argv.security != './security' + // Add static routes to the security client. + if (argv.security != './security') { app.use('/security', express.static(path.join(argv.packageDir, argv.security_type, 'client'), staticPathOptions)) + } - - ##### Set up standard environments. ##### - # In dev mode turn on console.log debugging as well as showing the stack on err. - if 'development' == app.get('env') + // ##### Set up standard environments. ##### + // In dev mode turn on console.log debugging as well as showing the stack on err. + if ('development' == app.get('env')) { app.use(errorHandler()) - argv.debug = console? and true - - # Show all of the options a server is using. - log argv - - #### Routes #### - # Routes currently make up the bulk of the Express port of - # Smallest Federated Wiki. Most routes use literal names, - # or regexes to match, and then access req.params directly. - - ##### Redirects ##### - # Common redirects that may get used throughout the routes. - index = argv.home + '.html' - - oops = '/oops' - - ##### Get routes ##### - # Routes have mostly been kept together by http verb, with the exception - # of the openID related routes which are at the end together. - - # Main route for initial contact. Allows us to - # link into a specific set of pages, local and remote. - # Can also be handled by the client, but it also sets up - # the login status, and related footer html, which the client - # relies on to know if it is logged in or not. - app.get ///^((/[a-zA-Z0-9:.-]+/[a-z0-9-]+(_rev\d+)?)+)/?$///, cors, (req, res, next) -> - urlPages = (i for i in req.params[0].split('/') by 2)[1..] - urlLocs = (j for j in req.params[0].split('/')[1..] by 2) - if ['plugin', 'auth'].indexOf(urlLocs[0]) > -1 + argv.debug = true + } + + // Show all of the options a server is using. + log(argv) + + // #### Routes #### + // Routes currently make up the bulk of the Express port of + // Smallest Federated Wiki. Most routes use literal names, + // or regexes to match, and then access req.params directly. + + // ##### Redirects ##### + // Common redirects that may get used throughout the routes. + const index = argv.home + '.html' + const oops = '/oops' + + // ##### Get routes ##### + // Routes have mostly been kept together by http verb, with the exception + // of the openID related routes which are at the end together. + + // Main route for initial contact. Allows us to + // link into a specific set of pages, local and remote. + // Can also be handled by the client, but it also sets up + // the login status, and related footer html, which the client + // relies on to know if it is logged in or not. + app.get(/^((\/[a-zA-Z0-9:.-]+\/[a-z0-9-]+(_rev\d+)?)+)\/?$/, cors, (req, res, next) => { + const urlPages = req.params[0].split('/').filter((_, index) => index % 2 === 1) + const urlLocs = req.params[0] + .split('/') + .slice(1) + .filter((_, index) => index % 2 === 0) + if (['plugin', 'auth'].indexOf(urlLocs[0]) > -1) { return next() - title = urlPages[..].pop().replace(/-+/g,' ') + } + const title = urlPages.slice().pop().replace(/-+/g, ' ') user = securityhandler.getUser(req) - info = { - title - pages: [] - authenticated: if user - true - else - false - user: user - seedNeighbors: argv.neighbors - owned: if owner - true - else - false - isOwner: if securityhandler.isAuthorized(req) - true - else - false - ownedBy: if owner - owner - else - '' + const info = { + title, + pages: [], + authenticated: user ? true : false, + user: user, + seedNeighbors: argv.neighbors, + owned: owner ? true : false, + isOwner: securityhandler.isAuthorized(req) ? true : false, + ownedBy: owner ? owner : '', } - for page, idx in urlPages - if urlLocs[idx] is 'view' - pageDiv = {page} - else - pageDiv = {page, origin: """data-site=#{urlLocs[idx]}"""} + for (const [page, idx] of urlPages.entries()) { + let pageDiv + if (urlLocs[idx] === 'view') { + pageDiv = { page } + } else { + pageDiv = { page, origin: `data-site=${urlLocs[idx]}` } + } info.pages.push(pageDiv) + } res.render('static.html', info) + }) - app.get ///^\/([a-z0-9-]+)\.html$///, cors, (req, res, next) -> - slug = req.params[0] + app.get(/^\/([a-z0-9-]+)\.html$/, cors, (req, res, next) => { + const slug = req.params[0] log(slug) - if slug is 'runtests' - return next() - pagehandler.get slug, (e, page, status) -> - if e then return res.e e - if status is 404 + if (slug === 'runtests') return next() + pagehandler.get(slug, (e, page, status) => { + if (e) { + return res.e(e) + } + if (status === 404) { return res.status(status).send(page) - page.title ||= slug.replace(/-+/g,' ') + } + page.title ||= slug.replace(/-+/g, ' ') page.story ||= [] user = securityhandler.getUser(req) - info = { - title: page.title + const info = { + title: page.title, pages: [ - page: slug - generated: """data-server-generated=true""" - story: render(page) - ] - authenticated: if user - true - else - false - user: user - seedNeighbors: argv.neighbors - owned: if owner - true - else - false - isOwner: if securityhandler.isAuthorized(req) - true - else - false - ownedBy: if owner - owner - else - '' + { + page: slug, + generated: 'data-server-generated=true', + story: render(page), + }, + ], + authenticated: user ? true : false, + user: user, + seedNeighbors: argv.neighbors, + owned: owner ? true : false, + isOwner: securityhandler.isAuthorized(req) ? true : false, + ownedBy: owner ? owner : '', } res.render('static.html', info) + }) + }) - app.get ///system/factories.json///, (req, res) -> + app.get('/system/factories.json', (req, res) => { res.status(200) res.header('Content-Type', 'application/json') -# Plugins are located in packages in argv.packageDir, with package names of the form wiki-plugin-* - glob path.join(argv.packageDir, 'wiki-plugin-*', 'factory.json'), (e, files) -> - if e then return res.e(e) - - doFactories = (file, cb) -> - fs.readFile file, (err, data) -> - return cb() if err - try - factory = JSON.parse data - cb null, factory - catch err - return cb() + // Plugins are located in packages in argv.packageDir, with package names of the form wiki-plugin-* + glob(path.join(argv.packageDir, 'wiki-plugin-*', 'factory.json'), (e, files) => { + if (e) { + return res.e(e) + } - async.map files, doFactories, (e, factories) -> - res.e(e) if e - res.end(JSON.stringify factories) - - - ###### Json Routes ###### - # Handle fetching local and remote json pages. - # Local pages are handled by the pagehandler module. - app.get ///^/([a-z0-9-]+)\.json$///, cors, (req, res) -> - file = req.params[0] - pagehandler.get file, (e, page, status) -> - if e then return res.e e - res.status(status or 200).send(page) - - # Remote pages use the http client to retrieve the page - # and sends it to the client. TODO: consider caching remote pages locally. - app.get ///^/remote/([a-zA-Z0-9:\.-]+)/([a-z0-9-]+)\.json$///, (req, res) -> - remoteGet req.params[0], req.params[1], (e, page, status) -> - if e - log "remoteGet error:", e - return res.e e - res.status(status or 200).send(page) - - - ###### Theme Routes ###### - # If themes doesn't exist send 404 and let the client - # deal with it. - app.get /^\/theme\/(\w+\.\w+)$/, cors, (req,res) -> - res.sendFile(path.join(argv.status, 'theme', req.params[0]), (e) -> - if (e) - # swallow the error if the theme does not exist... - if req.path is '/theme/style.css' + const doFactories = (file, cb) => { + fs.readFile(file, (err, data) => { + if (err) { + return cb() + } + try { + const factory = JSON.parse(data) + cb(null, factory) + } catch (err) { + return cb() + } + }) + } + async.map(files, doFactories, (e, factories) => { + if (e) { + res.e(e) + } + res.end(JSON.stringify(factories)) + }) + }) + }) + + // ###### Json Routes ###### + // Handle fetching local and remote json pages. + // Local pages are handled by the pagehandler module. + app.get(/^\/([a-z0-9-]+)\.json$/, cors, (req, res) => { + const file = req.params[0] + pagehandler.get(file, (e, page, status) => { + if (e) { + return res.e(e) + } + res.status(status || 200).send(page) + }) + }) + + // Remote pages use the http client to retrieve the page + // and sends it to the client. TODO: consider caching remote pages locally. + app.get(/^\/remote\/([a-zA-Z0-9:.-]+)\/([a-z0-9-]+)\.json$/, (req, res) => { + remoteGet(req.params[0], req.params[1], (e, page, status) => { + if (e) { + log('remoteGet error:', e) + return res.e(e) + } + res.status(status || 200).send(page) + }) + }) + + // ###### Theme Routes ###### + // If themes doesn't exist send 404 and let the client + // deal with it. + app.get(/^\/theme\/(\w+\.\w+)$/, cors, (req, res) => { + res.sendFile(path.join(argv.status, 'theme', req.params[0]), e => { + if (e) { + // swallow the error if the theme does not exist... + if (req.path === '/theme/style.css') { res.set('Content-Type', 'text/css') res.send('') - else + } else { res.sendStatus(404) - ) - - ###### Favicon Routes ###### - # If favLoc doesn't exist send the default favicon. - favLoc = path.join(argv.status, 'favicon.png') - defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png') - app.get '/favicon.png', cors, (req,res) -> - fs.exists favLoc, (exists) -> - if exists + } + } + }) + }) + + // ###### Favicon Routes ###### + // If favLoc doesn't exist send the default favicon. + const favLoc = path.join(argv.status, 'favicon.png') + const defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png') + app.get('/favicon.png', cors, (req, res) => { + fs.exists(favLoc, exists => { + if (exists) { res.sendFile(favLoc) - else + } else { res.sendFile(defaultFavLoc) + } + }) + }) - authorized = (req, res, next) -> - if securityhandler.isAuthorized(req) + const authorized = (req, res, next) => { + if (securityhandler.isAuthorized(req)) { next() - else - console.log 'rejecting', req.path + } else { + console.log('rejecting', req.path) res.sendStatus(403) + } + } - # Accept favicon image posted to the server, and if it does not already exist - # save it. - app.post '/favicon.png', authorized, (req, res) -> - favicon = req.body.image.replace(///^data:image/png;base64,///, "") - buf = new Buffer(favicon, 'base64') - fs.exists argv.status, (exists) -> - if exists - fs.writeFile favLoc, buf, (e) -> - if e then return res.e e + // Accept favicon image posted to the server, and if it does not already exist + // save it. + app.post('/favicon.png', authorized, (req, res) => { + const favicon = req.body.image.replace(/^data:image\/png;base64,/, '') + const buf = new Buffer(favicon, 'base64') + fs.exists(argv.status, exists => { + if (exists) { + fs.writeFile(favLoc, buf, e => { + if (e) { + return res.e(e) + } res.send('Favicon Saved') - - else - fs.mkdir argv.status, { recursive: true }, -> - fs.writeFile favLoc, buf, (e) -> - if e then return res.e e + }) + } else { + fs.mkdir(argv.status, { recursive: true }, () => { + fs.writeFile(favLoc, buf, e => { + if (e) { + return res.e(e) + } res.send('Favicon Saved') + }) + }) + } + }) + }) - # Redirect remote favicons to the server they are needed from. - app.get ///^/remote/([a-zA-Z0-9:\.-]+/favicon.png)$///, (req, res) -> - remotefav = "http://#{req.params[0]}" + // Redirect remote favicons to the server they are needed from. + app.get(/^\/remote\/([a-zA-Z0-9:.-]+\/favicon.png)$/, (req, res) => { + const remotefav = 'http://#{req.params[0]}' res.redirect(remotefav) + }) - ###### Recycler Routes ###### - # These routes are only available to the site's owner + // ###### Recycler Routes ###### + // These routes are only available to the site's owner - # Give the recycler a standard flag - use the Taiwan symbol as the use of - # negative space outward pointing arrows nicely indicates that items can be removed - recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png') - app.get '/recycler/favicon.png', authorized, (req, res) -> + // Give the recycler a standard flag - use the Taiwan symbol as the use of + // negative space outward pointing arrows nicely indicates that items can be removed + const recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png') + app.get('/recycler/favicon.png', authorized, (req, res) => { res.sendFile(recyclerFavLoc) - - # Send an array of pages currently in the recycler via json - app.get '/recycler/system/slugs.json', authorized, (req, res) -> - fs.readdir argv.recycler, (e, files) -> - - doRecyclermap = (file, cb) -> - recycleFile = 'recycler/' + file - pagehandler.get recycleFile, (e, page, status) -> - if e or status is 404 - console.log 'Problem building recycler map:', file, 'e: ',e - # this will leave an undefined/empty item in the array, which we will filter out later + }) + + // Send an array of pages currently in the recycler via json + app.get('/recycler/system/slugs.json', authorized, (req, res) => { + fs.readdir(argv.recycler, (e, files) => { + const doRecyclermap = (file, cb) => { + const recycleFile = 'recycler/' + file + pagehandler.get(recycleFile, (e, page, status) => { + if (e || status === 404) { + console.log('Problem building recycler map:', file, 'e: ', e) + // this will leave an undefined/empty item in the array, which we will filter out later return cb() - cb null, { - slug: file - title: page.title } + cb(null, { + slug: file, + title: page.title, + }) + }) + } - if e then return res.e e - async.map files, doRecyclermap, (e, recyclermap) -> - return cb(e) if e - # remove any empty items - recyclermap = recyclermap.filter( (el) -> return !!el ) + if (e) { + return res.e(e) + } + async.map(files, doRecyclermap, (e, recyclermap) => { + if (e) { + return cb(e) + } + // remove any empty items + recyclermap = recyclermap.filter(el => !!el) res.send(recyclermap) - - # Fetching page from the recycler - #///^/([a-z0-9-]+)\.json$/// - app.get ///^/recycler/([a-z0-9-]+)\.json$///, authorized, (req, res) -> - file = 'recycler/' + req.params[0] - pagehandler.get file, (e, page, status) -> - if e then return res.e e - res.status(status or 200).send(page) - - # Delete page from the recycler - app.delete ///^/recycler/([a-z0-9-]+)\.json$///, authorized, (req, res) -> - file = 'recycler/' + req.params[0] - pagehandler.delete file, (err) -> - if err then res.status(500).send(err) + }) + }) + }) + + // Fetching page from the recycler + /////^/([a-z0-9-]+)\.json$/// + app.get(/^\/recycler\/([a-z0-9-]+)\.json$/, authorized, (req, res) => { + const file = 'recycler/' + req.params[0] + pagehandler.get(file, (e, page, status) => { + if (e) { + return res.e(e) + } + res.status(status || 200).send(page) + }) + }) + + // Delete page from the recycler + app.delete(/^\/recycler\/([a-z0-9-]+)\.json$/, authorized, (req, res) => { + const file = 'recycler/' + req.params[0] + pagehandler.delete(file, err => { + if (err) { + res.status(500).send(err) + } res.status(200).send('') - - - ###### Meta Routes ###### - # Send an array of pages in the database via json - app.get '/system/slugs.json', cors, (req, res) -> - pagehandler.slugs (err, files) -> - if err then res.status(500).send(err) + }) + }) + + // ###### Meta Routes ###### + // Send an array of pages in the database via json + app.get('/system/slugs.json', cors, (req, res) => { + pagehandler.slugs((err, files) => { + if (err) { + res.status(500).send(err) + } res.send(files) - -# Returns a list of installed plugins. (does this get called anymore!) - app.get '/system/plugins.json', cors, (req, res) -> - glob "wiki-plugin-*", {cwd: argv.packageDir}, (e, files) -> - if e then return res.e e - # extract the plugin name from the name of the directory it's installed in - files = files.map (file) -> file.slice(12) + }) + }) + + // Returns a list of installed plugins. (does this get called anymore!) + app.get('/system/plugins.json', cors, (req, res) => { + glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, files) => { + if (e) { + return res.e(e) + } + // extract the plugin name from the name of the directory it's installed in + files = files.map(file => file.slice(12)) res.send(files) - -# - sitemapLoc = path.join(argv.status, 'sitemap.json') - app.get '/system/sitemap.json', cors, (req, res) -> - fs.exists sitemapLoc, (exists) -> - if exists + }) + }) + //{ + const sitemapLoc = path.join(argv.status, 'sitemap.json') + app.get('/system/sitemap.json', cors, (req, res) => { + fs.exists(sitemapLoc, exists => { + if (exists) { res.sendFile(sitemapLoc) - else - # only createSitemap if we are not already creating one - sitemaphandler.createSitemap (pagehandler) if !sitemaphandler.isWorking() - # wait for the sitemap file to be written, before sending - sitemaphandler.once 'finished', -> + } else { + // only createSitemap if we are not already creating one + if (!sitemaphandler.isWorking()) { + sitemaphandler.createSitemap(pagehandler) + } + // wait for the sitemap file to be written, before sending + sitemaphandler.once('finished', () => { res.sendFile(sitemapLoc) + }) + } + }) + }) - xmlSitemapLoc = path.join(argv.status, 'sitemap.xml') - app.get '/sitemap.xml', cors, (req, res) -> - fs.exists sitemapLoc, (exists) -> - if exists + const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml') + app.get('/sitemap.xml', cors, (req, res) => { + fs.exists(sitemapLoc, exists => { + if (exists) { res.sendFile(xmlSitemapLoc) - else - sitemaphandler.createSitemap (pagehandler) if !sitemaphandler.isWorking() - sitemaphandler.once 'finished', -> + } else { + if (!sitemaphandler.isWorking()) { + sitemaphandler.createSitemap(pagehandler) + } + sitemaphandler.once('finished', () => { res.sendFile(xmlSitemapLoc) + }) + } + }) + }) - searchIndexLoc = path.join(argv.status, 'site-index.json') - app.get '/system/site-index.json', cors, (req, res) -> - fs.exists searchIndexLoc, (exists) -> - if exists + const searchIndexLoc = path.join(argv.status, 'site-index.json') + app.get('/system/site-index.json', cors, (req, res) => { + fs.exists(searchIndexLoc, exists => { + if (exists) { res.sendFile(searchIndexLoc) - else - # only create index if we are not already creating one - searchhandler.createIndex(pagehandler) if !searchhandler.isWorking() - searchhandler.once 'indexed', -> + } else { + // only create index if we are not already creating one + if (!searchhandler.isWorking()) { + searchhandler.createIndex(pagehandler) + } + searchhandler.once('indexed', () => { res.sendFile(searchIndexLoc) + }) + } + }) + }) - app.get '/system/export.json', cors, (req, res) -> - pagehandler.pages (e, sitemap) -> - return res.e(e) if e + app.get('/system/export.json', cors, (req, res) => { + pagehandler.pages((e, sitemap) => { + if (e) { + return res.e(e) + } async.map( sitemap, - (stub, done) -> - pagehandler.get(stub.slug, (error, page) -> - return done(e) if e - done(null, {slug: stub.slug, page}) + (stub, done) => { + pagehandler.get(stub.slug, (error, page) => { + if (e) { + return done(e) + } + done(null, { slug: stub.slug, page }) + }) + }, + (e, pages) => { + if (e) { + return res.e(e) + } + res.json( + pages.reduce((dict, combined) => { + dict[combined.slug] = combined.page + return dict + }, {}), ) - , - (e, pages) -> - return res.e(e) if e - res.json(pages.reduce( (dict, combined) -> - dict[combined.slug] = combined.page - dict - , {})) + }, ) + }) + }) - admin = (req, res, next) -> - if securityhandler.isAdmin(req) + const admin = (req, res, next) => { + if (securityhandler.isAdmin(req)) { next() - else - console.log 'rejecting', req.path + } else { + console.log('rejecting', req.path) res.sendStatus(403) + } + } - app.get '/system/version.json', admin, (req, res) -> - versions = {} - wikiModule = module.parent.parent.parent + app.get('/system/version.json', admin, (req, res) => { + const versions = {} + const wikiModule = module.parent.parent.parent versions[wikiModule.require('./package').name] = wikiModule.require('./package').version versions[wikiModule.require('wiki-server/package').name] = wikiModule.require('wiki-server/package').version versions[wikiModule.require('wiki-client/package').name] = wikiModule.require('wiki-client/package').version versions['security'] = {} versions['plugins'] = {} - glob '+(wiki-security-*|wiki-plugin-*)', {cwd: argv.packageDir}, (e, plugins) -> - plugins.map (plugin) -> - if plugin.includes 'wiki-security' - versions.security[wikiModule.require(plugin + "/package").name] = wikiModule.require(plugin + "/package").version - else - versions.plugins[wikiModule.require(plugin + "/package").name] = wikiModule.require(plugin + "/package").version - res.json(versions) - - ##### Proxy routes ##### - - app.get '/proxy/*', authorized, (req, res) -> - pathParts = req.originalUrl.split('/') - remoteHost = pathParts[2] - pathParts.splice(0,3) - remoteResource = pathParts.join('/') - requestURL = 'http://' + remoteHost + '/' + remoteResource - console.log("PROXY Request: ", requestURL) - if requestURL.endsWith('.json') or requestURL.endsWith('.png') or requestURL.endsWith('.jpg') or pathParts[0] is "plugin" - fetch(requestURL, {timeout: 2000}) - .then (fetchRes) -> - if fetchRes.ok - res.set('content-type', fetchRes.headers.get('content-type')) - res.set('last-modified', fetchRes.headers.get('last-modified')) - await pipeline(fetchRes.body, res) - else - res.status(fetchRes.status).end() - .catch (err) -> - console.log("ERROR: Proxy Request ", requestURL, err) - res.status(500).end() - else + glob('+(wiki-security-*|wiki-plugin-*)', { cwd: argv.packageDir }, (e, plugins) => { + plugins.map(plugin => { + if (plugin.includes('wiki-security')) { + versions.security[wikiModule.require(plugin + '/package').name] = wikiModule.require( + plugin + '/package', + ).version + } else { + versions.plugins[wikiModule.require(plugin + '/package').name] = wikiModule.require( + plugin + '/package', + ).version + } + res.json(versions) + }) + }) + }) + + // ##### Proxy routes ##### + + app.get('/proxy/*', authorized, (req, res) => { + const pathParts = req.originalUrl.split('/') + const remoteHost = pathParts[2] + pathParts.splice(0, 3) + const remoteResource = pathParts.join('/') + // this will fail if remote is TLS only! + const requestURL = 'http://' + remoteHost + '/' + remoteResource + console.log('PROXY Request: ', requestURL) + if ( + requestURL.endsWith('.json') || + requestURL.endsWith('.png') || + requestURL.endsWith('.jpg') || + pathParts[0] === 'plugin' + ) { + fetch(requestURL, { timeout: 2000 }) + .then(async fetchRes => { + if (fetchRes.ok) { + res.set('content-type', fetchRes.headers.get('content-type')) + res.set('last-modified', fetchRes.headers.get('last-modified')) + await pipeline(fetchRes.body, res) + } else { + res.status(fetchRes.status).end() + } + }) + .catch(err => { + console.log('ERROR: Proxy Request ', requestURL, err) + res.status(500).end() + }) + } else { res.status(400).end() - - - ##### Put routes ##### - - app.put /^\/page\/([a-z0-9-]+)\/action$/i, authorized, (req, res) -> - action = JSON.parse(req.body.action) - # Handle all of the possible actions to be taken on a page, - actionCB = (e, page, status) -> - #if e then return res.e e - if status is 404 - # res.status(status).send(page) - return res.e page,status - # Using Coffee-Scripts implicit returns we assign page.story to the - # result of a list comprehension by way of a switch expression. - try - page.story = switch action.type - when 'move' - action.order.map (id) -> - page.story.filter((para) -> - id == para.id - )[0] or throw('Ignoring move. Try reload.') - - when 'add' - idx = page.story.map((para) -> para.id).indexOf(action.after) + 1 - page.story.splice(idx, 0, action.item) - page.story - - when 'remove' - page.story.filter (para) -> - para?.id != action.id - - when 'edit' - page.story.map (para) -> - if para.id is action.id - action.item - else - para - - - when 'create', 'fork' - page.story or [] - - else - log "Unfamiliar action:", action - #page.story - throw('Unfamiliar action ignored') - catch e - return res.e e - - # Add a blank journal if it does not exist. - # And add what happened to the journal. - if not page.journal + } + }) + + // ##### Put routes ##### + + app.put(/^\/page\/([a-z0-9-]+)\/action$/i, authorized, (req, res) => { + const action = JSON.parse(req.body.action) + // Handle all of the possible actions to be taken on a page, + const actionCB = (e, page, status) => { + //if e then return res.e e + if (status === 404) { + // res.status(status).send(page) + return res.e(page, status) + } + // Using Coffee-Scripts implicit returns we assign page.story to the + // result of a list comprehension by way of a switch expression. + try { + page.story = (() => { + switch (action.type) { + case 'move': + return action.order.map(id => { + const match = page.story.filter(para => id === para.id)[0] + if (!match) throw 'Ignoring move. Try reload.' + return match + }) + case 'add': { + const idx = page.story.map(para => para.id).indexOf(action.after) + 1 + page.story.splice(idx, 0, action.item) + return page.story + } + + case 'remove': + return page.story.filter(para => para?.id !== action.id) + + case 'edit': + return page.story.map(para => { + if (para.id === action.id) { + return action.item + } else { + return para + } + }) + + case 'create': + case 'fork': + return page.story || [] + + default: + log('Unfamiliar action:', action) + //page.story + throw 'Unfamiliar action ignored' + } + })() + } catch (e) { + return res.e(e) + } + // Add a blank journal if it does not exist. + // And add what happened to the journal. + if (!page.journal) { page.journal = [] - if action.fork - page.journal.push({type: "fork", site: action.fork, date: action.date - 1}) + } + if (action.fork) { + page.journal.push({ type: 'fork', site: action.fork, date: action.date - 1 }) delete action.fork + } page.journal.push(action) - pagehandler.put req.params[0], page, (e) -> - if e then return res.e e + pagehandler.put(req.params[0], page, e => { + if (e) return res.e(e) res.send('ok') - # log 'saved' - - # update sitemap + // log 'saved' + }) + // update sitemap sitemaphandler.update(req.params[0], page) - # update site index + // update site index searchhandler.update(req.params[0], page) - - # log action - - # If the action is a fork, get the page from the remote server, - # otherwise ask pagehandler for it. - if action.fork - pagehandler.saveToRecycler req.params[0], (err) -> - if err and err isnt 'page does not exist' - console.log "Error saving #{req.params[0]} before fork: #{err}" - if action.forkPage - forkPageCopy = JSON.parse(JSON.stringify(action.forkPage)) + } + // log action + + // If the action is a fork, get the page from the remote server, + // otherwise ask pagehandler for it. + if (action.fork) { + pagehandler.saveToRecycler(req.params[0], err => { + if (err && err !== 'page does not exist') { + console.log(`Error saving ${req.params[0]} before fork: ${err}`) + } + if (action.forkPage) { + const forkPageCopy = JSON.parse(JSON.stringify(action.forkPage)) delete action.forkPage actionCB(null, forkPageCopy) - else - # Legacy path, new clients will provide forkPage on implicit forks. + } else { + // Legacy path, new clients will provide forkPage on implicit forks. remoteGet(action.fork, req.params[0], actionCB) - else if action.type is 'create' - # Prevent attempt to write circular structure - itemCopy = JSON.parse(JSON.stringify(action.item)) - pagehandler.get req.params[0], (e, page, status) -> - if e then return actionCB(e) - unless status is 404 + } + }) + } else if (action.type === 'create') { + // Prevent attempt to write circular structure + const itemCopy = JSON.parse(JSON.stringify(action.item)) + pagehandler.get(req.params[0], (e, page, status) => { + if (e) return actionCB(e) + if (status !== 404) { res.status(409).send('Page already exists.') - else + } else { actionCB(null, itemCopy) - - else if action.type == 'fork' - pagehandler.saveToRecycler req.params[0], (err) -> - if err then console.log "Error saving #{req.params[0]} before fork: #{err}" - if action.forkPage # push - forkPageCopy = JSON.parse(JSON.stringify(action.forkPage)) + } + }) + } else if (action.type === 'fork') { + pagehandler.saveToRecycler(req.params[0], err => { + if (err) console.log(`Error saving ${req.params[0]} before fork: ${err}`) + if (action.forkPage) { + // push + const forkPageCopy = JSON.parse(JSON.stringify(action.forkPage)) delete action.forkPage actionCB(null, forkPageCopy) - else # pull + } else { + // pull remoteGet(action.site, req.params[0], actionCB) - else + } + }) + } else { pagehandler.get(req.params[0], actionCB) + } + }) - # Return the oops page when login fails. - app.get '/oops', (req, res) -> + // Return the oops page when login fails. + app.get('/oops', (req, res) => { res.statusCode = 403 - res.render('oops.html', {msg:'This is not your wiki!'}) - - # Traditional request to / redirects to index :) - app.get '/', cors, (req, res) -> - home = path.join argv.assets, 'home', 'index.html' - fs.stat home, (err, stats) -> - if err || !stats.isFile() + res.render('oops.html', { msg: 'This is not your wiki!' }) + }) + + // Traditional request to / redirects to index :) + app.get('/', cors, (req, res) => { + const home = path.join(argv.assets, 'home', 'index.html') + fs.stat(home, (err, stats) => { + if (err || !stats.isFile()) { res.redirect(index) - else - res.redirect("/assets/home/index.html") - - ##### Delete Routes ##### - - app.delete ///^/([a-z0-9-]+)\.json$///, authorized, (req, res) -> - pageFile = req.params[0] - # we need the original page text to remove it from the index, so get the original text before deleting it - pagehandler.get pageFile, (e, page, status) -> - title = page.title - pagehandler.delete pageFile, (err) -> - if err + } else { + res.redirect('/assets/home/index.html') + } + }) + }) + + // ##### Delete Routes ##### + + app.delete(/^\/([a-z0-9-]+)\.json$/, authorized, (req, res) => { + const pageFile = req.params[0] + // we need the original page text to remove it from the index, so get the original text before deleting it + pagehandler.get(pageFile, (e, page, status) => { + const title = page.title + pagehandler.delete(pageFile, err => { + if (err) { res.status(500).send(err) - else - sitemaphandler.removePage pageFile + } else { + sitemaphandler.removePage(pageFile) res.status(200).send('') - # update site index + // update site index searchhandler.removePage(req.params[0]) - - - - #### Start the server #### - # Wait to make sure owner is known before listening. - securityhandler.retrieveOwner (e) -> - # Throw if you can't find the initial owner - if e then throw e + } + }) + }) + }) + + // #### Start the server #### + // Wait to make sure owner is known before listening. + securityhandler.retrieveOwner(e => { + // Throw if you can't find the initial owner + if (e) throw e owner = securityhandler.getOwner() - console.log "owner: " + owner - app.emit 'owner-set' - - app.on 'running-serv', (server) -> - ### Plugins ### - # Should replace most WebSocketServers below. - plugins = pluginsFactory(argv) - plugins.startServers({argv, app}) - ### Sitemap ### - # create sitemap at start-up + console.log('owner: ' + owner) + app.emit('owner-set') + }) + + app.on('running-serv', server => { + // ### Plugins ### + // Should replace most WebSocketServers below. + const plugins = pluginsFactory(argv) + plugins.startServers({ argv, app }) + // ### Sitemap ### + // create sitemap at start-up sitemaphandler.createSitemap(pagehandler) - # create site index at start-up + // create site index at start-up searchhandler.startUp(pagehandler) + }) - - # Return app when called, so that it can be watched for events and shutdown with .close() externally. - app + // Return app when called, so that it can be watched for events and shutdown with .close() externally. + return app +} From e1dbb3cf0ce0b69f28e2b8cb41ac87e1d7fa307f Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Wed, 28 May 2025 10:51:38 +0100 Subject: [PATCH 22/45] Tidyup and fix search - Remove used variables and parameters. - The code to remove the update flag had dropped outside an `else` block, add the missing {}. --- lib/search.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/search.js b/lib/search.js index ccaa9bf9..90e4be40 100644 --- a/lib/search.js +++ b/lib/search.js @@ -72,7 +72,6 @@ module.exports = exports = argv => { const searchPageRemove = (slug, cb) => { // remove page from index - const timeLabel = 'SITE INDEX page remove #{slug} - #{wikiName}' try { siteIndex.discard(slug) } catch (err) { @@ -90,7 +89,7 @@ module.exports = exports = argv => { if (exists) { writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => { if (e) return cb(e) - touch(indexUpdateFlag, err => { + touch(indexUpdateFlag, () => { cb() }) }) @@ -98,7 +97,7 @@ module.exports = exports = argv => { fs.mkdir(argv.status, { recursive: true }, () => { writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => { if (e) return cb(e) - touch(indexUpdateFlag, err => { + touch(indexUpdateFlag, () => { cb() }) }) @@ -133,7 +132,7 @@ module.exports = exports = argv => { switch (item.action) { case 'update': itself.start() - searchPageUpdate(item.slug, item.page, e => { + searchPageUpdate(item.slug, item.page, () => { process.nextTick(() => { serial(queue.shift()) }) @@ -141,7 +140,7 @@ module.exports = exports = argv => { break case 'remove': itself.start() - searchPageRemove(item.slug, e => { + searchPageRemove(item.slug, () => { process.nextTick(() => { serial(queue.shift()) }) @@ -374,15 +373,16 @@ module.exports = exports = argv => { }) } }) - } else + } else { // index does not exist, so create it itself.createIndex(pagehandler) - // remove the update flag once the index has been created - itself.once('indexed', () => { - fs.unlink(indexUpdateFlag, err => { - if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + // remove the update flag once the index has been created + itself.once('indexed', () => { + fs.unlink(indexUpdateFlag, err => { + if (err) console.log(`+++ SITE INDEX ${wikiName} : unable to delete update flag`) + }) }) - }) + } }) } From 68acedd00047f7f1b8305c3171e690ba8c3c4fe8 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Wed, 28 May 2025 10:59:50 +0100 Subject: [PATCH 23/45] Fix lineup route fix the pair-wise splitting of lineup url into pages and locations fix the reversed element / index in iterating of the entries in urlPages --- lib/server.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index d131976b..b5b7082b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -314,7 +314,10 @@ module.exports = exports = argv => { // the login status, and related footer html, which the client // relies on to know if it is logged in or not. app.get(/^((\/[a-zA-Z0-9:.-]+\/[a-z0-9-]+(_rev\d+)?)+)\/?$/, cors, (req, res, next) => { - const urlPages = req.params[0].split('/').filter((_, index) => index % 2 === 1) + const urlPages = req.params[0] + .split('/') + .filter((_, index) => index % 2 === 0) + .slice(1) const urlLocs = req.params[0] .split('/') .slice(1) @@ -334,7 +337,7 @@ module.exports = exports = argv => { isOwner: securityhandler.isAuthorized(req) ? true : false, ownedBy: owner ? owner : '', } - for (const [page, idx] of urlPages.entries()) { + for (const [idx, page] of urlPages.entries()) { let pageDiv if (urlLocs[idx] === 'view') { pageDiv = { page } From 0d1110cc55203463bd85a51532bc4a5a7361bd16 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Wed, 28 May 2025 15:29:39 +0100 Subject: [PATCH 24/45] Remove use of Grunt Add update author script, with prior authors Add require for should to test, that use it Make async tests async, and await supertest requests Remove grunt file, and related packages --- AUTHORS.txt | 1 + Gruntfile.js | 44 - package-lock.json | 1971 ++----------------------------------- package.json | 35 +- scripts/update-authors.js | 32 + test/defaultargs.js | 2 +- test/page.js | 1 + test/random.js | 1 + test/server.js | 52 +- test/sitemap.js | 20 +- 10 files changed, 160 insertions(+), 1999 deletions(-) delete mode 100644 Gruntfile.js create mode 100644 scripts/update-authors.js diff --git a/AUTHORS.txt b/AUTHORS.txt index d39df252..651a637b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -19,3 +19,4 @@ Eric Dobbs Joshua Benuck Tom Lieber Andrew Shell +jon r diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 9a121345..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = function (grunt) { - 'use strict' - - grunt.loadNpmTasks('grunt-contrib-watch') - grunt.loadNpmTasks('grunt-mocha-test') - grunt.loadNpmTasks('grunt-git-authors') - - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - mochaTest: { - test: { - options: { - reporter: 'spec', - require: ['coffeescript/register', 'should'], - }, - src: ['test/defaultargs.js', 'test/page.js', 'test/random.js', 'test/server.js', 'test/sitemap.js'], - }, - }, - - authors: { - prior: [ - 'Ward Cunningham ', - 'Nick Niemeir ', - 'Patrick Mueller ', - 'Erkan Yilmaz ', - 'Tom Lee ', - 'Nicholas Hallahan ', - 'Paul Rodwell ', - 'Austin King ', - ], - }, - - watch: { - all: { - files: ['lib/*.js', 'test/*.js'], - tasks: ['mochaTest'], - }, - }, - }) - - grunt.registerTask('default', ['mochaTest']) - grunt.registerTask('check', ['retire']) -} diff --git a/package-lock.json b/package-lock.json index b2674f29..e0c7ff13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,6 @@ "@eslint/js": "^9.27.0", "eslint": "^9.27.0", "globals": "^16.1.0", - "grunt": "^1.6.1", - "grunt-contrib-watch": "^1.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", "mocha": "^11.1.0", @@ -3466,9 +3464,9 @@ "license": "ISC" }, "node_modules/globals": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", - "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", "dev": true, "license": "MIT", "engines": { @@ -3478,1896 +3476,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/grunt": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz", - "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dateformat": "~4.6.2", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~5.0.0", - "glob": "~7.1.6", - "grunt-cli": "~1.4.3", - "grunt-known-options": "~2.0.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.1", - "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "nopt": "~3.0.6" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/grunt-contrib-watch/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", - "dev": true - }, - "node_modules/grunt-contrib-watch/node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", - "dev": true - }, - "node_modules/grunt-contrib-watch/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha512-Xhj93RXbMSq8urNCUq4p9l0P6hnySJ/7YNRhYNug0bLOuii7pKO7xQFb5mx9xZXWCar88pLPb805PvUkwrLZpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt-contrib-watch/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/grunt-contrib-watch/node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/grunt-contrib-watch/node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt-contrib-watch/node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt-contrib-watch/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-contrib-watch/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/grunt-contrib-watch/node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha512-o0JmTu17WGUaUOHa1l0FPGXKBfijbxK6qoHzlkihsDXxzBHvJcA7zgviKR92Xs841rX9pK16unfphLq0/KqX7A==", - "dev": true - }, - "node_modules/grunt-contrib-watch/node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-contrib-watch/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt-contrib-watch/node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==", - "dev": true - }, - "node_modules/grunt-contrib-watch/node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-watch/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt-git-authors": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/grunt-git-authors/-/grunt-git-authors-3.2.0.tgz", - "integrity": "sha512-uDJGPKoO7H93wUnnDPsNIdIrfhifyhQln2tG1ulH25ceFeKuKTrTKYytzTUuxeaspss2t66amQlgaqUS9yo+2w==", - "dev": true, - "dependencies": { - "spawnback": "~1.0.0" - } - }, - "node_modules/grunt-git-authors/node_modules/spawnback": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/spawnback/-/spawnback-1.0.1.tgz", - "integrity": "sha512-340ZqtqJzWAZtHwaCC2gx4mdQOnkUWAWNDp7y0bCEatdjmgQ4j7b0qQ7qO5WIJWx/luNrKcrYzpKbH3NTR030A==", - "dev": true - }, - "node_modules/grunt-mocha-test": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.13.3.tgz", - "integrity": "sha512-zQGEsi3d+ViPPi7/4jcj78afKKAKiAA5n61pknQYi25Ugik+aNOuRmiOkmb8mN2CeG8YxT+YdT1H1Q7B/eNkoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hooker": "^0.2.3", - "mkdirp": "^0.5.0" - }, - "engines": { - "node": ">= 0.10.4" - }, - "peerDependencies": { - "mocha": ">=1.20.0" - } - }, - "node_modules/grunt-mocha-test/node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-mocha-test/node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-mocha-test/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/grunt/node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/grunt/node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/grunt/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/grunt/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt/node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/grunt/node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt/node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/grunt/node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/grunt/node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/grunt/node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt/node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt/node_modules/getobject": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", - "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/grunt/node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/grunt/node_modules/grunt-cli": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", - "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "grunt-known-options": "~2.0.0", - "interpret": "~1.1.0", - "liftup": "~3.0.1", - "nopt": "~4.0.1", - "v8flags": "~3.2.0" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt/node_modules/grunt-known-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", - "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt/node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/grunt-legacy-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", - "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "async": "~3.2.0", - "exit": "~0.1.2", - "getobject": "~1.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.21", - "underscore.string": "~3.3.5", - "which": "~2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt/node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/grunt/node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/grunt/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt/node_modules/interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt/node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/grunt/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/grunt/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/grunt/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/liftup": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", - "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^4.0.0", - "fined": "^1.2.0", - "flagged-respawn": "^1.0.1", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.1", - "rechoir": "^0.7.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt/node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/grunt/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt/node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/grunt/node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/grunt/node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "node_modules/grunt-git-authors": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/grunt-git-authors/-/grunt-git-authors-3.2.0.tgz", + "integrity": "sha512-uDJGPKoO7H93wUnnDPsNIdIrfhifyhQln2tG1ulH25ceFeKuKTrTKYytzTUuxeaspss2t66amQlgaqUS9yo+2w==", "dev": true, - "license": "MIT", "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/grunt/node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "spawnback": "~1.0.0" } }, - "node_modules/grunt/node_modules/path-is-absolute": { + "node_modules/grunt-git-authors/node_modules/spawnback": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/grunt/node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } + "resolved": "https://registry.npmjs.org/spawnback/-/spawnback-1.0.1.tgz", + "integrity": "sha512-340ZqtqJzWAZtHwaCC2gx4mdQOnkUWAWNDp7y0bCEatdjmgQ4j7b0qQ7qO5WIJWx/luNrKcrYzpKbH3NTR030A==", + "dev": true }, - "node_modules/grunt/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "node_modules/grunt-mocha-test": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.13.3.tgz", + "integrity": "sha512-zQGEsi3d+ViPPi7/4jcj78afKKAKiAA5n61pknQYi25Ugik+aNOuRmiOkmb8mN2CeG8YxT+YdT1H1Q7B/eNkoQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "hooker": "^0.2.3", + "mkdirp": "^0.5.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt/node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "node": ">= 0.10.4" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "mocha": ">=1.20.0" } }, - "node_modules/grunt/node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/grunt/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/grunt-mocha-test/node_modules/hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/grunt/node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/grunt-mocha-test/node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/grunt/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/grunt/node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt/node_modules/underscore.string": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", - "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "^1.1.1", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/underscore.string/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/grunt/node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/grunt/node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "node_modules/grunt-mocha-test/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/grunt/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" + "minimist": "^1.2.6" }, "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" + "mkdirp": "bin/cmd.js" } }, - "node_modules/grunt/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -5440,9 +3612,9 @@ } }, "node_modules/jsdom/node_modules/@csstools/css-calc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", - "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "funding": [ { "type": "github", @@ -5458,14 +3630,14 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/jsdom/node_modules/@csstools/css-color-parser": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", - "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", "funding": [ { "type": "github", @@ -5479,20 +3651,20 @@ "license": "MIT", "dependencies": { "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.3" + "@csstools/css-calc": "^2.1.4" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/jsdom/node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "funding": [ { "type": "github", @@ -5508,13 +3680,13 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/jsdom/node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "funding": [ { "type": "github", @@ -5922,9 +4094,9 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.4.0.tgz", - "integrity": "sha512-O6oi5Y9G6uu8f9iqXR6iKNLWHLRex3PKbmHynfpmUnMJJGrdgXh8ZmS85Ei5KR2Gnl+/gQ9s+Ktv5CqKybNw4A==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", + "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", "dev": true, "license": "MIT", "dependencies": { @@ -5938,7 +4110,7 @@ "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", @@ -6371,22 +4543,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6517,16 +4673,19 @@ "license": "ISC" }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mocha/node_modules/minipass": { diff --git a/package.json b/package.json index c8d3de65..95aadf6f 100644 --- a/package.json +++ b/package.json @@ -8,16 +8,26 @@ "url": "http://ward.fed.wiki.org" }, "contributors": [ - { - "name": "Nick Niemeir", - "email": "nick.niemeir@gmail.com", - "url": "http://nrn.io" - }, - { - "name": "Paul Rodwell", - "email": "paul.rodwell@btinternet.com", - "url": "http://wiki-paul90.rhcloud.com" - } + "Austin King ", + "Paul Rodwell ", + "Nicholas Hallahan ", + "Tom Lee ", + "Erkan Yilmaz ", + "Patrick Mueller ", + "Nick Niemeir ", + "Ward Cunningham ", + "Christian Smith ", + "Gui13 ", + "Merlyn Albery-Speyer ", + "Marcin Cieslak ", + "enyst ", + "Peter deHaan ", + "winckell benjamin ", + "Eric Dobbs ", + "Joshua Benuck ", + "Tom Lieber ", + "Andrew Shell ", + "jon r " ], "dependencies": { "async": "^3.2.4", @@ -42,14 +52,13 @@ "scripts": { "prettier:format": "prettier --write './**/*.js'", "prettier:check": "prettier --check ./**/*.js", - "test": "grunt mochaTest" + "test": "mocha", + "update-authors": "node scripts/update-authors.js" }, "devDependencies": { "@eslint/js": "^9.27.0", "eslint": "^9.27.0", "globals": "^16.1.0", - "grunt": "^1.6.1", - "grunt-contrib-watch": "^1.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", "mocha": "^11.1.0", diff --git a/scripts/update-authors.js b/scripts/update-authors.js new file mode 100644 index 00000000..f1e8663c --- /dev/null +++ b/scripts/update-authors.js @@ -0,0 +1,32 @@ +const gitAuthors = require('grunt-git-authors') + +// list of contributers from prior the split out of Smallest Federated Wiki repo. +const priorAuthors = [ + 'Ward Cunningham ', + 'Nick Niemeir ', + 'Patrick Mueller ', + 'Erkan Yilmaz ', + 'Tom Lee ', + 'Nicholas Hallahan ', + 'Paul Rodwell ', + 'Austin King ', +] + +gitAuthors.updatePackageJson({ priorAuthors: priorAuthors, order: 'date' }, error => { + if (error) { + console.log('Error: ', error) + } +}) + +gitAuthors.updateAuthors( + { + priorAuthors: priorAuthors.reverse(), + }, + (error, filename) => { + if (error) { + console.log('Error: ', error) + } else { + console.log(filename, 'updated') + } + }, +) diff --git a/test/defaultargs.js b/test/defaultargs.js index 363ce1ef..2ee94a9d 100644 --- a/test/defaultargs.js +++ b/test/defaultargs.js @@ -1,5 +1,5 @@ const defaultargs = require('../lib/defaultargs') - +const should = require('should') describe('defaultargs', () => { describe('#defaultargs()', () => { it('should not write over give args', () => { diff --git a/test/page.js b/test/page.js index da79ec96..f3102c81 100644 --- a/test/page.js +++ b/test/page.js @@ -1,3 +1,4 @@ +const should = require('should') const path = require('node:path') const random = require('../lib/random_id') const testid = random() diff --git a/test/random.js b/test/random.js index e685fea5..496b7b67 100644 --- a/test/random.js +++ b/test/random.js @@ -1,4 +1,5 @@ const random = require('../lib/random_id') +const should = require('should') describe('random', () => { describe('#random_id', () => { diff --git a/test/server.js b/test/server.js index a9b08e46..20938d71 100644 --- a/test/server.js +++ b/test/server.js @@ -1,4 +1,5 @@ const supertest = require('supertest') +const should = require('should') const fs = require('node:fs') const server = require('..') const path = require('node:path') @@ -13,6 +14,7 @@ const argv = require('../lib/defaultargs')({ describe('server', () => { var app = {} + let runningServer = null before(done => { // as starting the server this was does not create a sitemap file, create an empty one const sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') @@ -22,17 +24,21 @@ describe('server', () => { app = server(argv) app.once('owner-set', () => { - app.listen(app.startOpts.port, app.startOpts.host, done) + runningServer = app.listen(app.startOpts.port, app.startOpts.host, done) }) }) + after(() => { + runningServer.close() + }) + const request = supertest('http://localhost:55557') // location of the test page const loc = path.join('/tmp', 'sfwtests', testid, 'pages', 'adsf-test-page') - it('factories should return a list of plugin', () => { - request + it('factories should return a list of plugin', async () => { + await request .get('/system/factories.json') .expect(200) .expect('Content-Type', /json/) @@ -42,8 +48,8 @@ describe('server', () => { }) }) - it('new site should have an empty list of pages', () => { - request + it('new site should have an empty list of pages', async () => { + await request .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) @@ -58,7 +64,7 @@ describe('server', () => { }) }) - it('should create a page', () => { + it('should create a page', async () => { const body = JSON.stringify({ type: 'create', item: { @@ -73,7 +79,7 @@ describe('server', () => { date: 1234567890123, }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -82,10 +88,10 @@ describe('server', () => { }) }) - it('should move the paragraphs to the order given ', () => { + it('should move the paragraphs to the order given ', async () => { const body = '{ "type": "move", "order": [ "a1", "a3", "a2", "a4"] }' - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -105,14 +111,14 @@ describe('server', () => { }) }) - it('should add a paragraph', () => { + it('should add a paragraph', async () => { const body = JSON.stringify({ type: 'add', after: 'a2', item: { id: 'a5', type: 'paragraph', text: 'this is the NEW paragrpah' }, }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -132,13 +138,13 @@ describe('server', () => { }) }) - it('should remove a paragraph with given id', () => { + it('should remove a paragraph with given id', async () => { const body = JSON.stringify({ type: 'remove', id: 'a2', }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -160,14 +166,14 @@ describe('server', () => { }) }) - it('should edit a paragraph in place', () => { + it('should edit a paragraph in place', async () => { const body = JSON.stringify({ type: 'edit', item: { id: 'a3', type: 'paragraph', text: 'edited' }, id: 'a3', }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -186,12 +192,12 @@ describe('server', () => { }) }) - it('should default to no change', () => { + it('should default to no change', async () => { const body = JSON.stringify({ type: 'asdf', }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(500) @@ -213,14 +219,14 @@ describe('server', () => { }) }) - it('should refuse to create over a page', () => { + it('should refuse to create over a page', async () => { const body = JSON.stringify({ type: 'create', item: { title: 'Doh' }, id: 'c1', }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(409) @@ -238,8 +244,8 @@ describe('server', () => { }) }) - it('site should now have one page', () => { - request + it('site should now have one page', async () => { + await request .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) @@ -256,8 +262,4 @@ describe('server', () => { throw err }) }) - - after(() => { - if (app.close) app.close() - }) }) diff --git a/test/sitemap.js b/test/sitemap.js index c65bef87..1658fae4 100644 --- a/test/sitemap.js +++ b/test/sitemap.js @@ -1,4 +1,5 @@ const supertest = require('supertest') +const should = require('should') const fs = require('node:fs') const server = require('..') const path = require('node:path') @@ -22,6 +23,9 @@ describe('sitemap', () => { afterEach(() => { runningServer.close() }) + after(() => { + if (app.close) app.close() + }) const request = supertest('http://localhost:55556') fs.mkdirSync(path.join('/tmp', 'sfwtests', testid, 'pages'), { recursive: true }) @@ -29,8 +33,8 @@ describe('sitemap', () => { // location of the sitemap const sitemapLoc = path.join('/tmp', 'sfwtests', testid, 'status', 'sitemap.json') - it('new site should have an empty sitemap', () => { - request + it('new site should have an empty sitemap', async () => { + await request .get('/system/sitemap.json') .expect(200) .expect('Content-Type', /json/) @@ -78,14 +82,14 @@ describe('sitemap', () => { ) }) - it('synopsis should reflect edit to first paragraph', () => { + it('synopsis should reflect edit to first paragraph', async () => { const body = JSON.stringify({ type: 'edit', item: { id: 'a1', type: 'paragraph', text: 'edited' }, id: 'a1', }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) @@ -101,8 +105,8 @@ describe('sitemap', () => { }) }) - it('deleting a page should remove it from the sitemap', () => { - request + it('deleting a page should remove it from the sitemap', async () => { + await request .delete('/adsf-test-page.json') .send() .expect(200) @@ -116,8 +120,4 @@ describe('sitemap', () => { } }) }) - - after(() => { - if (app.close) app.close() - }) }) From 90639105bae96f63fce2e2035c509a6a1f960a7b Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Wed, 28 May 2025 15:32:46 +0100 Subject: [PATCH 25/45] Support shorter sitemap timeout in test The server used in the tests will continue running until the sitemap time ends. Add an arg for testing to use a much shorter timeout. --- lib/defaultargs.js | 1 + lib/sitemap.js | 2 +- test/server.js | 1 + test/sitemap.js | 5 +++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/defaultargs.js b/lib/defaultargs.js index 042aa1e8..1f050009 100644 --- a/lib/defaultargs.js +++ b/lib/defaultargs.js @@ -37,6 +37,7 @@ module.exports = argv => { argv.session_duration ||= 7 argv.neighbors ||= '' argv.debug ||= false + argv.test ||= false if (typeof argv.database === 'string') { argv.database = JSON.parse(argv.database) diff --git a/lib/sitemap.js b/lib/sitemap.js index 3fee3285..8823a8bd 100644 --- a/lib/sitemap.js +++ b/lib/sitemap.js @@ -33,7 +33,7 @@ module.exports = exports = argv => { let sitemapPageHandler = null // ms since last update we will remove sitemap from memory - const sitemapTimeoutMs = 120000 + const sitemapTimeoutMs = argv.test ? 1000 : 120000 let sitemapTimeoutHandler = null const sitemapLoc = path.join(argv.status, 'sitemap.json') diff --git a/test/server.js b/test/server.js index 20938d71..64a10d32 100644 --- a/test/server.js +++ b/test/server.js @@ -10,6 +10,7 @@ const argv = require('../lib/defaultargs')({ packageDir: path.join(__dirname, '..', 'node_modules'), port: 55557, security_legacy: true, + test: true, }) describe('server', () => { diff --git a/test/sitemap.js b/test/sitemap.js index 1658fae4..7ca0af93 100644 --- a/test/sitemap.js +++ b/test/sitemap.js @@ -9,6 +9,7 @@ const argv = require('../lib/defaultargs')({ data: path.join('/tmp', 'sfwtests', testid), port: 55556, security_legacy: true, + test: true, }) describe('sitemap', () => { @@ -48,7 +49,7 @@ describe('sitemap', () => { ) }) - it('creating a page should add it to the sitemap', () => { + it('creating a page should add it to the sitemap', async () => { const body = JSON.stringify({ type: 'create', item: { @@ -63,7 +64,7 @@ describe('sitemap', () => { date: 1234567890123, }) - request + await request .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) From 1ecf4c270681f7544b213722868793d6a037c408 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Wed, 28 May 2025 15:40:19 +0100 Subject: [PATCH 26/45] update ignore files --- .gitignore | 8 +------- .npmignore | 10 +++++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 19c53c96..34faae4d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,14 +7,8 @@ config.json /data /spec/data -/client/client.js -/client/test/testclient.js -/client/plugins/*.js -/client/plugins/**/*.js -/client/plugins/pushpin/**/*.js -/client/chart/ -/client/garden/ /default-data/status/sitemap.json node_modules npm-debug.log temp +coverage diff --git a/.npmignore b/.npmignore index fc4d4fd3..fbd220b3 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,11 @@ /data config.json - +.github/ +.mailmap +.prettier* +.vscode/ +.zed/ +eslint.config.js +scripts/ +test/ +coverage From 1cc4f98ca345bae19e0543e5c914b0122c4fa0f9 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Sun, 1 Jun 2025 12:02:22 +0100 Subject: [PATCH 27/45] asSlug returns slug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit remove {}’s so name is returned. --- lib/sitemap.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/sitemap.js b/lib/sitemap.js index 8823a8bd..28c05e3f 100644 --- a/lib/sitemap.js +++ b/lib/sitemap.js @@ -16,12 +16,11 @@ const xml2js = require('xml2js') const synopsis = require('wiki-client/lib/synopsis') -const asSlug = name => { +const asSlug = name => name .replace(/\s/g, '-') .replace(/[^A-Za-z0-9-]/g, '') .toLowerCase() -} module.exports = exports = argv => { const wikiName = new URL(argv.url).hostname From 026765df313bb53b490d5592c6e7801ecec0a425 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Sun, 1 Jun 2025 15:21:05 +0100 Subject: [PATCH 28/45] replace should MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit should hasn’t been archived, so replace with node’s builtin assert. add support for `node —test`, this is not ready as there appears to be a timeout issue causing describe to fail even though all the tests have passed. --- package-lock.json | 63 +-------------------- package.json | 3 +- test/defaultargs.js | 11 ++-- test/mocha.opts | 2 - test/page.js | 38 ++++++------- test/random.js | 8 ++- test/server.js | 131 ++++++++++++++++---------------------------- test/sitemap.js | 60 ++++++++------------ 8 files changed, 101 insertions(+), 215 deletions(-) delete mode 100644 test/mocha.opts diff --git a/package-lock.json b/package-lock.json index e0c7ff13..f6cef3d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,9 +34,8 @@ "globals": "^16.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", - "mocha": "^11.1.0", + "mocha": "^11.5.0", "prettier": "^3.5.3", - "should": "^13.2.3", "supertest": "^7.0.0", "wiki-client": "^0.31", "wiki-plugin-activity": "0.7", @@ -5445,66 +5444,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "node_modules/should/node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "should-type": "^1.4.0" - } - }, - "node_modules/should/node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "node_modules/should/node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/should/node_modules/should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "node_modules/should/node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "dev": true, - "license": "MIT" - }, "node_modules/supertest": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.1.tgz", diff --git a/package.json b/package.json index 95aadf6f..906a8bf9 100644 --- a/package.json +++ b/package.json @@ -61,9 +61,8 @@ "globals": "^16.1.0", "grunt-git-authors": "^3.2.0", "grunt-mocha-test": "^0.13.3", - "mocha": "^11.1.0", + "mocha": "^11.5.0", "prettier": "^3.5.3", - "should": "^13.2.3", "supertest": "^7.0.0", "wiki-client": "^0.31", "wiki-plugin-activity": "0.7", diff --git a/test/defaultargs.js b/test/defaultargs.js index 2ee94a9d..00ae570c 100644 --- a/test/defaultargs.js +++ b/test/defaultargs.js @@ -1,15 +1,18 @@ +const { describe, it } = require('node:test') +const assert = require('node:assert/strict') + const defaultargs = require('../lib/defaultargs') -const should = require('should') + describe('defaultargs', () => { describe('#defaultargs()', () => { it('should not write over give args', () => { - defaultargs({ port: 1234 }).port.should.equal(1234) + assert.equal(defaultargs({ port: 1234 }).port, 1234) }) it('should write non give args', () => { - defaultargs().port.should.equal(3000) + assert.equal(defaultargs().port, 3000) }) it('should modify dependant args', () => { - defaultargs({ data: '/tmp/asdf/' }).db.should.equal('/tmp/asdf/pages') + assert.equal(defaultargs({ data: '/tmp/asdf/' }).db, '/tmp/asdf/pages') }) }) }) diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 4e3e2af1..00000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---require should ---compilers coffee:coffeescript/register diff --git a/test/page.js b/test/page.js index f3102c81..3782cc81 100644 --- a/test/page.js +++ b/test/page.js @@ -1,4 +1,6 @@ -const should = require('should') +const { describe, it } = require('node:test') +const assert = require('node:assert/strict') + const path = require('node:path') const random = require('../lib/random_id') const testid = random() @@ -17,59 +19,53 @@ console.log('testid', testid) describe('page', () => { describe('#page.put()', () => { - it('should save a page', done => { + it('should save a page', () => { page.put('asdf', testpage, e => { - done(e) + if (e) throw e }) }) }) describe('#page.get()', () => { - it('should get a page if it exists', done => { + it('should get a page if it exists', () => { page.get('asdf', (e, got) => { if (e) throw e - got.title.should.equal('Asdf') - done() + assert.equal(got.title, 'Asdf') }) }) - it('should copy a page from default if nonexistant in db', done => { + it('should copy a page from default if nonexistant in db', () => { page.get('welcome-visitors', (e, got) => { if (e) throw e - got.title.should.equal('Welcome Visitors') - done() + assert.equal(got.title, 'Welcome Visitors') }) }) // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it('should copy a page from plugins if nonexistant in db', done => { + it('should copy a page from plugins if nonexistant in db', () => { page.get('recent-changes', (e, got) => { if (e) throw e - got.title.should.equal('Recent Changes') - done() + assert.equal(got.title, 'Recent Changes') }) }) // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it('should mark a page from plugins with the plugin name', done => { + it('should mark a page from plugins with the plugin name', () => { page.get('recent-changes', (e, got) => { if (e) throw e - got.plugin.should.equal('activity') - done() + assert.equal(got.plugin, 'activity') }) }) - it('should create a page if it exists nowhere', done => { + it('should create a page if it exists nowhere', () => { page.get(random(), (e, got) => { if (e) throw e - got.should.equal('Page not found') - done() + assert.equal(got, 'Page not found') }) }) - it('should eventually write the page to disk', done => { + it.skip('should eventually write the page to disk', async () => { const test = () => { console.log('should write', argv) fs.readFile(path.join(argv.db, 'asdf'), (err, data) => { if (err) throw err const readPage = JSON.parse(data) page.get('asdf', (e, got) => { - readPage.title.should.equal(got.title) - done() + assert.equal(readPage.title, got.title) }) }) } diff --git a/test/random.js b/test/random.js index 496b7b67..b2b88d9f 100644 --- a/test/random.js +++ b/test/random.js @@ -1,13 +1,15 @@ +const { describe, it } = require('node:test') +const assert = require('node:assert/strict') + const random = require('../lib/random_id') -const should = require('should') describe('random', () => { describe('#random_id', () => { it('should not be the same twice', () => { - random().should.not.equal(random()) + assert.notEqual(random(), random()) }) it('should be 16 digits', () => { - random().length.should.equal(16) + assert.equal(random().length, 16) }) }) }) diff --git a/test/server.js b/test/server.js index 64a10d32..7407dd78 100644 --- a/test/server.js +++ b/test/server.js @@ -1,5 +1,7 @@ +const { describe, it, before, after } = require('node:test') +const assert = require('node:assert/strict') + const supertest = require('supertest') -const should = require('should') const fs = require('node:fs') const server = require('..') const path = require('node:path') @@ -13,7 +15,7 @@ const argv = require('../lib/defaultargs')({ test: true, }) -describe('server', () => { +describe('server', d => { var app = {} let runningServer = null before(done => { @@ -29,7 +31,7 @@ describe('server', () => { }) }) - after(() => { + after(async () => { runningServer.close() }) @@ -44,8 +46,8 @@ describe('server', () => { .expect(200) .expect('Content-Type', /json/) .then(res => { - res.body[1].name.should.equal('Video') - res.body[1].category.should.equal('format') + assert.equal(res.body[1].name, 'Video') + assert.equal(res.body[1].category, 'format') }) }) @@ -54,15 +56,7 @@ describe('server', () => { .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) - .then( - res => res.body.should.be.empty, - error => { - throw error - }, - ) - .catch(error => { - throw error - }) + .then(res => assert.deepEqual(res.body, [])) }) it('should create a page', async () => { @@ -84,9 +78,6 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) - .catch(err => { - throw err - }) }) it('should move the paragraphs to the order given ', async () => { @@ -99,9 +90,9 @@ describe('server', () => { .then( () => { const page = JSON.parse(fs.readFileSync(loc)) - page.story[1].id.should.equal('a3') - page.story[2].id.should.equal('a2') - page.journal[1].type.should.equal('move') + assert(page.story[1].id, 'a3') + assert(page.story[2].id, 'a2') + assert(page.journal[1].type, 'move') }, err => { throw err @@ -123,17 +114,12 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) - .then( - () => { - const page = JSON.parse(fs.readFileSync(loc)) - page.story.length.should.equal(5) - page.story[3].id.should.equal('a5') - page.journal[2].type.should.equal('add') - }, - err => { - throw err - }, - ) + .then(() => { + const page = JSON.parse(fs.readFileSync(loc)) + assert.equal(page.story.length, 5) + assert.equal(page.story[3].id, 'a5') + assert.equal(page.journal[2].type, 'add') + }) .catch(err => { throw err }) @@ -149,19 +135,14 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) - .then( - () => { - const page = JSON.parse(fs.readFileSync(loc)) - page.story.length.should.equal(4) - page.story[1].id.should.equal('a3') - page.story[2].id.should.not.equal('a2') - page.story[2].id.should.equal('a5') - page.journal[3].type.should.equal('remove') - }, - err => { - throw err - }, - ) + .then(() => { + const page = JSON.parse(fs.readFileSync(loc)) + assert(page.story.length == 4) + assert.equal(page.story[1].id, 'a3') + assert.notEqual(page.story[2].id, 'a2') + assert.equal(page.story[2].id, 'a5') + assert.equal(page.journal[3].type, 'remove') + }) .catch(err => { throw err }) @@ -178,16 +159,11 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) - .then( - () => { - const page = JSON.parse(fs.readFileSync(loc)) - page.story[1].text.should.equal('edited') - page.journal[4].type.should.equal('edit') - }, - err => { - throw err - }, - ) + .then(() => { + const page = JSON.parse(fs.readFileSync(loc)) + assert.equal(page.story[1].text, 'edited') + assert.equal(page.journal[4].type, 'edit') + }) .catch(err => { throw err }) @@ -202,19 +178,14 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(500) - .then( - () => { - const page = JSON.parse(fs.readFileSync(loc)) - page.story.length.should.equal(4) - page.journal.length.should.equal(5) - page.story[0].id.should.equal('a1') - page.story[3].text.should.equal('this is the fourth paragraph') - page.journal[4].type.should.equal('edit') - }, - err => { - throw err - }, - ) + .then(() => { + const page = JSON.parse(fs.readFileSync(loc)) + assert(page.story.length == 4) + assert(page.journal.length == 5) + assert.equal(page.story[0].id, 'a1') + assert.equal(page.story[3].text, 'this is the fourth paragraph') + assert.equal(page.journal[4].type, 'edit') + }) .catch(err => { throw err }) @@ -231,15 +202,10 @@ describe('server', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(409) - .then( - () => { - const page = JSON.parse(fs.readFileSync(loc)) - page.title.should.not.equal('Doh') - }, - err => { - throw err - }, - ) + .then(() => { + const page = JSON.parse(fs.readFileSync(loc)) + assert.notEqual(page.title, 'Doh') + }) .catch(err => { throw err }) @@ -250,15 +216,10 @@ describe('server', () => { .get('/system/slugs.json') .expect(200) .expect('Content-Type', /json/) - .then( - res => { - res.body.length.should.equal[1] - res.body[0].should.equal['adsf-test-page'] - }, - err => { - throw err - }, - ) + .then(res => { + assert(res.body.length == 1) + assert.equal(res.body[0], 'adsf-test-page') + }) .catch(err => { throw err }) diff --git a/test/sitemap.js b/test/sitemap.js index 7ca0af93..b4d60c45 100644 --- a/test/sitemap.js +++ b/test/sitemap.js @@ -1,5 +1,7 @@ +const { describe, it, before, after } = require('node:test') +const assert = require('node:assert/strict') + const supertest = require('supertest') -const should = require('should') const fs = require('node:fs') const server = require('..') const path = require('node:path') @@ -15,17 +17,16 @@ const argv = require('../lib/defaultargs')({ describe('sitemap', () => { let app = {} let runningServer = null - beforeEach(done => { + + before(done => { app = server(argv) app.once('owner-set', () => { runningServer = app.listen(app.startOpts.port, app.startOpts.host, done) }) }) - afterEach(() => { - runningServer.close() - }) + after(() => { - if (app.close) app.close() + runningServer.close() }) const request = supertest('http://localhost:55556') @@ -39,14 +40,9 @@ describe('sitemap', () => { .get('/system/sitemap.json') .expect(200) .expect('Content-Type', /json/) - .then( - res => { - res.body.should.be.empty - }, - err => { - throw err - }, - ) + .then(res => { + assert.equal(res.body.length, 0) + }) }) it('creating a page should add it to the sitemap', async () => { @@ -57,7 +53,7 @@ describe('sitemap', () => { story: [ { id: 'a1', type: 'paragraph', text: 'this is the first paragraph' }, { id: 'a2', type: 'paragraph', text: 'this is the second paragraph' }, - { id: 'a3', type: 'paragraph', text: 'this is the third paragraph' }, + { id: 'a3', type: 'paragraph', text: 'this is the [[third]] paragraph' }, { id: 'a4', type: 'paragraph', text: 'this is the fourth paragraph' }, ], }, @@ -68,14 +64,14 @@ describe('sitemap', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) + // sitemap update does not happen until after the put has returned, so wait for it to finish + .then(() => new Promise(resolve => app.sitemaphandler.once('finished', () => resolve()))) .then( () => { - // sitemap update does not happen until after the put has returned, so wait for it to finish - app.sitemaphandler.once('finished', () => { - const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - sitemap[0].slug.should.equal['adsf-test-page'] - sitemap[0].synopsis.should.equal['this is the first paragraph'] - }) + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) + assert.equal(sitemap[0].slug, 'adsf-test-page') + assert.equal(sitemap[0].synopsis, 'this is the first paragraph') + assert.deepEqual(sitemap[0].links, { third: 'a3' }) }, err => { throw err @@ -94,15 +90,11 @@ describe('sitemap', () => { .put('/page/adsf-test-page/action') .send('action=' + body) .expect(200) + .then(() => new Promise(resolve => app.sitemaphandler.once('finished', () => resolve()))) .then(() => { - app.sitemaphandler.once('finished', () => { - const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - sitemap[0].slug.should.equal['adsf-test-page'] - sitemap[0].synopsis.should.equal['edited'] - }), - err => { - throw err - } + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) + assert.equal(sitemap[0].slug, 'adsf-test-page') + assert.equal(sitemap[0].synopsis, 'edited') }) }) @@ -111,14 +103,10 @@ describe('sitemap', () => { .delete('/adsf-test-page.json') .send() .expect(200) + .then(() => new Promise(resolve => app.sitemaphandler.once('finished', () => resolve()))) .then(() => { - app.sitemaphandler.once('finished', () => { - const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) - sitemap.should.be.empty - }), - err => { - throw err - } + const sitemap = JSON.parse(fs.readFileSync(sitemapLoc)) + assert.deepEqual(sitemap, []) }) }) }) From 9beea9e2a83c35dd5e997e1dcb6ccd2f8f9cdaa6 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 2 Jun 2025 14:23:14 +0100 Subject: [PATCH 29/45] Improve server shutdown when testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In `lib/server.js`, don’t set a default `process.exitCode` when running tests. So the tests don't report a failure when the server is closed. In `lib/sitemap.js`, the sitemap timeout is not relevant for the test environment. --- index.js | 10 +++------- lib/server.js | 8 +++++++- lib/sitemap.js | 5 +++-- test/server.js | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 8f4480d0..2c472d71 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,7 @@ // Simple file so that if you require this directory // in node it instead requires ./lib/server.coffee // with coffee-script already loaded. -require('coffeescript'); -require('coffeescript/register'); +require('coffeescript') +require('coffeescript/register') -// set a default process exitCode, so we can diferentiate between exiting as -// part of a reload, and an exit after an uncaught error -process.exitCode = 1 - -module.exports = require('./lib/server'); +module.exports = require('./lib/server') diff --git a/lib/server.js b/lib/server.js index b5b7082b..10b03d18 100644 --- a/lib/server.js +++ b/lib/server.js @@ -545,7 +545,7 @@ module.exports = exports = argv => { } async.map(files, doRecyclermap, (e, recyclermap) => { if (e) { - return cb(e) + return res.e(e) } // remove any empty items recyclermap = recyclermap.filter(el => !!el) @@ -910,6 +910,12 @@ module.exports = exports = argv => { }) // #### Start the server #### + // + // set a default process exitCode, so we can diferentiate between exiting as part of a reload, + // and an exit after an uncaught error. + // except when test is set, so the tests don't report a fail when closing the server process. + process.exitCode = argv.test ? 0 : 1 + // Wait to make sure owner is known before listening. securityhandler.retrieveOwner(e => { // Throw if you can't find the initial owner diff --git a/lib/sitemap.js b/lib/sitemap.js index 28c05e3f..d1fa29bb 100644 --- a/lib/sitemap.js +++ b/lib/sitemap.js @@ -32,7 +32,7 @@ module.exports = exports = argv => { let sitemapPageHandler = null // ms since last update we will remove sitemap from memory - const sitemapTimeoutMs = argv.test ? 1000 : 120000 + const sitemapTimeoutMs = 120000 let sitemapTimeoutHandler = null const sitemapLoc = path.join(argv.status, 'sitemap.json') @@ -228,7 +228,8 @@ module.exports = exports = argv => { sitemap.length = 0 clearTimeout(sitemapTimeoutHandler) } - sitemapTimeoutHandler = setTimeout(clearsitemap, sitemapTimeoutMs) + // don't clear sitemap when in test environment. It just delays the tests completing. + if (!argv.test) sitemapTimeoutHandler = setTimeout(clearsitemap, sitemapTimeoutMs) working = false itself.emit('finished') } diff --git a/test/server.js b/test/server.js index 7407dd78..14d9bfd4 100644 --- a/test/server.js +++ b/test/server.js @@ -31,7 +31,7 @@ describe('server', d => { }) }) - after(async () => { + after(() => { runningServer.close() }) From 71cdbac6fb5cf7acbc9ed96d6f2449e5ec877719 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 2 Jun 2025 15:32:58 +0100 Subject: [PATCH 30/45] Improve asynchronous behavior of page tests The changes in this commit focus on improving the asynchronous behavior of the page tests. The main changes are: - Refactor the tests to use `async/await` syntax for better readability and error handling. - Add `return new Promise` blocks to ensure the tests wait for callbacks to complete. --- test/page.js | 87 ++++++++++++++++++++++++++++---------------------- test/server.js | 16 +++++----- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/test/page.js b/test/page.js index 3782cc81..ebca772b 100644 --- a/test/page.js +++ b/test/page.js @@ -19,61 +19,72 @@ console.log('testid', testid) describe('page', () => { describe('#page.put()', () => { - it('should save a page', () => { - page.put('asdf', testpage, e => { - if (e) throw e + it('should save a page', async () => { + return new Promise(resolve => { + page.put('asdf', testpage, e => { + if (e) throw e + resolve() + }) }) }) }) describe('#page.get()', () => { - it('should get a page if it exists', () => { - page.get('asdf', (e, got) => { - if (e) throw e - assert.equal(got.title, 'Asdf') + it('should get a page if it exists', async () => { + return new Promise(resolve => { + page.get('asdf', (e, got) => { + if (e) throw e + assert.equal(got.title, 'Asdf') + resolve() + }) }) }) - it('should copy a page from default if nonexistant in db', () => { - page.get('welcome-visitors', (e, got) => { - if (e) throw e - assert.equal(got.title, 'Welcome Visitors') + it('should copy a page from default if nonexistant in db', async () => { + return new Promise(resolve => { + page.get('welcome-visitors', (e, got) => { + if (e) throw e + assert.equal(got.title, 'Welcome Visitors') + resolve() + }) }) }) // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it('should copy a page from plugins if nonexistant in db', () => { - page.get('recent-changes', (e, got) => { - if (e) throw e - assert.equal(got.title, 'Recent Changes') + it('should copy a page from plugins if nonexistant in db', async () => { + return new Promise(resolve => { + page.get('recent-changes', (e, got) => { + if (e) throw e + assert.equal(got.title, 'Recent Changes') + resolve() + }) }) }) // note: here we assume the wiki-plugin-activity repo has been cloned into an adjacent directory - it('should mark a page from plugins with the plugin name', () => { - page.get('recent-changes', (e, got) => { - if (e) throw e - assert.equal(got.plugin, 'activity') + it('should mark a page from plugins with the plugin name', async () => { + return new Promise(resolve => { + page.get('recent-changes', (e, got) => { + if (e) throw e + assert.equal(got.plugin, 'activity') + resolve() + }) }) }) - it('should create a page if it exists nowhere', () => { - page.get(random(), (e, got) => { - if (e) throw e - assert.equal(got, 'Page not found') + it('should create a page if it exists nowhere', async () => { + return new Promise(resolve => { + page.get(random(), (e, got) => { + if (e) throw e + assert.equal(got, 'Page not found') + resolve() + }) }) }) - it.skip('should eventually write the page to disk', async () => { - const test = () => { - console.log('should write', argv) - fs.readFile(path.join(argv.db, 'asdf'), (err, data) => { - if (err) throw err - const readPage = JSON.parse(data) - page.get('asdf', (e, got) => { - assert.equal(readPage.title, got.title) - }) + it('should eventually write the page to disk', async () => { + return new Promise(resolve => { + page.get('asdf', (e, got) => { + if (e) throw e + const page = JSON.parse(fs.readFileSync(path.join(path.sep, 'tmp', 'sfwtests', testid, 'pages', 'asdf'))) + assert.equal(got.title, page.title) + resolve() }) - } - if (page.isWorking()) { - page.on('finished', () => test()) - } else { - test() - } + }) }) }) }) diff --git a/test/server.js b/test/server.js index 14d9bfd4..8f132f32 100644 --- a/test/server.js +++ b/test/server.js @@ -15,7 +15,7 @@ const argv = require('../lib/defaultargs')({ test: true, }) -describe('server', d => { +describe('server', () => { var app = {} let runningServer = null before(done => { @@ -90,9 +90,9 @@ describe('server', d => { .then( () => { const page = JSON.parse(fs.readFileSync(loc)) - assert(page.story[1].id, 'a3') - assert(page.story[2].id, 'a2') - assert(page.journal[1].type, 'move') + assert.equal(page.story[1].id, 'a3') + assert.equal(page.story[2].id, 'a2') + assert.equal(page.journal[1].type, 'move') }, err => { throw err @@ -137,7 +137,7 @@ describe('server', d => { .expect(200) .then(() => { const page = JSON.parse(fs.readFileSync(loc)) - assert(page.story.length == 4) + assert.equal(page.story.length, 4) assert.equal(page.story[1].id, 'a3') assert.notEqual(page.story[2].id, 'a2') assert.equal(page.story[2].id, 'a5') @@ -180,8 +180,8 @@ describe('server', d => { .expect(500) .then(() => { const page = JSON.parse(fs.readFileSync(loc)) - assert(page.story.length == 4) - assert(page.journal.length == 5) + assert.equal(page.story.length, 4) + assert.equal(page.journal.length, 5) assert.equal(page.story[0].id, 'a1') assert.equal(page.story[3].text, 'this is the fourth paragraph') assert.equal(page.journal[4].type, 'edit') @@ -217,7 +217,7 @@ describe('server', d => { .expect(200) .expect('Content-Type', /json/) .then(res => { - assert(res.body.length == 1) + assert.equal(res.body.length, 1) assert.equal(res.body[0], 'adsf-test-page') }) .catch(err => { From ddef9e69aed2a5442597515d72a751c93e8ebff0 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 2 Jun 2025 15:38:32 +0100 Subject: [PATCH 31/45] replace mocha with node test runner --- package-lock.json | 1213 +-------------------------------------------- package.json | 9 +- 2 files changed, 3 insertions(+), 1219 deletions(-) diff --git a/package-lock.json b/package-lock.json index f6cef3d9..a485cf5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,8 +33,6 @@ "eslint": "^9.27.0", "globals": "^16.1.0", "grunt-git-authors": "^3.2.0", - "grunt-mocha-test": "^0.13.3", - "mocha": "^11.5.0", "prettier": "^3.5.3", "supertest": "^7.0.0", "wiki-client": "^0.31", @@ -42,7 +40,7 @@ "wiki-plugin-video": "^0.4" }, "engines": { - "node": ">=18.x" + "node": ">=20.x" } }, "node_modules/@eslint/js": { @@ -3490,55 +3488,6 @@ "integrity": "sha512-340ZqtqJzWAZtHwaCC2gx4mdQOnkUWAWNDp7y0bCEatdjmgQ4j7b0qQ7qO5WIJWx/luNrKcrYzpKbH3NTR030A==", "dev": true }, - "node_modules/grunt-mocha-test": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.13.3.tgz", - "integrity": "sha512-zQGEsi3d+ViPPi7/4jcj78afKKAKiAA5n61pknQYi25Ugik+aNOuRmiOkmb8mN2CeG8YxT+YdT1H1Q7B/eNkoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hooker": "^0.2.3", - "mkdirp": "^0.5.0" - }, - "engines": { - "node": ">= 0.10.4" - }, - "peerDependencies": { - "mocha": ">=1.20.0" - } - }, - "node_modules/grunt-mocha-test/node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/grunt-mocha-test/node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/grunt-mocha-test/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -4092,1166 +4041,6 @@ "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", "license": "MIT" }, - "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/mocha/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/mocha/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/mocha/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/mocha/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/mocha/node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/mocha/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mocha/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/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" - }, - "node_modules/mocha/node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/mocha/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/mocha/node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mocha/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/mocha/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mocha/node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/mocha/node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", diff --git a/package.json b/package.json index 906a8bf9..4a0e9b90 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "scripts": { "prettier:format": "prettier --write './**/*.js'", "prettier:check": "prettier --check ./**/*.js", - "test": "mocha", + "test": "node --test", "update-authors": "node scripts/update-authors.js" }, "devDependencies": { @@ -60,8 +60,6 @@ "eslint": "^9.27.0", "globals": "^16.1.0", "grunt-git-authors": "^3.2.0", - "grunt-mocha-test": "^0.13.3", - "mocha": "^11.5.0", "prettier": "^3.5.3", "supertest": "^7.0.0", "wiki-client": "^0.31", @@ -69,10 +67,7 @@ "wiki-plugin-video": "^0.4" }, "engines": { - "node": ">=18.x" - }, - "testling": { - "harness": "mocha" + "node": ">=20.x" }, "license": "MIT", "repository": { From 110d03545aac632180ce76f239a6ced07d1c607d Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 19 Jun 2025 08:33:48 +0100 Subject: [PATCH 32/45] update to express5, and devDependencies --- package-lock.json | 573 ++++++++++++++++++++-------------------------- package.json | 4 +- 2 files changed, 256 insertions(+), 321 deletions(-) diff --git a/package-lock.json b/package-lock.json index a485cf5c..5c765391 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "cookie-parser": "^1.4.4", "dompurify": "^3.1.0", "errorhandler": "^1.5.1", - "express": "^4.17.1", + "express": "^5.1.0", "express-hbs": "^2.5.0", "flates": "0.0.5", "glob": "^7.2.3", @@ -35,7 +35,7 @@ "grunt-git-authors": "^3.2.0", "prettier": "^3.5.3", "supertest": "^7.0.0", - "wiki-client": "^0.31", + "wiki-client": "^0.31.1", "wiki-plugin-activity": "0.7", "wiki-plugin-video": "^0.4" }, @@ -44,9 +44,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", - "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -708,19 +708,19 @@ } }, "node_modules/eslint": { - "version": "9.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", - "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.27.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -732,9 +732,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -811,9 +811,9 @@ } }, "node_modules/eslint/node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -826,9 +826,9 @@ } }, "node_modules/eslint/node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -883,19 +883,32 @@ } }, "node_modules/eslint/node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", + "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/eslint/node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", + "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -963,9 +976,9 @@ } }, "node_modules/eslint/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -977,9 +990,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1047,9 +1060,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1165,9 +1178,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -1182,9 +1195,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1195,15 +1208,15 @@ } }, "node_modules/eslint/node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1766,45 +1779,41 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", @@ -1904,9 +1913,9 @@ "optional": true }, "node_modules/express-hbs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "optional": true, "dependencies": { @@ -2580,57 +2589,18 @@ } }, "node_modules/express/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2661,9 +2631,9 @@ } }, "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -2682,27 +2652,38 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/express/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/express/node_modules/depd": { @@ -2714,16 +2695,6 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/express/node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2799,18 +2770,17 @@ } }, "node_modules/express/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" @@ -2826,12 +2796,12 @@ } }, "node_modules/express/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/express/node_modules/function-bind": { @@ -2932,16 +2902,13 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/express/node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, "node_modules/express/node_modules/inherits": { @@ -2959,6 +2926,12 @@ "node": ">= 0.10" } }, + "node_modules/express/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/express/node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2969,75 +2942,57 @@ } }, "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/express/node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/express/node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/express/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/express/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "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/express/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -3067,6 +3022,15 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/express/node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3077,10 +3041,13 @@ } }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/express/node_modules/proxy-addr": { "version": "2.0.7", @@ -3096,12 +3063,12 @@ } }, "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3119,19 +3086,20 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/express/node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 18" } }, "node_modules/express/node_modules/safe-buffer": { @@ -3154,64 +3122,41 @@ ], "license": "MIT" }, - "node_modules/express/node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/express/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": ">= 18" } }, - "node_modules/express/node_modules/send/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/express/node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/express/node_modules/setprototypeof": { @@ -3293,9 +3238,9 @@ } }, "node_modules/express/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3311,36 +3256,19 @@ } }, "node_modules/express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/express/node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/express/node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3350,6 +3278,12 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/flates": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/flates/-/flates-0.0.5.tgz", @@ -3386,9 +3320,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -3660,12 +3594,12 @@ } }, "node_modules/jsdom/node_modules/cssstyle": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", - "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.4.0.tgz", + "integrity": "sha512-W0Y2HOXlPkb2yaKrCVRjinYKciu/qSLEmK0K9mcfDei3zwlnHFEHAs/Du3cIRwPqY+J4JsiBzUjoHyc8RsJ03A==", "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^3.1.2", + "@asamuzakjp/css-color": "^3.2.0", "rrweb-cssom": "^0.8.0" }, "engines": { @@ -3709,9 +3643,9 @@ "license": "MIT" }, "node_modules/jsdom/node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4456,15 +4390,16 @@ "license": "MIT" }, "node_modules/supertest/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4800,9 +4735,9 @@ "license": "ISC" }, "node_modules/wiki-client": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/wiki-client/-/wiki-client-0.31.1.tgz", - "integrity": "sha512-xeLC/ya0GxiMaCPHavW+FPrFkN4kl8hmJZpvCGNNla6o4O1t+RU+mCmhjraGbkSjjAOyoivToylT9fOqwKTYuw==", + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/wiki-client/-/wiki-client-0.31.2.tgz", + "integrity": "sha512-dwHpfarpn25xlK86jNrXMqZJ5GrdGR4T6xL+6PPagx0+Wynf7P51ZXvJolvx4oA5y0JLpiXI7SBnX6iVEztKtg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4a0e9b90..fbe41bef 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "cookie-parser": "^1.4.4", "dompurify": "^3.1.0", "errorhandler": "^1.5.1", - "express": "^4.17.1", + "express": "^5.1.0", "express-hbs": "^2.5.0", "flates": "0.0.5", "glob": "^7.2.3", @@ -62,7 +62,7 @@ "grunt-git-authors": "^3.2.0", "prettier": "^3.5.3", "supertest": "^7.0.0", - "wiki-client": "^0.31", + "wiki-client": "^0.31.1", "wiki-plugin-activity": "0.7", "wiki-plugin-video": "^0.4" }, From 104ccde041ad8fd513c895e759dda7dac64aca4f Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 19 Jun 2025 10:01:25 +0100 Subject: [PATCH 33/45] Only express 4 -> 5 change needed from run test. --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index 10b03d18..49c6bf75 100644 --- a/lib/server.js +++ b/lib/server.js @@ -717,7 +717,7 @@ module.exports = exports = argv => { // ##### Proxy routes ##### - app.get('/proxy/*', authorized, (req, res) => { + app.get('/proxy/*splat', authorized, (req, res) => { const pathParts = req.originalUrl.split('/') const remoteHost = pathParts[2] pathParts.splice(0, 3) From 5e6a5a2f5ab2571fe3f9fa3ec5123661d9cb2f72 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Thu, 19 Jun 2025 10:34:24 +0100 Subject: [PATCH 34/45] Allow send of dotfiles --- lib/server.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/server.js b/lib/server.js index 49c6bf75..2bd1aa87 100644 --- a/lib/server.js +++ b/lib/server.js @@ -444,7 +444,7 @@ module.exports = exports = argv => { // If themes doesn't exist send 404 and let the client // deal with it. app.get(/^\/theme\/(\w+\.\w+)$/, cors, (req, res) => { - res.sendFile(path.join(argv.status, 'theme', req.params[0]), e => { + res.sendFile(path.join(argv.status, 'theme', req.params[0]), { dotfiles: 'allow' }, e => { if (e) { // swallow the error if the theme does not exist... if (req.path === '/theme/style.css') { @@ -464,7 +464,7 @@ module.exports = exports = argv => { app.get('/favicon.png', cors, (req, res) => { fs.exists(favLoc, exists => { if (exists) { - res.sendFile(favLoc) + res.sendFile(favLoc, { dotfiles: 'allow' }) } else { res.sendFile(defaultFavLoc) } @@ -519,7 +519,7 @@ module.exports = exports = argv => { // negative space outward pointing arrows nicely indicates that items can be removed const recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png') app.get('/recycler/favicon.png', authorized, (req, res) => { - res.sendFile(recyclerFavLoc) + res.sendFile(recyclerFavLoc, { dotfiles: 'allow' }) }) // Send an array of pages currently in the recycler via json @@ -604,7 +604,7 @@ module.exports = exports = argv => { app.get('/system/sitemap.json', cors, (req, res) => { fs.exists(sitemapLoc, exists => { if (exists) { - res.sendFile(sitemapLoc) + res.sendFile(sitemapLoc, { dotfiles: 'allow' }) } else { // only createSitemap if we are not already creating one if (!sitemaphandler.isWorking()) { @@ -612,7 +612,7 @@ module.exports = exports = argv => { } // wait for the sitemap file to be written, before sending sitemaphandler.once('finished', () => { - res.sendFile(sitemapLoc) + res.sendFile(sitemapLoc, { dotfiles: 'allow' }) }) } }) @@ -622,13 +622,13 @@ module.exports = exports = argv => { app.get('/sitemap.xml', cors, (req, res) => { fs.exists(sitemapLoc, exists => { if (exists) { - res.sendFile(xmlSitemapLoc) + res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' }) } else { if (!sitemaphandler.isWorking()) { sitemaphandler.createSitemap(pagehandler) } sitemaphandler.once('finished', () => { - res.sendFile(xmlSitemapLoc) + res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' }) }) } }) @@ -638,14 +638,14 @@ module.exports = exports = argv => { app.get('/system/site-index.json', cors, (req, res) => { fs.exists(searchIndexLoc, exists => { if (exists) { - res.sendFile(searchIndexLoc) + res.sendFile(searchIndexLoc, { dotfiles: 'allow' }) } else { // only create index if we are not already creating one if (!searchhandler.isWorking()) { searchhandler.createIndex(pagehandler) } searchhandler.once('indexed', () => { - res.sendFile(searchIndexLoc) + res.sendFile(searchIndexLoc, { dotfiles: 'allow' }) }) } }) From 60698754f7095e7b8b4ed4523bab2f34a2014c45 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Tue, 24 Jun 2025 10:55:18 +0100 Subject: [PATCH 35/45] replace CoffeeScript template literals to JavaScript --- lib/page.js | 16 ++++++++-------- lib/search.js | 2 +- lib/server.js | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/page.js b/lib/page.js index 68e55187..c077f281 100644 --- a/lib/page.js +++ b/lib/page.js @@ -51,9 +51,9 @@ module.exports = exports = argv => { if (exists) { fs.rename(loc, recyclePage, err => { if (err) { - console.log('ERROR: moving problem page #{loc} to recycler', err) + console.log(`ERROR: moving problem page ${loc} to recycler`, err) } else { - console.log('ERROR: problem page #{loc} moved to recycler') + console.log(`ERROR: problem page ${loc} moved to recycler`) } }) } else { @@ -63,9 +63,9 @@ module.exports = exports = argv => { } else { fs.rename(loc, recyclePage, err => { if (err) { - console.log('ERROR: moving problem page #{loc} to recycler', err) + console.log(`ERROR: moving problem page ${loc} to recycler`, err) } else { - console.log('ERROR: problem page #{loc} moved to recycler') + console.log(`ERROR: problem page ${loc} moved to recycler`) } }) } @@ -276,7 +276,7 @@ module.exports = exports = argv => { if (exists) { fs.writeFile(loc, page, err => { if (err) { - console.log('ERROR: write file #{loc} ', err) + console.log(`ERROR: write file ${loc} `, err) } cb(err) }) @@ -285,7 +285,7 @@ module.exports = exports = argv => { if (err) cb(err) fs.writeFile(loc, page, err => { if (err) { - console.log('ERROR: write file #{loc} ', err) + console.log(`ERROR: write file ${loc} `, err) } cb(err) }) @@ -294,7 +294,7 @@ module.exports = exports = argv => { }) break default: - console.log('pagehandler: unrecognized action #{action}') + console.log(`pagehandler: unrecognized action ${action}`) } } @@ -407,7 +407,7 @@ module.exports = exports = argv => { try { pageLinksMap = page.story.reduce(extractPageLinks, new Map()) } catch (err) { - console.log('METADATA *** #{wikiName} reduce to extract links on #{file} failed', err.message) + console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message) pageLinksMap = [] } // diff --git a/lib/search.js b/lib/search.js index 90e4be40..0ebbaca0 100644 --- a/lib/search.js +++ b/lib/search.js @@ -250,7 +250,7 @@ module.exports = exports = argv => { // we save the pagehandler, so we can recreate the site index if it is removed searchPageHandler = searchPageHandler ?? pagehandler - //timeLabel = "SITE INDEX #{wikiName} : Created" + //timeLabel = `SITE INDEX ${wikiName} : Created` //console.time timeLabel pagehandler.slugs((e, slugs) => { diff --git a/lib/server.js b/lib/server.js index 10b03d18..ce7014c8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -163,7 +163,7 @@ module.exports = exports = argv => { const remoteGet = (remote, slug, cb) => { // assume http, as we know no better at this point and we need to specify a protocol. - const remoteURL = new URL('http://#{remote}/#{slug}.json').toString() + const remoteURL = new URL(`http://${remote}/${slug}.json`).toString() // set a two second timeout fetch(remoteURL, { signal: AbortSignal.timeout(2000) }) .then(res => { @@ -508,7 +508,7 @@ module.exports = exports = argv => { // Redirect remote favicons to the server they are needed from. app.get(/^\/remote\/([a-zA-Z0-9:.-]+\/favicon.png)$/, (req, res) => { - const remotefav = 'http://#{req.params[0]}' + const remotefav = `http://${req.params[0]}` res.redirect(remotefav) }) From 3e9e29e3c1bfc476798775992f7a66d1d4936917 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Tue, 24 Jun 2025 10:59:01 +0100 Subject: [PATCH 36/45] replace node-fetch with native fetch api --- lib/server.js | 5 ++- package-lock.json | 92 ----------------------------------------------- package.json | 1 - 3 files changed, 2 insertions(+), 96 deletions(-) diff --git a/lib/server.js b/lib/server.js index ce7014c8..a275eeb4 100644 --- a/lib/server.js +++ b/lib/server.js @@ -38,8 +38,7 @@ const { JSDOM } = require('jsdom') const window = new JSDOM('').window const DOMPurify = createDOMPurify(window) -// node-fetch is now ESM only -const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args)) +// Using native fetch API (available in Node.js 18+) // Express 4 middleware const logger = require('morgan') @@ -731,7 +730,7 @@ module.exports = exports = argv => { requestURL.endsWith('.jpg') || pathParts[0] === 'plugin' ) { - fetch(requestURL, { timeout: 2000 }) + fetch(requestURL, { signal: AbortSignal.timeout(2000) }) .then(async fetchRes => { if (fetchRes.ok) { res.set('content-type', fetchRes.headers.get('content-type')) diff --git a/package-lock.json b/package-lock.json index a485cf5c..dedd2df2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,6 @@ "method-override": "^3.0.0", "minisearch": "^7.1.0", "morgan": "^1.9.1", - "node-fetch": "^3.3.2", "write-file-atomic": "^6.0.0", "xml2js": "^0.6.2" }, @@ -4126,97 +4125,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/node-fetch/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/node-fetch/node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/node-fetch/node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/node-fetch/node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch/node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/prettier": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", diff --git a/package.json b/package.json index 4a0e9b90..2185113e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "method-override": "^3.0.0", "minisearch": "^7.1.0", "morgan": "^1.9.1", - "node-fetch": "^3.3.2", "write-file-atomic": "^6.0.0", "xml2js": "^0.6.2" }, From 16cdfc178dc89d48d0f4ae1b409f00535b4e2fda Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Fri, 27 Jun 2025 17:27:21 +0100 Subject: [PATCH 37/45] Cache plugin page locations during startup Remove glob-based plugin page search in favor of a pre-populated Map of plugin page locations built during initialization --- lib/page.js | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/page.js b/lib/page.js index c077f281..39619ec9 100644 --- a/lib/page.js +++ b/lib/page.js @@ -13,7 +13,6 @@ const fs = require('fs') const path = require('path') const events = require('events') -const glob = require('glob') const async = require('async') @@ -35,6 +34,25 @@ module.exports = exports = argv => { if (e) throw e }) + // create a list of plugin pages. + const pluginPages = new Map() + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .forEach(plugin => { + const pagesPath = path.join( + path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })), + 'pages', + ) + fs.readdir(pagesPath, { withFileTypes: true }, (err, entries) => { + if (err) return + entries.forEach(entry => { + if (entry.isFile() && !pluginPages.has(entry.name)) { + pluginPages.set(entry.name, { pluginName: plugin, pluginPath: entry.parentPath }) + } + }) + }) + }) + // #### Private utility methods. #### const load_parse = (loc, cb, annotations = {}) => { let page @@ -238,33 +256,12 @@ module.exports = exports = argv => { if (defloc) { load_parse(defloc, cb) } else { - glob('wiki-plugin-*/pages', { cwd: argv.packageDir }, (e, plugins) => { - if (e) return cb(e) - - // if no plugins found - if (plugins.length === 0) { - cb(null, 'Page not found', 404) - } - - let count = plugins.length - const giveUp = () => { - count -= 1 - if (count === 0) { - cb(null, 'Page not found', 404) - } - } - plugins.forEach(plugin => { - const pluginName = plugin.slice(12, -6) - const pluginloc = path.join(argv.packageDir, plugin, file) - fs.exists(pluginloc, exists => { - if (exists) { - load_parse(pluginloc, cb, { plugin: pluginName }) - } else { - giveUp() - } - }) - }) - }) + if (pluginPages.has(file)) { + const { pluginName, pluginPath } = pluginPages.get(file) + load_parse(path.join(pluginPath, file), cb, { plugin: pluginName }) + } else { + cb(null, 'Page not found', 404) + } } }) } From 0fc3d7406a214547d314c2d27e678d92fb3ad5c2 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Fri, 27 Jun 2025 17:29:02 +0100 Subject: [PATCH 38/45] Replace glob-based plugin lookup with package.json scanning --- lib/plugins.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index b6c40b4f..457d6456 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -9,7 +9,6 @@ // support server-side plugins const fs = require('node:fs') -const glob = require('glob') const { pathToFileURL } = require('node:url') // forward = require './forward' @@ -43,11 +42,12 @@ module.exports = exports = argv => { // emitter = new events.EventEmitter() // forward.init params.app, emitter // params.emitter = emitter - glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, plugins) => { - plugins.forEach(plugin => { + + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .forEach(plugin => { startServer(params, plugin) }) - }) } return { startServers } From 6d311053c869fa906696640e03a50ba9c858e8fd Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 30 Jun 2025 19:13:44 +0100 Subject: [PATCH 39/45] Replace glob with package.json dependencies lookup --- lib/server.js | 94 ++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/lib/server.js b/lib/server.js index d513908e..43ebeb06 100644 --- a/lib/server.js +++ b/lib/server.js @@ -28,7 +28,6 @@ const { pipeline } = require('node:stream/promises') // From npm const express = require('express') const hbs = require('express-hbs') -const glob = require('glob') const async = require('async') const f = require('flates') @@ -270,13 +269,16 @@ module.exports = exports = argv => { app.use('/assets', cors, express.static(argv.assets)) // Add static routes to the plugins client. - glob('wiki-plugin-*/client', { cwd: argv.packageDir }, (e, plugins) => { - plugins.map(plugin => { - const pluginName = plugin.slice(12, -7) - const pluginPath = '/plugins/' + pluginName - app.use(pluginPath, cors, express.static(path.join(argv.packageDir, plugin), staticPathOptions)) + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .forEach(plugin => { + const clientPath = path.join( + path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })), + 'client', + ) + const pluginPath = '/plugins/' + plugin.slice(12) + app.use(pluginPath, cors, express.static(clientPath, staticPathOptions)) }) - }) // Add static routes to the security client. if (argv.security != './security') { @@ -386,32 +388,17 @@ module.exports = exports = argv => { app.get('/system/factories.json', (req, res) => { res.status(200) res.header('Content-Type', 'application/json') - // Plugins are located in packages in argv.packageDir, with package names of the form wiki-plugin-* - glob(path.join(argv.packageDir, 'wiki-plugin-*', 'factory.json'), (e, files) => { - if (e) { - return res.e(e) - } - - const doFactories = (file, cb) => { - fs.readFile(file, (err, data) => { - if (err) { - return cb() - } - try { - const factory = JSON.parse(data) - cb(null, factory) - } catch (err) { - return cb() - } - }) - } - async.map(files, doFactories, (e, factories) => { - if (e) { - res.e(e) + const factories = [] + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .forEach(plugin => { + try { + factories.push(require.main.require(`${plugin}/factory`)) + } catch { + // do nothing if plugin doesn't have a factory category. } - res.end(JSON.stringify(factories)) }) - }) + res.end(JSON.stringify(factories)) }) // ###### Json Routes ###### @@ -589,14 +576,14 @@ module.exports = exports = argv => { // Returns a list of installed plugins. (does this get called anymore!) app.get('/system/plugins.json', cors, (req, res) => { - glob('wiki-plugin-*', { cwd: argv.packageDir }, (e, files) => { - if (e) { - return res.e(e) - } - // extract the plugin name from the name of the directory it's installed in - files = files.map(file => file.slice(12)) - res.send(files) - }) + try { + const pluginNames = Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .map(name => name.slice(12)) + res.send(pluginNames) + } catch (e) { + return res.e(e) + } }) //{ const sitemapLoc = path.join(argv.status, 'sitemap.json') @@ -691,27 +678,26 @@ module.exports = exports = argv => { app.get('/system/version.json', admin, (req, res) => { const versions = {} - const wikiModule = module.parent.parent.parent + const wikiModule = require.main versions[wikiModule.require('./package').name] = wikiModule.require('./package').version versions[wikiModule.require('wiki-server/package').name] = wikiModule.require('wiki-server/package').version versions[wikiModule.require('wiki-client/package').name] = wikiModule.require('wiki-client/package').version + versions['security'] = {} - versions['plugins'] = {} + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-security')) + .forEach(key => { + versions.security[key] = wikiModule.require(`${key}/package`).version + }) - glob('+(wiki-security-*|wiki-plugin-*)', { cwd: argv.packageDir }, (e, plugins) => { - plugins.map(plugin => { - if (plugin.includes('wiki-security')) { - versions.security[wikiModule.require(plugin + '/package').name] = wikiModule.require( - plugin + '/package', - ).version - } else { - versions.plugins[wikiModule.require(plugin + '/package').name] = wikiModule.require( - plugin + '/package', - ).version - } - res.json(versions) + versions['plugins'] = {} + Object.keys(require.main.require('./package').dependencies) + .filter(depend => depend.startsWith('wiki-plugin')) + .forEach(key => { + versions.plugins[key] = wikiModule.require(`${key}/package`).version }) - }) + + res.json(versions) }) // ##### Proxy routes ##### From aed7cf9335a9a3d7f94a2ccb42d93f9256106f40 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 30 Jun 2025 19:14:15 +0100 Subject: [PATCH 40/45] and remove glob package --- package-lock.json | 103 ---------------------------------------------- package.json | 1 - 2 files changed, 104 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c949672..6a206b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "express": "^5.1.0", "express-hbs": "^2.5.0", "flates": "0.0.5", - "glob": "^7.2.3", "jsdom": "^26.0.0", "method-override": "^3.0.0", "minisearch": "^7.1.0", @@ -3291,108 +3290,6 @@ "node": ">0.6" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/glob/node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/glob/node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/glob/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob/node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/glob/node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob/node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/globals": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", diff --git a/package.json b/package.json index 54713dab..bc0a6fa4 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "express": "^5.1.0", "express-hbs": "^2.5.0", "flates": "0.0.5", - "glob": "^7.2.3", "jsdom": "^26.0.0", "method-override": "^3.0.0", "minisearch": "^7.1.0", From f8da5db6cd1f6da02a698973cbad183e887b2b31 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 30 Jun 2025 19:51:01 +0100 Subject: [PATCH 41/45] Add test package.json, no need for a mock. --- test/package.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/package.json diff --git a/test/package.json b/test/package.json new file mode 100644 index 00000000..c6dcf84e --- /dev/null +++ b/test/package.json @@ -0,0 +1,8 @@ +{ + "comment": "This exists so that the tests have a package to read", + "name": "wiki-server", + "dependencies": { + "wiki-plugin-activity": "0.1", + "wiki-plugin-video": "0.1" + } +} From fd1f51cdfc39691f630e6ae13c83dede77c50838 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 30 Jun 2025 19:51:39 +0100 Subject: [PATCH 42/45] Remember to remove `wiki-pligin-` from name --- lib/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/page.js b/lib/page.js index 39619ec9..b57cedc1 100644 --- a/lib/page.js +++ b/lib/page.js @@ -258,7 +258,7 @@ module.exports = exports = argv => { } else { if (pluginPages.has(file)) { const { pluginName, pluginPath } = pluginPages.get(file) - load_parse(path.join(pluginPath, file), cb, { plugin: pluginName }) + load_parse(path.join(pluginPath, file), cb, { plugin: pluginName.slice(12) }) } else { cb(null, 'Page not found', 404) } From 304e230fb9c8a212d4d208045f0a04c5ff530236 Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Tue, 1 Jul 2025 10:11:07 +0100 Subject: [PATCH 43/45] replace deprecated fs.exists with fs.access --- lib/page.js | 54 ++++++++++++++++++++----------------------------- lib/plugins.js | 4 ++-- lib/search.js | 8 ++++---- lib/security.js | 4 ++-- lib/server.js | 20 +++++++++--------- lib/sitemap.js | 12 +++++------ 6 files changed, 46 insertions(+), 56 deletions(-) diff --git a/lib/page.js b/lib/page.js index c077f281..67977848 100644 --- a/lib/page.js +++ b/lib/page.js @@ -46,9 +46,8 @@ module.exports = exports = argv => { const errorPage = path.basename(loc) const errorPagePath = path.dirname(loc) const recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage) - // TODO: fs.exists depreciated - fs.exists(path.dirname(recyclePage), exists => { - if (exists) { + fs.access(path.dirname(recyclePage), fs.constants.F_OK, err => { + if (!err) { fs.rename(loc, recyclePage, err => { if (err) { console.log(`ERROR: moving problem page ${loc} to recycler`, err) @@ -104,9 +103,8 @@ module.exports = exports = argv => { const tryDefaults = (file, cb) => { const lastDefault = cb => { const defloc = path.join(argv.root, 'default-data', 'pages', file) - // TODO: fs.exists depreciated - fs.exists(defloc, exists => { - if (exists) { + fs.access(defloc, fs.constants.F_OK, err => { + if (!err) { cb(defloc) } else { cb(null) @@ -115,10 +113,8 @@ module.exports = exports = argv => { } if (argv.defaults) { const defloc = path.join(argv.data, '..', argv.defaults, 'pages', file) - console.log('firstDefault', defloc) - // TODO: fs.exists depreciated - fs.exists(defloc, exists => { - if (exists) { + fs.access(defloc, fs.constants.F_OK, err => { + if (!err) { cb(defloc) } else { lastDefault(cb) @@ -138,22 +134,19 @@ module.exports = exports = argv => { case 'delete': if (file.startsWith('recycler/')) { // delete from recycler - // TODO: fs.exists depreciated - fs.exists(loc, exists => { - if (exists) + fs.access(loc, fs.constants.F_OK, err => { + if (!err) fs.unlink(loc, err => { cb(err) }) }) } else { // move page to recycler - // TODO: fs.exists depreciated - fs.exists(loc, exists => { - if (exists) { + fs.access(loc, fs.constants.F_OK, err => { + if (!err) { const recycleLoc = path.join(argv.recycler, file) - // TODO: fs.exists depreciated - fs.exists(path.dirname(recycleLoc), exists => { - if (exists) { + fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => { + if (!err) { fs.rename(loc, recycleLoc, err => { cb(err) }) @@ -203,13 +196,11 @@ module.exports = exports = argv => { return } - // TODO: fs.exists depreciated - fs.exists(loc, exists => { - if (exists) { + fs.access(loc, fs.constants.F_OK, err => { + if (!err) { const recycleLoc = path.join(argv.recycler, file) - // TODO: fs.exists depreciated - fs.exists(path.dirname(recycleLoc), exists => { - if (exists) { + fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => { + if (!err) { copyFile(loc, recycleLoc, err => { cb(err) }) @@ -229,9 +220,8 @@ module.exports = exports = argv => { break } case 'get': - // TODO: fs.exists depreciated - fs.exists(loc, exists => { - if (exists) { + fs.access(loc, fs.constants.F_OK, err => { + if (!err) { load_parse(loc, cb, { plugin: undefined }) } else { tryDefaults(file, defloc => { @@ -256,8 +246,8 @@ module.exports = exports = argv => { plugins.forEach(plugin => { const pluginName = plugin.slice(12, -6) const pluginloc = path.join(argv.packageDir, plugin, file) - fs.exists(pluginloc, exists => { - if (exists) { + fs.access(pluginloc, fs.constants.F_OK, err => { + if (!err) { load_parse(pluginloc, cb, { plugin: pluginName }) } else { giveUp() @@ -272,8 +262,8 @@ module.exports = exports = argv => { break case 'put': page = JSON.stringify(page, null, 2) - fs.exists(path.dirname(loc), exists => { - if (exists) { + fs.access(path.dirname(loc), fs.constants.F_OK, err => { + if (!err) { fs.writeFile(loc, page, err => { if (err) { console.log(`ERROR: write file ${loc} `, err) diff --git a/lib/plugins.js b/lib/plugins.js index b6c40b4f..294e3ef0 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -24,8 +24,8 @@ module.exports = exports = argv => { const startServer = (params, plugin) => { const server = `${argv.packageDir}/${plugin}/server/server.js` - fs.exists(server, exists => { - if (exists) { + fs.access(server, fs.constants.F_OK, err => { + if (!err) { console.log('starting plugin', plugin) import(pathToFileURL(server)) .then(exported => { diff --git a/lib/search.js b/lib/search.js index 0ebbaca0..2c5d167b 100644 --- a/lib/search.js +++ b/lib/search.js @@ -85,8 +85,8 @@ module.exports = exports = argv => { const searchSave = (siteIndex, cb) => { // save index to file - fs.exists(argv.status, exists => { - if (exists) { + fs.access(argv.status, fs.constants.F_OK, err => { + if (!err) { writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => { if (e) return cb(e) touch(indexUpdateFlag, () => { @@ -108,8 +108,8 @@ module.exports = exports = argv => { const searchRestore = cb => { // restore index, or create if it doesn't already exist - fs.exists(siteIndexLoc, exists => { - if (exists) { + fs.access(siteIndexLoc, fs.constants.F_OK, err => { + if (!err) { fs.readFile(siteIndexLoc, (err, data) => { if (err) return cb(err) try { diff --git a/lib/security.js b/lib/security.js index 7efe0a8d..e79228aa 100644 --- a/lib/security.js +++ b/lib/security.js @@ -41,8 +41,8 @@ module.exports = exports = (log, loga, argv) => { // Retrieve owner infomation from identity file in status directory security.retrieveOwner = cb => { - fs.exists(idFile, exists => { - if (exists) { + fs.access(idFile, fs.constants.F_OK, err => { + if (!err) { fs.readFile(idFile, (err, data) => { if (err) return cb(err) owner += data diff --git a/lib/server.js b/lib/server.js index d513908e..ce8b7b4f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -461,8 +461,8 @@ module.exports = exports = argv => { const favLoc = path.join(argv.status, 'favicon.png') const defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png') app.get('/favicon.png', cors, (req, res) => { - fs.exists(favLoc, exists => { - if (exists) { + fs.access(favLoc, fs.constants.F_OK, err => { + if (!err) { res.sendFile(favLoc, { dotfiles: 'allow' }) } else { res.sendFile(defaultFavLoc) @@ -484,8 +484,8 @@ module.exports = exports = argv => { app.post('/favicon.png', authorized, (req, res) => { const favicon = req.body.image.replace(/^data:image\/png;base64,/, '') const buf = new Buffer(favicon, 'base64') - fs.exists(argv.status, exists => { - if (exists) { + fs.access(argv.status, fs.constants.F_OK, err => { + if (!err) { fs.writeFile(favLoc, buf, e => { if (e) { return res.e(e) @@ -601,8 +601,8 @@ module.exports = exports = argv => { //{ const sitemapLoc = path.join(argv.status, 'sitemap.json') app.get('/system/sitemap.json', cors, (req, res) => { - fs.exists(sitemapLoc, exists => { - if (exists) { + fs.access(sitemapLoc, fs.constants.F_OK, err => { + if (!err) { res.sendFile(sitemapLoc, { dotfiles: 'allow' }) } else { // only createSitemap if we are not already creating one @@ -619,8 +619,8 @@ module.exports = exports = argv => { const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml') app.get('/sitemap.xml', cors, (req, res) => { - fs.exists(sitemapLoc, exists => { - if (exists) { + fs.access(sitemapLoc, fs.constants.F_OK, err => { + if (!err) { res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' }) } else { if (!sitemaphandler.isWorking()) { @@ -635,8 +635,8 @@ module.exports = exports = argv => { const searchIndexLoc = path.join(argv.status, 'site-index.json') app.get('/system/site-index.json', cors, (req, res) => { - fs.exists(searchIndexLoc, exists => { - if (exists) { + fs.access(searchIndexLoc, fs.constants.F_OK, err => { + if (!err) { res.sendFile(searchIndexLoc, { dotfiles: 'allow' }) } else { // only create index if we are not already creating one diff --git a/lib/sitemap.js b/lib/sitemap.js index d1fa29bb..feb758e6 100644 --- a/lib/sitemap.js +++ b/lib/sitemap.js @@ -119,8 +119,8 @@ module.exports = exports = argv => { } const sitemapSave = (sitemap, cb) => { - fs.exists(argv.status, exists => { - if (exists) { + fs.access(argv.status, fs.constants.F_OK, err => { + if (!err) { writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => { if (e) return cb(e) cb() @@ -136,8 +136,8 @@ module.exports = exports = argv => { } const sitemapRestore = cb => { - fs.exists(sitemapLoc, exists => { - if (exists) { + fs.access(sitemapLoc, fs.constants.F_OK, err => { + if (!err) { fs.readFile(sitemapLoc, (err, data) => { if (err) return cb(err) try { @@ -172,8 +172,8 @@ module.exports = exports = argv => { const xmlmap = { urlset: { $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' }, url: xmlmapPages } } const builder = new xml2js.Builder() const xml = builder.buildObject(xmlmap) - fs.exists(argv.status, exists => { - if (exists) { + fs.access(argv.status, fs.constants.F_OK, err => { + if (!err) { writeFileAtomic(xmlSitemapLoc, xml, e => { if (e) return cb(e) cb() From 5665d529d6faa8a43b3e7720bcaba92af0815c4a Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Tue, 1 Jul 2025 10:13:54 +0100 Subject: [PATCH 44/45] replace deprecated use of new Buffer() --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index ce8b7b4f..dc529b44 100644 --- a/lib/server.js +++ b/lib/server.js @@ -483,7 +483,7 @@ module.exports = exports = argv => { // save it. app.post('/favicon.png', authorized, (req, res) => { const favicon = req.body.image.replace(/^data:image\/png;base64,/, '') - const buf = new Buffer(favicon, 'base64') + const buf = Buffer.from(favicon, 'base64') fs.access(argv.status, fs.constants.F_OK, err => { if (!err) { fs.writeFile(favLoc, buf, e => { From 67cd70b01604139ec579da390669451ba397ab7b Mon Sep 17 00:00:00 2001 From: Paul Rodwell Date: Mon, 7 Jul 2025 15:08:18 +0100 Subject: [PATCH 45/45] Replace async.map lib/page.js, lib/server.js updated to use modern Promise-based async/await pattern instead of the callback-based async.map. Async dependency removed from package.json. --- lib/page.js | 69 ++++++++++++++++++------------------- lib/search.js | 8 +++-- lib/server.js | 86 ++++++++++++++++++++++++----------------------- package-lock.json | 2 +- package.json | 1 - 5 files changed, 86 insertions(+), 80 deletions(-) diff --git a/lib/page.js b/lib/page.js index 8d9c5ca3..da44f7eb 100644 --- a/lib/page.js +++ b/lib/page.js @@ -14,8 +14,6 @@ const fs = require('fs') const path = require('path') const events = require('events') -const async = require('async') - const random_id = require('./random_id') const synopsis = require('wiki-client/lib/synopsis') @@ -379,43 +377,46 @@ module.exports = exports = argv => { } return collaborativeLinks } + fs.readdir(argv.db, (e, files) => { if (e) return cb(e) - // used to make sure all of the files are read - // and processesed in the site map before responding - const doSitemap = (file, cb) => { - itself.get(file, (e, page, status) => { - if (file.match(/^\./)) return cb() - if (e || status === 404) { - console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status) - return cb() // Ignore errors in the pagehandler get. - } - let pageLinksMap - try { - pageLinksMap = page.story.reduce(extractPageLinks, new Map()) - } catch (err) { - console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message) - pageLinksMap = [] - } - // - const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined - - cb(null, { - slug: file, - title: page.title, - date: editDate(page.journal), - synopsis: synopsis(page), - links: pageLinks, + const doSitemap = async file => { + return new Promise(resolve => { + itself.get(file, (e, page, status) => { + if (file.match(/^\./)) return resolve(null) + if (e || status === 404) { + console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status) + return resolve(null) // Ignore errors in the pagehandler get. + } + let pageLinksMap + try { + pageLinksMap = page.story.reduce(extractPageLinks, new Map()) + } catch (err) { + console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message) + pageLinksMap = [] + } + // + const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined + + resolve({ + slug: file, + title: page.title, + date: editDate(page.journal), + synopsis: synopsis(page), + links: pageLinks, + }) }) }) } - async.map(files, doSitemap, (e, sitemap) => { - if (e) return cb(e) - cb( - null, - sitemap.filter(item => item != null), - ) - }) + + Promise.all(files.map(doSitemap)) + .then(sitemap => { + cb( + null, + sitemap.filter(item => item != null), + ) + }) + .catch(e => cb(e)) }) } diff --git a/lib/search.js b/lib/search.js index 2c5d167b..f318f74a 100644 --- a/lib/search.js +++ b/lib/search.js @@ -169,8 +169,12 @@ module.exports = exports = argv => { .replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ') .replace(/<(?:[^>])+>/g, ' ') .replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, match => { - const myUrl = url.parse(match) - return myUrl.hostname + ' ' + myUrl.pathname + try { + const myUrl = new URL(match) + return myUrl.hostname + } catch { + return ' ' + } }) .replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ') .replace(/[\p{White_Space}\n\t]+/gu, ' ') diff --git a/lib/server.js b/lib/server.js index 0b068b76..3ea16e98 100644 --- a/lib/server.js +++ b/lib/server.js @@ -28,7 +28,6 @@ const { pipeline } = require('node:stream/promises') // From npm const express = require('express') const hbs = require('express-hbs') -const async = require('async') const f = require('flates') const createDOMPurify = require('dompurify') @@ -511,32 +510,34 @@ module.exports = exports = argv => { // Send an array of pages currently in the recycler via json app.get('/recycler/system/slugs.json', authorized, (req, res) => { fs.readdir(argv.recycler, (e, files) => { - const doRecyclermap = (file, cb) => { - const recycleFile = 'recycler/' + file - pagehandler.get(recycleFile, (e, page, status) => { - if (e || status === 404) { - console.log('Problem building recycler map:', file, 'e: ', e) - // this will leave an undefined/empty item in the array, which we will filter out later - return cb() - } - cb(null, { - slug: file, - title: page.title, + if (e) { + return res.e(e) + } + const doRecyclermap = async file => { + return new Promise(resolve => { + const recycleFile = 'recycler/' + file + pagehandler.get(recycleFile, (e, page, status) => { + if (e || status === 404) { + console.log('Problem building recycler map:', file, 'e: ', e) + // this will leave an undefined/empty item in the array, which we will filter out later + return resolve(null) + } + resolve({ + slug: file, + title: page.title, + }) }) }) } - if (e) { - return res.e(e) - } - async.map(files, doRecyclermap, (e, recyclermap) => { - if (e) { - return res.e(e) - } - // remove any empty items - recyclermap = recyclermap.filter(el => !!el) - res.send(recyclermap) - }) + Promise.all(files.map(doRecyclermap)) + .then(recyclermap => { + recyclermap = recyclermap.filter(el => !!el) + res.send(recyclermap) + }) + .catch(error => { + res.e(error) + }) }) }) @@ -642,28 +643,29 @@ module.exports = exports = argv => { if (e) { return res.e(e) } - async.map( - sitemap, - (stub, done) => { + const pagePromises = sitemap.map(stub => { + return new Promise((resolve, reject) => { pagehandler.get(stub.slug, (error, page) => { - if (e) { - return done(e) + if (error) { + return reject(error) } - done(null, { slug: stub.slug, page }) + resolve({ slug: stub.slug, page }) }) - }, - (e, pages) => { - if (e) { - return res.e(e) - } - res.json( - pages.reduce((dict, combined) => { - dict[combined.slug] = combined.page - return dict - }, {}), - ) - }, - ) + }) + }) + + Promise.all(pagePromises) + .then(pages => { + const pageExport = pages.reduce((dict, combined) => { + dict[combined.slug] = combined.page + return dict + }, {}) + // TODO: this fails for a very large site + res.json(pageExport) + }) + .catch(error => { + res.e(error) + }) }) }) diff --git a/package-lock.json b/package-lock.json index 6a206b7d..a05b5f92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.25.10", "license": "MIT", "dependencies": { - "async": "^3.2.4", "body-parser": "^2.2.0", "client-sessions": "^0.8.0", "coffeescript": "^2.5.0", @@ -58,6 +57,7 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, "license": "MIT" }, "node_modules/body-parser": { diff --git a/package.json b/package.json index bc0a6fa4..24b995b6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "jon r " ], "dependencies": { - "async": "^3.2.4", "body-parser": "^2.2.0", "client-sessions": "^0.8.0", "coffeescript": "^2.5.0",