From e2ee0c84817cdcd624587cb4113ee5c565dfddba Mon Sep 17 00:00:00 2001 From: AnSq Date: Thu, 6 Aug 2026 08:58:27 -0700 Subject: [PATCH 1/3] Use ESLint and Stylelint to check for potential errors and enforce a consistent coding style * ESLint checks JS, JSON, and some CSS * Stylelint checks CSS * Stylistic plugins for both enforce coding style * Adds GitHub workflow to run checks on push and PR * Adds pre-commit hook to run checks that devs can install * Update CONTRIBUTING.md --- .gitattributes | 2 + .github/workflows/lint.yml | 32 + .gitignore | 3 + CONTRIBUTING.md | 9 + eslint.config.js | 134 ++ package-lock.json | 2744 ++++++++++++++++++++++++++++--- package.json | 14 +- pre-commit | 50 + sources/css/critical.css | 106 +- sources/css/style.css | 98 +- sources/js/app.js | 250 +-- sources/js/constants.js | 4 +- sources/js/functions.js | 43 +- sources/js/icons.js | 12 +- sources/js/moreInfo.js | 6 +- sources/tests/app.test.js | 10 +- sources/tests/functions.test.js | 10 +- sources/tests/tasks.test.js | 18 +- sources/tests/version.test.js | 2 +- stylelint.config.mjs | 45 + vite.config.js | 8 +- 21 files changed, 3104 insertions(+), 496 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 eslint.config.js create mode 100644 pre-commit create mode 100644 stylelint.config.mjs diff --git a/.gitattributes b/.gitattributes index dfe0770..903d9d2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ # Auto detect text files and perform LF normalization * text=auto + +pre-commit eol=lf diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..4c4f037 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,32 @@ +name: Lint + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies + run: npm install + + - parallel: + - name: ESLint + run: npm run lint:js + + - name: Stylelint + run: npm run lint:css diff --git a/.gitignore b/.gitignore index 1503da4..3bb52ea 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ /.vite/ /.coverage/ +.eslintcache +.stylelintcache + # Log files *.log npm-debug.log* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18b359c..4cf7191 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,6 +32,15 @@ If you encounter a bug, graphical glitch, or something isn't working as expected **Create a Pull Request:** Do you have a feature you would like added, or a task that you would like added that could benefit other Tenno? Fork the repository, and create a pull request! +### Coding Style + +This project uses [ESLint](https://eslint.org/) and [Stylelint](https://stylelint.io/) to enforce a consistent coding style. +A GitHub workflow will check your code automatically when you push changes or create a pull request. +You can also run the checks locally with `npm run lint`, and [a git pre-commit hook is provided](./pre-commit) that you can install. +Extensions may also be available for your editor. + +### AI Policy + You must disclose any use of AI that went in to preparing your pull request by **both**: 1. Marking the appropriate box in the pull request template, *and* diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..407d459 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,134 @@ +import js from "@eslint/js"; +import globals from "globals"; +import json from "@eslint/json"; +import css from "@eslint/css"; +import { defineConfig } from "eslint/config"; +import stylistic from "@stylistic/eslint-plugin"; + +export default defineConfig([ + { ignores: [".coverage/**/*"] }, + { + linterOptions: { + reportUnusedInlineConfigs: "warn", + }, + }, + { + files: ["sources/**/*.{js,mjs,cjs}", "*.config.*js"], + plugins: { js }, + languageOptions: { + globals: globals.browser, + }, + extends: [ + "js/recommended", + stylistic.configs.customize({ + indent: 4, + semi: true, + quotes: "double", + jsx: false, + arrowParens: true, + quoteProps: "consistent", + }), + ], + rules: { + // "important" stuff + "curly": ["error", "all"], + "eqeqeq": ["error", "always"], + "no-var": "error", + "prefer-const": "error", + "radix": "error", + // niche stuff + "array-callback-return": "error", + "no-duplicate-imports": "error", + "no-self-compare": "error", + "no-template-curly-in-string": "warn", + "no-unreachable-loop": "error", + "no-use-before-define": ["error", "nofunc"], + "block-scoped-var": "error", + "consistent-return": "error", + "default-param-last": "error", + "dot-notation": "warn", + "func-style": ["error", "declaration"], + "no-empty-function": "error", + "no-extend-native": "error", + "no-labels": "error", + "no-implicit-coercion": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-new-wrappers": "error", + "no-object-constructor": "error", + "no-proto": "error", + "no-return-assign": ["error", "always"], + "no-sequences": ["error", { "allowInParentheses": false }], + "no-shadow": "error", + "no-throw-literal": "error", + "no-unused-expressions": "error", + "no-useless-concat": "error", + "no-useless-rename": "error", + "no-void": "error", + "prefer-arrow-callback": "error", + "prefer-numeric-literals": "error", + "unicode-bom": "error", + // eval + "no-eval": "error", + "no-implied-eval": "error", + "no-new-func": "error", + "no-script-url": "error", + // complexity + "complexity": ["warn", 20], + "id-length": ["warn", { "min": 1, "max": 30 }], + "max-depth": ["warn", 4], + "max-lines-per-function": ["warn", { + "max": 60, + "skipBlankLines": true, + "skipComments": true, + }], + "max-nested-callbacks": ["warn", 4], + "max-params": ["warn", 4], + // stylistic + "@stylistic/quotes": ["error", "double", { "avoidEscape": true }], + "@stylistic/brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "@stylistic/no-multiple-empty-lines": ["error", { "max": 2 }], + "@stylistic/max-statements-per-line": ["error", { "max": 2 }], + "@stylistic/object-curly-spacing": ["error", "always", { "emptyObjects": "never" }], + }, + }, + { + files: ["sources/tests/*.js", "*.config.*js"], + languageOptions: { + globals: globals.node, + }, + }, + { + files: ["sources/**/*.json"], + plugins: { json }, + language: "json/json", + extends: ["json/recommended"], + }, + { + files: ["sources/**/*.css"], + plugins: { css }, + language: "css/css", + extends: ["css/recommended"], + languageOptions: { + tolerant: true, + }, + rules: { + /* allowUnknownVariables suppresses errors from variables defined in other files. + Stylelint does proper checking for this kind of error with its `referenceFiles` feature. */ + "css/no-invalid-properties": ["error", { "allowUnknownVariables": true }], + "css/use-baseline": ["warn", { + "available": "widely", + "allowProperties": [ + "backdrop-filter", // baseline 2024 + ], + "allowPropertyValues": { + "background-attachment": ["fixed"], // only unavailable in iOS + }, + "allowAtRules": [ + "starting-style", // baseline 2024 + ], + }], + "css/no-important": "warn", + }, + }, +]); diff --git a/package-lock.json b/package-lock.json index 07d1c85..646611c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,21 @@ "version": "5.3", "license": "GPL-3.0-or-later", "devDependencies": { + "@eslint/css": "^1.4.0", + "@eslint/js": "^10.0.1", + "@eslint/json": "^2.0.1", + "@stylistic/eslint-plugin": "^5.10.0", + "@stylistic/stylelint-config": "^5.0.0", "@vitest/coverage-v8": "^4.1.5", "@vitest/ui": "^4.1.5", "ajv": "^8.20.0", "ajv-formats": "^3.0.1", + "eslint": "^10.8.0", + "globals": "^17.8.0", "jsdom": "^29.1.1", "sanitize.css": "^13.0.0", + "stylelint": "^17.14.1", + "stylelint-config-standard": "^40.0.0", "vite": "^8.0.16", "vite-plugin-singlefile": "^2.3.3", "vitest": "^4.1.5" @@ -71,6 +80,28 @@ "dev": true, "license": "MIT" }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -82,9 +113,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -144,6 +175,67 @@ "specificity": "bin/cli.js" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/@csstools/color-helpers": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", @@ -165,9 +257,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", - "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", "dev": true, "funding": [ { @@ -240,9 +332,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", - "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", "dev": true, "funding": [ { @@ -284,6 +376,76 @@ "node": ">=20.19.0" } }, + "node_modules/@csstools/media-query-list-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.0.tgz", + "integrity": "sha512-T9lXmZOfnam3eMERPsszjY5NK0jX8RmThmmm99FZ8b7z8yMaFZWKwLWGZuTwdO3ddRY5fy13GmmEYZXB4I98Eg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.1.tgz", + "integrity": "sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz", + "integrity": "sha512-4sSgl78OtOXEX/2d++8A83zHNTgwCJMaR24FvsYL7Uf/VS8HZk9PTwR51elTbGqMuwH3szLvvOXEaVnqn0Z3zA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -318,6 +480,186 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "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-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-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/css/-/css-1.4.0.tgz", + "integrity": "sha512-OnRNKgnbX+tufPuZc2kOJS+v1iIARvfJHRNEAVwN77DBpojl+rGjEP8NKTL6pEZ7BR+HtwIuSSdrymEFlANGxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "@eslint/css-tree": "^4.0.4", + "@eslint/plugin-kit": "^0.7.2" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/css-tree": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.0.5.tgz", + "integrity": "sha512-iPmijIAq4hlIJB86PYmY/fcZORHtjphSqICDbwuw32A/JmkhZQ/K/6TjHE03zqf3n5yABpVcbRAMG8Mi9ojy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.29.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/css-tree/node_modules/mdn-data": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.29.0.tgz", + "integrity": "sha512-pVxQFCcaYUEAH853+v7yoI/qzhxXSq1bTb9obMYGYAN1c3Hen+XDCEvr296XhstrwlSTNgOR7mCSD4JPjbJe5A==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/json/-/json-2.0.1.tgz", + "integrity": "sha512-Thz2j92ceUF3Bq/0TuWb3MWn3Z+Cwc8k5ptF0fakl2D4Mp8mSx07Xr1aQM4R5NoihzarWUdxfOmQ8DesGy4jOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanwhocodes/momoa": "^3.3.10", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@exodus/bytes": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", @@ -336,6 +678,82 @@ } } }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "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/@humanwhocodes/momoa": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.10.tgz", + "integrity": "sha512-KWiFQpSAqEIyrTXko3hFNLeQvSK8zXlJQzhhxsyVn58WFRYXST99b3Nqnu+ttOtjds2Pl2grUHGpe2NzhPynuQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "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/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -363,6 +781,13 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", @@ -382,20 +807,58 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "dev": true, "license": "MIT" }, @@ -681,12 +1144,112 @@ "dev": true, "license": "MIT" }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "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": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "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.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/stylelint-config": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-config/-/stylelint-config-5.0.0.tgz", + "integrity": "sha512-AW6S27wEm4DzB+uOZMvRkONIu0ba50VRbFte7qYJgGP4dacS8kAuj8HZJ+s+8kE4GK3XXBcSKLb75a+NfJlbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@stylistic/stylelint-plugin": "^5.1.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.6.0" + } + }, + "node_modules/@stylistic/stylelint-plugin": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-5.2.1.tgz", + "integrity": "sha512-BMRWJVtO16uADJmKcGi6mreei/QYdOviABXuaCeHEc3T3pxo4wFYYSvnxIoyiPVfDJqX+DxQdnGkuc6KxsRaSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "postcss": "^8.5.8", + "postcss-selector-parser": "^7.1.1", + "postcss-value-parser": "^4.2.0", + "style-search": "^0.1.0" + }, + "peerDependencies": { + "stylelint": "^17.6.0" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", @@ -714,12 +1277,40 @@ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, + "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/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", @@ -907,6 +1498,29 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -942,6 +1556,42 @@ } } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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/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/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -973,6 +1623,26 @@ "@types/estree": "^1.0.0" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -983,6 +1653,19 @@ "require-from-string": "^2.0.2" } }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -996,6 +1679,40 @@ "node": ">=8" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "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/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1005,12 +1722,91 @@ "node": ">=18" } }, + "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/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/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", @@ -1025,6 +1821,19 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/data-urls": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", @@ -1039,6 +1848,24 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/decimal.js": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", @@ -1046,6 +1873,13 @@ "dev": true, "license": "MIT" }, + "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/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1056,28 +1890,291 @@ "node": ">=8" } }, + "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/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "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": ">=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-deep-equal": { + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.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", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/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/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/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/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/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "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/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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/fast-uri": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", @@ -1095,6 +2192,26 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1119,6 +2236,19 @@ "dev": true, "license": "MIT" }, + "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/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1132,6 +2262,37 @@ "node": ">=8" } }, + "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/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/flatted": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", @@ -1154,103 +2315,392 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "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==", + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "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": "MIT", + "license": "ISC", "dependencies": { - "@exodus/bytes": "^1.6.0" + "is-glob": "^4.0.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=10.13.0" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } }, - "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==", + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, "engines": { - "node": ">=0.12.0" + "node": ">=6" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "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": "MIT" + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/globals": { + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/globby": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz", + "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=10" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/globby/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 4" } }, - "node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true, "license": "MIT" }, - "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "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/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/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/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "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/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/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/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/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "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/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1288,6 +2738,20 @@ } } }, + "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/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1295,6 +2759,47 @@ "dev": true, "license": "MIT" }, + "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/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/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/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/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -1568,6 +3073,36 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "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/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.3.6", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", @@ -1615,6 +3150,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mathml-tag-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -1622,21 +3168,44 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/meow": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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/micromatch/node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", @@ -1649,6 +3218,22 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -1659,10 +3244,17 @@ "node": ">=10" } }, + "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/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -1678,6 +3270,23 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", @@ -1688,6 +3297,88 @@ "https://opencollective.com/debug" ] }, + "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/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/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/parent-module": { + "version": "1.0.1", + "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": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -1714,6 +3405,26 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "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/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/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1721,197 +3432,693 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "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.8.0" + } + }, + "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": ">=6" + } + }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "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/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true, + "license": "ISC" + }, + "node_modules/stylelint": { + "version": "17.14.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.14.1.tgz", + "integrity": "sha512-xVQwyiuxALUBNB2fBe0tmNemg9KqLtdj3T64mioFDar79B2cU8LIyz+3KL6LdiHs9NkeNfwxpKSaIVOY8f112g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@csstools/css-calc": "^3.2.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.6", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.0", + "@csstools/selector-specificity": "^6.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.2", + "css-functions-list": "^3.3.3", + "css-tree": "^3.2.1", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.5", + "global-modules": "^2.0.0", + "globby": "^16.2.1", + "globjoin": "^0.1.4", + "html-tags": "^5.1.0", + "ignore": "^7.0.5", + "import-meta-resolve": "^4.2.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.1.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.16", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.4", + "postcss-value-parser": "^4.2.0", + "string-width": "^8.2.1", + "supports-hyperlinks": "^4.5.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^7.0.1" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=20.19.0" } }, - "node_modules/postcss": { - "version": "8.5.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "node_modules/stylelint-config-recommended": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", "dev": true, "funding": [ { "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://opencollective.com/stylelint" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.0.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-40.0.0.tgz", + "integrity": "sha512-EznGJxOUhtWck2r6dJpbgAdPATIzvpLdK9+i5qPd4Lx70es66TkBPljSg4wN3Qnc6c4h2n+WbUrUynQ3fanjHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" }, { "type": "github", - "url": "https://github.com/sponsors/ai" + "url": "https://github.com/sponsors/stylelint" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "stylelint-config-recommended": "^18.0.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^17.0.0" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "flat-cache": "^6.1.23" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "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": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" + "has-flag": "^4.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" + "node": ">=8" } }, - "node_modules/sanitize.css": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", - "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "node_modules/supports-hyperlinks": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.5.0.tgz", + "integrity": "sha512-ZW2OvfeCXrNTbLakPUzjQG922EeGCOteFSVoek5DKStTh898wf7zgtuFlzQN8HfZCxC3Eh02yJVrRW51hADf+w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "xmlchars": "^2.2.0" + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" }, "engines": { - "node": ">=v12.22.7" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=18" + "node": ">=10.0.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/table/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": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true - }, - "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/table/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": { - "has-flag": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "node_modules/table/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" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/tinybench": { "version": "2.9.0", @@ -2031,6 +4238,19 @@ "license": "0BSD", "optional": true }, + "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": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/undici": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", @@ -2041,6 +4261,36 @@ "node": ">=20.18.1" } }, + "node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/vite": { "version": "8.0.16", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", @@ -2284,6 +4534,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "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/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -2300,6 +4566,29 @@ "node": ">=8" } }, + "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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "dev": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -2316,6 +4605,19 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true, "license": "MIT" + }, + "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" + } } } } diff --git a/package.json b/package.json index c7d0be7..63d51c3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "build-release": "vite build --mode release", "preview": "vite preview", "test": "vitest", - "test:ui": "vitest --ui --open=false --coverage" + "test:ui": "vitest --ui --open=false --coverage", + "lint": "npm run lint:js && npm run lint:css", + "lint:js": "eslint --concurrency=auto --cache", + "lint:css": "stylelint \"sources/**/*.css\" --cache" }, "repository": { "type": "git", @@ -29,12 +32,21 @@ }, "homepage": "https://warframetools.com/Task-Checklist/", "devDependencies": { + "@eslint/css": "^1.4.0", + "@eslint/js": "^10.0.1", + "@eslint/json": "^2.0.1", + "@stylistic/eslint-plugin": "^5.10.0", + "@stylistic/stylelint-config": "^5.0.0", "@vitest/coverage-v8": "^4.1.5", "@vitest/ui": "^4.1.5", "ajv": "^8.20.0", "ajv-formats": "^3.0.1", + "eslint": "^10.8.0", + "globals": "^17.8.0", "jsdom": "^29.1.1", "sanitize.css": "^13.0.0", + "stylelint": "^17.14.1", + "stylelint-config-standard": "^40.0.0", "vite": "^8.0.16", "vite-plugin-singlefile": "^2.3.3", "vitest": "^4.1.5" diff --git a/pre-commit b/pre-commit new file mode 100644 index 0000000..6efd652 --- /dev/null +++ b/pre-commit @@ -0,0 +1,50 @@ +#!/bin/bash +# This is a git pre-commit hook to check for linting errors. +# Copy or symlink this file into .git/hooks/ to install it. + +if [[ -t 1 && -z "$NO_COLOR" ]]; then + color="--color" +else + color="--no-color" +fi + +if [[ pre-commit -nt .git/hooks/pre-commit ]]; then + echo "The pre-commit hook version distributed by the repository is newer than the installed version. You may wish to update." + stat -c "repository: %y : %n" pre-commit + stat -c "installed: %y : %n" .git/hooks/pre-commit + echo +fi + +# Redirect output to stderr. +exec 1>&2 + +# Run commands in the background and redirect their output to new file descriptors +exec 3< <(npm run -s test -- run --silent --reporter=minimal --bail=1 $color 2>&1); test_pid=$! +exec 4< <(npm run -s lint:js -- $color 2>&1); js_pid=$! +exec 5< <(npm run -s lint:css -- $color 2>&1); css_pid=$! + +# Wait for commands to complete and capture their exit codes +wait $test_pid; test_exit=$? +wait $js_pid; js_exit=$? +wait $css_pid; css_exit=$? + +if [[ $test_exit != 0 || $js_exit != 0 || $css_exit != 0 ]]; then + msg="Fix errors before committing, or skip this check with --no-verify" + echo -e "$msg\n" # this message is for the Commit button in the vscode ui + if [[ $test_exit != 0 ]]; then + echo "----- Vitest report - (exit code $test_exit) --------------" + cat <&3 + echo -e "Only the first failing test is reported. Run \`npm run test\` to check for more.\n" + fi + if [[ $js_exit != 0 ]]; then + echo "----- ESLint report - (exit code $js_exit) --------------" + cat <&4 + fi + if [[ $css_exit != 0 ]]; then + echo "----- Stylelint report - (exit code $css_exit) -----------" + cat <&5 + fi + echo -e "--------------------------------------------------\n" + echo "$msg" # this message is for if you're committing on a terminal + exit 1 +fi diff --git a/sources/css/critical.css b/sources/css/critical.css index 26e9782..b29be6f 100644 --- a/sources/css/critical.css +++ b/sources/css/critical.css @@ -1,37 +1,37 @@ /* @font-face rules for self-hosted Inter font */ @font-face { - font-family: 'Inter'; + font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; - src: url('/fonts/inter-v18-latin-regular.woff2') format('woff2'); + src: url("/fonts/inter-v18-latin-regular.woff2") format("woff2"); } @font-face { - font-family: 'Inter'; + font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; - src: url('/fonts/inter-v18-latin-500.woff2') format('woff2'); + src: url("/fonts/inter-v18-latin-500.woff2") format("woff2"); } @font-face { - font-family: 'Inter'; + font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; - src: url('/fonts/inter-v18-latin-600.woff2') format('woff2'); + src: url("/fonts/inter-v18-latin-600.woff2") format("woff2"); } @font-face { - font-family: 'Inter'; + font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; - src: url('/fonts/inter-v18-latin-700.woff2') format('woff2'); + src: url("/fonts/inter-v18-latin-700.woff2") format("woff2"); } /* --- Global Theme Variables (Critical for initial paint) --- */ :root { /* Default: Dark Mode */ - --bg-content: rgba(31, 41, 55, 0.95); + --bg-content: rgb(31 41 55 / 95%); --text-primary: #f3f4f6; --text-secondary: #9ca3af; --text-muted: #6b7280; @@ -41,27 +41,27 @@ --border-color: #4b5563; --checkbox-check-color: var(--text-primary); --checkbox-lock-color: var(--text-primary); - --checkbox-bg: rgba(255, 255, 255, 0.1); + --checkbox-bg: rgb(255 255 255 / 10%); --checkbox-border: var(--text-muted); --checkbox-checked-bg: var(--text-header); --checkbox-checked-border: var(--text-muted); - --checkbox-locked-bg: hsla(17, 100%, 70%, 0.15); + --checkbox-locked-bg: hsl(17deg 100% 70% / 15%); --checkbox-locked-border: var(--text-muted); --link-color: var(--text-header); --link-hover-color: var(--text-primary); --theme-toggle-border: var(--border-color); - --theme-toggle-hover-bg: rgba(255, 255, 255, 0.1); + --theme-toggle-hover-bg: rgb(255 255 255 / 10%); --theme-toggle-hover-border: var(--text-secondary); --theme-toggle-color: var(--text-secondary); - --section-header-hover-bg: rgba(255, 255, 255, 0.05); + --section-header-hover-bg: rgb(255 255 255 / 5%); --confirm-bg: #f59e0b; --confirm-hover-bg: #d97706; --confirm-text: #1f2937; --error-bg: #ef4444; --error-border: #dc2626; - --error-text: #ffffff; - --error-button-bg: rgba(255, 255, 255, 0.1); - --error-button-hover-bg: rgba(255, 255, 255, 0.2); + --error-text: white; + --error-button-bg: rgb(255 255 255 / 10%); + --error-button-hover-bg: rgb(255 255 255 / 20%); --menu-panel-bg: #272e3a; --menu-panel-border: #374151; --menu-title-color: var(--text-primary); @@ -77,7 +77,7 @@ } body.light-mode { - --bg-content: rgba(255, 255, 255, 0.95); + --bg-content: rgb(255 255 255 / 95%); --text-primary: #1f2937; --text-secondary: #4b5563; --text-muted: #6b7280; @@ -87,21 +87,21 @@ body.light-mode { --border-color: #e5e7eb; --checkbox-check-color: var(--text-primary); --checkbox-lock-color: var(--text-primary); - --checkbox-bg: rgba(0, 0, 0, 0.02); + --checkbox-bg: rgb(0 0 0 / 2%); --checkbox-border: #cbd5e1; - --checkbox-locked-bg: hsla(17, 100%, 30%, 0.15); + --checkbox-locked-bg: hsl(17deg 100% 30% / 15%); --link-color: var(--text-header); --link-hover-color: var(--text-primary); --theme-toggle-border: #d1d5db; - --theme-toggle-hover-bg: rgba(0, 0, 0, 0.05); + --theme-toggle-hover-bg: rgb(0 0 0 / 5%); --theme-toggle-hover-border: var(--text-muted); --theme-toggle-color: var(--text-muted); - --section-header-hover-bg: rgba(0, 0, 0, 0.03); + --section-header-hover-bg: rgb(0 0 0 / 3%); --error-bg: #fee2e2; --error-border: #f87171; --error-text: #b91c1c; - --error-button-bg: rgba(0, 0, 0, 0.05); - --error-button-hover-bg: rgba(0, 0, 0, 0.1); + --error-button-bg: rgb(0 0 0 / 5%); + --error-button-hover-bg: rgb(0 0 0 / 10%); --menu-panel-bg: #f9fafb; --menu-panel-border: var(--border-color); --menu-title-color: var(--text-primary); @@ -138,7 +138,7 @@ body.light-mode { /* --- Base Body Styles --- */ body { - font-family: 'Inter', sans-serif; + font-family: "Inter", sans-serif; background-size: cover; background-position: center center; background-repeat: no-repeat; @@ -152,12 +152,12 @@ body { body::before { content: ""; position: fixed; - top: 0; left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.6); + inset: 0; + background-color: rgb(0 0 0 / 60%); z-index: -1; } body.light-mode::before { - background-color: rgba(255, 255, 255, 0.6); + background-color: rgb(255 255 255 / 60%); } /* --- Content Box Element Styling --- */ @@ -170,8 +170,8 @@ body.light-mode::before { margin: 2rem auto; padding: 2rem; border-radius: 0.5rem; - box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), - 0 8px 10px -6px rgb(0 0 0 / 0.1); + box-shadow: 0 20px 25px -5px rgb(0 0 0 / 10%), + 0 8px 10px -6px rgb(0 0 0 / 10%); @media (width < 48rem) { padding: 1.5rem; margin: 0 auto; @@ -230,6 +230,20 @@ h1 { margin-bottom: 0.5rem; } +.task-info-expander { + --current-cycle-svg-size: 1.125rem; + --task-info-expander-svg-mr: 0.2em; +} + +.current-cycle svg { + width: var(--current-cycle-svg-size); + height: var(--current-cycle-svg-size); +} +.info-line svg { + width: 0.9375rem; + height: 0.9375rem; +} + /* Theme switching animation */ @media (prefers-reduced-motion: no-preference) { body { transition: background-color 0.3s ease, color 0.3s ease; } @@ -259,7 +273,8 @@ h1 { .parent-task-header[aria-expanded="false"] .collapse-btn svg { transform: rotate(-90deg); } -.section-content, .subtask-collapsible { +.section-content, +.subtask-collapsible { display: grid; grid-template-rows: 1fr; overflow: hidden; @@ -267,15 +282,18 @@ h1 { padding-left: 0.5rem; } @media (prefers-reduced-motion: no-preference) { - .section-content, .subtask-collapsible { - transition: grid-template-rows 0.5s cubic-bezier(.23,1,.32,1), opacity 0.5s ease-in-out; + .section-content, + .subtask-collapsible { + transition: grid-template-rows 0.5s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.5s ease-in-out; } } -.section-content.collapsed, .subtask-collapsible.collapsed { +.section-content.collapsed, +.subtask-collapsible.collapsed { opacity: 0.5; grid-template-rows: 0fr; } -.section-content > :first-child, .subtask-collapsible > :first-child { +.section-content > :first-child, +.subtask-collapsible > :first-child { min-height: 0; } @@ -310,7 +328,7 @@ body:not(.light-mode) #sun-icon { display: none; } dialog { background-color: var(--menu-panel-bg); border: 1px solid var(--menu-panel-border); - box-shadow: 0 8px 16px rgba(0,0,0,0.2); + box-shadow: 0 8px 16px rgb(0 0 0 / 20%); border-radius: 0.5rem; } dialog > div { @@ -359,22 +377,8 @@ dialog > div { } #error-display.visible { display: flex; } #error-message { flex-grow: 1; } -#error-copy-button, #error-close-button { +#error-copy-button, +#error-close-button { cursor: pointer; flex-shrink: 0; } - - -.task-info-expander { - --current-cycle-svg-size: 1.125rem; - --task-info-expander-svg-mr: 0.2em; -} - -.current-cycle svg { - width: var(--current-cycle-svg-size); - height: var(--current-cycle-svg-size); -} -.info-line svg { - width: 0.9375rem; - height: 0.9375rem; -} diff --git a/sources/css/style.css b/sources/css/style.css index d7eca21..14ae7f6 100644 --- a/sources/css/style.css +++ b/sources/css/style.css @@ -3,11 +3,11 @@ /* --- Background Image Styles --- */ /* Paths are relative to this style.css file (e.g., if style.css is in sources/css/ and images are in sources/img/) */ -#bg-image-0 { background-image: url('../img/garuda-fortuna.webp'); } -#bg-image-1 { background-image: url('../img/gara-eidolon.webp'); } -#bg-image-2 { background-image: url('../img/heart-of-deimos-warframe.webp'); } -#bg-image-3 { background-image: url('../img/duviri.webp'); } -#bg-image-4 { background-image: url('../img/perita.webp'); } +#bg-image-0 { background-image: url("../img/garuda-fortuna.webp"); } +#bg-image-1 { background-image: url("../img/gara-eidolon.webp"); } +#bg-image-2 { background-image: url("../img/heart-of-deimos-warframe.webp"); } +#bg-image-3 { background-image: url("../img/duviri.webp"); } +#bg-image-4 { background-image: url("../img/perita.webp"); } /* Add more #bg-image-X rules if you have more images */ .daily-background-image { @@ -31,7 +31,6 @@ #checklist-container h2:hover { background-color: var(--section-header-hover-bg); } -.task-description { color: var(--text-label); } #save-info { margin-top: 1rem; @@ -43,7 +42,7 @@ margin-top: 0.25rem; } #save-status { - color: rgb(22 163 74 / 1); + color: rgb(22 163 74 / 100%); opacity: 0; font-size: 0.875rem; line-height: 1.25rem; @@ -118,6 +117,7 @@ a.git-hash-text { margin-bottom: 0.25rem; } .task-description { + color: var(--text-label); flex: 1 1 0%; margin-left: 0.5rem; cursor: pointer; @@ -160,7 +160,7 @@ a.git-hash-text { opacity: 1; } .section-is-hidden-by-user { - display: none !important; + display: none; } .hide-section-button { background-color: var(--hide-section-btn-bg); @@ -188,15 +188,15 @@ a.git-hash-text { font-variant-numeric: tabular-nums; } - /* --- Interactive Elements (Using Variables) --- */ input[type="checkbox"] { - appearance: none; -webkit-appearance: none; -moz-appearance: none; + appearance: none; width: 1.25rem; height: 1.25rem; margin: 0.125rem 0.5rem 0.125rem 0; border: 2px solid var(--checkbox-border); - border-radius: 0.25rem; cursor: pointer; + border-radius: 0.25rem; + cursor: pointer; position: relative; transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; background-color: var(--checkbox-bg); @@ -248,36 +248,35 @@ input[type="checkbox"]:indeterminate::after { cursor: pointer; line-height: 1.5; } -/* Specific Button Colors (Default State) */ -#reset-daily-button:not(.confirming) { background-color: #3b82f6; border-color: #3b82f6; color: white;} -#reset-daily-button:not(.confirming):hover { background-color: #2563eb; border-color: #2563eb; color: white;} -#reset-weekly-button:not(.confirming) { background-color: #14b8a6; border-color: #14b8a6; color: white;} -#reset-weekly-button:not(.confirming):hover { background-color: #0d9488; border-color: #0d9488; color: white;} -#reset-button:not(.confirming) { background-color: #ef4444; border-color: #ef4444; color: white;} -#reset-button:not(.confirming):hover { background-color: #dc2626; border-color: #dc2626; color: white;} -#unhide-tasks-button:not(.confirming) { background-color: #8b5cf6; border-color: #8b5cf6; color: white; } +/* Specific Button Colors (Default State) */ /* stylelint-disable declaration-block-single-line-max-declarations, @stylistic/block-opening-brace-space-before */ +#reset-daily-button:not(.confirming) { background-color: #3b82f6; border-color: #3b82f6; color: white; } +#reset-daily-button:not(.confirming):hover { background-color: #2563eb; border-color: #2563eb; color: white; } +#reset-weekly-button:not(.confirming) { background-color: #14b8a6; border-color: #14b8a6; color: white; } +#reset-weekly-button:not(.confirming):hover { background-color: #0d9488; border-color: #0d9488; color: white; } +#reset-button:not(.confirming) { background-color: #ef4444; border-color: #ef4444; color: white; } +#reset-button:not(.confirming):hover { background-color: #dc2626; border-color: #dc2626; color: white; } +#unhide-tasks-button:not(.confirming) { background-color: #8b5cf6; border-color: #8b5cf6; color: white; } #unhide-tasks-button:not(.confirming):hover { background-color: #7c3aed; border-color: #7c3aed; color: white; } - +/* stylelint-enable declaration-block-single-line-max-declarations, @stylistic/block-opening-brace-space-before */ /* Confirmation State Styling (Applies to all reset buttons) */ .menu-btn.confirming { background-color: var(--confirm-bg); border-color: var(--confirm-bg); - color: var(--confirm-text) !important; + color: var(--confirm-text); } .menu-btn.confirming:hover { background-color: var(--confirm-hover-bg); border-color: var(--confirm-hover-bg); } - /* Theme Toggle Button Specifics (Non-critical hover states) */ -#theme-toggle-button:hover, #hamburger-button:hover { +#theme-toggle-button:hover, +#hamburger-button:hover { background-color: var(--theme-toggle-hover-bg); border-color: var(--theme-toggle-hover-border); } - /* Error Display (Non-critical hover states for buttons) */ #error-copy-button { background-color: var(--error-button-bg); @@ -290,13 +289,15 @@ input[type="checkbox"]:indeterminate::after { } #error-copy-button:hover { background-color: var(--error-button-hover-bg); } #error-close-button { - background: none; border: none; + background: none; + border: none; color: var(--error-text); - font-size: 1.5rem; line-height: 1; + font-size: 1.5rem; + line-height: 1; padding: 0 0.5rem; opacity: 0.7; } - #error-close-button:hover { opacity: 1; } +#error-close-button:hover { opacity: 1; } /* Hamburger Menu (Non-critical hover states) */ .menu-close-button:hover { @@ -340,7 +341,7 @@ dialog hr { dialog { --dialog-anim-duration: 0.15s; --dialog-anim-easing: ease-out; - transition: transform var(--dialog-anim-duration) cubic-bezier(.25,1,.5,1), + transition: transform var(--dialog-anim-duration) cubic-bezier(0.25, 1, 0.5, 1), opacity var(--dialog-anim-duration) var(--dialog-anim-easing), display var(--dialog-anim-duration) allow-discrete, overlay var(--dialog-anim-duration) allow-discrete; @@ -370,7 +371,6 @@ dialog hr { } } - /* Task Icons */ .task-icon { height: 1.5em; @@ -403,7 +403,9 @@ body:not(.light-mode) img.icon-filter { overflow: hidden; } @media (prefers-reduced-motion: no-preference) { - .task-info-expander { transition: grid-template-rows 200ms; /* https://css-tricks.com/css-grid-can-do-auto-height-transitions/ */ } + .task-info-expander { + transition: grid-template-rows 0.2s; /* https://css-tricks.com/css-grid-can-do-auto-height-transitions/ */ + } } .task-item .checked .task-info-expander { grid-template-rows: 0fr; @@ -418,6 +420,7 @@ body:not(.light-mode) img.icon-filter { font-size: 90%; color: color-mix(in hsl, var(--text-label) 50%, var(--text-secondary) 50%); margin-left: 0.5em; + /* eslint-disable-next-line css/use-baseline -- (text-indent: hanging; baseline 2026) only relevant on very narrow screens for only a small effect */ text-indent: calc(var(--current-cycle-svg-size) + var(--task-info-expander-svg-mr)) hanging; } .cycle-icon { @@ -448,7 +451,8 @@ body:not(.light-mode) img.icon-filter { color: var(--text-label); } -#cycle-schedule, #more-info { +#cycle-schedule, +#more-info { table { margin: auto; } @@ -458,7 +462,8 @@ body:not(.light-mode) img.icon-filter { text-decoration: underline var(--menu-panel-border) 1px; text-underline-offset: 0.25em; } - th::before, th::after { + th::before, /* eslint-disable-line css/no-invalid-properties -- eslint bug workaround */ + th::after { content: "\00a0\00a0\00a0\00a0"; /* some no-break spaces added so the underline extends a bit on both sides */ } :is(th, td):not(:last-child) { @@ -467,7 +472,8 @@ body:not(.light-mode) img.icon-filter { td { vertical-align: top; } - th, td:not(:has(.cycle-icon)) { + th, + td { text-align: center; } td:has(.cycle-icon) { @@ -488,16 +494,14 @@ body:not(.light-mode) img.icon-filter { /* More Info */ #more-info { - p:not(:last-child):not(:has(+ :is(ul, table))), table { + p:not(:last-child, :has(+ :is(ul, table))), /* eslint-disable-line css/no-invalid-properties -- eslint bug workaround */ + table { margin-bottom: 1em; } ul { list-style: "\2212 " outside; padding-left: 2em; } - th, td { - text-align: center; - } a { color: var(--link-color); text-decoration: underline; @@ -526,7 +530,7 @@ body:not(.light-mode) img.icon-filter { content: "|"; margin: 0 0.5em; } -.info-line > span > span { +.info-line > span > span { /* stylelint-disable-line no-descending-specificity -- the complained-about selector cannot match the same element as this */ /* keep the info line icon on the same line as the first word of the info line item. * e.g.: {prereq_icon}[NO_BREAK]Mastery[BREAK_OK]Rank[BREAK_OK]3 */ white-space: nowrap; @@ -553,10 +557,10 @@ body:not(.light-mode) img.icon-filter { } @media (prefers-reduced-motion: no-preference) { .task-expander { - transition: grid-template-rows 200ms; + transition: grid-template-rows 0.2s; } .task-expander > .task-item { - transition: margin 200ms; + transition: margin 0.2s; } } @@ -565,28 +569,28 @@ body.hide-completed-tasks .task-expander:has(> .task-item > .checked), body.hide-completed-tasks .task-expander:has(> .parent-task-container > .parent-task-header > .checked) { grid-template-rows: 0fr; @media (prefers-reduced-motion: no-preference) { - transition: grid-template-rows 200ms 5s; + transition: grid-template-rows 0.2s 5s; } @media (prefers-reduced-motion: reduce) { transition: grid-template-rows 0s 5s; } &::after { - background-color: color(from var(--checkbox-checked-bg) srgb r g b / 0.25); + background-color: color(from var(--checkbox-checked-bg) srgb r g b / 25%); width: 0; } } body.hide-completed-tasks .task-expander > .task-item:has(> .checked) { margin: 0; @media (prefers-reduced-motion: no-preference) { - transition: margin 200ms 5s; + transition: margin 0.2s 5s; } @media (prefers-reduced-motion: reduce) { transition: margin 0s 5s; } } /* countdown bar */ -.task-expander { +.task-expander { /* stylelint-disable-line no-descending-specificity -- better to group blocks by function than split them up by specificity */ position: relative; } .task-expander::after { @@ -612,16 +616,14 @@ body.hide-completed-tasks .task-expander > .task-item:has(> .checked) { .list-stats > div { overflow: hidden; display: flex; + margin-bottom: 0.5em; } body.hide-completed-tasks .list-stats:has(> div > button[data-count]:not([data-count="0"])), /* with autohide, show when any stat is nonzero */ body:not(.hide-completed-tasks) .list-stats:has(> div > button:not(.stats-completed)[data-count]:not([data-count="0"])) /* without autohide, show when any stat except completed is nonzero */ { grid-template-rows: 1fr; } @media (prefers-reduced-motion: no-preference) { - .list-stats { transition: grid-template-rows 200ms; } -} -.list-stats > div { - margin-bottom: 0.5em; + .list-stats { transition: grid-template-rows 0.2s; } } .list-stats > div > button[data-count="0"], /* hide stat when equal to 0 */ body:not(.hide-completed-tasks) .list-stats .stats-completed /* hide completed when autohide is off */ { diff --git a/sources/js/app.js b/sources/js/app.js index 67a0e97..4fe1600 100644 --- a/sources/js/app.js +++ b/sources/js/app.js @@ -27,26 +27,26 @@ import * as svgIcons from "./icons.js"; // --- Configuration --- // Array of background div IDs (must match IDs in sources/index.html) const dailyBackgroundImageIds = [ - 'bg-image-0', - 'bg-image-1', - 'bg-image-2', - 'bg-image-3', - 'bg-image-4', + "bg-image-0", + "bg-image-1", + "bg-image-2", + "bg-image-3", + "bg-image-4", // Add more IDs if you add more background image divs in HTML ]; export const APP_VERSION = "5.3"; const GIT_COMMIT_HASH_LONG = import.meta.env.VITE_GIT_COMMIT_HASH; -const GIT_COMMIT_HASH = GIT_COMMIT_HASH_LONG.slice(0,7); +const GIT_COMMIT_HASH = GIT_COMMIT_HASH_LONG.slice(0, 7); const WARFRAME_VERSION = "43.0.8"; -const THEME_STORAGE_KEY = 'warframeChecklistTheme'; +const THEME_STORAGE_KEY = "warframeChecklistTheme"; // only update DATA_STORAGE_KEY when the data storage format changes // in a backwards-incompatible way (this should be *very* rare) const DATA_STORAGE_KEY = "warframeChecklistData_format1"; // --- Task Data --- -import tasks from "./tasks.json" with {type: "json"}; -import cycles from "./cycles.json" with {type: "json"}; +import tasks from "./tasks.json" with { type: "json" }; +import cycles from "./cycles.json" with { type: "json" }; import moreInfo from "./moreInfo.js"; // --- DOM Elements (defined after DOMContentLoaded) --- @@ -62,7 +62,7 @@ const confirmState = { all: { timeout: null, isConfirming: false }, daily: { timeout: null, isConfirming: false }, weekly: { timeout: null, isConfirming: false }, - unhide: { timeout: null, isConfirming: false } + unhide: { timeout: null, isConfirming: false }, }; let checklistData = newChecklistData(); @@ -115,7 +115,7 @@ function initializeDOMElements() { * `callback` function with the task definition object as a parameter. */ function forEachTask(callback) { - let stack = []; + const stack = []; for (const section in tasks) { for (let i = 0; i < tasks[section].length; i++) { // top-level tasks are not pushed in reverse order because they're immediately popped @@ -124,9 +124,9 @@ function forEachTask(callback) { const t = stack.pop(); callback(t); // do the thing if (t.subtasks) { - for (let i = t.subtasks.length - 1; i >= 0 ; i--) { + for (let j = t.subtasks.length - 1; j >= 0; j--) { /* eslint-disable-line max-depth */ // subtasks are pushed in reverse order so that they're popped in the correct order - stack.push(t.subtasks[i]); + stack.push(t.subtasks[j]); } } } @@ -178,7 +178,7 @@ function displayError(message) { function hideError() { if (!errorDisplayElement) { return; } errorDisplayElement.classList.remove("visible"); - errorMessageElement.textContent = ''; + errorMessageElement.textContent = ""; if (errorCopyButton) { errorCopyButton.textContent = "Copy"; } } @@ -210,16 +210,16 @@ function copyErrorToClipboard() { function applyTheme(theme) { if (!bodyElement || !themeToggleButton) { return; } - if (theme === 'light') { - bodyElement.classList.add('light-mode'); + if (theme === "light") { + bodyElement.classList.add("light-mode"); } else { - bodyElement.classList.remove('light-mode'); + bodyElement.classList.remove("light-mode"); } currentTheme = theme; } function handleThemeToggle() { - const newTheme = currentTheme === 'light' ? 'dark' : 'light'; + const newTheme = currentTheme === "light" ? "dark" : "light"; applyTheme(newTheme); try { localStorage.setItem(THEME_STORAGE_KEY, newTheme); @@ -235,10 +235,10 @@ function loadThemePreference() { return; } - if (bodyElement.classList.contains('light-mode')) { - currentTheme = 'light'; + if (bodyElement.classList.contains("light-mode")) { + currentTheme = "light"; } else { - currentTheme = 'dark'; + currentTheme = "dark"; } try { const storedTheme = localStorage.getItem(THEME_STORAGE_KEY); @@ -261,8 +261,8 @@ function updateLastSavedDisplay(timestamp) { function showSaveStatus() { if (!saveStatusElement) { return; } clearTimeout(saveStatusTimeout); - saveStatusElement.style.opacity = '1'; - saveStatusTimeout = setTimeout(() => { saveStatusElement.style.opacity = '0'; }, 1500); + saveStatusElement.style.opacity = "1"; + saveStatusTimeout = setTimeout(() => { saveStatusElement.style.opacity = "0"; }, 1500); } function setDailyBackground() { @@ -275,14 +275,14 @@ function setDailyBackground() { const dayOfYear = getUTCDayOfYear(now); const imageIndex = dayOfYear % backgroundDivs.length; - console.log(`Setting daily background to show div: ${backgroundDivs[imageIndex] ? backgroundDivs[imageIndex].id : 'N/A'} (Index: ${imageIndex})`); + console.log(`Setting daily background to show div: ${backgroundDivs[imageIndex] ? backgroundDivs[imageIndex].id : "N/A"} (Index: ${imageIndex})`); backgroundDivs.forEach((div, index) => { if (div) { // Ensure div exists if (index === imageIndex) { - div.style.display = 'block'; + div.style.display = "block"; } else { - div.style.display = 'none'; + div.style.display = "none"; } } }); @@ -320,8 +320,8 @@ function displayLocalResetTimes() { tasks.other.forEach((task) => displayOtherTaskCountdown(task)); } catch (e) { console.error("Error calculating or displaying local reset times:", e); - if (dailyResetTimeElement) { dailyResetTimeElement.innerHTML = `(Resets 00:00 UTC)`; } - if (weeklyResetTimeElement) { weeklyResetTimeElement.innerHTML = `(Resets Mon 00:00 UTC)`; } + if (dailyResetTimeElement) { dailyResetTimeElement.innerHTML = "(Resets 00:00 UTC)"; } + if (weeklyResetTimeElement) { weeklyResetTimeElement.innerHTML = "(Resets Mon 00:00 UTC)"; } } } @@ -331,6 +331,7 @@ export function displayOtherTaskCountdown(task) { const now = new Date(); const taskTimes = calcTaskTimes(task, now); + const cycleNumber = calcCycleNumber(task, now); if (task.duration) { // intermittently available task (e.g., Baro) const leaveNotifId = `${task.id}/departure`; @@ -339,7 +340,7 @@ export function displayOtherTaskCountdown(task) { const diff = taskTimes.thisCycleLeaveTimestamp - now.getTime(); resetTimer.innerHTML = `(Available for ${formatCountdown(diff)})`; - // Leaving soon notification (Arrival notification is handled in runAutoResets, the same as always available tasks) + // Leaving soon notification (Arrival notification is handled in otherTaskReset, the same as always available tasks) if (diff < C.MILLISECONDS_PER_HOUR && checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[leaveNotifId] !== cycleNumber) { showNotification(`${task.title} Leaving Soon!`, `Approximately ${Math.round(diff / C.MILLISECONDS_PER_MINUTE)} minutes remaining.`); checklistData.notificationsSent[leaveNotifId] = cycleNumber; @@ -347,7 +348,7 @@ export function displayOtherTaskCountdown(task) { } } else { // task not available resetTimer.innerHTML = `(Available in ${formatCountdown(taskTimes.nextResetTimestamp - now.getTime())})`; - if (checklistData.notificationsSent[leaveNotifId]) {delete checklistData.notificationsSent[leaveNotifId];} + if (checklistData.notificationsSent[leaveNotifId]) { delete checklistData.notificationsSent[leaveNotifId]; } } } else { // always available task resetTimer.innerHTML = `(Resets in ${formatCountdown(taskTimes.nextResetTimestamp - now.getTime())})`; @@ -360,7 +361,7 @@ function runAutoResets() { const todayUTCString = getUTCDateString(now); const lastDailyResetDate = checklistData.lastDailyReset ? new Date(checklistData.lastDailyReset) : null; - let lastDailyResetUTCString = lastDailyResetDate ? getUTCDateString(lastDailyResetDate) : null; + const lastDailyResetUTCString = lastDailyResetDate ? getUTCDateString(lastDailyResetDate) : null; if (!lastDailyResetUTCString || lastDailyResetUTCString !== todayUTCString) { console.log(`Performing daily auto-reset (UTC). Today: ${todayUTCString}, Last Reset: ${lastDailyResetUTCString}`); resetSection("daily", false); @@ -395,7 +396,7 @@ function otherTaskReset(task) { return false; } if (!task.ref) { - console.warn(`[${task.id}] other_* tasks SHOULD specify a "ref". The default ref of 0 ("1970-01-01T00:00:00Z") will be used otherwise.`) + console.warn(`[${task.id}] other_* tasks SHOULD specify a "ref". The default ref of 0 ("1970-01-01T00:00:00Z") will be used otherwise.`); } let didReset = false; @@ -467,7 +468,7 @@ function showNotification(title, body) { } } -function createChecklistItem(task) { +function createChecklistItem(task) { /* eslint-disable-line complexity, max-lines-per-function */ const isChecked = checklistData.progress[task.id] || false; const isAvailable = calcTaskTimes(task, new Date()).isAvailable; @@ -566,7 +567,7 @@ function createChecklistItem(task) { let skipTooltip; if (isAvailable) { let when = "this cycle"; - if (task.id.startsWith("daily_")) { when = "today"; } + if (task.id.startsWith("daily_")) { when = "today"; } /* eslint-disable-line @stylistic/brace-style */ else if (task.id.startsWith("weekly_")) { when = "this week"; } skipTooltip = `Skip task ${when}: ${task.title}`; } else { @@ -623,14 +624,14 @@ function createChecklistItem(task) { subtaskList.appendChild(makeSectionStats(task.id)); calcSectionStats(task.id, subtaskList); - subtaskCollapsible.appendChild(subtaskList) + subtaskCollapsible.appendChild(subtaskList); taskItem.appendChild(subtaskCollapsible); updateIncompleteSubtaskCount(task, taskItem); // On Click -> Collapse/Expand parentHeaderDiv.addEventListener("click", (e) => { - if (e.target !== checkbox && !checkbox.contains(e.target) && !controlsContainer.contains(e.target) && !collapseButton.contains(e.target) ) { + if (e.target !== checkbox && !checkbox.contains(e.target) && !controlsContainer.contains(e.target) && !collapseButton.contains(e.target)) { toggleCollapseSubtasks(task); } }); @@ -641,8 +642,7 @@ function createChecklistItem(task) { // Checkbox Changed -> Change Subtasks Checkboxes checkbox.addEventListener("change", checkboxChangeAction(task)); - - } else { //no subtasks + } else { // no subtasks taskItem.appendChild(checkbox); if (task.icon) { taskItem.appendChild(icon); } taskItem.appendChild(taskDescription); @@ -678,7 +678,7 @@ function checkboxChangeAction(task) { // don't know about the checkbox status of the originating event. So we attach that status to the // `detail` of a CustomEvent, which takes priority over the local status. (Recursive firings like this // allow for nested subtasks of arbitrary depth) - subCheckbox.dispatchEvent(new CustomEvent("change", {detail: currentlyChecked})); + subCheckbox.dispatchEvent(new CustomEvent("change", { detail: currentlyChecked })); }); updateIncompleteSubtaskCount(task); } else { @@ -688,28 +688,28 @@ function checkboxChangeAction(task) { calcSectionStats(task.section); saveData(); - } + }; } function hideOrSkipTaskAction(task, skip) { return (event) => { event.stopPropagation(); - if (skip) { checklistData.skippedTasks[task.id] = true; } + if (skip) { checklistData.skippedTasks[task.id] = true; } /* eslint-disable-line @stylistic/brace-style */ else { checklistData.hiddenTasks[task.id] = true; } const taskItem = event.target.closest(".task-item"); taskItem.classList.add("hidden-task"); updateParentCheckboxes(task); updateSectionControls(taskItem.closest("section").id); saveData(false); - } + }; } function updateParentCheckboxes(task) { let t = task; - while (t.parentId) { // walk up the task tree + while (t.parentId) { // walk up the task tree calcSectionStats(t.parentId); - let parentTaskDefinition = getTaskById(t.parentId); + const parentTaskDefinition = getTaskById(t.parentId); if (parentTaskDefinition && parentTaskDefinition.subtasks) { const allSubtasksDone = parentTaskDefinition.subtasks.every((st) => (checklistData.progress[st.id] || checklistData.hiddenTasks[st.id] || checklistData.skippedTasks[st.id])); @@ -723,7 +723,7 @@ function updateParentCheckboxes(task) { if (parentDescription) { parentDescription.classList.toggle("checked", allSubtasksDone); } } - t = parentTaskDefinition; // move up a level + t = parentTaskDefinition; // move up a level } calcSectionStats(task.section); saveData(); @@ -739,10 +739,10 @@ function toggleCollapseSubtasks(task) { updateIncompleteSubtaskCount(task); } -function updateIncompleteSubtaskCount(task, queryFrom=document) { +function updateIncompleteSubtaskCount(task, queryFrom = document) { if (task.subtasks) { const element = queryFrom.querySelector(`#${task.id} ~ .collapse-btn .incomplete-count > div`); - const unchecked = queryFrom.querySelectorAll(`input[data-parent-id="${task.id}"]:not(:checked, :indeterminate, .hidden-task *)`) + const unchecked = queryFrom.querySelectorAll(`input[data-parent-id="${task.id}"]:not(:checked, :indeterminate, .hidden-task *)`); element.innerText = unchecked.length; element.parentElement.dataset.count = unchecked.length; element.parentElement.title = `${unchecked.length} incomplete subtasks of ${task.title}`; @@ -752,7 +752,7 @@ function updateIncompleteSubtaskCount(task, queryFrom=document) { function taskDialogHeaderSetup(task, dialog) { dialog.querySelector(":scope header .task-title").innerHTML = task?.title || ""; - let taskIcon = dialog.querySelector(":scope .menu-title img.task-icon"); + const taskIcon = dialog.querySelector(":scope .menu-title img.task-icon"); taskIcon.className = "task-icon"; // remove possible `icon-filter` from previous opening taskIcon.src = ""; if (task?.icon) { @@ -774,7 +774,7 @@ function showScheduleAction(task, period, cycleIndex, isAvailable) { thead.innerHTML = ""; tbody.innerHTML = ""; - let header = `Date`; + let header = "Date"; for (const column of cycles[task.id].columns) { header += `${column.name}`; } @@ -810,7 +810,7 @@ function showScheduleAction(task, period, cycleIndex, isAvailable) { tbody.innerHTML += row; } scheduleDialog.showModal(); - } + }; } function showMoreInfoAction(task) { @@ -818,10 +818,10 @@ function showMoreInfoAction(task) { taskDialogHeaderSetup(task, moreInfoDialog); document.getElementById("more-info-content").innerHTML = task.moreInfo; moreInfoDialog.showModal(); - } + }; } -function makeInfoLine(task, appendTo) { +function makeInfoLine(task, appendTo) { /* eslint-disable-line max-lines-per-function */ const hasCycle = Object.hasOwn(cycles, task.id); const hasInfoLine = ["location", "npc", "terminal", "prereq", "info", "moreInfo"].some((prop) => task[prop]); @@ -849,14 +849,12 @@ function makeInfoLine(task, appendTo) { if (ref.getUTCDay() !== 1) { console.warn(`${task.id} cycle ref ${cycles[task.id].ref} is not a Monday`); } - } - else if (task.id.startsWith("daily_")) { + } else if (task.id.startsWith("daily_")) { prefix = "Today"; period = C.MILLISECONDS_PER_DAY; - } - else { + } else { prefix = "Current Cycle"; - if (!isAvailable) {prefix = "Next Cycle";} + if (!isAvailable) { prefix = "Next Cycle"; } period = parseDuration(task.period); } @@ -887,7 +885,7 @@ function makeInfoLine(task, appendTo) { // Info Line if (hasInfoLine) { - if (task.npc && task.terminal) {console.warn(`[${task.id}] Tasks should specify only one of [npc, terminal].`);} + if (task.npc && task.terminal) { console.warn(`[${task.id}] Tasks should specify only one of [npc, terminal].`); } const infoLine = document.createElement("div"); infoLine.classList.add("info-line"); let infoLineHTML = ""; @@ -932,7 +930,7 @@ function populateSection(section) { sectionElement.appendChild(makeSectionStats(section)); calcSectionStats(section); - let updatedParents = new Set(); + const updatedParents = new Set(); forEachTask((task) => { if (task.section === section && task.parentId && !task.subtasks && !updatedParents.has(task.parentId)) { // if the task is a non-root leaf node in the current section, and the first among its siblings @@ -950,7 +948,7 @@ function populateSection(section) { * @param parent - what task list to count stats on. This can be the name of a section, or the id of a task with subtasks. * @param queryFrom - DOM element to find the stats box in. Defaults to `document`. Override this if the element is not inserted into the document yet. */ -function calcSectionStats(parent, queryFrom=document) { +function calcSectionStats(parent, queryFrom = document) { let taskList; if (parent.includes("_")) { taskList = getTaskById(parent).subtasks; @@ -959,15 +957,15 @@ function calcSectionStats(parent, queryFrom=document) { } const statsBox = queryFrom.querySelector(`#stats_${parent} > div`); - let stats = { + const stats = { completed: 0, skipped: 0, - hidden: 0 - } + hidden: 0, + }; for (const task of taskList) { - if (checklistData.progress[task.id]) { stats.completed++; } + if (checklistData.progress[task.id]) { stats.completed++; } if (checklistData.skippedTasks[task.id]) { stats.skipped++; } - if (checklistData.hiddenTasks[task.id]) { stats.hidden++; } + if (checklistData.hiddenTasks[task.id]) { stats.hidden++; } } statsBox.innerHTML = ""; @@ -975,8 +973,8 @@ function calcSectionStats(parent, queryFrom=document) { const statIcons = { completed: "✔", skipped: svgIcons.skipIcon, - hidden: svgIcons.hideIcon - } + hidden: svgIcons.hideIcon, + }; for (const stat in stats) { const statButton = document.createElement("button"); @@ -1054,7 +1052,7 @@ function showHiddenTasksAction(parent, taskList, stat) { } hiddenTasksDialog.showModal(); - } + }; } function hiddenTasksButtonAction(taskElement, task, stat) { @@ -1078,7 +1076,7 @@ function hiddenTasksButtonAction(taskElement, task, stat) { if (!hiddenTasksDialog.querySelector("li > :not(.hidden-task)")) { hiddenTasksDialog.close(); } - } + }; } function resetSpecificButtonState(buttonElement, defaultText, stateKey) { @@ -1087,7 +1085,7 @@ function resetSpecificButtonState(buttonElement, defaultText, stateKey) { confirmState[stateKey].timeout = null; confirmState[stateKey].isConfirming = false; buttonElement.textContent = defaultText; - buttonElement.classList.remove('confirming'); + buttonElement.classList.remove("confirming"); console.log(`Button ${buttonElement.id} state reverted.`); } @@ -1101,10 +1099,12 @@ function handleResetConfirmation(buttonElement, confirmKey, defaultText, resetAc Object.keys(confirmState).forEach((key) => { if (key !== confirmKey && confirmState[key].isConfirming) { let btnElement, btnText; - if (key === 'all') { btnElement = resetButton; btnText = 'Reset All Checks'; } - else if (key === 'daily') { btnElement = resetDailyButton; btnText = 'Reset Daily Checks'; } - else if (key === 'weekly') { btnElement = resetWeeklyButton; btnText = 'Reset Weekly Checks'; } - else if (key === 'unhide') { btnElement = unhideTasksButton; btnText = 'Unhide All Tasks'; } + /* eslint-disable @stylistic/max-statements-per-line, @stylistic/brace-style */ + if (key === "all") { btnElement = resetButton; btnText = "Reset All Checks"; } + else if (key === "daily") { btnElement = resetDailyButton; btnText = "Reset Daily Checks"; } + else if (key === "weekly") { btnElement = resetWeeklyButton; btnText = "Reset Weekly Checks"; } + else if (key === "unhide") { btnElement = unhideTasksButton; btnText = "Unhide All Tasks"; } + /* eslint-enable @stylistic/max-statements-per-line, @stylistic/brace-style */ if (btnElement) { resetSpecificButtonState(btnElement, btnText, key); } @@ -1118,8 +1118,8 @@ function handleResetConfirmation(buttonElement, confirmKey, defaultText, resetAc resetSpecificButtonState(buttonElement, defaultText, confirmKey); } else { confirmState[confirmKey].isConfirming = true; - buttonElement.textContent = 'Are you Sure?'; - buttonElement.classList.add('confirming'); + buttonElement.textContent = "Are you Sure?"; + buttonElement.classList.add("confirming"); clearTimeout(confirmState[confirmKey].timeout); confirmState[confirmKey].timeout = setTimeout(() => { @@ -1138,7 +1138,7 @@ function resetAllAction() { console.log("Checklist reset complete."); } -function resetSection(section, resetAltRefTasks=false) { +function resetSection(section, resetAltRefTasks = false) { const validSections = ["daily", "weekly"]; if (!validSections.includes(section)) { console.error("Section '%s' is not a valid section name: %s", section, validSections); @@ -1160,7 +1160,7 @@ function resetSection(section, resetAltRefTasks=false) { tasks[section].forEach(resetTask); const now = new Date().toISOString(); - if (section === "daily") { checklistData.lastDailyReset = now; } + if (section === "daily") { checklistData.lastDailyReset = now; } /* eslint-disable-line @stylistic/brace-style */ else if (section === "weekly") { checklistData.lastWeeklyReset = now; } saveData(); if (didReset) { @@ -1178,17 +1178,17 @@ function resetWeeklyAction() { } function handleSectionToggle(event) { - const header = event.target.closest('.section-toggle'); + const header = event.target.closest(".section-toggle"); if (!header) { return; } - const contentId = header.getAttribute('aria-controls'); + const contentId = header.getAttribute("aria-controls"); const contentDiv = document.getElementById(contentId); if (!contentDiv) { return; } - const isExpanded = header.getAttribute('aria-expanded') === 'true'; - header.setAttribute('aria-expanded', !isExpanded); - contentDiv.classList.toggle('collapsed', isExpanded); - console.log(`Toggled section ${contentId} to ${!isExpanded ? 'expanded' : 'collapsed'}`); + const isExpanded = header.getAttribute("aria-expanded") === "true"; + header.setAttribute("aria-expanded", !isExpanded); + contentDiv.classList.toggle("collapsed", isExpanded); + console.log(`Toggled section ${contentId} to ${isExpanded ? "collapsed" : "expanded"}`); } function unhideAllAction() { @@ -1208,35 +1208,35 @@ function updateSectionControls(sectionElementId) { const sectionElement = document.getElementById(sectionElementId); if (!sectionElement) { return; } - const hideSectionButton = sectionElement.querySelector('.hide-section-button'); + const hideSectionButton = sectionElement.querySelector(".hide-section-button"); if (!hideSectionButton) { return; } if (checklistData.manuallyHiddenSections[sectionElementId]) { - sectionElement.classList.add('section-is-hidden-by-user'); - hideSectionButton.classList.remove('visible'); + sectionElement.classList.add("section-is-hidden-by-user"); + hideSectionButton.classList.remove("visible"); return; } else { - sectionElement.classList.remove('section-is-hidden-by-user'); + sectionElement.classList.remove("section-is-hidden-by-user"); } - const contentUl = sectionElement.querySelector('.section-content ul'); + const contentUl = sectionElement.querySelector(".section-content ul"); if (!contentUl) { - hideSectionButton.classList.remove('visible'); + hideSectionButton.classList.remove("visible"); return; } - const allTaskItems = Array.from(contentUl.querySelectorAll('li.task-item')); + const allTaskItems = Array.from(contentUl.querySelectorAll("li.task-item")); if (allTaskItems.length === 0) { - hideSectionButton.classList.remove('visible'); + hideSectionButton.classList.remove("visible"); return; } const allTasksIndividuallyHidden = allTaskItems.every((item) => item.classList.contains("hidden-task")); if (allTasksIndividuallyHidden) { - hideSectionButton.classList.add('visible'); + hideSectionButton.classList.add("visible"); } else { - hideSectionButton.classList.remove('visible'); + hideSectionButton.classList.remove("visible"); } } @@ -1259,7 +1259,7 @@ function saveData(showStatus = true) { } catch (e) { console.error("Error saving data to localStorage:", e); let userMessage = "Could not save progress."; - if (e.name === 'QuotaExceededError' || (e.code && (e.code === 22 || e.code === 1014))) { + if (e.name === "QuotaExceededError" || (e.code && (e.code === 22 || e.code === 1014))) { userMessage = "Could not save progress. Browser storage might be full."; } displayError(userMessage); @@ -1273,7 +1273,7 @@ function loadData() { const parsedData = JSON.parse(savedData); if (parsedData && typeof parsedData === "object") { for (const key of Object.keys(checklistData)) { - if (key in parsedData) { + if (key in parsedData) { /* eslint-disable-line max-depth */ checklistData[key] = parsedData[key]; } } @@ -1286,7 +1286,7 @@ function loadData() { } } -export function loadAndInitializeApp() { +export function loadAndInitializeApp() { /* eslint-disable-line max-lines-per-function */ initializeDOMElements(); hideError(); loadThemePreference(); @@ -1299,7 +1299,7 @@ export function loadAndInitializeApp() { updateLastSavedDisplay(checklistData.lastSaved); // Setup event listeners - if (appVersionElement) { appVersionElement.textContent = APP_VERSION; } + if (appVersionElement) { appVersionElement.textContent = APP_VERSION; } /* eslint-disable-line @stylistic/brace-style */ else { console.error("App version element not found!"); } if (gitHashElement) { @@ -1307,8 +1307,9 @@ export function loadAndInitializeApp() { gitHashElement.href = `https://github.com/warframe-tools/Task-Checklist/tree/${GIT_COMMIT_HASH_LONG}`; } else { console.error("git hash element not found!"); } - if (wfVersionElement) { wfVersionElement.textContent = `Warframe Version ${WARFRAME_VERSION}`; } - else { console.error("Warframe version element not found!"); } + if (wfVersionElement) { + wfVersionElement.textContent = `Warframe Version ${WARFRAME_VERSION}`; + } else { console.error("Warframe version element not found!"); } if (hideCompletedToggle) { hideCompletedToggle.addEventListener("change", (event) => { @@ -1318,59 +1319,64 @@ export function loadAndInitializeApp() { }); hideCompletedToggle.checked = checklistData.hideCompletedTasks; bodyElement.classList.toggle("hide-completed-tasks", checklistData.hideCompletedTasks); - } - else { console.error("Hide Completed Toggle not found!"); } + } else { console.error("Hide Completed Toggle not found!"); } - if (resetDailyButton) { resetDailyButton.addEventListener('click', () => handleResetConfirmation(resetDailyButton, 'daily', 'Reset Daily Checks', resetDailyAction)); } - else { console.error("Reset Daily button element not found!"); } + if (resetDailyButton) { + resetDailyButton.addEventListener("click", () => handleResetConfirmation(resetDailyButton, "daily", "Reset Daily Checks", resetDailyAction)); + } else { console.error("Reset Daily button element not found!"); } - if (resetWeeklyButton) { resetWeeklyButton.addEventListener('click', () => handleResetConfirmation(resetWeeklyButton, 'weekly', 'Reset Weekly Checks', resetWeeklyAction)); } - else { console.error("Reset Weekly button element not found!"); } + if (resetWeeklyButton) { + resetWeeklyButton.addEventListener("click", () => handleResetConfirmation(resetWeeklyButton, "weekly", "Reset Weekly Checks", resetWeeklyAction)); + } else { console.error("Reset Weekly button element not found!"); } - if (resetButton) { resetButton.addEventListener('click', () => handleResetConfirmation(resetButton, 'all', 'Reset All Checks', resetAllAction)); } - else { console.error("Reset All button element not found!"); } + if (resetButton) { + resetButton.addEventListener("click", () => handleResetConfirmation(resetButton, "all", "Reset All Checks", resetAllAction)); + } else { console.error("Reset All button element not found!"); } - if (unhideTasksButton) { unhideTasksButton.addEventListener('click', () => handleResetConfirmation(unhideTasksButton, 'unhide', 'Unhide All Tasks', unhideAllAction)); } - else { console.error("Unhide Tasks button not found!");} + if (unhideTasksButton) { + unhideTasksButton.addEventListener("click", () => handleResetConfirmation(unhideTasksButton, "unhide", "Unhide All Tasks", unhideAllAction)); + } else { console.error("Unhide Tasks button not found!"); } - if (themeToggleButton) { themeToggleButton.addEventListener('click', handleThemeToggle); } - else { console.error("Theme toggle button not found!"); } + if (themeToggleButton) { + themeToggleButton.addEventListener("click", handleThemeToggle); + } else { console.error("Theme toggle button not found!"); } - if (hamburgerButton) { hamburgerButton.addEventListener('click', () => {optionsMenu.showModal();}); } - else { console.error("Hamburger button not found!"); } + if (hamburgerButton) { + hamburgerButton.addEventListener("click", () => { optionsMenu.showModal(); }); + } else { console.error("Hamburger button not found!"); } sectionToggles.forEach((toggle) => { toggle.addEventListener("click", handleSectionToggle); }); try { - document.getElementById('checklist-container').addEventListener('click', function(event) { - if (event.target.classList.contains('hide-section-button')) { + document.getElementById("checklist-container").addEventListener("click", (event) => { + if (event.target.classList.contains("hide-section-button")) { const sectionId = event.target.dataset.sectionId; if (sectionId) { const sectionElement = document.getElementById(sectionId); if (sectionElement) { checklistData.manuallyHiddenSections[sectionId] = true; - sectionElement.classList.add('section-is-hidden-by-user'); - event.target.classList.remove('visible'); + sectionElement.classList.add("section-is-hidden-by-user"); + event.target.classList.remove("visible"); saveData(false); console.log(`Section ${sectionId} manually hidden.`); } } } }); - } catch (e) { + } catch { console.error("Checklist container not found!"); } if (errorCloseButton) { - errorCloseButton.addEventListener('click', hideError); + errorCloseButton.addEventListener("click", hideError); } else { console.error("Error close button not found!"); } if (errorCopyButton) { - errorCopyButton.addEventListener('click', copyErrorToClipboard); + errorCopyButton.addEventListener("click", copyErrorToClipboard); } else { console.error("Error copy button not found!"); } for (const dialog of document.getElementsByTagName("dialog")) { @@ -1398,7 +1404,7 @@ document.addEventListener("DOMContentLoaded", () => { try { loadAndInitializeApp(); startCountdown(); - } catch(error) { + } catch (error) { console.error("Critical Error during app.js initialization:", error); const errDisp = document.getElementById("error-display"); const errMsg = document.getElementById("error-message"); diff --git a/sources/js/constants.js b/sources/js/constants.js index 2c8253b..a2f4d11 100644 --- a/sources/js/constants.js +++ b/sources/js/constants.js @@ -4,7 +4,7 @@ export const MILLISECONDS_PER_MINUTE = 60 * MILLISECONDS_PER_SECOND; export const MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE; export const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR; -export const SERVER_TIMEZONE = "America/Toronto"; // used for determining Daylight Saving Time +export const SERVER_TIMEZONE = "America/Toronto"; // used for determining Daylight Saving Time export const BASE_OF_OPERATIONS_TOOLTIP = 'Base of Operations'; @@ -17,7 +17,7 @@ export const TASKLIST_STAT_NAMES = { export const TASKLIST_STAT_PROPERTIES = { "completed": "progress", "skipped": "skippedTasks", - "hidden": "hiddenTasks" + "hidden": "hiddenTasks", }; export const SECTION_NAMES = { diff --git a/sources/js/functions.js b/sources/js/functions.js index 61e510d..99060f6 100644 --- a/sources/js/functions.js +++ b/sources/js/functions.js @@ -28,7 +28,7 @@ export function newChecklistData() { }; } -const taskIcons = import.meta.glob("../img/icons/**/*.png", {eager: true, query: '?url', import: 'default'}); +const taskIcons = import.meta.glob("../img/icons/**/*.png", { eager: true, query: "?url", import: "default" }); export function iconURL(iconName) { return taskIcons["../img/icons/" + iconName]; } @@ -51,7 +51,7 @@ export function makeTaskIcon(task) { export function makeCycleIcon(cycleData) { if (cycleData.icon) { - const src = iconURL(`cycles/${cycleData.icon}`) + const src = iconURL(`cycles/${cycleData.icon}`); let classList = "cycle-icon"; if (cycleData.iconFilter) { classList += " icon-filter"; @@ -63,12 +63,15 @@ export function makeCycleIcon(cycleData) { } export function formatTimestamp(timestamp) { - if (!timestamp) return 'Never'; + if (!timestamp) { return "Never"; } try { const date = new Date(timestamp); - if (isNaN(date.getTime())) return 'Invalid Date'; - return date.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }); - } catch (e) { console.error("Error formatting timestamp:", e); return 'Error'; } + if (isNaN(date.getTime())) { return "Invalid Date"; } + return date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); + } catch (e) { + console.error("Error formatting timestamp:", e); + return "Error"; + } } export function getMostRecentMondayMidnightUTC() { @@ -91,8 +94,8 @@ export function getNextDailyMidnightUTC() { export function getUTCDateString(dateObj) { const year = dateObj.getUTCFullYear(); - const month = (dateObj.getUTCMonth() + 1).toString().padStart(2, '0'); - const day = dateObj.getUTCDate().toString().padStart(2, '0'); + const month = (dateObj.getUTCMonth() + 1).toString().padStart(2, "0"); + const day = dateObj.getUTCDate().toString().padStart(2, "0"); return `${year}-${month}-${day}`; } @@ -113,7 +116,7 @@ export function formatCountdown(ms) { ms %= C.MILLISECONDS_PER_MINUTE; const seconds = Math.floor(ms / C.MILLISECONDS_PER_SECOND); - const pad = (num) => String(num).padStart(2, '0'); + function pad(num) { return String(num).padStart(2, "0"); } if (days > 0) { return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; @@ -127,16 +130,16 @@ export function formatCountdown(ms) { * Case insensitive. Integers only. */ export function parseDuration(str) { - if (!str || typeof str !== "string") {return undefined;} + if (!str || typeof str !== "string") { return undefined; } const map = { "d": C.MILLISECONDS_PER_DAY, "h": C.MILLISECONDS_PER_HOUR, "m": C.MILLISECONDS_PER_MINUTE, - "s": C.MILLISECONDS_PER_SECOND + "s": C.MILLISECONDS_PER_SECOND, }; return str.toLowerCase().matchAll(/((\d+)([a-z]))/g).toArray().reduce((acc, curr) => { - const n = parseInt(curr[2]); + const n = parseInt(curr[2], 10); let mult; if (Object.hasOwn(map, curr[3])) { mult = map[curr[3]]; @@ -150,10 +153,10 @@ export function parseDuration(str) { /** returns whether the given date is in Daylight Saving Time in the named timezone */ export function isDst(date, timezone) { - if (typeof date === "undefined" || Number.isNaN(date)) {return undefined;} - if (typeof date === "number" || typeof date === "string") {date = new Date(date);} + if (typeof date === "undefined" || Number.isNaN(date)) { return undefined; } + if (typeof date === "number" || typeof date === "string") { date = new Date(date); } - const dateFormat = Intl.DateTimeFormat("en-CA", {timeZone: timezone, timeZoneName: "shortOffset"}); + const dateFormat = Intl.DateTimeFormat("en-CA", { timeZone: timezone, timeZoneName: "shortOffset" }); function wholeHourOffset(d) { // returns the whole hour part of the UTC offset as an integer. @@ -165,7 +168,7 @@ export function isDst(date, timezone) { const year = date.getUTCFullYear(); const janOffset = wholeHourOffset(new Date(Date.UTC(year, 0, 1))); const julOffset = wholeHourOffset(new Date(Date.UTC(year, 6, 1))); - if (janOffset === julOffset) {return false;} + if (janOffset === julOffset) { return false; } const currentOffset = wholeHourOffset(date); const dstOffset = Math.max(janOffset, julOffset); return currentOffset === dstOffset; @@ -202,14 +205,14 @@ export function calcTaskTimes(task, date) { let thisCycleLeaveTimestamp = prevResetTimestamp + parseDuration(task.duration); if (task.observesDst) { - if (isDst(nextResetTimestamp, C.SERVER_TIMEZONE)) {nextResetTimestamp -= C.MILLISECONDS_PER_HOUR;} - if (isDst(thisCycleLeaveTimestamp, C.SERVER_TIMEZONE)) {thisCycleLeaveTimestamp -= C.MILLISECONDS_PER_HOUR;} + if (isDst(nextResetTimestamp, C.SERVER_TIMEZONE)) { nextResetTimestamp -= C.MILLISECONDS_PER_HOUR; } + if (isDst(thisCycleLeaveTimestamp, C.SERVER_TIMEZONE)) { thisCycleLeaveTimestamp -= C.MILLISECONDS_PER_HOUR; } } let isAvailable = true; - if (task.duration) {isAvailable = (date.getTime() < thisCycleLeaveTimestamp);} + if (task.duration) { isAvailable = (date.getTime() < thisCycleLeaveTimestamp); } - return {nextResetTimestamp, thisCycleLeaveTimestamp, isAvailable}; + return { nextResetTimestamp, thisCycleLeaveTimestamp, isAvailable }; } export function factionIcon(name) { diff --git a/sources/js/icons.js b/sources/js/icons.js index 1cdbffd..d9f8cfb 100644 --- a/sources/js/icons.js +++ b/sources/js/icons.js @@ -1,3 +1,5 @@ +/* eslint-disable @stylistic/operator-linebreak, @stylistic/indent */ + /* Heroicons https://heroicons.com/ * MIT license https://github.com/tailwindlabs/heroicons/blob/master/LICENSE */ @@ -25,7 +27,7 @@ const eyeOutline = c-4.638 0-8.573-3.007-9.963-7.178Z" /> -` +`; const bellOutline = ` @@ -100,7 +102,7 @@ const calendarDays16 = a.75.75 0 0 0-1.5 0V3h-5V1.75A.75.75 0 0 0 4.75 1ZM3.5 7a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v4.5a1 1 0 0 1-1 1h-7 a1 1 0 0 1-1-1V7Z" /> -` +`; /* Lucide icons https://github.com/lucide-icons/lucide @@ -110,11 +112,13 @@ const calendarDays16 = // https://icon-sets.iconify.design/lucide/skip-forward/ const skipForward = ` - -` +`; + +/* eslint-enable @stylistic/indent */ export { chevronDownOutline as collapseIcon, eyeSlashOutline as hideIcon, diff --git a/sources/js/moreInfo.js b/sources/js/moreInfo.js index e213152..24846fb 100644 --- a/sources/js/moreInfo.js +++ b/sources/js/moreInfo.js @@ -12,7 +12,7 @@ const factions = `

The Faction Syndicates are ${factionIcon("FactionSigilBusiness.png")}The Perrin Sequence, ${factionIcon("FactionSigilAssassins.png")}Red Veil, and ${factionIcon("FactionSigilChurch.png")}New Loka.

-

Pledge your loyalty to a faction syndicate at the Syndicates console in your ${C.BASE_OF_OPERATIONS_TOOLTIP}.

` +

Pledge your loyalty to a faction syndicate at the Syndicates console in your ${C.BASE_OF_OPERATIONS_TOOLTIP}.

`; export default { daily_first_win_bonus: `

Your first completed mission after the daily reset gives double base credit rewards. This only applies to the end-of-mission bonus, and does not apply to credits picked up in the mission.

@@ -70,5 +70,5 @@ export default {

Each week, a random planet will reward double bonus Vault resources (and double reward track progress) for missions played there.

Check the current boosted planet and track your reward progress in the Clan menu.

Note: You can use the "Clan Only" matchmaking option to join squads with your Clan or Alliance members, but playing with Alliance members does not give bonus Vault resources or reward progress.

-

* Descendia missions do not contribute to Clan Weekly Initiatives.

` -} +

* Descendia missions do not contribute to Clan Weekly Initiatives.

`, +}; diff --git a/sources/tests/app.test.js b/sources/tests/app.test.js index 49fde38..f5f35b3 100644 --- a/sources/tests/app.test.js +++ b/sources/tests/app.test.js @@ -9,7 +9,7 @@ beforeEach(() => { afterEach(() => { vi.useRealTimers(); -}) +}); describe("displayOtherTaskCountdown({period, duration, ref, observesDst})", () => { beforeEach(() => { @@ -50,9 +50,9 @@ describe("displayOtherTaskCountdown({period, duration, ref, observesDst})", () = ["14d", "2d", "2022-12-30T09:00:00-05:00", true, "2024-11-03T09:00:00-05:00", "(Available in 12d 00:00:00)"], ])("(%s, %s, %s, %s) @ %s -> %s", ([period, duration, ref, observesDst, date, countdown]) => { vi.setSystemTime(date); - app.displayOtherTaskCountdown({id: "other_test", period, duration, ref, observesDst}); + app.displayOtherTaskCountdown({ id: "other_test", period, duration, ref, observesDst }); expect(document.querySelector(".other-countdown").textContent).toEqual(countdown); - }) + }); }); describe("getTaskById", () => { @@ -64,8 +64,8 @@ describe("getTaskById", () => { ["weekly_netracells", "NetraRequiemIcon.png"], // 2nd level weekly ["daily_kim_hex_parent", "KIM/RetroPfpG.png"], // both a parent and child ["daily_kim_kaya", "KIM/RetroPfpH.png"], // 3rd level task - ["fake_task_id", undefined] + ["fake_task_id", undefined], ])("%s", ([id, icon]) => { expect(app.getTaskById(id)?.icon).toEqual(icon); - }) + }); }); diff --git a/sources/tests/functions.test.js b/sources/tests/functions.test.js index dbb78d6..7cc162e 100644 --- a/sources/tests/functions.test.js +++ b/sources/tests/functions.test.js @@ -47,7 +47,7 @@ describe("Daylight Saving Time", () => { ["2025-10-05T01:59:59+10:30", "Australia/Lord_Howe", false], // just before start ["2025-10-05T02:00:00+10:30", "Australia/Lord_Howe", true], // just after start ["2025-10-05T03:00:00+11:00", "Australia/Lord_Howe", true], // just after start - ])(`isDst(%s, %s) -> %o`, ([d, tz, expected]) => { + ])("isDst(%s, %s) -> %o", ([d, tz, expected]) => { expect(isDst(new Date(d), tz)).toBe(expected); expect(isDst(new Date(d).getTime(), tz)).toBe(expected); expect(isDst(d, tz)).toBe(expected); @@ -63,8 +63,8 @@ describe("parseDuration", () => { ["12h", 43_200_000], ["14d", 1_209_600_000], ["2d3H10M12s", 184_212_000], - ["1m, 359h 1d 3d, 5m6s 4h", 1_652_766_000] - ])(`parseDuration(%s) -> %i`, ([d, expected]) => { + ["1m, 359h 1d 3d, 5m6s 4h", 1_652_766_000], + ])("parseDuration(%s) -> %i", ([d, expected]) => { expect(parseDuration(d)).toEqual(expected); }); @@ -75,8 +75,8 @@ describe("parseDuration", () => { test.for([ ["1s 10n", 1, 1000], ["10y5w2d3h8x10m12s13g", 4, 184_212_000], - ["1m, 359h 9p1d 3d, 5m6s 4h\t1234v", 2, 1_652_766_000] - ])(`parseDuration(%s) warn %i`, ([d, warnings, expected]) => { + ["1m, 359h 9p1d 3d, 5m6s 4h\t1234v", 2, 1_652_766_000], + ])("parseDuration(%s) warn %i", ([d, warnings, expected]) => { const consoleWarn = vi.spyOn(console, "warn"); expect(parseDuration(d)).toEqual(expected); expect(consoleWarn).toHaveBeenCalledTimes(warnings); diff --git a/sources/tests/tasks.test.js b/sources/tests/tasks.test.js index 9988ade..4a83761 100644 --- a/sources/tests/tasks.test.js +++ b/sources/tests/tasks.test.js @@ -2,10 +2,10 @@ import { describe, expect, test } from "vitest"; import Ajv from "ajv/dist/2020"; import addFormats from "ajv-formats"; -import tasks from "../js/tasks.json" with {type: "json"}; -import tasks_schema from "./tasks.schema.json" with {type: "json"}; -import cycles from "../js/cycles.json" with {type: "json"}; -import cycles_schema from "./cycles.schema.json" with {type: "json"}; +import tasks from "../js/tasks.json" with { type: "json" }; +import tasks_schema from "./tasks.schema.json" with { type: "json" }; +import cycles from "../js/cycles.json" with { type: "json" }; +import cycles_schema from "./cycles.schema.json" with { type: "json" }; import moreInfo from "../js/moreInfo"; const ajv = new Ajv({ allErrors: true }); @@ -17,7 +17,7 @@ const validate_cycles_schema = ajv.compile(cycles_schema); describe("valildate task definitions", () => { test.for([ ["tasks", validate_tasks_schema, tasks], - ["cycles", validate_cycles_schema, cycles] + ["cycles", validate_cycles_schema, cycles], ])("validate %s against schema", ([which, validation_function, data]) => { const valid = validation_function(data); if (!valid) { console.error(validation_function.errors); } @@ -26,8 +26,8 @@ describe("valildate task definitions", () => { }); // flatten the nested tree of [sub]tasks into an array - let stack = []; - let flatTasks = []; + const stack = []; + const flatTasks = []; for (const section in tasks) { for (const t of tasks[section]) { stack.push(t); @@ -46,7 +46,7 @@ describe("valildate task definitions", () => { const task_ids = flatTasks.map((t) => t.id); test("verify unique task ids", () => { - let used_ids = []; + const used_ids = []; for (const id of task_ids) { expect(used_ids, "task ids must be unique").not.toContain(id); used_ids.push(id); @@ -58,7 +58,7 @@ describe("valildate task definitions", () => { describe("verify equal `order` lengths", () => { test.for(cycle_keys_for_test)("%s", ([task_id]) => { for (const col of cycles[task_id].columns) { - expect(col.order.length, "column lengths are not equal").toEqual(cycles[task_id].columns[0].order.length) + expect(col.order.length, "column lengths are not equal").toEqual(cycles[task_id].columns[0].order.length); } }); }); diff --git a/sources/tests/version.test.js b/sources/tests/version.test.js index 4779748..6dac8a3 100644 --- a/sources/tests/version.test.js +++ b/sources/tests/version.test.js @@ -12,6 +12,6 @@ describe("check version numbers", () => { test("SECURITY.md", () => { const security = readFileSync(path.resolve(__dirname, "../../SECURITY.md"), "utf-8"); const major = security.match(/\| Version \| Supported\s*\|[\r\n]*\| -* \| -* \|[\r\n]*\| (\d+)\.x\.x/)[1]; - expect(major, "SECURITY.md version number does not match app.js APP_VERSION major version number").toEqual(APP_VERSION.split(".")[0]) + expect(major, "SECURITY.md version number does not match app.js APP_VERSION major version number").toEqual(APP_VERSION.split(".")[0]); }); }); diff --git a/stylelint.config.mjs b/stylelint.config.mjs new file mode 100644 index 0000000..4fa8f81 --- /dev/null +++ b/stylelint.config.mjs @@ -0,0 +1,45 @@ +/** @type {import("stylelint").Config} */ +export default { + "referenceFiles": ["sources/**/*.css"], + "extends": [ + "stylelint-config-standard", + "@stylistic/stylelint-config", + ], + "reportInvalidScopeDisables": true, + "reportNeedlessDisables": true, + "reportUnscopedDisables": true, + "rules": { + "font-family-name-quotes": "always-unless-keyword", + "value-keyword-case": ["lower", { "ignoreKeywords": ["currentColor"] }], + "color-hex-length": null, + "property-no-vendor-prefix": [true, { "severity": "warning" }], + "selector-no-deprecated": true, + "no-descending-specificity": [true, { "severity": "warning" }], + "selector-no-invalid": true, + "no-unknown-animations": true, + "no-unknown-custom-media": true, + "no-unknown-custom-properties": true, + "unit-no-unknown": true, + "at-rule-property-required-list": { + "font-face": ["font-family", "src", "font-style", "font-weight", "font-display"], + }, + "color-named": ["always-where-possible", { "severity": "warning" }], + "declaration-no-important": [true, { "severity": "warning" }], + "unit-disallowed-list": ["ms"], // use "s" instead + "font-weight-notation": "numeric", + // complexity + "max-nesting-depth": [3, { "severity": "warning" }], + "selector-max-compound-selectors": [5, { "severity": "warning" }], + // empty lines + "rule-empty-line-before": null, + "at-rule-empty-line-before": null, + "comment-empty-line-before": null, + "declaration-empty-line-before": null, + "custom-property-empty-line-before": null, + // stylistic + "@stylistic/indentation": [4, { "ignore": "value" }], + "@stylistic/declaration-colon-newline-after": null, + "@stylistic/max-line-length": null, + "@stylistic/unicode-bom": "never", + }, +}; diff --git a/vite.config.js b/vite.config.js index c4d7e3d..68cac98 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,7 +11,7 @@ export default defineConfig(({ mode }) => ({ cacheDir: "../.vite", plugins: [ - mode === "release" ? viteSingleFile({removeViteModuleLoader: true}) : null, + mode === "release" ? viteSingleFile({ removeViteModuleLoader: true }) : null, ], build: { @@ -24,7 +24,7 @@ export default defineConfig(({ mode }) => ({ restoreMocks: true, coverage: { reportsDirectory: "../.coverage", - exclude: ["img/", "*.json"] - } - } + exclude: ["img/", "*.json"], + }, + }, })); From 1793d10993902603dc4a8d85ad12273579d6c515 Mon Sep 17 00:00:00 2001 From: AnSq Date: Thu, 6 Aug 2026 12:49:48 -0700 Subject: [PATCH 2/3] baro fixes: * now becomes locked when becoming unavailable while not checked * now becomes unlocked when becoming available without needing a refresh * v5.3.1 --- package.json | 2 +- sources/js/app.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 63d51c3..e6f15c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-checklist", - "version": "5.3", + "version": "5.3.1", "description": "A Warframe Task Checklist to track daily, weekly, and other tasks. Built with HTML, CSS, and vanilla JavaScript, processed with Vite.", "type": "module", "scripts": { diff --git a/sources/js/app.js b/sources/js/app.js index 4fe1600..f1a8685 100644 --- a/sources/js/app.js +++ b/sources/js/app.js @@ -34,7 +34,7 @@ const dailyBackgroundImageIds = [ "bg-image-4", // Add more IDs if you add more background image divs in HTML ]; -export const APP_VERSION = "5.3"; +export const APP_VERSION = "5.3.1"; const GIT_COMMIT_HASH_LONG = import.meta.env.VITE_GIT_COMMIT_HASH; const GIT_COMMIT_HASH = GIT_COMMIT_HASH_LONG.slice(0, 7); const WARFRAME_VERSION = "43.0.8"; @@ -406,15 +406,16 @@ function otherTaskReset(task) { const lastResetTime = checklistData.lastTaskResetTimes[task.id] || 0; const lastResetCycleNumber = calcCycleNumber(task, lastResetTime); - if (checklistData.progress[task.id] && !calcTaskTimes(task, now).isAvailable) { + if (!calcTaskTimes(task, now).isAvailable && (checklistData.progress[task.id] || !document.getElementById(task.id)?.indeterminate)) { // Uncheck unavailable task checklistData.progress[task.id] = false; + console.log(`${task.id} is now unavailalbe`); didReset = true; } if (cycleNumber > lastResetCycleNumber) { // Reset task - if (checklistData.progress[task.id] || checklistData.skippedTasks[task.id]) { + if (checklistData.progress[task.id] || checklistData.skippedTasks[task.id] || task.duration) { checklistData.progress[task.id] = false; checklistData.skippedTasks[task.id] = false; console.log(`Resetting task: ${task.id}`); From e363adb9df9e026e76ed32f0ca1677a181293f22 Mon Sep 17 00:00:00 2001 From: AnSq Date: Sat, 8 Aug 2026 16:51:46 -0700 Subject: [PATCH 3/3] cycle schedule DST fix: Fixed tasks with cycle schedules that observe DST (only Baro) not showing the correct current offering when they reset when DST is active. * renamed "cycle number" to "reset count" because it doesn't have anything to do with the cycle schedule feature and that's confusing * all daily and weekly tasks are now prepped with a period, not just alternate ref ones * new `fixTimeTravel` function to aid with debugging * removed some error checking for stuff that's checked in tests * added test to validate cycle refs --- sources/js/app.js | 90 +++++++++++++++++++------------- sources/js/functions.js | 14 +++-- sources/tests/cycles.schema.json | 2 +- sources/tests/tasks.test.js | 24 ++++++++- 4 files changed, 88 insertions(+), 42 deletions(-) diff --git a/sources/js/app.js b/sources/js/app.js index f1a8685..59f37b0 100644 --- a/sources/js/app.js +++ b/sources/js/app.js @@ -14,7 +14,7 @@ import { getUTCDayOfYear, formatCountdown, parseDuration, - calcCycleNumber, + calcResetCount, makeInfoLineItem, calcTaskTimes, makeSectionStats, @@ -149,9 +149,7 @@ function prepTasks() { task.section = task.id.split("_")[0]; if (["daily", "weekly"].includes(task.section)) { - if (task.ref) { // alternate ref tasks need a period for countdown and reset to work correctly - task.period = (task.section === "daily") ? "1d" : "7d"; - } + task.period = (task.section === "daily") ? "1d" : "7d"; } if (task.id in moreInfo) { @@ -331,7 +329,7 @@ export function displayOtherTaskCountdown(task) { const now = new Date(); const taskTimes = calcTaskTimes(task, now); - const cycleNumber = calcCycleNumber(task, now); + const resetCount = calcResetCount(task, now); if (task.duration) { // intermittently available task (e.g., Baro) const leaveNotifId = `${task.id}/departure`; @@ -341,9 +339,9 @@ export function displayOtherTaskCountdown(task) { resetTimer.innerHTML = `(Available for ${formatCountdown(diff)})`; // Leaving soon notification (Arrival notification is handled in otherTaskReset, the same as always available tasks) - if (diff < C.MILLISECONDS_PER_HOUR && checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[leaveNotifId] !== cycleNumber) { + if (diff < C.MILLISECONDS_PER_HOUR && checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[leaveNotifId] !== resetCount) { showNotification(`${task.title} Leaving Soon!`, `Approximately ${Math.round(diff / C.MILLISECONDS_PER_MINUTE)} minutes remaining.`); - checklistData.notificationsSent[leaveNotifId] = cycleNumber; + checklistData.notificationsSent[leaveNotifId] = resetCount; saveData(false); } } else { // task not available @@ -355,7 +353,39 @@ export function displayOtherTaskCountdown(task) { } } +/** + * Time travel (adjusting the system clock) may be used for debugging purposes, which leaves behind some annoying + * artifacts in the save data. This cleans them up. + */ +function fixTimeTravel() { + const now = new Date(); + + for (const prop of ["lastSaved", "lastDailyReset", "lastWeeklyReset"]) { + if (new Date(checklistData[prop]) > now) { + checklistData[prop] = null; + console.warn(`fixed time travel of ${prop}`); + } + } + + for (const taskId in checklistData.lastTaskResetTimes) { + if (checklistData.lastTaskResetTimes[taskId] > now.getTime()) { + checklistData.lastTaskResetTimes[taskId] = 0; + console.warn(`fixed time travel of ${taskId} reset`); + } + } + + for (const notifId in checklistData.notificationsSent) { + const taskId = notifId.split("/")[0]; + const resetCount = calcResetCount(getTaskById(taskId), now); + if (checklistData.notificationsSent[notifId] > resetCount) { + delete checklistData.notificationsSent[notifId]; + console.warn(`fixed time travel of ${notifId} notification`); + } + } +} + function runAutoResets() { + fixTimeTravel(); const now = new Date(); const nowUTCTimestamp = now.getTime(); const todayUTCString = getUTCDateString(now); @@ -389,22 +419,16 @@ function runAutoResets() { } function otherTaskReset(task) { - if (["daily", "weekly"].includes(task.section) && !task.ref) { // daily and weekly tasks with an alternate ref are handled as "other" tasks + if (["daily", "weekly"].includes(task.section) && !task.ref) { // daily and weekly tasks without an alternate ref are not handled as "other" tasks return false; - } else if (!task.period) { - console.error(`[${task.id}] other_* tasks MUST specify a "period"`); - return false; - } - if (!task.ref) { - console.warn(`[${task.id}] other_* tasks SHOULD specify a "ref". The default ref of 0 ("1970-01-01T00:00:00Z") will be used otherwise.`); } let didReset = false; const now = new Date(); - const cycleNumber = calcCycleNumber(task, now); + const resetCount = calcResetCount(task, now); const lastResetTime = checklistData.lastTaskResetTimes[task.id] || 0; - const lastResetCycleNumber = calcCycleNumber(task, lastResetTime); + const lastResetCount = calcResetCount(task, lastResetTime); if (!calcTaskTimes(task, now).isAvailable && (checklistData.progress[task.id] || !document.getElementById(task.id)?.indeterminate)) { // Uncheck unavailable task @@ -413,7 +437,7 @@ function otherTaskReset(task) { didReset = true; } - if (cycleNumber > lastResetCycleNumber) { + if (resetCount > lastResetCount) { // Reset task if (checklistData.progress[task.id] || checklistData.skippedTasks[task.id] || task.duration) { checklistData.progress[task.id] = false; @@ -425,14 +449,14 @@ function otherTaskReset(task) { // Delete old notification record const notifSent = checklistData.notificationsSent[task.id]; - if (notifSent && notifSent !== cycleNumber) { + if (notifSent && notifSent !== resetCount) { delete checklistData.notificationsSent[task.id]; } // Send and record new notification - if (checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[task.id] !== cycleNumber) { + if (checklistData.notificationPreferences[task.id] && checklistData.notificationsSent[task.id] !== resetCount) { showNotification(`${task.title} has reset!`, "Vendor stock may have updated."); - checklistData.notificationsSent[task.id] = cycleNumber; + checklistData.notificationsSent[task.id] = resetCount; saveData(false); } } @@ -526,7 +550,7 @@ function createChecklistItem(task) { /* eslint-disable-line complexity, max-line } // Reset Timer - if (task.period) { + if (task.ref) { const resetTimer = document.createElement("span"); resetTimer.classList.add("other-countdown"); resetTimer.textContent = "(Loading...)"; @@ -764,9 +788,7 @@ function taskDialogHeaderSetup(task, dialog) { } } -function showScheduleAction(task, period, cycleIndex, isAvailable) { - const cycleCount = cycles[task.id].columns[0].order.length; - +function showScheduleAction(task, cycleIndex, isAvailable) { return () => { taskDialogHeaderSetup(task, scheduleDialog); @@ -782,6 +804,9 @@ function showScheduleAction(task, period, cycleIndex, isAvailable) { header += ""; thead.innerHTML += header; + const cycleCount = cycles[task.id].columns[0].order.length; + const period = parseDuration(task.period); + const now = new Date(); const ref = new Date(cycles[task.id].ref); const cyclesSinceRef = Math.floor((now.getTime() - ref.getTime()) / period) + (isAvailable ? 0 : 1); // add 1 if unavailable to skip the current cycle for unavailable intermittent tasks @@ -842,24 +867,19 @@ function makeInfoLine(task, appendTo) { /* eslint-disable-line max-lines-per-fun const ref = new Date(cycles[task.id].ref); const cycleCount = cycles[task.id].columns[0].order.length; - let prefix, period, cycleIndex; + let prefix; const isAvailable = calcTaskTimes(task, now).isAvailable; - if (task.id.startsWith("weekly_")) { + if (task.section === "weekly") { prefix = "This Week"; - period = 7 * C.MILLISECONDS_PER_DAY; - if (ref.getUTCDay() !== 1) { - console.warn(`${task.id} cycle ref ${cycles[task.id].ref} is not a Monday`); - } - } else if (task.id.startsWith("daily_")) { + } else if (task.section === "daily") { prefix = "Today"; - period = C.MILLISECONDS_PER_DAY; } else { prefix = "Current Cycle"; if (!isAvailable) { prefix = "Next Cycle"; } - period = parseDuration(task.period); } - cycleIndex = modulo(Math.floor((now.getTime() - ref.getTime()) / period), cycleCount); + const resetCount = calcResetCount(task, now, ref); // calcResetCount handles DST + let cycleIndex = modulo(resetCount, cycleCount); if (!isAvailable) { cycleIndex = modulo(cycleIndex + 1, cycleCount); } console.log(`${task.id} cycleIndex ${cycleIndex}`); const cycleData = cycles[task.id].columns[0].order[cycleIndex]; @@ -878,7 +898,7 @@ function makeInfoLine(task, appendTo) { /* eslint-disable-line max-lines-per-fun showSchedule.type = "button"; showSchedule.classList.add("more-info-btn"); showSchedule.innerHTML = "Show Schedule"; - showSchedule.addEventListener("click", showScheduleAction(task, period, cycleIndex, isAvailable)); + showSchedule.addEventListener("click", showScheduleAction(task, cycleIndex, isAvailable)); currentCycle.appendChild(showSchedule); taskInfoExpanderContent.appendChild(currentCycle); diff --git a/sources/js/functions.js b/sources/js/functions.js index 99060f6..9da275f 100644 --- a/sources/js/functions.js +++ b/sources/js/functions.js @@ -174,10 +174,14 @@ export function isDst(date, timezone) { return currentOffset === dstOffset; } -/** calculates the cycleNumber (number of resets since the reference time) of the given task at the given time (a Date object or timestamp) */ -export function calcCycleNumber(task, time) { +/** + * Calculates the reset count (number of resets since the reference time) of the given task + * at the given time (a Date object or timestamp). + * Uses the task definition ref by default, if it has one. If you need to use the cycle ref, pass it as `altRef`. +*/ +export function calcResetCount(task, time, altRef = undefined) { time = new Date(time); - const ref = new Date(task.ref || 0); + const ref = new Date(altRef || task.ref || 0); const period = parseDuration(task.period); let diff = time.getTime() - ref.getTime(); if (task.observesDst && isDst(time, C.SERVER_TIMEZONE)) { @@ -199,8 +203,8 @@ export function calcTaskTimes(task, date) { date = new Date(date); const ref = new Date(task.ref || 0); const period = parseDuration(task.period); - const cycleNumber = calcCycleNumber(task, date); - const prevResetTimestamp = ref.getTime() + (cycleNumber * period); + const resetCount = calcResetCount(task, date); + const prevResetTimestamp = ref.getTime() + (resetCount * period); let nextResetTimestamp = prevResetTimestamp + period; let thisCycleLeaveTimestamp = prevResetTimestamp + parseDuration(task.duration); diff --git a/sources/tests/cycles.schema.json b/sources/tests/cycles.schema.json index 5c12a1f..0d4dca7 100644 --- a/sources/tests/cycles.schema.json +++ b/sources/tests/cycles.schema.json @@ -54,7 +54,7 @@ "type": "string" }, "iconFilter": { - "description": "set to `true` to convert the `icon` to monochrome black or white depending on the theme. Otherwise, it will be used in color as-is. Note the opposite name and default bahaviour to `tasks.json`", + "description": "set to `true` to convert the `icon` to monochrome black or white depending on the theme. Otherwise, it will be used in color as-is. Note the opposite name and default behaviour to `tasks.json`", "type": "boolean", "default": false } diff --git a/sources/tests/tasks.test.js b/sources/tests/tasks.test.js index 4a83761..99142fa 100644 --- a/sources/tests/tasks.test.js +++ b/sources/tests/tasks.test.js @@ -14,7 +14,7 @@ addFormats(ajv); const validate_tasks_schema = ajv.compile(tasks_schema); const validate_cycles_schema = ajv.compile(cycles_schema); -describe("valildate task definitions", () => { +describe("valildate task definitions", () => { /* eslint-disable-line max-lines-per-function */ test.for([ ["tasks", validate_tasks_schema, tasks], ["cycles", validate_cycles_schema, cycles], @@ -69,6 +69,28 @@ describe("valildate task definitions", () => { }); }); + describe("check cycle refs", () => { + test.for(cycle_keys_for_test)("%s", ([task_id]) => { + const section = task_id.split("_")[0]; + const ref_text = cycles[task_id].ref; + const ref = new Date(ref_text); + + const task_def = flatTasks.find((t) => t.id === task_id); + if (task_def.ref) { // cycle ref for alternate ref task must match task's alternate ref + expect(ref, `cycle ref ${ref_text} does not equal task ref ${task_def.ref}`).toEqual(new Date(task_def.ref)); + } else if (["daily", "weekly"].includes(section)) { // cycle for standard ref tasks must match standard resets + if (section === "weekly") { + expect(ref.getUTCDay(), `cycle ref ${ref_text} is not a Monday`).toEqual(1); + } + + expect( + [ref.getUTCHours(), ref.getUTCMinutes(), ref.getUTCSeconds(), ref.getUTCMilliseconds()], + `cycle ref ${ref_text} is not midnight`, + ).toEqual([0, 0, 0, 0]); + } + }); + }); + describe("validate moreInfo", () => { test.for(Object.keys(moreInfo).map((i) => [i]))("%s", ([task_id]) => { expect(task_ids, "moreInfo keys must be task ids from `tasks.json`").toContain(task_id);