From 31d4636ddef0c91f897a406eb385343dbf658355 Mon Sep 17 00:00:00 2001 From: Codinget Date: Wed, 22 Jul 2026 21:50:29 +0000 Subject: [PATCH] ci: add pull request quality gates Co-Authored-By: gpt-5.6-terra --- .gitea/actions/setup/action.yml | 9 + .gitea/workflows/ci.yml | 124 ++ .node-version | 1 + .prettierignore | 2 + eslint.config.js | 22 + package-lock.json | 1323 ++++++++++++++++++++- package.json | 10 +- src/bin/abode-migrate.ts | 6 +- src/bin/abode-sources.ts | 8 +- src/bin/abode-tui.ts | 4 +- src/db/api/ApiInterface.ts | 6 +- src/db/api/getdb.dyn.ts | 2 +- src/db/api/url.ts | 6 +- src/db/dbSources.dyn.ts | 2 +- src/db/dbSources.shared.ts | 2 +- src/db/postgres/PostgresInterface.ts | 61 +- src/db/postgres/PostgresMigrator.ts | 11 +- src/db/postgres/pool.ts | 8 +- src/db/postgres/query.ts | 30 +- src/db/postgres/sql.ts | 2 +- src/db/postgres/url.ts | 5 +- src/db/sqlite/SqliteInterface.ts | 56 +- src/db/sqlite/SqliteMigrator.ts | 11 +- src/db/sqlite/cast.ts | 6 +- src/db/sqlite/getdb.dyn.ts | 2 +- src/db/sqlite/impl/better-sqlite3.ts | 2 +- src/db/sqlite/impl/index.ts | 2 +- src/db/sqlite/query.ts | 24 +- src/db/sqlite/sql.ts | 2 +- src/db/sqlite/url.ts | 6 +- src/db/types/DbInterface.ts | 6 +- src/db/types/User.ts | 2 +- src/meta/dev/restart.ts | 2 +- src/meta/pack/natives.ts | 8 +- src/meta/pack/validators.ts | 4 +- src/react/contexts/PopupManager.tsx | 8 +- src/react/hooks/data/residents.ts | 4 +- src/react/hooks/useAction.ts | 4 +- src/react/hooks/useLoad.ts | 2 +- src/react/store/actions/abodes.ts | 6 +- src/react/store/actions/users.ts | 6 +- src/react/store/load.ts | 11 +- src/react/store/react.tsx | 2 +- src/react/store/slices/loading.ts | 2 +- src/react/store/slices/users.ts | 2 +- src/react/store/utils.ts | 4 +- src/schema/rawSchemas.ts | 38 +- src/schema/schemas.ts | 2 +- src/schema/validators.ts | 2 +- src/tui/App.tsx | 2 +- src/tui/components/panels/AbodesPanel.tsx | 4 +- src/tui/components/panels/UsersPanel.tsx | 4 +- src/tui/components/ui/Button.tsx | 4 +- src/tui/components/ui/ListBox.tsx | 4 +- src/tui/components/ui/ListDisplay.tsx | 6 +- src/tui/components/ui/Popup.tsx | 2 +- src/tui/components/ui/SearchPanel.tsx | 2 +- src/util/hash.ts | 4 +- src/util/xmlwriter.ts | 26 +- src/webapi/apirouter.ts | 14 +- src/webapi/schemarouter.ts | 2 +- test/backends/api/api-interface.test.ts | 30 +- test/backends/api/auth-http.test.ts | 6 +- test/backends/sqlite/index.test.ts | 4 +- test/backends/sqlite/migrator.test.ts | 4 +- test/backends/sqlite/sql.test.ts | 8 +- test/backends/sqlite/wrapped-db.test.ts | 32 +- test/helpers/koa.ts | 4 +- test/shared/abodes.ts | 39 +- test/shared/apikeys.ts | 8 +- test/shared/auth.ts | 22 +- test/shared/residents.ts | 33 +- test/shared/sessions.ts | 6 +- test/shared/users.ts | 26 +- test/tools/authenticate.test.ts | 139 ++- test/tools/convertError.test.ts | 4 +- test/tools/jsonBody.test.ts | 34 +- tsconfig.json | 28 +- tsconfig.test.json | 10 +- webpack.config.ts | 24 +- 80 files changed, 2007 insertions(+), 398 deletions(-) create mode 100644 .gitea/actions/setup/action.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 .node-version create mode 100644 .prettierignore create mode 100644 eslint.config.js diff --git a/.gitea/actions/setup/action.yml b/.gitea/actions/setup/action.yml new file mode 100644 index 0000000..8bd1405 --- /dev/null +++ b/.gitea/actions/setup/action.yml @@ -0,0 +1,9 @@ +name: Setup +description: Set up the Node version required by this repository. Requires actions/checkout to have already run. + +runs: + using: composite + steps: + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..1338420 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,124 @@ +name: CI + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + install-and-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - name: Clean install dependencies + run: npm ci + + - name: Production build + run: npm run build + + - name: Archive dependencies + run: tar -czf node_modules.tar.gz node_modules + + - uses: actions/upload-artifact@v3 + with: + name: node_modules-${{ github.run_id }} + path: node_modules.tar.gz + + - name: Archive build artifacts + run: tar -czf build-artifacts.tar.gz dist + + - uses: actions/upload-artifact@v3 + with: + name: build-artifacts-${{ github.run_id }} + path: build-artifacts.tar.gz + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - name: Clean install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - name: Clean install dependencies + run: npm ci + + - name: Check formatting + run: npm run format:check + + typecheck-source: + runs-on: ubuntu-latest + needs: install-and-build + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - uses: actions/download-artifact@v3 + with: + name: node_modules-${{ github.run_id }} + + - uses: actions/download-artifact@v3 + with: + name: build-artifacts-${{ github.run_id }} + + - name: Restore dependencies and build artifacts + run: | + tar -xzf node_modules.tar.gz + tar -xzf build-artifacts.tar.gz + + - name: Typecheck source + run: npm run typecheck + + typecheck-tests: + runs-on: ubuntu-latest + needs: install-and-build + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - uses: actions/download-artifact@v3 + with: + name: node_modules-${{ github.run_id }} + + - name: Restore dependencies + run: tar -xzf node_modules.tar.gz + + - name: Typecheck tests + run: npm run typecheck:test + + test: + runs-on: ubuntu-latest + needs: install-and-build + steps: + - uses: actions/checkout@v4 + + - uses: ./.gitea/actions/setup + + - uses: actions/download-artifact@v3 + with: + name: node_modules-${{ github.run_id }} + + - name: Restore dependencies + run: tar -xzf node_modules.tar.gz + + - name: Run full test suite + run: npm test diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..de4d1f0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c5c8b5f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { + ignores: ["dist/**", "node_modules/**"], + }, + eslint.configs.recommended, + tseslint.configs.recommended, + { + rules: { + "no-control-regex": "off", + "no-empty": "off", + "no-fallthrough": "off", + "prefer-const": "off", + "@typescript-eslint/no-empty-object-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-asserted-optional-chain": "off", + "@typescript-eslint/no-unused-vars": "off", + }, + }, +); diff --git a/package-lock.json b/package-lock.json index cf90d16..afe0d6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@reduxjs/toolkit": "^2.9.0", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", - "better-sqlite3": "^12.2.0", "fullscreen-ink": "^0.1.0", "hash-wasm": "^4.12.0", "ink": "^6.3.0", @@ -32,26 +31,34 @@ "abode-web": "dist/bin/abode-web.cjs" }, "devDependencies": { + "@eslint/js": "^9.39.5", "@types/better-sqlite3": "^7.6.13", "@types/koa": "^3.0.0", "@types/koa__router": "^12.0.4", + "@types/pg": "^8.20.0", "@types/react": "^19.1.12", "@types/webpack-bundle-analyzer": "^4.7.0", "copy-webpack-plugin": "^13.0.1", "css-loader": "^7.1.2", "dynohot": "^2.1.1", + "eslint": "^9.39.5", "mini-css-extract-plugin": "^2.9.4", + "prettier": "^3.6.2", "raw-loader": "^4.0.2", "scss-loader": "^0.0.1", "ts-loader": "^9.5.4", "tsx": "^4.20.5", "typescript": "^5.9.2", + "typescript-eslint": "^8.65.0", "val-loader": "^6.0.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^6.0.1" }, "engines": { "node": "^22" + }, + "optionalDependencies": { + "better-sqlite3": "^12.2.0" } }, "node_modules/@alcalzone/ansi-tokenize": { @@ -798,12 +805,321 @@ "node": ">=18" } }, + "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.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@hapi/bourne": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", "license": "BSD-3-Clause" }, + "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/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/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1149,6 +1465,18 @@ "undici-types": "~7.10.0" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -1213,6 +1541,236 @@ "webpack": "^5" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/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": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "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/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -1449,9 +2007,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1474,6 +2032,16 @@ "acorn": "^8.14.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/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -1576,6 +2144,13 @@ "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/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -1588,6 +2163,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1606,7 +2191,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/better-sqlite3": { "version": "12.2.0", @@ -1614,6 +2200,7 @@ "integrity": "sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==", "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -1637,6 +2224,7 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", + "optional": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -1646,12 +2234,26 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1717,6 +2319,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1767,6 +2370,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/caniuse-lite": { "version": "1.0.30001737", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", @@ -1809,7 +2422,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/chrome-trace-event": { "version": "1.0.4", @@ -2003,6 +2617,13 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2156,9 +2777,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2177,6 +2798,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -2198,10 +2820,18 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } }, + "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/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2232,6 +2862,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "license": "Apache-2.0", + "optional": true, "engines": { "node": ">=8" } @@ -2315,6 +2946,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -2472,6 +3104,66 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@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", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2486,6 +3178,246 @@ "node": ">=8.0.0" } }, + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.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/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/espree/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/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/esquery/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/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -2519,6 +3451,16 @@ "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/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -2534,6 +3476,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, "engines": { "node": ">=6" } @@ -2551,6 +3494,13 @@ "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.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -2577,11 +3527,25 @@ "node": ">= 4.9.1" } }, + "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/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fill-range": { "version": "7.1.1", @@ -2620,6 +3584,27 @@ "flat": "cli.js" } }, + "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.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", + "dev": true, + "license": "ISC" + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2633,7 +3618,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -2745,7 +3731,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/glob-parent": { "version": "6.0.2", @@ -2767,6 +3754,19 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2955,7 +3955,18 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true + }, + "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/immer": { "version": "10.1.3", @@ -2967,6 +3978,33 @@ "url": "https://opencollective.com/immer" } }, + "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-fresh/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/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -2987,6 +4025,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -3018,7 +4066,8 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/ink": { "version": "6.3.0", @@ -3277,6 +4326,29 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "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/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3290,6 +4362,13 @@ "node": ">=6" } }, + "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", @@ -3303,6 +4382,13 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "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/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3328,6 +4414,16 @@ "node": ">= 0.6" } }, + "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", @@ -3394,6 +4490,20 @@ "node": ">= 0.6" } }, + "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/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -3522,6 +4632,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -3550,11 +4661,28 @@ "webpack": "^5.0.0" } }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", + "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3563,7 +4691,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/mrmime": { "version": "2.0.1", @@ -3604,6 +4733,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT", + "optional": true + }, + "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/negotiator": { @@ -3627,6 +4764,7 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", "license": "MIT", + "optional": true, "dependencies": { "semver": "^7.3.5" }, @@ -3680,6 +4818,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", + "optional": true, "dependencies": { "wrappy": "1" } @@ -3709,6 +4848,24 @@ "opener": "bin/opener-bin.js" } }, + "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": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -3748,6 +4905,19 @@ "node": ">=6" } }, + "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/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4081,6 +5251,7 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -4102,11 +5273,38 @@ "node": ">=10" } }, + "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/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4241,6 +5439,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -4315,6 +5514,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4515,9 +5715,10 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4674,7 +5875,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -4695,6 +5897,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -4820,6 +6023,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", + "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -4861,6 +6065,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -4910,6 +6115,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "license": "MIT", + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -4922,6 +6128,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -5067,6 +6274,19 @@ "node": ">=6" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-loader": { "version": "9.5.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", @@ -5129,6 +6349,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -5136,6 +6357,19 @@ "node": "*" } }, + "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/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -5197,6 +6431,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", @@ -5266,6 +6524,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "devOptional": true, "license": "MIT" }, "node_modules/val-loader": { @@ -5555,6 +6814,16 @@ "dev": true, "license": "MIT" }, + "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/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -5588,7 +6857,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/ws": { "version": "8.18.3", @@ -5627,6 +6897,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", diff --git a/package.json b/package.json index 52e74a3..9c234a9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "test:shared": "node --import tsx/esm --import ./src/meta/dev/register.ts --test $(find test/backends -name 'index.test.ts' | sort)", "test:tools": "node --import tsx/esm --import ./src/meta/dev/register.ts --test $(find test/tools -name '*.test.ts' | sort)", "typecheck": "tsc --noEmit", - "typecheck:test": "tsc --noEmit -p tsconfig.test.json" + "typecheck:test": "tsc --noEmit -p tsconfig.test.json", + "lint": "eslint .", + "format:check": "prettier --check ." }, "dependencies": { "@koa/bodyparser": "^6.0.0", @@ -44,21 +46,25 @@ "react-redux": "^9.2.0" }, "devDependencies": { + "@eslint/js": "^9.39.5", "@types/better-sqlite3": "^7.6.13", - "@types/pg": "^8.20.0", "@types/koa": "^3.0.0", "@types/koa__router": "^12.0.4", + "@types/pg": "^8.20.0", "@types/react": "^19.1.12", "@types/webpack-bundle-analyzer": "^4.7.0", "copy-webpack-plugin": "^13.0.1", "css-loader": "^7.1.2", "dynohot": "^2.1.1", + "eslint": "^9.39.5", "mini-css-extract-plugin": "^2.9.4", + "prettier": "^3.6.2", "raw-loader": "^4.0.2", "scss-loader": "^0.0.1", "ts-loader": "^9.5.4", "tsx": "^4.20.5", "typescript": "^5.9.2", + "typescript-eslint": "^8.65.0", "val-loader": "^6.0.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^6.0.1" diff --git a/src/bin/abode-migrate.ts b/src/bin/abode-migrate.ts index 57c789f..898b6d3 100644 --- a/src/bin/abode-migrate.ts +++ b/src/bin/abode-migrate.ts @@ -40,7 +40,7 @@ switch (cmd) { if (!current.length) console.log("(none)"); for (const migration of current) { console.log( - `- ${migration.id} (${migration.name}) applied at ${migration.applied_at}` + `- ${migration.id} (${migration.name}) applied at ${migration.applied_at}`, ); } break; @@ -73,7 +73,7 @@ switch (cmd) { password: await hashPassword("changeme"), }); console.log( - `Created user 'admin@codi.moe' (${uid}) with password 'changeme' and admin flag` + `Created user 'admin@codi.moe' (${uid}) with password 'changeme' and admin flag`, ); } users = await db.listUsers(); @@ -88,7 +88,7 @@ switch (cmd) { expires_at: null, }); console.log( - `Created apikey '${token}' (${apikey.kid}) with permissions admin, all and no expiry` + `Created apikey '${token}' (${apikey.kid}) with permissions admin, all and no expiry`, ); } } diff --git a/src/bin/abode-sources.ts b/src/bin/abode-sources.ts index 0ffec96..5e70df5 100644 --- a/src/bin/abode-sources.ts +++ b/src/bin/abode-sources.ts @@ -21,7 +21,7 @@ if (args.length > 1) printUsage("too many arguments"); console.log( `Compiled with ${compiledSources.length} sources:`, - compiledSources.join(", ") + compiledSources.join(", "), ); const url = args[0] ?? "abode://"; @@ -31,7 +31,7 @@ for (const source of sources) { console.log(`- ${source.name}`); console.log( " - protocols:", - source.protocols.map((x) => `'${x}'`).join(" ") + source.protocols.map((x) => `'${x}'`).join(" "), ); const match = source.checkUrl(url); console.log(` - matches url: ${match}`); @@ -41,7 +41,7 @@ for (const source of sources) { console.log( ` - generates an interface named ${db.name} ${ db.backend ? "with" : "without" - } backend` + } backend`, ); await db.close().catch(console.error); } catch (e) { @@ -53,7 +53,7 @@ for (const source of sources) { console.log( ` - generates a migrator knowing ${ db.listAvailableMigrations().length - } migrations` + } migrations`, ); } catch (e) { console.error(e); diff --git a/src/bin/abode-tui.ts b/src/bin/abode-tui.ts index 8010c19..15b5974 100644 --- a/src/bin/abode-tui.ts +++ b/src/bin/abode-tui.ts @@ -30,7 +30,7 @@ const bgColor = await new Promise((ok, ko) => { process.stdin.once("data", (chunk) => { const result = chunk.toString("utf8"); const match = result.match( - /^\u001b]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)$/ + /^\u001b]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)$/, ); if (!match) return ko("Didn't recognize terminal bg color"); const [r, g, b] = match @@ -78,7 +78,7 @@ if (import.meta.hot) { import.meta.hot.accept("../tui/App.js", (mod) => { fullscreenApp.instance.rerender( - (mod.app as typeof app)({ db, bgColor, store }) + (mod.app as typeof app)({ db, bgColor, store }), ); }); } diff --git a/src/db/api/ApiInterface.ts b/src/db/api/ApiInterface.ts index 9a68d4a..f376280 100644 --- a/src/db/api/ApiInterface.ts +++ b/src/db/api/ApiInterface.ts @@ -39,7 +39,7 @@ export class ApiInterface implements DbInterface { }: { headers?: Record; readonly?: boolean; - } = {} + } = {}, ) { if (root.endsWith("/")) root = root.slice(0, -1); this.#root = root; @@ -82,7 +82,7 @@ export class ApiInterface implements DbInterface { params?: Record; body?: unknown; headers?: Record; - } = {} + } = {}, ): Promise { const resolvedHeaders = { ...this.#headers, ...headers }; if (body !== undefined) { @@ -247,7 +247,7 @@ export class ApiInterface implements DbInterface { }); } async createApikey( - apikey: CreateApikey + apikey: CreateApikey, ): Promise<[ClientApikey, `at_${string}`]> { this.#checkReadonly(); const { apikey: key, token } = await this.#call<{ diff --git a/src/db/api/getdb.dyn.ts b/src/db/api/getdb.dyn.ts index ba68584..71d97cc 100644 --- a/src/db/api/getdb.dyn.ts +++ b/src/db/api/getdb.dyn.ts @@ -3,7 +3,7 @@ import { apiProtocols } from "./url.js"; const getApi = () => import(/* webpackChunkName: 'dbsource-api' */ "./getdb.static.js").then( - (x) => x.default + (x) => x.default, ); const getApiDynamic: GetDbDynamic = { diff --git a/src/db/api/url.ts b/src/db/api/url.ts index 97372d9..e916866 100644 --- a/src/db/api/url.ts +++ b/src/db/api/url.ts @@ -17,7 +17,9 @@ export function parseApiUrl(url: string) { urlObj.href = urlObj.href.replace(/^abode\+/, ""); const readonly = (urlObj.searchParams.get("readonly") ?? "0") !== "0"; const headers = Object.fromEntries( - [...urlObj.searchParams.entries()].filter(([param]) => param !== "readonly") + [...urlObj.searchParams.entries()].filter( + ([param]) => param !== "readonly", + ), ); if (urlObj.username) { headers["Authorization"] = @@ -26,7 +28,7 @@ export function parseApiUrl(url: string) { [ decodeURIComponent(urlObj.username), decodeURIComponent(urlObj.password), - ].join(":") + ].join(":"), ); urlObj.username = ""; urlObj.password = ""; diff --git a/src/db/dbSources.dyn.ts b/src/db/dbSources.dyn.ts index 61c07d2..191d032 100644 --- a/src/db/dbSources.dyn.ts +++ b/src/db/dbSources.dyn.ts @@ -15,7 +15,7 @@ export async function getDbSources(url: string): Promise { .catch(() => null) .then((dbSource) => { if (dbSource) dbSources.push(dbSource); - }) + }), ); } } diff --git a/src/db/dbSources.shared.ts b/src/db/dbSources.shared.ts index 3bdef46..76b1704 100644 --- a/src/db/dbSources.shared.ts +++ b/src/db/dbSources.shared.ts @@ -3,7 +3,7 @@ import type { GetDbStatic } from "./types/GetDb.js"; let rawGetDbSources: typeof getDbSources | undefined = undefined; const getGetDbSources = () => import(/* webpackChunkName: 'dbsources' */ "./dbSources.static.js").then( - (x) => x.getDbSources + (x) => x.getDbSources, ); export async function getDbSources(url: string): Promise { diff --git a/src/db/postgres/PostgresInterface.ts b/src/db/postgres/PostgresInterface.ts index 0f123c4..a92aa21 100644 --- a/src/db/postgres/PostgresInterface.ts +++ b/src/db/postgres/PostgresInterface.ts @@ -92,7 +92,7 @@ export class PostgresInterface implements BackendDbInterface { async getUserByEmail(email: string): Promise { const user = await selectClientUser( this.#db, - sql`u."email" = ${{ text: email }}` + sql`u."email" = ${{ text: email }}`, ); if (!user) throw new NotFoundAbodeError(); return user; @@ -111,7 +111,7 @@ export class PostgresInterface implements BackendDbInterface { SELECT "uid", "email", "name", "flags", "created_at", "updated_at", "password" FROM "users" WHERE "email" = ${{ text: email }} - ` + `, ); if (!rawUser) throw new NotFoundAbodeError(); if (rawUser.password.startsWith("#")) throw new ConflictAbodeError(); @@ -125,7 +125,7 @@ export class PostgresInterface implements BackendDbInterface { sql` DELETE FROM "users" WHERE "uid" = ${{ uuid: id }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } @@ -145,10 +145,10 @@ export class PostgresInterface implements BackendDbInterface { ${{ text: user.password }}, ${{ jsonb: user.flags }} ) - ` + `, ); return this.#getUserById(uid, tx); - }) + }), ); } async updateUser(user: UpdateUser): Promise { @@ -161,7 +161,8 @@ export class PostgresInterface implements BackendDbInterface { if ("name" in user && user.name !== undefined) updates.push(sql`"name" = ${{ text: user.name }}`); if ("password" in user && user.password !== undefined) { - if (!isValidUserPassword(user.password)) throw new InvalidAbodeError(); + if (!isValidUserPassword(user.password)) + throw new InvalidAbodeError(); if (user.password.startsWith("#")) { await tx.run(sql` DELETE FROM "apikeys" @@ -185,11 +186,11 @@ export class PostgresInterface implements BackendDbInterface { "updated_at" = NOW(), ${joinSql(updates, sql`, `)} WHERE "uid" = ${{ uuid: user.uid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getUserById(user.uid, tx); - }) + }), ); } @@ -210,7 +211,7 @@ export class PostgresInterface implements BackendDbInterface { sql` DELETE FROM "abodes" WHERE "aid" = ${{ uuid: id }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } @@ -228,10 +229,10 @@ export class PostgresInterface implements BackendDbInterface { ${{ uuid: ctx.uid }}, ${{ uuid: ctx.uid }} ) - ` + `, ); return this.#getAbodeById(aid, tx); - }) + }), ); } async updateAbode(abode: UpdateAbode, ctx: { uid: string }): Promise { @@ -250,11 +251,11 @@ export class PostgresInterface implements BackendDbInterface { "updated_by" = ${{ uuid: ctx.uid }}, ${joinSql(updates, sql`, `)} WHERE "aid" = ${{ uuid: abode.aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getAbodeById(abode.aid, tx); - }) + }), ); } @@ -270,11 +271,11 @@ export class PostgresInterface implements BackendDbInterface { async #getResidentById( uid: string, aid: string, - db: WrappedPgClient + db: WrappedPgClient, ): Promise { const resident = await selectResident( db, - sql`"uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }}` + sql`"uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }}`, ); if (!resident) throw new NotFoundAbodeError(); return resident; @@ -288,13 +289,13 @@ export class PostgresInterface implements BackendDbInterface { sql` DELETE FROM "residents" WHERE "uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } async createResident( resident: CreateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise { this.#checkReadonly(); return this.#db.rethrow(() => @@ -309,15 +310,15 @@ export class PostgresInterface implements BackendDbInterface { ${{ uuid: ctx.uid }}, ${{ uuid: ctx.uid }} ) - ` + `, ); return this.#getResidentById(resident.uid, resident.aid, tx); - }) + }), ); } async updateResident( resident: updateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise { this.#checkReadonly(); const updates = calcUpdates({ @@ -336,11 +337,11 @@ export class PostgresInterface implements BackendDbInterface { WHERE "uid" = ${{ uuid: resident.uid }} AND "aid" = ${{ uuid: resident.aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getResidentById(resident.uid, resident.aid, tx); - }) + }), ); } @@ -350,7 +351,7 @@ export class PostgresInterface implements BackendDbInterface { sql` JOIN "residents" r ON u."uid" = r."uid" WHERE r."aid" = ${{ uuid: id }} - ` + `, ); } async listAbodesByUserId(id: string): Promise { @@ -359,7 +360,7 @@ export class PostgresInterface implements BackendDbInterface { sql` JOIN "residents" r ON a."aid" = r."aid" WHERE r."uid" = ${{ uuid: id }} - ` + `, ); } @@ -418,17 +419,17 @@ export class PostgresInterface implements BackendDbInterface { async #getApikeyByToken( token: `at_${string}`, - db: WrappedPgClient + db: WrappedPgClient, ): Promise { const apikey = await selectClientApikey( db, - sql`k."token" = ${{ text: token }}` + sql`k."token" = ${{ text: token }}`, ); if (!apikey) throw new NotFoundAbodeError(); return apikey; } async getUserByApikey( - token: `at_${string}` + token: `at_${string}`, ): Promise<[ClientUser, ClientApikey]> { const apikey = await this.#getApikeyByToken(token, this.#db); if ( @@ -445,13 +446,13 @@ export class PostgresInterface implements BackendDbInterface { async getApikeyById(kid: string): Promise { const apikey = await selectClientApikey( this.#db, - sql`k."kid" = ${{ uuid: kid }}` + sql`k."kid" = ${{ uuid: kid }}`, ); if (!apikey) throw new NotFoundAbodeError(); return apikey; } async createApikey( - apikey: CreateApikey + apikey: CreateApikey, ): Promise<[ClientApikey, `at_${string}`]> { this.#checkReadonly(); const token = createApikeyToken(); @@ -459,7 +460,7 @@ export class PostgresInterface implements BackendDbInterface { let expires = apikey.expires_at; if (expires === undefined) expires = new Date( - new Date().getTime() + 1000 * 60 * 60 * 24 * 365 + new Date().getTime() + 1000 * 60 * 60 * 24 * 365, ).toISOString(); if (expires && new Date(expires).getTime() < Date.now()) throw new InvalidAbodeError(); diff --git a/src/db/postgres/PostgresMigrator.ts b/src/db/postgres/PostgresMigrator.ts index a4da392..eab7d36 100644 --- a/src/db/postgres/PostgresMigrator.ts +++ b/src/db/postgres/PostgresMigrator.ts @@ -24,7 +24,7 @@ export class PostgresMigrator implements Migrator { `SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '_migrations' - ) AS "exists"` + ) AS "exists"`, ); if (!existsResult.rows[0]?.exists) return null; @@ -33,7 +33,7 @@ export class PostgresMigrator implements Migrator { name: string; applied_at: Date; }>( - `SELECT "id", "name", "applied_at" FROM "_migrations" ORDER BY "id" ASC` + `SELECT "id", "name", "applied_at" FROM "_migrations" ORDER BY "id" ASC`, ); return result.rows.map((x) => ({ ...x, @@ -73,17 +73,16 @@ export class PostgresMigrator implements Migrator { throw new Error(`Applied migration ${id} (${name}) not known`); if (migration.name !== name) throw new Error( - `Applied migration ${id} (${name}) has a different name from expected (${migration.name})` + `Applied migration ${id} (${name}) has a different name from expected (${migration.name})`, ); } - const start = - migrations.findIndex((x) => x.id === current.at(-1)?.id) + 1; + const start = migrations.findIndex((x) => x.id === current.at(-1)?.id) + 1; const end = migrations.indexOf(target) + 1; if (end < start) { throw new Error( - `Cannot migrate backward, at ${current.at(-1)?.id ?? 0}, going to ${target.id}` + `Cannot migrate backward, at ${current.at(-1)?.id ?? 0}, going to ${target.id}`, ); } diff --git a/src/db/postgres/pool.ts b/src/db/postgres/pool.ts index 85fe5e2..b82048b 100644 --- a/src/db/postgres/pool.ts +++ b/src/db/postgres/pool.ts @@ -31,9 +31,7 @@ async function rethrow(fn: () => Promise): Promise { // Rollback on a broken connection can itself throw; the original error is // the one worth surfacing. -export async function rollbackQuietly( - client: pg.PoolClient -): Promise { +export async function rollbackQuietly(client: pg.PoolClient): Promise { try { await client.query("ROLLBACK"); } catch {} @@ -58,7 +56,7 @@ abstract class WrappedPgBase implements WrappedPgClient { async all(stmt: SqlCode): Promise { const result = await this.#queryable.query( toPositional(stmt._sql), - stmt._vars + stmt._vars, ); return result.rows as R[]; } @@ -72,7 +70,7 @@ abstract class WrappedPgBase implements WrappedPgClient { async run(stmt: SqlCode): Promise<{ changes: number }> { const result = await this.#queryable.query( toPositional(stmt._sql), - stmt._vars + stmt._vars, ); return { changes: result.rowCount ?? 0 }; } diff --git a/src/db/postgres/query.ts b/src/db/postgres/query.ts index bb2d4b3..766bb04 100644 --- a/src/db/postgres/query.ts +++ b/src/db/postgres/query.ts @@ -26,20 +26,18 @@ const sqlClientUser = sql` export async function selectClientUser( db: WrappedPgClient, - where: SqlCode + where: SqlCode, ): Promise { - const raw = await db.get( - sql`${sqlClientUser} WHERE ${where}` - ); + const raw = await db.get(sql`${sqlClientUser} WHERE ${where}`); if (raw) return pgToClientUser(raw); return null; } export async function selectClientUsers( db: WrappedPgClient, - rest?: SqlCode + rest?: SqlCode, ): Promise { const rows = await db.all( - rest ? sql`${sqlClientUser} ${rest}` : sqlClientUser + rest ? sql`${sqlClientUser} ${rest}` : sqlClientUser, ); return rows.map(pgToClientUser); } @@ -59,7 +57,7 @@ const sqlAbode = sql` export async function selectAbode( db: WrappedPgClient, - where: SqlCode + where: SqlCode, ): Promise { const raw = await db.get(sql`${sqlAbode} WHERE ${where}`); if (raw) return pgToAbode(raw); @@ -67,10 +65,10 @@ export async function selectAbode( } export async function selectAbodes( db: WrappedPgClient, - rest?: SqlCode + rest?: SqlCode, ): Promise { const rows = await db.all( - rest ? sql`${sqlAbode} ${rest}` : sqlAbode + rest ? sql`${sqlAbode} ${rest}` : sqlAbode, ); return rows.map(pgToAbode); } @@ -91,7 +89,7 @@ const sqlResident = sql` export async function selectResident( db: WrappedPgClient, - where: SqlCode + where: SqlCode, ): Promise { const raw = await db.get(sql`${sqlResident} WHERE ${where}`); if (raw) return pgToResident(raw); @@ -99,10 +97,10 @@ export async function selectResident( } export async function selectResidents( db: WrappedPgClient, - where?: SqlCode + where?: SqlCode, ): Promise { const rows = await db.all( - where ? sql`${sqlResident} WHERE ${where}` : sqlResident + where ? sql`${sqlResident} WHERE ${where}` : sqlResident, ); return rows.map(pgToResident); } @@ -122,20 +120,20 @@ const sqlClientApikey = sql` export async function selectClientApikey( db: WrappedPgClient, - where: SqlCode + where: SqlCode, ): Promise { const raw = await db.get( - sql`${sqlClientApikey} WHERE ${where}` + sql`${sqlClientApikey} WHERE ${where}`, ); if (raw) return pgToClientApikey(raw); return null; } export async function selectClientApikeys( db: WrappedPgClient, - where: SqlCode + where: SqlCode, ): Promise { const rows = await db.all( - sql`${sqlClientApikey} WHERE ${where}` + sql`${sqlClientApikey} WHERE ${where}`, ); return rows.map(pgToClientApikey); } diff --git a/src/db/postgres/sql.ts b/src/db/postgres/sql.ts index 653812a..24f126d 100644 --- a/src/db/postgres/sql.ts +++ b/src/db/postgres/sql.ts @@ -65,7 +65,7 @@ export function calcUpdates(updater: { for (const [prop, update] of Object.entries(updater)) { if (prop in obj) { updates.push( - (update as (value: unknown) => SqlCode)(obj[prop as keyof T]!) + (update as (value: unknown) => SqlCode)(obj[prop as keyof T]!), ); } } diff --git a/src/db/postgres/url.ts b/src/db/postgres/url.ts index ccb2eb6..631f6e2 100644 --- a/src/db/postgres/url.ts +++ b/src/db/postgres/url.ts @@ -9,7 +9,10 @@ export function isPgUrl(url: string): boolean { } } -export function parsePgUrl(url: string): { connectionString: string; readonly: boolean } { +export function parsePgUrl(url: string): { + connectionString: string; + readonly: boolean; +} { if (!isPgUrl(url)) throw new Error("Not a postgres: URL"); const urlObj = new URL(url); const readonly = (urlObj.searchParams.get("readonly") ?? "0") !== "0"; diff --git a/src/db/sqlite/SqliteInterface.ts b/src/db/sqlite/SqliteInterface.ts index a8f2332..e5a0102 100644 --- a/src/db/sqlite/SqliteInterface.ts +++ b/src/db/sqlite/SqliteInterface.ts @@ -110,7 +110,7 @@ export class SqliteInterface implements BackendDbInterface { SELECT "uid", "email", "name", json("flags") AS "flags", "created_at", "updated_at", "password" FROM "users" WHERE "email" = ${{ text: email }} - ` + `, ); if (!rawUser) throw new NotFoundAbodeError(); if (rawUser.password.startsWith("#")) throw new ConflictAbodeError(); @@ -124,7 +124,7 @@ export class SqliteInterface implements BackendDbInterface { sql` DELETE FROM "users" WHERE "uid" = ${{ uuid: id }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } @@ -142,10 +142,10 @@ export class SqliteInterface implements BackendDbInterface { ${{ text: user.email }}, ${{ text: user.name }}, ${{ text: user.password }},${{ jsonb: user.flags }}) - ` + `, ); return this.#getUserById(uid); - }) + }), ); } async updateUser(user: UpdateUser): Promise { @@ -179,11 +179,11 @@ export class SqliteInterface implements BackendDbInterface { "updated_at" = datetime('now', 'localtime', 'subsec'), ${joinSql(updates, sql`, `)} WHERE "uid" = ${{ uuid: user.uid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getUserById(user.uid); - }) + }), ); } @@ -204,7 +204,7 @@ export class SqliteInterface implements BackendDbInterface { sql` DELETE FROM "abodes" WHERE "aid" = ${{ uuid: id }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } @@ -222,10 +222,10 @@ export class SqliteInterface implements BackendDbInterface { ${{ uuid: ctx.uid }}, ${{ uuid: ctx.uid }} ) - ` + `, ); return this.#getAbodeById(aid); - }) + }), ); } async updateAbode(abode: UpdateAbode, ctx: { uid: string }): Promise { @@ -244,11 +244,11 @@ export class SqliteInterface implements BackendDbInterface { "updated_by" = ${{ uuid: ctx.uid }}, ${joinSql(updates, sql`, `)} WHERE "aid" = ${{ uuid: abode.aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getAbodeById(abode.aid); - }) + }), ); } @@ -264,7 +264,7 @@ export class SqliteInterface implements BackendDbInterface { #getResidentById(uid: string, aid: string): Resident { const resident = selectResident( this.#db, - sql`"uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }}` + sql`"uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }}`, ); if (!resident) throw new NotFoundAbodeError(); return resident; @@ -278,13 +278,13 @@ export class SqliteInterface implements BackendDbInterface { sql` DELETE FROM "residents" WHERE "uid" = ${{ uuid: uid }} AND "aid" = ${{ uuid: aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); } async createResident( resident: CreateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise { this.#checkReadonly(); return this.#db.rethrow(() => @@ -299,15 +299,15 @@ export class SqliteInterface implements BackendDbInterface { ${{ uuid: ctx.uid }}, ${{ uuid: ctx.uid }} ) - ` + `, ); return this.#getResidentById(resident.uid, resident.aid); - }) + }), ); } async updateResident( resident: updateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise { this.#checkReadonly(); const updates = calcUpdates({ @@ -326,11 +326,11 @@ export class SqliteInterface implements BackendDbInterface { WHERE "uid" = ${{ uuid: resident.uid }} AND "aid" = ${{ uuid: resident.aid }} - ` + `, ); if (!changes) throw new NotFoundAbodeError(); return this.#getResidentById(resident.uid, resident.aid); - }) + }), ); } @@ -340,7 +340,7 @@ export class SqliteInterface implements BackendDbInterface { sql` JOIN "residents" r ON u."uid" = r."uid" WHERE r."aid" = ${{ uuid: id }} - ` + `, ); } async listAbodesByUserId(id: string): Promise { @@ -349,7 +349,7 @@ export class SqliteInterface implements BackendDbInterface { sql` JOIN "residents" r ON a."aid" = r."aid" WHERE r."uid" = ${{ uuid: id }} - ` + `, ); } @@ -405,13 +405,13 @@ export class SqliteInterface implements BackendDbInterface { #getApikeyByToken(token: `at_${string}`): ClientApikey { const apikey = selectClientApikey( this.#db, - sql`"token" = ${{ text: token }}` + sql`"token" = ${{ text: token }}`, ); if (!apikey) throw new NotFoundAbodeError(); return apikey; } async getUserByApikey( - token: `at_${string}` + token: `at_${string}`, ): Promise<[ClientUser, ClientApikey]> { const apikey = this.#getApikeyByToken(token); if ( @@ -431,7 +431,7 @@ export class SqliteInterface implements BackendDbInterface { return apikey; } async createApikey( - apikey: CreateApikey + apikey: CreateApikey, ): Promise<[ClientApikey, `at_${string}`]> { this.#checkReadonly(); const token = createApikeyToken(); @@ -439,7 +439,7 @@ export class SqliteInterface implements BackendDbInterface { let expires = apikey.expires_at; if (expires === undefined) expires = new Date( - new Date().getTime() + 1000 * 60 * 60 * 24 * 365 + new Date().getTime() + 1000 * 60 * 60 * 24 * 365, ).toISOString(); if (expires && new Date(expires).getTime() < Date.now()) throw new InvalidAbodeError(); @@ -480,7 +480,7 @@ export class SqliteInterface implements BackendDbInterface { async deleteNoteById(nid: string): Promise { this.#checkReadonly(); const { changes } = this.#db.run( - sql`DELETE FROM "notes" WHERE "nid" = ${{ uuid: nid }}` + sql`DELETE FROM "notes" WHERE "nid" = ${{ uuid: nid }}`, ); if (!changes) throw new NotFoundAbodeError(); } @@ -502,7 +502,7 @@ export class SqliteInterface implements BackendDbInterface { ) `); return this.#getNoteById(nid); - }) + }), ); } async updateNote(note: UpdateNote, ctx: { uid: string }): Promise { @@ -525,7 +525,7 @@ export class SqliteInterface implements BackendDbInterface { `); if (!changes) throw new NotFoundAbodeError(); return this.#getNoteById(note.nid); - }) + }), ); } async listNotesByAbodeId(aid: string): Promise { diff --git a/src/db/sqlite/SqliteMigrator.ts b/src/db/sqlite/SqliteMigrator.ts index 1b56a82..46253e4 100644 --- a/src/db/sqlite/SqliteMigrator.ts +++ b/src/db/sqlite/SqliteMigrator.ts @@ -16,12 +16,11 @@ export class SqliteMigrator implements Migrator { } #listAppliedMigrations(): - | { id: number; name: string; applied_at: string }[] - | null { + { id: number; name: string; applied_at: string }[] | null { try { return this.#db .all<{ id: number; name: string; applied_at: string }>( - sql`SELECT "id", "name", "applied_at" FROM "_migrations" ORDER BY "id" ASC` + sql`SELECT "id", "name", "applied_at" FROM "_migrations" ORDER BY "id" ASC`, ) .map((x) => ({ ...x, applied_at: sqliteToDate(x.applied_at) })); } catch (e) { @@ -59,7 +58,7 @@ export class SqliteMigrator implements Migrator { throw new Error(`Applied migration ${id} (${name}) not known`); if (migration.name !== name) throw new Error( - `Applied migration ${id} (${name}) has a different name from expected (${migration.name})` + `Applied migration ${id} (${name}) has a different name from expected (${migration.name})`, ); } @@ -70,7 +69,7 @@ export class SqliteMigrator implements Migrator { throw new Error( `Cannot migrate backward, at ${current.at(-1)?.id ?? 0}, going to ${ target.id - }` + }`, ); } @@ -94,7 +93,7 @@ export class SqliteMigrator implements Migrator { sql` INSERT INTO "_migrations"("id", "name") VALUES (${{ int: migration.id }}, ${{ text: migration.name }}) - ` + `, ); this.#db.run(sql`COMMIT`); } catch (e) { diff --git a/src/db/sqlite/cast.ts b/src/db/sqlite/cast.ts index c4e9115..fb2dc9f 100644 --- a/src/db/sqlite/cast.ts +++ b/src/db/sqlite/cast.ts @@ -22,7 +22,7 @@ export function uuidToSqlite(uuid: string) { } export function sqliteToUuid(uuid: Buffer | Uint8Array) { const hex = (uuid instanceof Buffer ? uuid : Buffer.from(uuid)).toString( - "hex" + "hex", ); return [ hex.slice(0, 8), @@ -126,7 +126,7 @@ export function sqliteToResident(resident: { const defaultApikeyPermissions: ApikeyPermissions = {}; export function sqliteToApikeyPermissions( - permissions: string + permissions: string, ): ApikeyPermissions { const parsed = JSON.parse(permissions); const out = { ...defaultApikeyPermissions }; @@ -184,7 +184,7 @@ export function sqliteToNoteProperties(props: string): NoteProperties { } export function sqliteToPartialNoteProperties( - props: string + props: string, ): PartialNoteProperties { const base = sqliteToNoteProperties(props); return { type: base.type ?? "note" }; diff --git a/src/db/sqlite/getdb.dyn.ts b/src/db/sqlite/getdb.dyn.ts index 98d057a..c40a9ab 100644 --- a/src/db/sqlite/getdb.dyn.ts +++ b/src/db/sqlite/getdb.dyn.ts @@ -3,7 +3,7 @@ import { sqliteProtocols } from "./url.js"; const getSqlite = () => import(/* webpackChunkName: 'dbsource-sqlite' */ "./getdb.static.js").then( - (x) => x.default + (x) => x.default, ); const getSqliteDynamic: GetDbDynamic = { diff --git a/src/db/sqlite/impl/better-sqlite3.ts b/src/db/sqlite/impl/better-sqlite3.ts index d7994c0..0e24f51 100644 --- a/src/db/sqlite/impl/better-sqlite3.ts +++ b/src/db/sqlite/impl/better-sqlite3.ts @@ -6,7 +6,7 @@ import type { WrappedDb, WrappedDbOptions } from "./types.js"; function getDatabase( path: string, - options?: Omit + options?: Omit, ): sqlite.Database { if (!natives.sqlite) throw new Error("No natives found for better-sqlite3"); options = { ...options }; diff --git a/src/db/sqlite/impl/index.ts b/src/db/sqlite/impl/index.ts index 6e99946..c74d25b 100644 --- a/src/db/sqlite/impl/index.ts +++ b/src/db/sqlite/impl/index.ts @@ -4,7 +4,7 @@ import { node, bs3 } from "./implementations.js"; export function getWrappedDb( kind: "any" | "node" | "bs3", path: string, - options: WrappedDbOptions + options: WrappedDbOptions, ): WrappedDb { if (kind === "node") { if (!node) throw new Error("Requesting unavailable node backend"); diff --git a/src/db/sqlite/query.ts b/src/db/sqlite/query.ts index 79534f9..81b31e9 100644 --- a/src/db/sqlite/query.ts +++ b/src/db/sqlite/query.ts @@ -29,7 +29,7 @@ const sqlClientUser = sql` export function selectClientUser( db: WrappedDb, - where: SqlCode + where: SqlCode, ): ClientUser | null { const rawUser = db.get(sql`${sqlClientUser} WHERE ${where}`); if (rawUser) return sqliteToClientUser(rawUser); @@ -37,7 +37,7 @@ export function selectClientUser( } export function selectClientUsers(db: WrappedDb, rest?: SqlCode): ClientUser[] { const rawUsers = db.all( - rest ? sql`${sqlClientUser} ${rest}` : sqlClientUser + rest ? sql`${sqlClientUser} ${rest}` : sqlClientUser, ); return rawUsers.map(sqliteToClientUser); } @@ -62,7 +62,7 @@ export function selectAbode(db: WrappedDb, where: SqlCode): Abode | null { } export function selectAbodes(db: WrappedDb, rest?: SqlCode): Abode[] { const rawAbodes = db.all( - rest ? sql`${sqlAbode} ${rest}` : sqlAbode + rest ? sql`${sqlAbode} ${rest}` : sqlAbode, ); return rawAbodes.map(sqliteToAbode); } @@ -88,7 +88,7 @@ export function selectResident(db: WrappedDb, where: SqlCode): Resident | null { } export function selectResidents(db: WrappedDb, where?: SqlCode): Resident[] { const rawResidents = db.all( - where ? sql`${sqlResident} WHERE ${where}` : sqlResident + where ? sql`${sqlResident} WHERE ${where}` : sqlResident, ); return rawResidents.map(sqliteToResident); } @@ -108,20 +108,20 @@ const sqlClientApikey = sql` export function selectClientApikey( db: WrappedDb, - where: SqlCode + where: SqlCode, ): ClientApikey | null { const rawApikey = db.get( - sql`${sqlClientApikey} WHERE ${where}` + sql`${sqlClientApikey} WHERE ${where}`, ); if (rawApikey) return sqliteToClientApikey(rawApikey); return null; } export function selectClientApikeys( db: WrappedDb, - where: SqlCode + where: SqlCode, ): ClientApikey[] { const rawApikeys = db.all( - sql`${sqlClientApikey} WHERE ${where}` + sql`${sqlClientApikey} WHERE ${where}`, ); return rawApikeys.map(sqliteToClientApikey); } @@ -156,15 +156,17 @@ export function selectNote(db: WrappedDb, where: SqlCode): Note | null { return null; } export function selectNotes(db: WrappedDb, where?: SqlCode): Note[] { - const raws = db.all(where ? sql`${sqlNote} WHERE ${where}` : sqlNote); + const raws = db.all( + where ? sql`${sqlNote} WHERE ${where}` : sqlNote, + ); return raws.map(sqliteToNote); } export function selectPartialNotes( db: WrappedDb, - where?: SqlCode + where?: SqlCode, ): PartialNote[] { const raws = db.all( - where ? sql`${sqlPartialNote} WHERE ${where}` : sqlPartialNote + where ? sql`${sqlPartialNote} WHERE ${where}` : sqlPartialNote, ); return raws.map(sqliteToPartialNote); } diff --git a/src/db/sqlite/sql.ts b/src/db/sqlite/sql.ts index f3e43b7..7a97644 100644 --- a/src/db/sqlite/sql.ts +++ b/src/db/sqlite/sql.ts @@ -65,7 +65,7 @@ export function calcUpdates(updater: { for (const [prop, update] of Object.entries(updater)) { if (prop in obj) { updates.push( - (update as (value: unknown) => SqlCode)(obj[prop as keyof T]!) + (update as (value: unknown) => SqlCode)(obj[prop as keyof T]!), ); } } diff --git a/src/db/sqlite/url.ts b/src/db/sqlite/url.ts index acbf3a6..499f334 100644 --- a/src/db/sqlite/url.ts +++ b/src/db/sqlite/url.ts @@ -27,7 +27,7 @@ export function isSqliteUrl(url: string) { } export function parseSqliteUrl( - url: string + url: string, ): ["any" | "node" | "bs3", string, WrappedDbOptions] { if (!isSqliteUrl(url)) throw new Error("Not sqlite: protocol"); const urlObj = new URL(url); @@ -39,7 +39,7 @@ export function parseSqliteUrl( urlObj.protocol === "node+sqlite:" ? "node" : urlObj.protocol === "bs3+sqlite:" - ? "bs3" - : "any"; + ? "bs3" + : "any"; return [kind, urlObj.pathname, options]; } diff --git a/src/db/types/DbInterface.ts b/src/db/types/DbInterface.ts index c85dc75..54d32c3 100644 --- a/src/db/types/DbInterface.ts +++ b/src/db/types/DbInterface.ts @@ -40,11 +40,11 @@ export interface DbInterface { deleteResidentById(uid: string, aid: string): Promise; createResident( resident: CreateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise; updateResident( resident: updateResident, - ctx: { uid: string } + ctx: { uid: string }, ): Promise; // list residents by member @@ -104,7 +104,7 @@ export function isBackendInterface(db: DbInterface): db is BackendDbInterface { ] as const ).every( (x) => - x in db && typeof (db as Partial)[x] === "function" + x in db && typeof (db as Partial)[x] === "function", ) ); } diff --git a/src/db/types/User.ts b/src/db/types/User.ts index 8e035f6..d0322f9 100644 --- a/src/db/types/User.ts +++ b/src/db/types/User.ts @@ -27,7 +27,7 @@ export type LoginUser = { }; export function isValidUserPassword( - password: string + password: string, ): password is User["password"] { if (password.startsWith("#")) { return ["unset"].includes(password.slice(1)); diff --git a/src/meta/dev/restart.ts b/src/meta/dev/restart.ts index 101415d..1c73de5 100644 --- a/src/meta/dev/restart.ts +++ b/src/meta/dev/restart.ts @@ -2,7 +2,7 @@ if (import.meta.hot) { import.meta.hot.on("message", (msg) => { if ( msg.includes( - "A pending update was not accepted, and reached the root module:" + "A pending update was not accepted, and reached the root module:", ) ) { throw new Error("[hot] Restarting due to unaccepted pending update"); diff --git a/src/meta/pack/natives.ts b/src/meta/pack/natives.ts index 19670fd..3f4c353 100644 --- a/src/meta/pack/natives.ts +++ b/src/meta/pack/natives.ts @@ -32,23 +32,23 @@ async function getPackageJsonDir(path: string): Promise { export async function findNative( module: string, - native: string + native: string, ): Promise { const path = await getPackageJsonDir( - createRequire(import.meta.url).resolve(module) + createRequire(import.meta.url).resolve(module), ); if (!path) throw new Error(`Cannot find module directory for ${module}`); const file = await find(path, native); if (!file) throw new Error( - `Cannot find native ${native} of package ${module} in ${path}` + `Cannot find native ${native} of package ${module} in ${path}`, ); return file; } export async function tryFindNative( module: string, - native: string + native: string, ): Promise { try { return await findNative(module, native); diff --git a/src/meta/pack/validators.ts b/src/meta/pack/validators.ts index 0baccf8..cebedba 100644 --- a/src/meta/pack/validators.ts +++ b/src/meta/pack/validators.ts @@ -22,8 +22,8 @@ export async function webpack(): Promise<{ code: string }> { let code: string = standaloneCode( validator, Object.fromEntries( - Object.entries(schemas).map(([id, schema]) => [id, schema.$id]) - ) + Object.entries(schemas).map(([id, schema]) => [id, schema.$id]), + ), ); // assign the .schema ourselves to the validation functions diff --git a/src/react/contexts/PopupManager.tsx b/src/react/contexts/PopupManager.tsx index d87d953..4229dcb 100644 --- a/src/react/contexts/PopupManager.tsx +++ b/src/react/contexts/PopupManager.tsx @@ -12,7 +12,7 @@ export interface PopupManagerContextData { openPopup(popup: ComponentType<{ id: string; onClose: () => void }>): string; openPopup( popup: ComponentType<{ id: string; onClose: () => void } & T>, - props: T + props: T, ): string; closePopup(id: string): void; @@ -34,7 +34,7 @@ export function PopupManager({ children }: { children: ReactNode }) { const openPopup = useCallback( ( Component: ComponentType<{ id: string; onClose: () => void }>, - props = {} + props = {}, ) => { const id = crypto.randomUUID(); Object.assign(props, { @@ -45,14 +45,14 @@ export function PopupManager({ children }: { children: ReactNode }) { setPopups((prev) => [...prev, { id, Component, props }]); return id; }, - [] + [], ); const closePopup = useCallback((id: string) => { setPopups((prev) => prev.filter((x) => x.id !== id)); }, []); const ctx = useMemo( () => ({ openPopup, closePopup }), - [] + [], ); return ( diff --git a/src/react/hooks/data/residents.ts b/src/react/hooks/data/residents.ts index d58a3cb..109c783 100644 --- a/src/react/hooks/data/residents.ts +++ b/src/react/hooks/data/residents.ts @@ -19,7 +19,7 @@ export function useDataResidentsByAbodeId(aid: string) { const status = useLoad(loadResidentsByAbodeId, { aid }); const residents = useMemo( () => Object.values(allResidents).filter((x) => x.aid === aid), - [allResidents, aid] + [allResidents, aid], ); return { ...status, residents }; } @@ -29,7 +29,7 @@ export function useDataResidentsByUserId(uid: string) { const status = useLoad(loadResidentsByUserId, { uid }); const residents = useMemo( () => Object.values(allResidents).filter((x) => x.uid === uid), - [allResidents, uid] + [allResidents, uid], ); return { ...status, residents }; } diff --git a/src/react/hooks/useAction.ts b/src/react/hooks/useAction.ts index d5dde48..b8fe8b6 100644 --- a/src/react/hooks/useAction.ts +++ b/src/react/hooks/useAction.ts @@ -5,7 +5,7 @@ import type { Store } from "../store/store.js"; import { useStore } from "../store/react.js"; export function useAction

( - action: (...params: [...P, { db: DbInterface; store: Store }]) => Promise + action: (...params: [...P, { db: DbInterface; store: Store }]) => Promise, ): (...args: P) => Promise { const db = use(DbContext); const store = useStore(); @@ -15,6 +15,6 @@ export function useAction

( if (!db) throw new Error("DB not present"); return action(...params, { db, store }); }, - [action, db] + [action, db], ); } diff --git a/src/react/hooks/useLoad.ts b/src/react/hooks/useLoad.ts index b7fcb52..adf4a2e 100644 --- a/src/react/hooks/useLoad.ts +++ b/src/react/hooks/useLoad.ts @@ -32,7 +32,7 @@ export function useLoad

(loader: Loader

, params?: P): UseLoadResult { const refresh = useCallback( () => load({ loader, params: params!, store, db, refresh: true }), - [loader, params, db] + [loader, params, db], ); return { diff --git a/src/react/store/actions/abodes.ts b/src/react/store/actions/abodes.ts index a25b228..e4b7ead 100644 --- a/src/react/store/actions/abodes.ts +++ b/src/react/store/actions/abodes.ts @@ -8,7 +8,7 @@ import type { Store } from "../store.js"; export async function deleteAbodeById( aid: string, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await Promise.all([ waitForLoadIfLoading(store, "loadAllAbodes"), @@ -22,7 +22,7 @@ export async function deleteAbodeById( export async function updateAbode( abode: UpdateAbode, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await Promise.all([ waitForLoadIfLoading(store, "loadAllAbodes"), @@ -38,7 +38,7 @@ export async function updateAbode( export async function createAbode( abode: CreateAbode, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await waitForLoadIfLoading(store, "loadAllAbodes"); diff --git a/src/react/store/actions/users.ts b/src/react/store/actions/users.ts index ab5c22a..23bf0b8 100644 --- a/src/react/store/actions/users.ts +++ b/src/react/store/actions/users.ts @@ -7,7 +7,7 @@ import type { Store } from "../store.js"; export async function deleteUserById( uid: string, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await Promise.all([ waitForLoadIfLoading(store, "loadAllUsers"), @@ -24,7 +24,7 @@ export async function deleteUserById( export async function updateUser( user: UpdateUser, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await Promise.all([ waitForLoadIfLoading(store, "loadAllUsers"), @@ -41,7 +41,7 @@ export async function updateUser( export async function createUser( user: CreateUser, - { store, db }: { store: Store; db: DbInterface } + { store, db }: { store: Store; db: DbInterface }, ): Promise { await waitForLoadIfLoading(store, "loadAllUsers"); diff --git a/src/react/store/load.ts b/src/react/store/load.ts index c898bd2..626aafa 100644 --- a/src/react/store/load.ts +++ b/src/react/store/load.ts @@ -48,7 +48,7 @@ async function loadImpl

({ setLoading([ id, { status: refresh ? "refreshing" : "loading", type, params }, - ]) + ]), ); const controller = new AbortController(); try { @@ -69,7 +69,10 @@ async function loadImpl

({ store.dispatch(setLoading([id, { status: "loaded", type, params }])); } catch (e) { store.dispatch( - setLoading([id, { status: "error", type, params, error: objectError(e) }]) + setLoading([ + id, + { status: "error", type, params, error: objectError(e) }, + ]), ); throw e; } @@ -114,7 +117,7 @@ export function loader

(loader: Loader

): Loader

{ export async function waitForLoadIfLoading( store: Store, id: string, - { signal }: { signal?: AbortSignal } = {} + { signal }: { signal?: AbortSignal } = {}, ) { if (!getLoadingStatus(store.getState(), id)) return; return waitFor( @@ -123,6 +126,6 @@ export async function waitForLoadIfLoading( const status = getLoadingStatus(state, id); return status === "loaded" || status === "error"; }, - { signal } + { signal }, ); } diff --git a/src/react/store/react.tsx b/src/react/store/react.tsx index abfc715..4938113 100644 --- a/src/react/store/react.tsx +++ b/src/react/store/react.tsx @@ -16,7 +16,7 @@ export function Provider( props: Omit & { store: Store; serverState?: State; - } + }, ) { return ; } diff --git a/src/react/store/slices/loading.ts b/src/react/store/slices/loading.ts index 65f7e4f..6c3b227 100644 --- a/src/react/store/slices/loading.ts +++ b/src/react/store/slices/loading.ts @@ -40,7 +40,7 @@ const loadingSlice = createSlice({ reducers: { setLoading: ( state, - action: PayloadAction<[id: string, state: LoadingState]> + action: PayloadAction<[id: string, state: LoadingState]>, ) => { state[action.payload[0]] = action.payload[1]; }, diff --git a/src/react/store/slices/users.ts b/src/react/store/slices/users.ts index ee36e2d..5097ca2 100644 --- a/src/react/store/slices/users.ts +++ b/src/react/store/slices/users.ts @@ -19,7 +19,7 @@ const usersSlice = createSlice({ getUser: usersSelectors.selectById, getUserByEmail: (state, email: string) => Object.values(state.entities).find( - (x): x is ClientUser => "email" in x && x.email === email + (x): x is ClientUser => "email" in x && x.email === email, ), getUsers: usersSelectors.selectEntities, getUserIds: usersSelectors.selectIds, diff --git a/src/react/store/utils.ts b/src/react/store/utils.ts index 7bcc1c2..613b1e0 100644 --- a/src/react/store/utils.ts +++ b/src/react/store/utils.ts @@ -3,7 +3,7 @@ import type { State, Store } from "./store.js"; export async function waitFor( store: Store, cond: (state: State) => boolean, - { signal }: { signal?: AbortSignal } = {} + { signal }: { signal?: AbortSignal } = {}, ): Promise { return new Promise((ok, ko) => { signal?.throwIfAborted(); @@ -28,7 +28,7 @@ export async function waitFor( () => { controller.abort(); }, - { signal: controller.signal } + { signal: controller.signal }, ); }); } diff --git a/src/schema/rawSchemas.ts b/src/schema/rawSchemas.ts index 4644ac1..bf03e4c 100644 --- a/src/schema/rawSchemas.ts +++ b/src/schema/rawSchemas.ts @@ -1,23 +1,23 @@ -export { default as user } from "./user/user.schema.json" with {type: 'json'}; -export { default as createuser } from "./user/createuser.schema.json" with {type: 'json'}; -export { default as updateuser } from "./user/updateuser.schema.json" with {type: 'json'}; -export { default as partialuser } from "./user/partialuser.schema.json" with {type: 'json'}; -export { default as clientuser } from "./user/clientuser.schema.json" with {type: 'json'}; -export { default as userflags } from "./user/userflags.schema.json" with {type: 'json'}; -export { default as loginuser } from "./user/loginuser.schema.json" with {type: 'json'}; +export { default as user } from "./user/user.schema.json" with { type: "json" }; +export { default as createuser } from "./user/createuser.schema.json" with { type: "json" }; +export { default as updateuser } from "./user/updateuser.schema.json" with { type: "json" }; +export { default as partialuser } from "./user/partialuser.schema.json" with { type: "json" }; +export { default as clientuser } from "./user/clientuser.schema.json" with { type: "json" }; +export { default as userflags } from "./user/userflags.schema.json" with { type: "json" }; +export { default as loginuser } from "./user/loginuser.schema.json" with { type: "json" }; -export { default as abode } from './abode/abode.schema.json' with {type: 'json'}; -export { default as createabode } from './abode/createabode.schema.json' with {type: 'json'}; -export { default as updateabode } from './abode/updateabode.schema.json' with {type: 'json'}; +export { default as abode } from "./abode/abode.schema.json" with { type: "json" }; +export { default as createabode } from "./abode/createabode.schema.json" with { type: "json" }; +export { default as updateabode } from "./abode/updateabode.schema.json" with { type: "json" }; -export { default as resident } from './resident/resident.schema.json' with {type: 'json'}; -export { default as createresident } from './resident/createresident.schema.json' with {type: 'json'}; -export { default as updateresident } from './resident/updateresident.schema.json' with {type: 'json'}; -export { default as residentflags } from './resident/residentflags.schema.json' with {type: 'json'}; +export { default as resident } from "./resident/resident.schema.json" with { type: "json" }; +export { default as createresident } from "./resident/createresident.schema.json" with { type: "json" }; +export { default as updateresident } from "./resident/updateresident.schema.json" with { type: "json" }; +export { default as residentflags } from "./resident/residentflags.schema.json" with { type: "json" }; -export { default as createapikey } from './apikey/createapikey.schema.json' with {type: 'json'}; -export { default as apikeypermissions } from './apikey/apikeypermissions.schema.json' with {type: 'json'}; +export { default as createapikey } from "./apikey/createapikey.schema.json" with { type: "json" }; +export { default as apikeypermissions } from "./apikey/apikeypermissions.schema.json" with { type: "json" }; -export { default as createnote } from './note/createnote.schema.json' with {type: 'json'}; -export { default as updatenote } from './note/updatenote.schema.json' with {type: 'json'}; -export { default as partialnoteproperties } from './note/partialnoteproperties.schema.json' with {type: 'json'}; +export { default as createnote } from "./note/createnote.schema.json" with { type: "json" }; +export { default as updatenote } from "./note/updatenote.schema.json" with { type: "json" }; +export { default as partialnoteproperties } from "./note/partialnoteproperties.schema.json" with { type: "json" }; diff --git a/src/schema/schemas.ts b/src/schema/schemas.ts index fced54b..8f9cd65 100644 --- a/src/schema/schemas.ts +++ b/src/schema/schemas.ts @@ -39,7 +39,7 @@ function checkSchema(name: string, schema: AnySchema) { if (!schema.$id) throw new Error(`Missing $id for schema ${name}`); if ( !schema.$id.match( - /^https:\/\/abode\.codi\.moe\/schema\/[a-zA-Z0_9_-]+\.schema\.json$/ + /^https:\/\/abode\.codi\.moe\/schema\/[a-zA-Z0_9_-]+\.schema\.json$/, ) ) throw new Error(`Unexpected $id for schema ${name}`); diff --git a/src/schema/validators.ts b/src/schema/validators.ts index 5b4bdc2..caacc09 100644 --- a/src/schema/validators.ts +++ b/src/schema/validators.ts @@ -18,7 +18,7 @@ const validators = Object.fromEntries( Object.entries(schemas).map(([name, schema]) => [ name, validator.compile(schema), - ]) + ]), ) as unknown as { [T in keyof Types]: { (obj: unknown): obj is Types[T]; diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 2d2925a..5551c8b 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -69,7 +69,7 @@ function App() { (input, key) => { if (input === "q" || key.escape) app.exit(); }, - { isActive } + { isActive }, ); const [activeCollection, setActiveCollection] = diff --git a/src/tui/components/panels/AbodesPanel.tsx b/src/tui/components/panels/AbodesPanel.tsx index 33dd248..709bb83 100644 --- a/src/tui/components/panels/AbodesPanel.tsx +++ b/src/tui/components/panels/AbodesPanel.tsx @@ -41,11 +41,11 @@ export function AbodesPanel() { const onSelect = useCallback( (abode: Abode) => openPopup(AbodePopup, { aid: abode.aid }), - [openPopup] + [openPopup], ); const buttons = useMemo( () => [{ children: "New", onClick: () => openPopup(CreateAbodePopup) }], - [openPopup] + [openPopup], ); return ( diff --git a/src/tui/components/panels/UsersPanel.tsx b/src/tui/components/panels/UsersPanel.tsx index eff7fee..d223fdd 100644 --- a/src/tui/components/panels/UsersPanel.tsx +++ b/src/tui/components/panels/UsersPanel.tsx @@ -59,11 +59,11 @@ export function UsersPanel() { const onSelect = useCallback( (user: ClientUser | PartialUser) => openPopup(UserPopup, { uid: user.uid }), - [openPopup] + [openPopup], ); const buttons = useMemo( () => [{ children: "New", onClick: () => openPopup(CreateUserPopup) }], - [openPopup] + [openPopup], ); return ( diff --git a/src/tui/components/ui/Button.tsx b/src/tui/components/ui/Button.tsx index fe7ac32..8ca9a43 100644 --- a/src/tui/components/ui/Button.tsx +++ b/src/tui/components/ui/Button.tsx @@ -18,7 +18,7 @@ export function Button({ (input, key) => { if (input === " " || key.return) onClick(); }, - { isActive: isFocused } + { isActive: isFocused }, ); return [{children}]; @@ -66,7 +66,7 @@ export function ButtonList({ setSelected((prev) => (prev - 1 + buttons.length) % buttons.length); } }, - { isActive: isFocused || forceFocus || false } + { isActive: isFocused || forceFocus || false }, ); return ( diff --git a/src/tui/components/ui/ListBox.tsx b/src/tui/components/ui/ListBox.tsx index 28b48fa..c62ec76 100644 --- a/src/tui/components/ui/ListBox.tsx +++ b/src/tui/components/ui/ListBox.tsx @@ -49,7 +49,7 @@ export function ListBox({ setSelected(items[(items.indexOf(selected) + 1) % items.length]); } else if (key.upArrow) { setSelected( - items[(items.indexOf(selected) - 1 + items.length) % items.length] + items[(items.indexOf(selected) - 1 + items.length) % items.length], ); } else if (key.pageUp) { setSelected(items[0]); @@ -57,7 +57,7 @@ export function ListBox({ setSelected(items[items.length - 1]); } }, - { isActive: isFocused } + { isActive: isFocused }, ); return ( diff --git a/src/tui/components/ui/ListDisplay.tsx b/src/tui/components/ui/ListDisplay.tsx index 6b01c1e..d8522e9 100644 --- a/src/tui/components/ui/ListDisplay.tsx +++ b/src/tui/components/ui/ListDisplay.tsx @@ -36,14 +36,14 @@ export function ListDisplay({ onSelect?.(items[selected]); } }, - { isActive: isFocused } + { isActive: isFocused }, ); useEffect(() => { if (selected < start) setStart(selected); else if (selected >= slice) setStart( - Math.max(Math.min(selected - slice + start + 1, items.length - 1), 0) + Math.max(Math.min(selected - slice + start + 1, items.length - 1), 0), ); }, [selected, start, slice, items.length]); useEffect(() => { @@ -56,7 +56,7 @@ export function ListDisplay({ const indexed = useMemo( () => items.map((item, index) => ({ item, index })), - [items] + [items], ); return ( diff --git a/src/tui/components/ui/Popup.tsx b/src/tui/components/ui/Popup.tsx index 9b7726c..212023a 100644 --- a/src/tui/components/ui/Popup.tsx +++ b/src/tui/components/ui/Popup.tsx @@ -16,7 +16,7 @@ export function Popup({ (_, key) => { if (key.escape) onClose?.(); }, - { isActive: active && !!onClose } + { isActive: active && !!onClose }, ); return ( diff --git a/src/tui/components/ui/SearchPanel.tsx b/src/tui/components/ui/SearchPanel.tsx index 56b0475..7ea80f3 100644 --- a/src/tui/components/ui/SearchPanel.tsx +++ b/src/tui/components/ui/SearchPanel.tsx @@ -52,7 +52,7 @@ export function SearchPanel({ refresh?.(); } }, - { isActive: isFocused && !!refresh } + { isActive: isFocused && !!refresh }, ); const topbar = !!match || !!buttons?.length; diff --git a/src/util/hash.ts b/src/util/hash.ts index 918cd15..143c9f7 100644 --- a/src/util/hash.ts +++ b/src/util/hash.ts @@ -2,13 +2,13 @@ import { argon2id, argon2Verify } from "hash-wasm"; export async function validatePassword( password: string, - hash: string + hash: string, ): Promise { return await argon2Verify({ password, hash }); } export async function hashPassword( - password: string + password: string, ): Promise<`$${string}$${string}`> { const salt = new Uint8Array(16); crypto.getRandomValues(salt); diff --git a/src/util/xmlwriter.ts b/src/util/xmlwriter.ts index 61383a4..2a50ab3 100644 --- a/src/util/xmlwriter.ts +++ b/src/util/xmlwriter.ts @@ -7,11 +7,11 @@ const escapes = { }; function parseAdd( - rest: (Record | string | ((writer: XmlWriter) => T))[] + rest: (Record | string | ((writer: XmlWriter) => T))[], ): [ props?: Record, content?: string, - children?: (writer: XmlWriter) => T + children?: (writer: XmlWriter) => T, ] { let props: Record | undefined; let children: ((writer: XmlWriter) => T) | undefined; @@ -55,7 +55,7 @@ export class XmlWriter { tag: string, props?: Record, content?: string, - children?: NonNullable + children?: NonNullable, ) { const top = this.#stack.at(-1); if (top && !top.children) { @@ -104,7 +104,7 @@ export class XmlWriter { add( tag: string, props: Record, - children: (writer: XmlWriter) => void + children: (writer: XmlWriter) => void, ): XmlWriter; add( tag: string, @@ -125,23 +125,21 @@ export class XmlWriter { addAsync( tag: string, props: Record, - content: string + content: string, ): Promise; addAsync( tag: string, - children: (writer: XmlWriter) => Promise + children: (writer: XmlWriter) => Promise, ): Promise; addAsync( tag: string, props: Record, - children: (writer: XmlWriter) => Promise + children: (writer: XmlWriter) => Promise, ): Promise; async addAsync( tag: string, ...rest: ( - | Record - | string - | ((writer: XmlWriter) => Promise) + Record | string | ((writer: XmlWriter) => Promise) )[] ): Promise { const [props, content, children] = parseAdd(rest); @@ -164,7 +162,7 @@ export class XmlWriter { | Parameters["add"]> | [ NonNullable[0]>, - ...Parameters["add"]> + ...Parameters["add"]>, ] ): string { let options: ConstructorParameters[0]; @@ -172,7 +170,7 @@ export class XmlWriter { options = rest.shift()! as ConstructorParameters[0]; } return new XmlWriter(options).add( - ...(rest as Parameters["add"]>) + ...(rest as Parameters["add"]>), ).content; } @@ -188,7 +186,7 @@ export class XmlWriter { | Parameters["addAsync"]> | [ NonNullable[0]>, - ...Parameters["addAsync"]> + ...Parameters["addAsync"]>, ] ): Promise { let options: ConstructorParameters[0]; @@ -197,7 +195,7 @@ export class XmlWriter { } const writer = new XmlWriter(options); await writer.addAsync( - ...(rest as Parameters["addAsync"]>) + ...(rest as Parameters["addAsync"]>), ); return writer.content; } diff --git a/src/webapi/apirouter.ts b/src/webapi/apirouter.ts index b34b360..8ff5f0d 100644 --- a/src/webapi/apirouter.ts +++ b/src/webapi/apirouter.ts @@ -61,7 +61,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { jsonBody({ validate: updateuser, includeParams: ["uid"] }), async (ctx) => { ctx.body = await db.updateUser(ctx.request.body); - } + }, ); router.delete("/users/:uid", async (ctx) => { await db.deleteUserById(ctx.params.uid); @@ -82,7 +82,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { async (ctx) => { const [apikey, token] = await db.createApikey(ctx.request.body); ctx.body = { apikey, token }; - } + }, ); router.get("/users/:uid/apikeys/:kid", async (ctx) => { const apikey = await db.getApikeyById(ctx.params.kid); @@ -118,7 +118,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { jsonBody({ validate: updateabode, includeParams: ["aid"] }), async (ctx) => { ctx.body = await db.updateAbode(ctx.request.body, { uid: ctx.user!.uid }); - } + }, ); router.delete("/abodes/:aid", async (ctx) => { await db.deleteAbodeById(ctx.params.aid); @@ -138,7 +138,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { jsonBody({ validate: createnote, includeParams: ["aid"] }), async (ctx) => { ctx.body = await db.createNote(ctx.request.body, { uid: ctx.user!.uid }); - } + }, ); router.use("/residents", authenticate(db)); @@ -152,7 +152,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { ctx.body = await db.createResident(ctx.request.body, { uid: ctx.user!.uid, }); - } + }, ); router.get("/residents/:uid/:aid", async (ctx) => { ctx.body = await db.getResidentById(ctx.params.uid, ctx.params.aid); @@ -164,7 +164,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { ctx.body = await db.updateResident(ctx.request.body, { uid: ctx.user!.uid, }); - } + }, ); router.delete("/residents/:uid/:aid", async (ctx) => { await db.deleteResidentById(ctx.params.uid, ctx.params.aid); @@ -192,7 +192,7 @@ export function apirouter(db: BackendDbInterface): KoaRouter { jsonBody({ validate: updatenote, includeParams: ["nid"] }), async (ctx) => { ctx.body = await db.updateNote(ctx.request.body, { uid: ctx.user!.uid }); - } + }, ); router.delete("/notes/:nid", async (ctx) => { await db.deleteNoteById(ctx.params.nid); diff --git a/src/webapi/schemarouter.ts b/src/webapi/schemarouter.ts index c696c82..e5a419f 100644 --- a/src/webapi/schemarouter.ts +++ b/src/webapi/schemarouter.ts @@ -16,7 +16,7 @@ export function schemarouter(): KoaRouter { url: `${ctx.URL}/${name}.schema.json`, }, ]; - }) + }), ); }); diff --git a/test/backends/api/api-interface.test.ts b/test/backends/api/api-interface.test.ts index 4813f54..f702ce1 100644 --- a/test/backends/api/api-interface.test.ts +++ b/test/backends/api/api-interface.test.ts @@ -3,7 +3,11 @@ import assert from "node:assert/strict"; import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; import { ApiInterface } from "../../../src/db/api/ApiInterface.js"; -import { apiProtocols, isApiUrl, parseApiUrl } from "../../../src/db/api/url.js"; +import { + apiProtocols, + isApiUrl, + parseApiUrl, +} from "../../../src/db/api/url.js"; import { NotFoundAbodeError, NotAuthorizedAbodeError, @@ -86,7 +90,7 @@ describe("parseApiUrl", () => { it("extra query params become headers", () => { const [, { headers }] = parseApiUrl( - "http://example.com?X-Custom-Header=value" + "http://example.com?X-Custom-Header=value", ); assert.equal(headers["X-Custom-Header"], "value"); }); @@ -94,7 +98,7 @@ describe("parseApiUrl", () => { it("throws for non-api protocol", () => { assert.throws( () => parseApiUrl("sqlite:///db.sqlite"), - /Not an \{abode\+,\}http\{s,\}: protocol/ + /Not an \{abode\+,\}http\{s,\}: protocol/, ); }); }); @@ -106,18 +110,22 @@ describe("ApiInterface HTTP error mapping", () => { before(async () => { let nextStatus = 500; - respondWith = (s) => { nextStatus = s; }; + respondWith = (s) => { + nextStatus = s; + }; const server = createServer((req, res) => { res.writeHead(nextStatus, { "Content-Type": "application/json" }); res.end(JSON.stringify({ ok: false, error: "test" })); }); - await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + await new Promise((resolve) => + server.listen(0, "127.0.0.1", resolve), + ); const { port } = server.address() as AddressInfo; serverUrl = `http://127.0.0.1:${port}`; closeServer = () => new Promise((resolve, reject) => - server.close((err) => (err ? reject(err) : resolve())) + server.close((err) => (err ? reject(err) : resolve())), ); }); @@ -131,7 +139,7 @@ describe("ApiInterface HTTP error mapping", () => { (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -143,7 +151,7 @@ describe("ApiInterface HTTP error mapping", () => { (err) => { assert.ok(err instanceof NotAuthorizedAbodeError); return true; - } + }, ); }); @@ -155,7 +163,7 @@ describe("ApiInterface HTTP error mapping", () => { (err) => { assert.ok(err instanceof ReadonlyAbodeError); return true; - } + }, ); }); @@ -167,7 +175,7 @@ describe("ApiInterface HTTP error mapping", () => { (err) => { assert.ok(err instanceof InvalidAbodeError); return true; - } + }, ); }); @@ -179,7 +187,7 @@ describe("ApiInterface HTTP error mapping", () => { (err) => { assert.ok(err instanceof ConflictAbodeError); return true; - } + }, ); }); }); diff --git a/test/backends/api/auth-http.test.ts b/test/backends/api/auth-http.test.ts index dbe287e..aefce0b 100644 --- a/test/backends/api/auth-http.test.ts +++ b/test/backends/api/auth-http.test.ts @@ -92,7 +92,11 @@ describe("api backend: auth over HTTP", async () => { const self = await fetch(`${server.url}/auth/self`, { headers: { Cookie: `abode_session=${cookie}` }, }); - assert.equal(self.status, 401, "session was invalidated server-side, not just the cookie cleared"); + assert.equal( + self.status, + 401, + "session was invalidated server-side, not just the cookie cleared", + ); }); it("POST /auth/clear-sessions invalidates outstanding session cookies", async () => { diff --git a/test/backends/sqlite/index.test.ts b/test/backends/sqlite/index.test.ts index 459be89..993b234 100644 --- a/test/backends/sqlite/index.test.ts +++ b/test/backends/sqlite/index.test.ts @@ -10,10 +10,10 @@ import { runAuthTests } from "../../shared/auth.js"; async function createExpiredApikey( db: BackendDbInterface, - uid: string + uid: string, ): Promise<`at_${string}`> { const si = db as SqliteInterface; - const token = (`at_${"e".repeat(32)}`) as `at_${string}`; + const token = `at_${"e".repeat(32)}` as `at_${string}`; const kid = crypto.randomUUID(); const { sql } = si._; si._.db.run(sql` diff --git a/test/backends/sqlite/migrator.test.ts b/test/backends/sqlite/migrator.test.ts index ecd18d4..469fb60 100644 --- a/test/backends/sqlite/migrator.test.ts +++ b/test/backends/sqlite/migrator.test.ts @@ -46,7 +46,7 @@ describe("SqliteMigrator", () => { assert.equal(applied.length, 3); assert.deepEqual( applied.map((m) => m.id), - [1, 2, 3] + [1, 2, 3], ); db.destroy(); }); @@ -66,7 +66,7 @@ describe("SqliteMigrator", () => { const migrator = new SqliteMigrator(db); await assert.rejects( () => migrator.migrateTo(9999), - /No known migration with id 9999/ + /No known migration with id 9999/, ); db.destroy(); }); diff --git a/test/backends/sqlite/sql.test.ts b/test/backends/sqlite/sql.test.ts index b0d6ee3..d775308 100644 --- a/test/backends/sqlite/sql.test.ts +++ b/test/backends/sqlite/sql.test.ts @@ -1,6 +1,12 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import { sql, catSql, joinSql, calcUpdates, unsafeSql } from "../../../src/db/sqlite/sql.js"; +import { + sql, + catSql, + joinSql, + calcUpdates, + unsafeSql, +} from "../../../src/db/sqlite/sql.js"; describe("sql template tag", () => { it("produces correct sql and empty vars for plain text", () => { diff --git a/test/backends/sqlite/wrapped-db.test.ts b/test/backends/sqlite/wrapped-db.test.ts index 2ad6fba..4768a9d 100644 --- a/test/backends/sqlite/wrapped-db.test.ts +++ b/test/backends/sqlite/wrapped-db.test.ts @@ -10,13 +10,19 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { before(() => { db = makeDb(); - db.run(unsafeSql("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)")); + db.run( + unsafeSql( + "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, val TEXT NOT NULL)", + ), + ); }); after(() => db.destroy()); it("run INSERT returns changes count", () => { - const { changes } = db.run(sql`INSERT INTO test(val) VALUES(${{ text: "hello" }})`); + const { changes } = db.run( + sql`INSERT INTO test(val) VALUES(${{ text: "hello" }})`, + ); assert.equal(changes, 1); }); @@ -24,7 +30,9 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { db.run(unsafeSql("DELETE FROM test")); db.run(sql`INSERT INTO test(val) VALUES(${{ text: "a" }})`); db.run(sql`INSERT INTO test(val) VALUES(${{ text: "b" }})`); - const rows = db.all<{ val: string }>(unsafeSql("SELECT val FROM test ORDER BY val")); + const rows = db.all<{ val: string }>( + unsafeSql("SELECT val FROM test ORDER BY val"), + ); assert.equal(rows.length, 2); assert.equal(rows[0].val, "a"); assert.equal(rows[1].val, "b"); @@ -38,7 +46,7 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { assert.equal(row.val, "one"); const none = db.get<{ val: string }>( - sql`SELECT val FROM test WHERE val = ${{ text: "none" }}` + sql`SELECT val FROM test WHERE val = ${{ text: "none" }}`, ); assert.equal(none, null); }); @@ -49,7 +57,7 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { db.run(sql`INSERT INTO test(val) VALUES(${{ text: "dup2" }})`); assert.throws( () => db.get<{ val: string }>(unsafeSql("SELECT val FROM test")), - /Multiple results/ + /Multiple results/, ); }); @@ -69,7 +77,7 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { db.multi(() => { db.run(sql`INSERT INTO test(val) VALUES(${{ text: "rollback" }})`); throw new Error("abort!"); - }) + }), ); const rows = db.all<{ val: string }>(unsafeSql("SELECT val FROM test")); assert.equal(rows.length, 0); @@ -82,7 +90,13 @@ function runWrappedDbSuite(name: string, makeDb: () => WrappedDb) { it("rethrow propagates non-SQLite errors unchanged", () => { const err = new Error("custom error"); - assert.throws(() => db.rethrow(() => { throw err; }), (e) => e === err); + assert.throws( + () => + db.rethrow(() => { + throw err; + }), + (e) => e === err, + ); }); }); } @@ -93,9 +107,7 @@ describe("better-sqlite3 WrappedDb", async () => { let bs3Ctor: (new (path: string) => WrappedDb) | null = null; try { - const mod = await import( - "../../../src/db/sqlite/impl/better-sqlite3.js" - ); + const mod = await import("../../../src/db/sqlite/impl/better-sqlite3.js"); bs3Ctor = mod.WrappedBetterSqlite3Db; } catch { // better-sqlite3 not available, skip diff --git a/test/helpers/koa.ts b/test/helpers/koa.ts index 9e57bb2..ce06668 100644 --- a/test/helpers/koa.ts +++ b/test/helpers/koa.ts @@ -10,7 +10,7 @@ export interface TestServer { } export async function createTestServer( - db: BackendDbInterface + db: BackendDbInterface, ): Promise { const app = new Koa(); const router = apirouter(db); @@ -23,7 +23,7 @@ export async function createTestServer( url: `http://127.0.0.1:${port}`, close: () => new Promise((resolve, reject) => - server.close((err) => (err ? reject(err) : resolve())) + server.close((err) => (err ? reject(err) : resolve())), ), }; } diff --git a/test/shared/abodes.ts b/test/shared/abodes.ts index d535c29..1c72dc0 100644 --- a/test/shared/abodes.ts +++ b/test/shared/abodes.ts @@ -1,12 +1,15 @@ import { describe, it, before, after } from "node:test"; import assert from "node:assert/strict"; import type { DbInterface } from "../../src/db/types/DbInterface.js"; -import { NotFoundAbodeError, InvalidAbodeError } from "../../src/db/types/errors.js"; +import { + NotFoundAbodeError, + InvalidAbodeError, +} from "../../src/db/types/errors.js"; import { hashPassword } from "../../src/util/hash.js"; export function runAbodeTests( name: string, - getDb: () => Promise<{ db: DbInterface; close(): void }> + getDb: () => Promise<{ db: DbInterface; close(): void }>, ): void { describe(`${name}: abodes`, async () => { let db: DbInterface; @@ -28,7 +31,10 @@ export function runAbodeTests( after(() => close()); it("createAbode returns an Abode with expected fields", async () => { - const abode = await db.createAbode({ name: "Test Abode" }, { uid: ctxUid }); + const abode = await db.createAbode( + { name: "Test Abode" }, + { uid: ctxUid }, + ); assert.ok(abode.aid, "has aid"); assert.equal(abode.name, "Test Abode"); assert.ok(abode.created_at); @@ -36,7 +42,10 @@ export function runAbodeTests( }); it("getAbodeById returns the created abode", async () => { - const created = await db.createAbode({ name: "ById Abode" }, { uid: ctxUid }); + const created = await db.createAbode( + { name: "ById Abode" }, + { uid: ctxUid }, + ); const found = await db.getAbodeById(created.aid); assert.equal(found.aid, created.aid); assert.equal(found.name, "ById Abode"); @@ -48,14 +57,14 @@ export function runAbodeTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); it("listAbodes includes the created abode", async () => { const created = await db.createAbode( { name: `Listed Abode ${Date.now()}` }, - { uid: ctxUid } + { uid: ctxUid }, ); const abodes = await db.listAbodes(); assert.ok(Array.isArray(abodes)); @@ -67,32 +76,38 @@ export function runAbodeTests( const created = await db.createAbode({ name: "Before" }, { uid: ctxUid }); const updated = await db.updateAbode( { aid: created.aid, name: "After" }, - { uid: ctxUid } + { uid: ctxUid }, ); assert.equal(updated.aid, created.aid); assert.equal(updated.name, "After"); }); it("updateAbode with no fields throws InvalidAbodeError", async () => { - const created = await db.createAbode({ name: "No Update" }, { uid: ctxUid }); + const created = await db.createAbode( + { name: "No Update" }, + { uid: ctxUid }, + ); await assert.rejects( () => db.updateAbode({ aid: created.aid }, { uid: ctxUid }), (err) => { assert.ok(err instanceof InvalidAbodeError); return true; - } + }, ); }); it("deleteAbodeById removes the abode", async () => { - const created = await db.createAbode({ name: "To Delete" }, { uid: ctxUid }); + const created = await db.createAbode( + { name: "To Delete" }, + { uid: ctxUid }, + ); await db.deleteAbodeById(created.aid); await assert.rejects( () => db.getAbodeById(created.aid), (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -102,7 +117,7 @@ export function runAbodeTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); diff --git a/test/shared/apikeys.ts b/test/shared/apikeys.ts index b064558..45e0cf3 100644 --- a/test/shared/apikeys.ts +++ b/test/shared/apikeys.ts @@ -6,7 +6,7 @@ import { hashPassword } from "../../src/util/hash.js"; export function runApikeyTests( name: string, - getDb: () => Promise<{ db: DbInterface; close(): void }> + getDb: () => Promise<{ db: DbInterface; close(): void }>, ): void { describe(`${name}: apikeys`, async () => { let db: DbInterface; @@ -69,7 +69,7 @@ export function runApikeyTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -85,7 +85,7 @@ export function runApikeyTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -95,7 +95,7 @@ export function runApikeyTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); diff --git a/test/shared/auth.ts b/test/shared/auth.ts index c2a8a30..47fe740 100644 --- a/test/shared/auth.ts +++ b/test/shared/auth.ts @@ -11,7 +11,10 @@ import { hashPassword } from "../../src/util/hash.js"; export function runAuthTests( name: string, getDb: () => Promise<{ db: BackendDbInterface; close(): void }>, - createExpiredApikey?: (db: BackendDbInterface, uid: string) => Promise<`at_${string}`> + createExpiredApikey?: ( + db: BackendDbInterface, + uid: string, + ) => Promise<`at_${string}`>, ): void { describe(`${name}: auth`, async () => { let db: BackendDbInterface; @@ -23,7 +26,12 @@ export function runAuthTests( ({ db, close } = await getDb()); email = `auth-user-${Date.now()}@test.example`; const pw = await hashPassword(password); - await db.createUser({ email, name: "Auth User", password: pw, flags: {} }); + await db.createUser({ + email, + name: "Auth User", + password: pw, + flags: {}, + }); }); after(() => close()); @@ -40,7 +48,7 @@ export function runAuthTests( (err) => { assert.ok(err instanceof NotAuthorizedAbodeError); return true; - } + }, ); }); @@ -54,7 +62,7 @@ export function runAuthTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -71,7 +79,7 @@ export function runAuthTests( (err) => { assert.ok(err instanceof ConflictAbodeError); return true; - } + }, ); }); }); @@ -118,7 +126,7 @@ export function runAuthTests( (err) => { assert.ok(err instanceof NotAuthorizedAbodeError); return true; - } + }, ); }); @@ -129,7 +137,7 @@ export function runAuthTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); diff --git a/test/shared/residents.ts b/test/shared/residents.ts index 6fdfd0f..1d08179 100644 --- a/test/shared/residents.ts +++ b/test/shared/residents.ts @@ -1,12 +1,15 @@ import { describe, it, before, after } from "node:test"; import assert from "node:assert/strict"; import type { DbInterface } from "../../src/db/types/DbInterface.js"; -import { NotFoundAbodeError, InvalidAbodeError } from "../../src/db/types/errors.js"; +import { + NotFoundAbodeError, + InvalidAbodeError, +} from "../../src/db/types/errors.js"; import { hashPassword } from "../../src/util/hash.js"; export function runResidentTests( name: string, - getDb: () => Promise<{ db: DbInterface; close(): void }> + getDb: () => Promise<{ db: DbInterface; close(): void }>, ): void { describe(`${name}: residents`, async () => { let db: DbInterface; @@ -36,7 +39,7 @@ export function runResidentTests( uid = resUser.uid; const abode = await db.createAbode( { name: `Resident Abode ${Date.now()}` }, - { uid: ctxUid } + { uid: ctxUid }, ); aid = abode.aid; await db.createResident({ uid, aid, flags: {} }, { uid: ctxUid }); @@ -56,12 +59,12 @@ export function runResidentTests( () => db.getResidentById( "00000000-0000-0000-0000-000000000000", - "00000000-0000-0000-0000-000000000001" + "00000000-0000-0000-0000-000000000001", ), (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -101,7 +104,7 @@ export function runResidentTests( it("updateResident updates flags", async () => { const updated = await db.updateResident( { uid, aid, flags: { admin: true } }, - { uid: ctxUid } + { uid: ctxUid }, ); assert.equal(updated.uid, uid); assert.deepEqual(updated.flags, { admin: true }); @@ -113,7 +116,7 @@ export function runResidentTests( (err) => { assert.ok(err instanceof InvalidAbodeError); return true; - } + }, ); }); @@ -125,15 +128,21 @@ export function runResidentTests( password: pw, flags: {}, }); - const abode2 = await db.createAbode({ name: "Del Abode" }, { uid: ctxUid }); - await db.createResident({ uid: user2.uid, aid: abode2.aid, flags: {} }, { uid: ctxUid }); + const abode2 = await db.createAbode( + { name: "Del Abode" }, + { uid: ctxUid }, + ); + await db.createResident( + { uid: user2.uid, aid: abode2.aid, flags: {} }, + { uid: ctxUid }, + ); await db.deleteResidentById(user2.uid, abode2.aid); await assert.rejects( () => db.getResidentById(user2.uid, abode2.aid), (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -142,12 +151,12 @@ export function runResidentTests( () => db.deleteResidentById( "00000000-0000-0000-0000-000000000002", - "00000000-0000-0000-0000-000000000003" + "00000000-0000-0000-0000-000000000003", ), (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); diff --git a/test/shared/sessions.ts b/test/shared/sessions.ts index 6988f15..8e31ce2 100644 --- a/test/shared/sessions.ts +++ b/test/shared/sessions.ts @@ -6,7 +6,7 @@ import { hashPassword } from "../../src/util/hash.js"; export function runSessionTests( name: string, - getDb: () => Promise<{ db: BackendDbInterface; close(): void }> + getDb: () => Promise<{ db: BackendDbInterface; close(): void }>, ): void { describe(`${name}: sessions`, async () => { let db: BackendDbInterface; @@ -46,7 +46,7 @@ export function runSessionTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -58,7 +58,7 @@ export function runSessionTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); diff --git a/test/shared/users.ts b/test/shared/users.ts index f20fd2c..f4d7435 100644 --- a/test/shared/users.ts +++ b/test/shared/users.ts @@ -11,7 +11,7 @@ import { hashPassword } from "../../src/util/hash.js"; export function runUserTests( name: string, getDb: () => Promise<{ db: DbInterface; close(): void }>, - getReadonlyDb?: () => Promise<{ db: DbInterface; close(): void }> + getReadonlyDb?: () => Promise<{ db: DbInterface; close(): void }>, ): void { describe(`${name}: users`, async () => { let db: DbInterface; @@ -57,13 +57,18 @@ export function runUserTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); it("getUserByEmail returns the created user", async () => { const email = `user-byemail-${Date.now()}@test.example`; - await db.createUser({ email, name: "ByEmail User", password: hashedPw, flags: {} }); + await db.createUser({ + email, + name: "ByEmail User", + password: hashedPw, + flags: {}, + }); const found = await db.getUserByEmail(email); assert.ok("email" in found, "result includes email"); assert.equal(found.email, email); @@ -75,7 +80,7 @@ export function runUserTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -100,7 +105,10 @@ export function runUserTests( password: hashedPw, flags: {}, }); - const updated = await db.updateUser({ uid: created.uid, name: "After Update" }); + const updated = await db.updateUser({ + uid: created.uid, + name: "After Update", + }); assert.equal(updated.uid, created.uid); assert.equal(updated.name, "After Update"); }); @@ -117,7 +125,7 @@ export function runUserTests( (err) => { assert.ok(err instanceof InvalidAbodeError); return true; - } + }, ); }); @@ -134,7 +142,7 @@ export function runUserTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); @@ -144,7 +152,7 @@ export function runUserTests( (err) => { assert.ok(err instanceof NotFoundAbodeError); return true; - } + }, ); }); }); @@ -178,7 +186,7 @@ export function runUserTests( (err) => { assert.ok(err instanceof ReadonlyAbodeError); return true; - } + }, ); }); }); diff --git a/test/tools/authenticate.test.ts b/test/tools/authenticate.test.ts index 6fa6a8a..bc97138 100644 --- a/test/tools/authenticate.test.ts +++ b/test/tools/authenticate.test.ts @@ -29,7 +29,7 @@ const MOCK_APIKEY: ClientApikey = { }; function makeMockDb( - overrides: Partial = {} + overrides: Partial = {}, ): BackendDbInterface { return { readonly: false, @@ -37,42 +37,97 @@ function makeMockDb( name: "mock", close: async () => {}, listUsers: async () => [], - getUserById: async () => { throw new NotFoundAbodeError(); }, + getUserById: async () => { + throw new NotFoundAbodeError(); + }, deleteUserById: async () => {}, createUser: async () => MOCK_USER, updateUser: async () => MOCK_USER, - getUserByEmail: async () => { throw new NotFoundAbodeError(); }, + getUserByEmail: async () => { + throw new NotFoundAbodeError(); + }, listAbodes: async () => [], - getAbodeById: async () => { throw new NotFoundAbodeError(); }, + getAbodeById: async () => { + throw new NotFoundAbodeError(); + }, deleteAbodeById: async () => {}, - createAbode: async () => ({ aid: "a", name: "A", created_at: "", created_by: null, updated_at: "", updated_by: null }), - updateAbode: async () => ({ aid: "a", name: "A", created_at: "", created_by: null, updated_at: "", updated_by: null }), + createAbode: async () => ({ + aid: "a", + name: "A", + created_at: "", + created_by: null, + updated_at: "", + updated_by: null, + }), + updateAbode: async () => ({ + aid: "a", + name: "A", + created_at: "", + created_by: null, + updated_at: "", + updated_by: null, + }), listResidents: async () => [], - getResidentById: async () => { throw new NotFoundAbodeError(); }, + getResidentById: async () => { + throw new NotFoundAbodeError(); + }, deleteResidentById: async () => {}, - createResident: async () => ({ uid: "", aid: "", flags: {}, created_at: "", created_by: null, updated_at: "", updated_by: null }), - updateResident: async () => ({ uid: "", aid: "", flags: {}, created_at: "", created_by: null, updated_at: "", updated_by: null }), + createResident: async () => ({ + uid: "", + aid: "", + flags: {}, + created_at: "", + created_by: null, + updated_at: "", + updated_by: null, + }), + updateResident: async () => ({ + uid: "", + aid: "", + flags: {}, + created_at: "", + created_by: null, + updated_at: "", + updated_by: null, + }), listResidentsByUserId: async () => [], listResidentsByAbodeId: async () => [], listUsersByAbodeId: async () => [], listAbodesByUserId: async () => [], listNotes: async () => [], - getNoteById: async () => { throw new NotFoundAbodeError(); }, + getNoteById: async () => { + throw new NotFoundAbodeError(); + }, deleteNoteById: async () => {}, - createNote: async () => { throw new Error("unimplemented"); }, - updateNote: async () => { throw new Error("unimplemented"); }, + createNote: async () => { + throw new Error("unimplemented"); + }, + updateNote: async () => { + throw new Error("unimplemented"); + }, listNotesByAbodeId: async () => [], listNotesByUserId: async () => [], deleteSessionsByUser: async () => {}, listApikeysByUser: async () => [], - getApikeyById: async () => { throw new NotFoundAbodeError(); }, - createApikey: async () => [MOCK_APIKEY, "at_" + "0".repeat(32) as `at_${string}`], + getApikeyById: async () => { + throw new NotFoundAbodeError(); + }, + createApikey: async () => [ + MOCK_APIKEY, + ("at_" + "0".repeat(32)) as `at_${string}`, + ], deleteApikeyById: async () => {}, - getUserByLogin: async () => { throw new NotFoundAbodeError(); }, - getUserBySession: async () => { throw new NotFoundAbodeError(); }, + getUserByLogin: async () => { + throw new NotFoundAbodeError(); + }, + getUserBySession: async () => { + throw new NotFoundAbodeError(); + }, createSession: async () => `as_${"0".repeat(32)}`, deleteSession: async () => {}, - getUserByApikey: async () => { throw new NotFoundAbodeError(); }, + getUserByApikey: async () => { + throw new NotFoundAbodeError(); + }, ...overrides, }; } @@ -92,7 +147,10 @@ type MockCtx = { }; }; -function makeMockCtx(headerOverrides: Record = {}, cookieOverrides: Record = {}): MockCtx { +function makeMockCtx( + headerOverrides: Record = {}, + cookieOverrides: Record = {}, +): MockCtx { const clearedCookies = new Set(); const ctx: MockCtx = { headers: headerOverrides, @@ -110,7 +168,10 @@ function makeMockCtx(headerOverrides: Record = {}, cookieOverrid return cookieOverrides[name]; }, set(name: string, value: string, opts?: unknown) { - if (value === "" || (opts && (opts as { expires?: Date }).expires?.getFullYear()! < 2000)) { + if ( + value === "" || + (opts && (opts as { expires?: Date }).expires?.getFullYear()! < 2000) + ) { clearedCookies.add(name); } }, @@ -121,11 +182,13 @@ function makeMockCtx(headerOverrides: Record = {}, cookieOverrid async function runMiddleware( db: BackendDbInterface, - ctx: MockCtx + ctx: MockCtx, ): Promise { let nextCalled = false; const mw = authenticate(db); - await mw(ctx as any, async () => { nextCalled = true; }); + await mw(ctx as any, async () => { + nextCalled = true; + }); return nextCalled; } @@ -157,7 +220,9 @@ describe("authenticate middleware", () => { it("wrong password (NotAuthorizedAbodeError) → 401 invalid_password", async () => { const db = makeMockDb({ - getUserByLogin: async () => { throw new NotAuthorizedAbodeError(); }, + getUserByLogin: async () => { + throw new NotAuthorizedAbodeError(); + }, }); const ctx = makeMockCtx({ Authorization: "Basic " + btoa("user:wrong") }); await runMiddleware(db, ctx); @@ -167,9 +232,13 @@ describe("authenticate middleware", () => { it("unknown user (NotFoundAbodeError) → 401 unknown_user", async () => { const db = makeMockDb({ - getUserByLogin: async () => { throw new NotFoundAbodeError(); }, + getUserByLogin: async () => { + throw new NotFoundAbodeError(); + }, + }); + const ctx = makeMockCtx({ + Authorization: "Basic " + btoa("nobody:pass"), }); - const ctx = makeMockCtx({ Authorization: "Basic " + btoa("nobody:pass") }); await runMiddleware(db, ctx); assert.equal(ctx.status, 401); assert.deepEqual((ctx.body as any)?.error, "unknown_user"); @@ -177,7 +246,9 @@ describe("authenticate middleware", () => { it("ConflictAbodeError (#unset password) → 401 user_not_loggable", async () => { const db = makeMockDb({ - getUserByLogin: async () => { throw new ConflictAbodeError(); }, + getUserByLogin: async () => { + throw new ConflictAbodeError(); + }, }); const ctx = makeMockCtx({ Authorization: "Basic " + btoa("user:pass") }); await runMiddleware(db, ctx); @@ -211,7 +282,9 @@ describe("authenticate middleware", () => { it("invalid/expired at_ token → 401 invalid_apikey", async () => { const db = makeMockDb({ - getUserByApikey: async () => { throw new NotFoundAbodeError(); }, + getUserByApikey: async () => { + throw new NotFoundAbodeError(); + }, }); const ctx = makeMockCtx({ Authorization: `Bearer ${validToken}` }); await runMiddleware(db, ctx); @@ -246,17 +319,25 @@ describe("authenticate middleware", () => { const db = makeMockDb(); const ctx = makeMockCtx({}, { abode_session: "not-a-session-token" }); await runMiddleware(db, ctx); - assert.ok(ctx.clearedCookies.has("abode_session"), "cookie should be cleared"); + assert.ok( + ctx.clearedCookies.has("abode_session"), + "cookie should be cleared", + ); assert.equal(ctx.status, 401); }); it("expired/unknown session token clears cookie and returns 401", async () => { const db = makeMockDb({ - getUserBySession: async () => { throw new NotFoundAbodeError(); }, + getUserBySession: async () => { + throw new NotFoundAbodeError(); + }, }); const ctx = makeMockCtx({}, { abode_session: validToken }); await runMiddleware(db, ctx); - assert.ok(ctx.clearedCookies.has("abode_session"), "cookie should be cleared"); + assert.ok( + ctx.clearedCookies.has("abode_session"), + "cookie should be cleared", + ); assert.equal(ctx.status, 401); }); }); diff --git a/test/tools/convertError.test.ts b/test/tools/convertError.test.ts index e5e2be0..96ed634 100644 --- a/test/tools/convertError.test.ts +++ b/test/tools/convertError.test.ts @@ -25,7 +25,9 @@ describe("convertError middleware", () => { it("does not interfere when next succeeds", async () => { const ctx = makeCtx(); let nextCalled = false; - await convertError(ctx as any, async () => { nextCalled = true; }); + await convertError(ctx as any, async () => { + nextCalled = true; + }); assert.equal(nextCalled, true); assert.equal(ctx.status, 200); }); diff --git a/test/tools/jsonBody.test.ts b/test/tools/jsonBody.test.ts index 2d3eacb..c8c8280 100644 --- a/test/tools/jsonBody.test.ts +++ b/test/tools/jsonBody.test.ts @@ -8,7 +8,7 @@ import { jsonBody } from "../../src/webapi/middleware/jsonBody.js"; async function request( url: string, - opts: { method?: string; body?: unknown; contentType?: string } = {} + opts: { method?: string; body?: unknown; contentType?: string } = {}, ): Promise<{ status: number; body: unknown }> { const method = opts.method ?? "POST"; const bodyStr = @@ -21,18 +21,19 @@ async function request( const res = await fetch(url, { method, headers, body: bodyStr }); const text = await res.text(); let parsed: unknown; - try { parsed = JSON.parse(text); } catch { parsed = text; } + try { + parsed = JSON.parse(text); + } catch { + parsed = text; + } return { status: res.status, body: parsed }; } async function makeTestServer() { - const failValidator = Object.assign( - (_obj: unknown): _obj is never => false, - { - errors: [{ message: "required" }] as unknown[], - schema: { $id: "test-schema", title: "Test", description: "" }, - } - ); + const failValidator = Object.assign((_obj: unknown): _obj is never => false, { + errors: [{ message: "required" }] as unknown[], + schema: { $id: "test-schema", title: "Test", description: "" }, + }); const app = new Koa(); const router = new KoaRouter(); @@ -48,7 +49,7 @@ async function makeTestServer() { async (ctx) => { ctx.status = 200; ctx.body = { ok: true }; - } + }, ); router.post( @@ -57,7 +58,7 @@ async function makeTestServer() { async (ctx) => { ctx.status = 200; ctx.body = { ok: true, body: ctx.request.body }; - } + }, ); app.use(router.routes()); @@ -69,7 +70,7 @@ async function makeTestServer() { const url = `http://127.0.0.1:${port}`; const close = () => new Promise((resolve, reject) => - server.close((err) => (err ? reject(err) : resolve())) + server.close((err) => (err ? reject(err) : resolve())), ); return { url, close }; } @@ -107,11 +108,16 @@ describe("jsonBody middleware", () => { }); it("failing validator → 400 jsonchema_validation_failed with schema and errors", async () => { - const res = await request(url + "/fail-validate", { body: { any: "thing" } }); + const res = await request(url + "/fail-validate", { + body: { any: "thing" }, + }); assert.equal(res.status, 400); assert.equal((res.body as any).error, "jsonchema_validation_failed"); assert.ok((res.body as any).schema, "response includes schema"); - assert.ok(Array.isArray((res.body as any).errors), "response includes errors"); + assert.ok( + Array.isArray((res.body as any).errors), + "response includes errors", + ); }); it("includeParams: param absent from body → merged in", async () => { diff --git a/tsconfig.json b/tsconfig.json index f33ae09..915c0c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "rootDir": "src", - "strict": true, - "verbatimModuleSyntax": true, - "moduleResolution": "nodenext", - "module": "nodenext", - "target": "esnext", - "allowImportingTsExtensions": false, - "noEmit": true, - "sourceMap": true, - "jsx": "react-jsxdev" - }, - "include": ["src/**/*.ts","src/**/*.tsx"] -} \ No newline at end of file + "compilerOptions": { + "rootDir": "src", + "strict": true, + "verbatimModuleSyntax": true, + "moduleResolution": "nodenext", + "module": "nodenext", + "target": "esnext", + "allowImportingTsExtensions": false, + "noEmit": true, + "sourceMap": true, + "jsx": "react-jsxdev" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/tsconfig.test.json b/tsconfig.test.json index 670bb02..1d5a44f 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,7 +1,7 @@ { - "extends": "./tsconfig.json", - "compilerOptions": { - "rootDir": "." - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"] + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "test/**/*.ts"] } diff --git a/webpack.config.ts b/webpack.config.ts index 70e257d..3140fdf 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -25,8 +25,8 @@ export default async (): Promise => { process.env.DB_SOURCES === "dynamic" ? "dynamic" : process.env.DB_SOURCES === "static" - ? "static" - : "shared"; + ? "static" + : "shared"; let disableDbSqlite = process.env.DISABLE_DB_SQLITE === "1"; let disableBs3 = disableDbSqlite || process.env.DISABLE_BS3 === "1"; @@ -56,7 +56,7 @@ export default async (): Promise => { (await readdir(file("./src/bin"))).map((bin) => [ bin.replace(/\..+$/, ""), file(`./src/bin/${bin}`), - ]) + ]), ); for (const bin of Object.keys(binaries)) { copies.push({ @@ -93,12 +93,12 @@ export default async (): Promise => { } else if (dbSources === "static") { console.log("Resolving db interfaces statically"); aliases[file("./src/db/dbSources.ts")] = file( - "./src/db/dbSources.static.ts" + "./src/db/dbSources.static.ts", ); } else { console.log("Resolving db interfaces shared"); aliases[file("./src/db/dbSources.ts")] = file( - "./src/db/dbSources.shared.ts" + "./src/db/dbSources.shared.ts", ); } @@ -111,21 +111,21 @@ export default async (): Promise => { } else if (disableBs3) { console.log("Disabling better-sqlite3 sqlite db backend"); aliases[file("./src/db/sqlite/impl/implementations.ts")] = file( - "./src/db/sqlite/impl/implementations.node.ts" + "./src/db/sqlite/impl/implementations.node.ts", ); compiledSources.push("sqlite"); } else if (disableNodeSqlite) { console.log("Disabling node:sqlite sqlite db backend"); aliases[file("./src/db/sqlite/impl/implementations.ts")] = file( - "./src/db/sqlite/impl/implementations.bs3.ts" + "./src/db/sqlite/impl/implementations.bs3.ts", ); compiledSources.push("sqlite"); } else { console.log( - "Enabling sqlite db interface with better-sqlite3 and node:sqlite backends" + "Enabling sqlite db interface with better-sqlite3 and node:sqlite backends", ); aliases[file("./src/db/sqlite/impl/implementations.ts")] = file( - "./src/db/sqlite/impl/implementations.all.ts" + "./src/db/sqlite/impl/implementations.all.ts", ); compiledSources.push("sqlite"); } @@ -145,18 +145,18 @@ export default async (): Promise => { defines.compiledSources = JSON.stringify(compiledSources); for (const source of existingSources) defines[`compiledSources.${source}`] = JSON.stringify( - compiledSources.includes(source) + compiledSources.includes(source), ); if (!compiledSources.length) { console.warn( - "No db interface enabled, the builds will be completely useless" + "No db interface enabled, the builds will be completely useless", ); process.exit(1); } // log about the natives we have console.log( - `Using ${Object.values(natives).filter(Boolean).length} natives:` + `Using ${Object.values(natives).filter(Boolean).length} natives:`, ); for (const [key, path] of Object.entries(natives)) { if (path) console.log(`- ${key}: ${path}`);