diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..4fc8189 --- /dev/null +++ b/.env.test @@ -0,0 +1,10 @@ +MYSQL_DATABASE=testdb +MYSQL_USER=testuser +MYSQL_PASSWORD=testpassword +MYSQL_ROOT_PASSWORD=rootcipassword +# MYSQL_HOST_PORT=23306 +MYSQL_PORT=23407 +MYSQL_HOST=localhost +PRISMA_DATABASE_URL=mysql://testuser:testpassword@tagtool_mysqldb:3306/testdb + +MYSQL_CI_VERSION=8.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8b7e345..0a48095 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["github.vscode-github-actions"] + "recommendations": ["github.vscode-github-actions", "vitest.explorer", "hbenl.vscode-test-explorer"] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0c52060 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "jest.enable": false, + "jest.rootPath": "${workspaceFolder}" +} \ No newline at end of file diff --git a/Dockerfile.dbmigrate b/Dockerfile.dbmigrate index 9057f34..3d1e11c 100644 --- a/Dockerfile.dbmigrate +++ b/Dockerfile.dbmigrate @@ -5,7 +5,7 @@ FROM ghcr.io/tjsr/node_patched_npm:${NODE_VERSION}-alpine${ALPINE_VERSION}-npm${ RUN --mount=type=cache,target=/root/.npm mkdir /opt/tagtool && npm config set fund false --location=global -COPY [ "package.json", "package-lock.json", "/opt/tagtool/" ] +COPY [ "package.json", "package-lock.json", ".npmrc", "/opt/tagtool/" ] COPY prisma /opt/tagtool WORKDIR /opt/tagtool @@ -13,7 +13,8 @@ RUN --mount=type=secret,id=github,target=/root/.npm/github_pat if [ ! -s /root/. RUN --mount=type=secret,id=github,target=/root/.npm/github_pat --mount=type=cache,target=/root/.npm \ echo "//npm.pkg.github.com/:_authToken=$(cat /root/.npm/github_pat)" >> /root/.npmrc && \ - npm install && \ + echo "@tjsr:registry=https://npm.pkg.github.com/" >> /root/.npmrc && \ + npm ci && \ rm -f /root/.npmrc CMD ["npm", "run", "db:upgrade:deploy"] diff --git a/docker-compose.yml b/docker-compose.yml index a4a5646..d422ee4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,38 +3,46 @@ services: container_name: tagtool_mysqldb build: args: - - MYSQL_VERSION=${MYSQL_CI_VERSION:-5.7} + MYSQL_VERSION: ${MYSQL_CI_VERSION:-5.7} context: . dockerfile_inline: | ARG MYSQL_VERSION 5.7 FROM mysql:${MYSQL_VERSION:-5.7} as mysql #COPY resources/*.sql /docker-entrypoint-initdb.d/ - # env_file: - # - ./.env.test - environment: - MYSQL_ROOT_PASSWORD: cirootpassword - MYSQL_DATABASE: tagtool_ci - MYSQL_USER: ciuser - MYSQL_PASSWORD: cipassword - MYSQL_HOST: '127.0.0.1' + env_file: + - ./.env.test + # environment: + # MYSQL_ROOT_PASSWORD: cirootpassword + # MYSQL_DATABASE: tagtool_ci + # MYSQL_USER: ciuser + # MYSQL_PASSWORD: cipassword + # MYSQL_HOST: '127.0.0.1' ports: - "${MYSQL_CI_HOST_PORT:-23407}:3306" command: --default-authentication-plugin=mysql_native_password healthcheck: - test: ["CMD", "mysql", "-h", "127.0.0.1", "--silent", "-uciuser", "-pcipassword", "-e", "SELECT 1"] + test: ["CMD-SHELL", "mysql -h 127.0.0.1 --silent -u$$MYSQL_USER -p$$MYSQL_PASSWORD", "-e", "SELECT 1"] interval: 3s retries: 5 start_period: 30s cidbmig: container_name: tagtool_dbmigration build: + args: + NODE_VERSION: 20.13.1 + ALPINE_VERSION: 3.19 + NPM_VERSION: 10.7.0 context: . dockerfile: Dockerfile.dbmigrate secrets: - github - environment: - - PRISMA_DATABASE_URL=mysql://ciuser:cipassword@tagtool_mysqldb:3306/tagtool_ci + env_file: + - path: ./.env.test + required: true + # environment: + # - PRISMA_DATABASE_URL=mysql://testuser:testpassword@tagtool_mysqldb:3306/testdb + # - PRISMA_DATABASE_URL="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tagtool_mysqldb:${MYSQL_PORT}/${MYSQL_DATABASE}" depends_on: - mysqldb diff --git a/index.ts b/index.ts index 02ddb58..1adca6a 100644 --- a/index.ts +++ b/index.ts @@ -1,10 +1,9 @@ -import * as dotenv from 'dotenv'; +import { loadEnv, requireEnv } from '@tjsr/simple-env-utils'; import express from 'express'; -import { requireEnv } from '@tjsr/simple-env-utils'; import { startApp } from './src/server.js'; -dotenv.config(); +loadEnv(); requireEnv('SESSION_SECRET'); requireEnv('USERID_UUID_NAMESPACE'); diff --git a/jest.config.ts b/jest.config.ts index 807f3c1..e9802cc 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,7 +1,7 @@ import type { JestConfigWithTsJest } from 'ts-jest'; -import dotenvFlow from 'dotenv-flow'; +import { loadEnv } from '@tjsr/simple-env-utils'; -dotenvFlow.config({ silent: true }); +loadEnv({ path: process.cwd() }); const config: JestConfigWithTsJest = { bail: 1, @@ -10,7 +10,7 @@ const config: JestConfigWithTsJest = { preset: 'ts-jest', setupFiles: ['/src/setup-tests.ts'], testEnvironment: 'node', - testRunner: 'jest-jasmine2', + testRunner: 'node', extensionsToTreatAsEsm: ['.ts'], moduleFileExtensions: ['js', 'mjs', 'json', 'ts'], moduleNameMapper: { diff --git a/package-lock.json b/package-lock.json index d9c8b5f..a490576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "license": "ISC", "dependencies": { "@tjsr/eslint-config": "github:tjsr/eslint-config#main", - "@tjsr/mysql-pool-utils": "^0.1.0", - "@tjsr/simple-env-utils": "^0.0.10", - "@tjsr/user-session-middleware": "^0.0.4", + "@tjsr/mysql-pool-utils": "^0.1.2", + "@tjsr/simple-env-utils": "^0.0.13", + "@tjsr/user-session-middleware": "^0.0.5", "@vitejs/plugin-react": "^4.2.1", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -69,7 +69,8 @@ "tsx": "^4.9.3", "typescript": "^5.4.5", "typescript-eslint": "^7.8.0", - "vite": "^5.2.11" + "vite": "^5.2.11", + "vitest": "^1.6.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2641,9 +2642,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2973,11 +2974,12 @@ } }, "node_modules/@tjsr/mysql-pool-utils": { - "version": "0.1.0", - "resolved": "https://npm.pkg.github.com/download/@tjsr/mysql-pool-utils/0.1.0/acaf3dc17d41e9ebc70683f5bff85f3af0c3925d", - "integrity": "sha512-nbv98uGnQTt+5JDJLsN9ZX1+DKcd4noPaS+NQOxlmYlJiNiE4fGl/68VomuZU5scFmnyokylABljZymgtYKm2Q==", + "version": "0.1.2", + "resolved": "https://npm.pkg.github.com/download/@tjsr/mysql-pool-utils/0.1.2/ce26d8a17e82314dda26d8d932cac6bfa30c425d", + "integrity": "sha512-wPwaTfcRpbtIgWkIFtvcq4pwro+UBbrYLaSOVVnSo2mZu+pW2Fx2WV8E9cZSE6E3O16ARPZ589tR2iaFvbiFjQ==", + "hasInstallScript": true, "dependencies": { - "@tjsr/simple-env-utils": "^0.0.10", + "@tjsr/simple-env-utils": "^0.0.13", "dotenv-flow": "^4.1.0", "mysql2": "^3.9.7" }, @@ -2986,20 +2988,22 @@ } }, "node_modules/@tjsr/simple-env-utils": { - "version": "0.0.10", - "resolved": "https://npm.pkg.github.com/download/@tjsr/simple-env-utils/0.0.10/c9949e89c541879b545cc9eb97e4ccfe4db985f8", - "integrity": "sha512-NuZ8tPu06eRoBglN6irHZFiJO2bZd9tTDxVYQgxcbpKXltBkkNbgMDDNiWmRLrVy0Zi3NcmuydyFDgCWlKxYQA==", + "version": "0.0.13", + "resolved": "https://npm.pkg.github.com/download/@tjsr/simple-env-utils/0.0.13/8e6fb9c6b31aadf01ccef80814a445448e382121", + "integrity": "sha512-/LFWbFaGcyrJ4MJas+vDjTTc59Y00oUJSRfXLXnMLRZLoMTGiubcCObsvig4UJBGX94JDsXoD0aKVCSq+IZvbw==", "dependencies": { "dotenv-flow": "^4.1.0" } }, "node_modules/@tjsr/user-session-middleware": { - "version": "0.0.4", - "resolved": "https://npm.pkg.github.com/download/@tjsr/user-session-middleware/0.0.4/0647e6888e90300292506f13789d58b1d9c21730", - "integrity": "sha512-/bPkWQLuY9pT16FOoYoDKG+Jc/QQI8TYRoQHg2J/0zrZB5lwuVWFoVtS1d4impHqHF8Wu4fjOQrbUbw00wf3ww==", + "version": "0.0.5", + "resolved": "https://npm.pkg.github.com/download/@tjsr/user-session-middleware/0.0.5/5e9fe7b6561c802c27169c41a06a2c2362a5bb62", + "integrity": "sha512-WQWYJbldn9LMTZbFDDvWu30drdm2ZgvHxFbt83rdY8Z5+hEd3rVygvvCWsp1BicAHYOL0VZngs5jAOdooT3/Qw==", + "hasInstallScript": true, "dependencies": { - "@tjsr/mysql-pool-utils": "^0.1.0", - "express-mysql-session": "^3.0.1", + "@tjsr/mysql-pool-utils": ">=0.1.0", + "@tjsr/simple-env-utils": "^0.0.11", + "express-mysql-session": "^3.0.2", "express-session": "^1.18.0", "snowflake-uuid": "^1.0.0", "uuid": "^9.0.1" @@ -3008,10 +3012,19 @@ "nodemon": "^3.1.0" }, "peerDependencies": { + "@tjsr/mysql-pool-utils": ">=0.1.0", "dotenv-flow": "^4.1.0", "express": "^4.19.2" } }, + "node_modules/@tjsr/user-session-middleware/node_modules/@tjsr/simple-env-utils": { + "version": "0.0.11", + "resolved": "https://npm.pkg.github.com/download/@tjsr/simple-env-utils/0.0.11/f6c5ba2bbb30c0f2a294d0b5400f8c436f04e295", + "integrity": "sha512-oUdvabt7j84/I4oSOxB1mv8FFM/egBFiB9OeVorC5T7uCOFAytHTJlWNYT8FyI6FI8WSt7l2peYIyPmB2WCYlQ==", + "dependencies": { + "dotenv-flow": "^4.1.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -3703,6 +3716,166 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/expect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", + "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", + "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.6.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/@vitest/spy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", + "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vitest/utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@vitest/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -3847,6 +4020,15 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4169,6 +4351,15 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -4220,6 +4411,24 @@ } ] }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4242,6 +4451,18 @@ "node": ">=10" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4371,6 +4592,12 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4618,6 +4845,18 @@ "ms": "2.0.0" } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5234,6 +5473,15 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5791,6 +6039,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -9653,6 +9910,22 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9696,6 +9969,15 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -9704,6 +9986,15 @@ "node": ">=12" } }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9849,6 +10140,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mlly": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz", + "integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.0", + "ufo": "^1.5.3" + } + }, "node_modules/mock-session": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/mock-session/-/mock-session-0.0.5.tgz", @@ -10285,6 +10588,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -10374,6 +10692,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", + "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.0", + "pathe": "^1.1.2" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", @@ -10976,6 +11305,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -11059,6 +11394,12 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -11067,6 +11408,12 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -11174,6 +11521,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", + "dev": true + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", @@ -11321,6 +11686,30 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "peer": true }, + "node_modules/tinybench": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -11666,6 +12055,12 @@ } } }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -11843,6 +12238,273 @@ } } }, + "node_modules/vite-node": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/vitest": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/vitest/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vitest/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -11871,6 +12533,22 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index fecd82d..ac33e8a 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "scripts": { "clean": "rimraf dist build", "clean:all": "npm run clean && rimraf node_modules package-lock.json", - "test": "jest --runInBand --detectOpenHandles", - "test:coverage": "jest --runInBand --detectOpenHandles --forceExit --coverage", - "test:ci": "docker-compose -f docker-compose-ci.yml build && docker-compose -f docker-compose-ci.yml up && cross-env PRISMA_DATABASE_URL=mysql://root:cirootpassword@localhost:23406/tagtool_ci jest", + "test": "vitest --run", + "test:watch": "vitest", + "test:jest": "jest --runInBand --detectOpenHandles", + "test:jest:coverage": "jest --runInBand --detectOpenHandles --forceExit --coverage", + "test:ci": "docker-compose -f docker-compose-ci.yml build && docker-compose -f docker-compose-ci.yml up && cross-env PRISMA_DATABASE_URL=mysql://root:cirootpassword@localhost:23406/tagtool_ci vitest", "start:frontend": "cross-env GENERATE_SOURCEMAP=true PORT=3008 vite dev", "start:dev": "nodemon index.ts && cross-env PORT=3008 vite dev", "start:prod": "node index.js", @@ -22,7 +24,7 @@ "docker:build": "npx tsx bin/docker-build-with-secret.ts tagtool Dockerfile", "docker:build:dbmigrate": "npx tsx bin/docker-build-with-secret.ts tagtool-dbmigrate Dockerfile.dbmigrate", "docker:test": "npm run docker:build && docker run --rm --name tagtool-test -it tagtool npm i && npm test", - "link": "npm link @tjsr/user-session-middleware @tjsr/simple-env-utils @tjsr/mysql-pool-utils @tjsr/eslint-config", + "link": "npm link @tjsr/user-session-middleware @tjsr/simple-env-utils @tjsr/mysql-pool-utils", "lint": "eslint ", "lint:fix": "eslint --fix \"./src/**/*.{ts,tsx}\"", "prettier": "prettier --single-quote --write \"./src/**/*.{ts,tsx}\"", @@ -32,7 +34,7 @@ "db:upgrade:deploy": "prisma migrate deploy", "docker:build:old": "docker build --secret id=github,env=TAGTOOL_GITHUB_PAT -t tagtool -f Dockerfile .", "docker:build:dbmigrate:old": "docker build --secret id=github,env=TAGTOOL_GITHUB_PAT -t tagtool-dbmigrate -f Dockerfile.dbmigrate .", - "postinstall": "npm run link" + "disable:postinstall": "npm run link" }, "repository": { "type": "git", @@ -46,9 +48,9 @@ }, "dependencies": { "@tjsr/eslint-config": "github:tjsr/eslint-config#main", - "@tjsr/mysql-pool-utils": "^0.1.0", - "@tjsr/simple-env-utils": "^0.0.10", - "@tjsr/user-session-middleware": "^0.0.4", + "@tjsr/mysql-pool-utils": "^0.1.2", + "@tjsr/simple-env-utils": "^0.0.13", + "@tjsr/user-session-middleware": "^0.0.5", "@vitejs/plugin-react": "^4.2.1", "cookie-parser": "^1.4.6", "cors": "^2.8.5", @@ -104,7 +106,8 @@ "tsx": "^4.9.3", "typescript": "^5.4.5", "typescript-eslint": "^7.8.0", - "vite": "^5.2.11" + "vite": "^5.2.11", + "vitest": "^1.6.0" }, "overrides": { "formidable": ">=3.2.4", diff --git a/src/api/tags.spec.test.ts b/src/api/tags.spec.test.ts index 8a568a9..4563cbd 100644 --- a/src/api/tags.spec.test.ts +++ b/src/api/tags.spec.test.ts @@ -1,9 +1,18 @@ import { ObjectId, UserId } from '../types.js'; - -import { TagResponse } from './apiTypes.js'; import { - closeConnectionPool + afterAll, + beforeAll, + beforeEach, + describe, + expect, + test, +} from 'vitest'; +import { + closeConnectionPool, + verifyDatabaseReady, } from '@tjsr/mysql-pool-utils'; + +import { TagResponse } from './apiTypes.js'; import { connectionDetails } from '../setup-tests.js'; import { createRandomId } from '../utils/createRandomId.js'; import { createRandomUserId } from '../auth/user.js'; @@ -22,6 +31,7 @@ describe('GET /tags', () => { 'some-tag-' + createRandomId(randomUUID()).substring(0, 7); beforeAll(async () => { + console.log('connectionDetails:', JSON.stringify(connectionDetails)); const dbReadyPromise: Promise = verifyDatabaseReady(connectionDetails); dbReadyPromise.catch((err) => { @@ -56,7 +66,8 @@ describe('GET /tags', () => { expect(response.body.message).not.toBe( `Invalid objectId ${generatedObjectId}`, ); - done(); + return Promise.resolve(); + // done(); }); }); diff --git a/src/api/tags.ts b/src/api/tags.ts index 32f50a6..dc31630 100644 --- a/src/api/tags.ts +++ b/src/api/tags.ts @@ -73,6 +73,12 @@ const tagsToTagResponse = (tags: Tag[], userId: UserId|undefined): TagResponse = throw new Error('Tags must be defined and not empty'); } const objectId: ObjectId = tags[0].objectId; + + if ( + tags.filter((current: Tag) => current.objectId === undefined).length !== 0 + ) { + throw new Error('objectId must be defined on each returned tag'); + } const responseTags: TagResponseElement[] = []; tags.forEach((t) => { const foundTag:TagResponseElement|undefined = responseTags.find((ft) => ft.tag === t.tag); diff --git a/src/api/user.spec.ts b/src/api/user.spec.ts index 31a8ac4..888d4e0 100644 --- a/src/api/user.spec.ts +++ b/src/api/user.spec.ts @@ -1,3 +1,5 @@ +import { afterAll, beforeAll, describe, expect, test } from 'vitest'; + import { TagtoolSessionData } from '../session.js'; import express from 'express'; import session from 'express-session'; @@ -5,7 +7,7 @@ import { startApp } from '../server.js'; import supertest from 'supertest'; describe('API tests for tags', () => { - let app:express.Express; + let app: express.Express; const testSessionId = 's1234'; const testUserId = 'u1234'; diff --git a/src/database/deleteOwnedTag.ts b/src/database/deleteOwnedTag.ts index 943f267..f141a44 100644 --- a/src/database/deleteOwnedTag.ts +++ b/src/database/deleteOwnedTag.ts @@ -1,12 +1,10 @@ -import { FieldPacket, QueryError, QueryResult, ResultSetHeader } from 'mysql2/promise'; import { ObjectId, UserId } from '../types.js'; import { deleteFromTable, mysqlQuery } from '@tjsr/mysql-pool-utils'; export const deleteOwnedTag = async ( userId: UserId, objectId: ObjectId, - tag: string + tag: string, ): Promise => { return deleteFromTable('Tags', { createdByUserId: userId, objectId, tag }); }; - diff --git a/src/database/findTagsByObjectId.ts b/src/database/findTagsByObjectId.ts index 99ad19c..28ae3e4 100644 --- a/src/database/findTagsByObjectId.ts +++ b/src/database/findTagsByObjectId.ts @@ -2,24 +2,27 @@ import { FieldPacket, QueryResult } from 'mysql2/promise'; import { ObjectId, Tag, UserId } from '../types.js'; import { getConnection, mysqlQuery, safeReleaseConnection } from '@tjsr/mysql-pool-utils'; +type TagQueryResult = [ObjectId, UserId, string]; + export const findTagsByObjectId = async ( - objectId: ObjectId + objectId: ObjectId, ): Promise => { // const conn = (await getConnection()).promise(); const conn = await getConnection(); return new Promise((resolve, reject) => { mysqlQuery( - 'select objectId, createdByUserId, tag from Tags where objectId=?', - [objectId]).then(([queryResult, fieldPacket]: [QueryResult, FieldPacket[]]) => { - - const tags: Tag[] = queryResult as Tag[]; + 'select objectId, createdByUserId, tag from Tags where objectId=?', + [objectId], + ) + .then(([queryResult, fieldPacket]: [QueryResult, FieldPacket[]]) => { + const tags: TagQueryResult[] = queryResult as TagQueryResult[]; // Convert to an in-memory array so the query cursor is closed off. - const outputTags: Tag[] = tags.map((queryTag: Tag) => { + const outputTags: Tag[] = tags.map((queryTag: TagQueryResult) => { const outputTag: Tag = { - createdByUserId: queryTag.createdByUserId as UserId, - objectId: queryTag.objectId as ObjectId, - tag: queryTag.tag as string - }; + createdByUserId: queryTag[1] as UserId, + objectId: queryTag[0] as ObjectId, + tag: queryTag[2] as string, + } as Tag; return outputTag; }); safeReleaseConnection(conn); @@ -40,12 +43,12 @@ export const findTagsByObjectId = async ( // } // tags.push(currentTag); // } - - }).catch((err) => { - safeReleaseConnection(conn); - conn.release(); - return reject(err); - }); + }) + .catch((err) => { + safeReleaseConnection(conn); + conn.release(); + return reject(err); + }); // (err, results) => { // if (err) { // } diff --git a/src/database/mysql.ts b/src/database/mysql.ts index 91f9eea..f3d9cf6 100644 --- a/src/database/mysql.ts +++ b/src/database/mysql.ts @@ -1,10 +1,9 @@ -import * as dotenv from 'dotenv'; - import { EmailAddress } from '../types.js'; import { UserModel } from '../types/model.js'; import { createUserIdFromEmail } from '../auth/user.js'; +import { loadEnv } from '@tjsr/simple-env-utils'; -dotenv.config(); +loadEnv(); export const getDbUserByEmail = (email: EmailAddress): UserModel => { return { diff --git a/src/server.ts b/src/server.ts index 1d69dbf..32fbe8b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,10 @@ -import * as dotenv from 'dotenv-flow'; - -import { addTag, deleteTags, getTags, validateHasUserId, validateTags } from './api/tags.js'; +import { + addTag, + deleteTags, + getTags, + validateHasUserId, + validateTags, +} from './api/tags.js'; import { getSession, simpleSessionId } from '@tjsr/user-session-middleware'; import { IPAddress } from './types.js'; @@ -10,13 +14,14 @@ import cookieParser from 'cookie-parser'; import cors from 'cors'; import express from 'express'; import { getUser } from './api/user.js'; +import { loadEnv } from '@tjsr/simple-env-utils'; import { login } from './api/login.js'; import { logout } from './api/logout.js'; import morgan from 'morgan'; import requestIp from 'request-ip'; import session from 'express-session'; -dotenv.config(); +loadEnv(); const morganLog = morgan('common'); // process.env.PRODUCTION =='true' ? 'common' : 'dev' @@ -24,28 +29,30 @@ const morganLog = morgan('common'); const corsOptions = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Expose-Headers': '*', - 'optionsSuccessStatus': 200, - 'origin': '*', + optionsSuccessStatus: 200, + origin: '*', }; export const getIp = (req: express.Request): IPAddress => { try { if (req.headers.forwarded) { - const forwardedForHeader: string|undefined = req.headers.forwarded + const forwardedForHeader: string | undefined = req.headers.forwarded .split(';') .find((header: string) => header?.startsWith('for=')); - const forParts: string[]|undefined = forwardedForHeader?.split('='); + const forParts: string[] | undefined = forwardedForHeader?.split('='); if (forParts !== undefined && forParts.length == 2) { return forParts[1]; } } } catch (err) { - console.warn('Got part of forwarded header, but couldn\'t parse it.'); + console.warn("Got part of forwarded header, but couldn't parse it."); } return (req as any).clientIp; }; -export const startApp = (sessionStore?: session.MemoryStore): express.Express => { +export const startApp = ( + sessionStore?: session.MemoryStore, +): express.Express => { const app: express.Express = express(); if (process.env['USE_LOGGING'] !== 'false') { app.use(morganLog); @@ -68,7 +75,7 @@ export const startApp = (sessionStore?: session.MemoryStore): express.Express => app.use( express.urlencoded({ extended: true, - }) + }), ); app.use(express.json()); diff --git a/src/session.test.ts b/src/session.test.ts index aa5d83c..2b418ff 100644 --- a/src/session.test.ts +++ b/src/session.test.ts @@ -1,3 +1,4 @@ +import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import { closeConnectionPool, verifyDatabaseReady, @@ -21,7 +22,7 @@ describe('useSessionId', () => { await verifyDatabaseReady(connectionDetails); }); - beforeAll((done) => { + beforeAll(() => { memoryStore = new session.MemoryStore(); realAppMemoryStore = new session.MemoryStore(); @@ -36,64 +37,88 @@ describe('useSessionId', () => { realApp = startApp(realAppMemoryStore); - done(); + // done(); }); - afterAll(() => { - return closeConnectionPool(); + afterAll(async () => { + return Promise.resolve(); // closeConnectionPool(); }); - test('Should reject a made-up SessionID that we dont know about', (done) => { - supertest(app) - .get('/') - .set('x-session-id', 'abcd-1234') - .set('Content-Type', 'application/json') - .expect(401, () => { - done(); - }); - }); + test( + 'Should reject a made-up SessionID that we dont know about', + {}, + async () => + new Promise((done) => { + supertest(app) + .get('/') + .set('x-session-id', 'abcd-1234') + .set('Content-Type', 'application/json') + .expect(401, () => { + done(); + }); + }), + ); - test('Should reject a made-up SessionID that we dont know about in real app', (done) => { - supertest(realApp) - .get('/') - .set('x-session-id', 'abcd-1234') - .set('Content-Type', 'application/json') - .expect(401, () => { - done(); - }); - }); + test( + 'Should reject a made-up SessionID that we dont know about in real app', + {}, + async () => + new Promise((done) => { + supertest(realApp) + .get('/') + .set('x-session-id', 'abcd-1234') + .set('Content-Type', 'application/json') + .expect(401, () => { + done(); + }); + }), + ); - test('Should reject a made-up SessionID that we dont know about in real app- mode B', async () => { - const response = await supertest(realApp) - .get('/') - .set('x-session-id', 'abcd-1234') - .set('Content-Type', 'application/json'); + test( + 'Should reject a made-up SessionID that we dont know about in real app- mode B', + {}, + async () => { + const response = await supertest(realApp) + .get('/') + .set('x-session-id', 'abcd-1234') + .set('Content-Type', 'application/json'); - expect(response.status).toBe(401); - return Promise.resolve(); - }); + expect(response.status).toBe(401); + return Promise.resolve(); + }, + ); - test('Should accept a request with no sessionId', (done) => { - supertest(app) - .get('/') - .set('Content-Type', 'application/json') - .expect(200, () => { - done(); - }); - }); + test( + 'Should accept a request with no sessionId', + {}, + async () => + new Promise((done) => { + supertest(app) + .get('/') + .set('Content-Type', 'application/json') + .expect(200, () => { + done(); + }); + }), + ); - test('Should accept a request with a valid sessionId', (done) => { - const testUserId = 'user-4321'; - memoryStore.set('abcd-1234', { - userId: testUserId, - } as TagtoolSessionData); + test( + 'Should accept a request with a valid sessionId', + {}, + async () => + new Promise((done) => { + const testUserId = 'user-4321'; + memoryStore.set('abcd-1234', { + userId: testUserId, + } as TagtoolSessionData); - supertest(app) - .get('/') - .set('x-session-id', 'abcd-1234') - .set('Content-Type', 'application/json') - .expect(200, () => { - done(); - }); - }); + supertest(app) + .get('/') + .set('x-session-id', 'abcd-1234') + .set('Content-Type', 'application/json') + .expect(200, () => { + done(); + }); + }), + ); }); diff --git a/src/session.ts b/src/session.ts index 333ab87..39c0f65 100644 --- a/src/session.ts +++ b/src/session.ts @@ -11,6 +11,7 @@ import { getCallbackConnectionPromise, getConnection, getConnectionPool } from ' import MySQLStore from 'express-mysql-session'; import { Session } from 'express-session'; +import { loadEnv } from '@tjsr/simple-env-utils'; // const SESSION_ID_NAMESPACE = '0fac0952-9b54-43a9-be74-8d60533aa667'; @@ -20,14 +21,15 @@ export interface TagtoolSessionData extends SystemSessionDataType { userId: UserId; } -export interface TagtoolRequest extends SystemHttpRequestType { +export interface TagtoolRequest + extends SystemHttpRequestType { newSessionIdGenerated?: boolean; session: Session & Partial; } export interface TagtoolResponse extends express.Response {} -dotenv.config(); +loadEnv(); const defaultSessionStoreOptions: MySQLStore.Options = { schema: { diff --git a/src/setup-tests.ts b/src/setup-tests.ts index 53c4cb9..b74d0ad 100644 --- a/src/setup-tests.ts +++ b/src/setup-tests.ts @@ -1,18 +1,17 @@ -import * as dotenvFlow from 'dotenv-flow'; +import { intEnv, loadEnv, setTestMode } from '@tjsr/simple-env-utils'; import { ConnectionOptions } from 'mysql2/promise'; -import { setTestMode } from '@tjsr/simple-env-utils'; -dotenvFlow.config({ path: process.cwd() }); +loadEnv(); process.env['USE_LOGGING'] = 'false'; setTestMode(); export const connectionDetails: ConnectionOptions = { - host: '127.0.0.1', - user: 'testuser', - password: 'testpassword', - database: 'testdb', - port: 23406, + database: process.env['MYSQL_DATABASE'] || 'testdb', + host: process.env['MYSQL_HOST'] || '127.0.0.1', + password: process.env['MYSQL_PASSWORD'] || 'testpassword', + port: intEnv('MYSQL_PORT', 3306), + user: process.env['MYSQL_USER'] || 'testuser', } as const; diff --git a/src/utils/validateObjectId.test.ts b/src/utils/validateObjectId.test.ts index e995e34..6da35ac 100644 --- a/src/utils/validateObjectId.test.ts +++ b/src/utils/validateObjectId.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, test } from 'vitest'; + import { validateObjectId } from './validateObjectId.js'; describe('validateObjectId', () => { @@ -6,7 +8,9 @@ describe('validateObjectId', () => { }); test('Should reject really long tag', () => { - expect(validateObjectId('abcdef01234567890abcdef01234567890abcdef01234567890')).toBe(false); + expect( + validateObjectId('abcdef01234567890abcdef01234567890abcdef01234567890'), + ).toBe(false); }); test('Should reject tag with invalid characters', () => { @@ -17,4 +21,3 @@ describe('validateObjectId', () => { expect(validateObjectId('550e8400-e29b-41d4-a716-446655440000')).toBe(true); }); }); - diff --git a/vite.config.ts b/vite.config.ts index 5fe4d7e..d4e3b06 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,23 @@ import { defineConfig } from 'vite'; +import fs from 'fs'; +import path from 'path'; import react from '@vitejs/plugin-react'; + // import svgrPlugin from 'vite-plugin-svgr'; // import viteTsconfigPaths from 'vite-tsconfig-paths'; +const searchUpwardsForEnvFile = (): string => { + let currentPath = __dirname; + while (currentPath !== '/') { + const envFilePath = path.join(currentPath, '.env.test'); + if (fs.existsSync(envFilePath)) { + return currentPath; + } + currentPath = path.dirname(currentPath); + } + return ''; +}; + // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], @@ -19,4 +34,10 @@ export default defineConfig({ usePolling: true, }, }, + test: { + env: { + DOTENV_FLOW_PATH: searchUpwardsForEnvFile(), + DOTENV_FLOW_PATTERN: '.env.test', + }, + }, });