diff --git a/.env.example b/.env.example index 8d0072e..e6ff17d 100644 --- a/.env.example +++ b/.env.example @@ -20,11 +20,9 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 ALTCHA_ENABLED=true ALTCHA_HMAC_KEY=change-me-use-long-random-secret -ALTCHA_MAX_NUMBER=50000 +ALTCHA_MAX_NUMBER=250000 ALTCHA_CHALLENGE_TTL_MS=600000 -NEVER_MODE_INACTIVITY_DAYS=365 - THROTTLE_TTL_MS=60000 THROTTLE_LIMIT=1000 THROTTLE_UPLOAD_TTL_MS=60000 @@ -35,8 +33,11 @@ VALIDATION_UPLOAD_MAX_IMAGE_BYTES=10485760 # 10MiB VALIDATION_UPLOAD_JSON_BODY_PADDING_BYTES=2097152 # 2MiB VITE_API_BASE_URL=http://localhost:3000/api +VITE_CLEARNET_SCHEME=http VITE_CLEARNET_DOMAIN=localhost:5173 -VITE_ONION_HOSTNAME= +VITE_ONION_HOSTNAME=abcdefghijklmnop.onion +VITE_ALTCHA_ENABLED=true +VITE_ALTCHA_MAX_NUMBER=250000 VITE_UPLOAD_MAX_QUEUE_SIZE=10 VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX=1000000 VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES=10485760 # 10MiB diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev index d56d43c..1cdc39c 100644 --- a/backend/Dockerfile.dev +++ b/backend/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22.23.4-alpine WORKDIR /backend diff --git a/backend/src/config/validate.spec.ts b/backend/src/config/validate.spec.ts index 17c168c..c266bb4 100644 --- a/backend/src/config/validate.spec.ts +++ b/backend/src/config/validate.spec.ts @@ -17,7 +17,6 @@ const validConfig = (overrides: Record = {}): Record=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -2056,6 +2081,75 @@ "dev": true, "license": "MIT" }, + "node_modules/altcha-lib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/altcha-lib/-/altcha-lib-2.7.0.tgz", + "integrity": "sha512-gnysjfIpeT6k3RLaWRd7xVvfs8U0a5DHIS95Xgwn8znKq5uIj6c/oTH8DtcdyJS/ZV3I5kYpyurvmJFb0eFWvA==", + "license": "MIT", + "bin": { + "altcha-lib": "bin/cli.mjs" + }, + "peerDependencies": { + "@fastify/cookie": "^11.0.0", + "@fastify/cors": "^11.0.0", + "@fastify/formbody": "^8.0.0 || ^9.0.0", + "@nestjs/common": "^10.0.0 || ^11.0.0 || ^12.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0 || ^12.0.0", + "@nestjs/platform-express": "^10.0.0 || ^11.0.0 || ^12.0.0", + "@nestjs/platform-fastify": "^10.0.0 || ^11.0.0 || ^12.0.0", + "@sveltejs/kit": "^2.0.0 || ^3.0.0", + "cookie-parser": "^1.0.0", + "cors": "^2.0.0", + "express": "^4.0.0 || ^5.0.0", + "fastify": "^5.0.0", + "h3": "^2.0.0", + "hono": "^4.0.0" + }, + "peerDependenciesMeta": { + "@fastify/cookie": { + "optional": true + }, + "@fastify/cors": { + "optional": true + }, + "@fastify/formbody": { + "optional": true + }, + "@nestjs/common": { + "optional": true + }, + "@nestjs/core": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/platform-fastify": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "cookie-parser": { + "optional": true + }, + "cors": { + "optional": true + }, + "express": { + "optional": true + }, + "fastify": { + "optional": true + }, + "h3": { + "optional": true + }, + "hono": { + "optional": true + } + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2144,6 +2238,22 @@ "node": ">= 16" } }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2156,12 +2266,25 @@ "node": ">= 0.8" } }, + "node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2340,6 +2463,12 @@ "@esbuild/win32-x64": "0.28.2" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -2356,6 +2485,13 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", + "dev": true, + "license": "MIT" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2817,6 +2953,24 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -2863,6 +3017,38 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2900,6 +3086,20 @@ "integrity": "sha512-wzRsrJwNlFbqIRQt8aCeL2WE8USip/uuvGSOZV1D02CxX8ABTMbw8IywKjH7yWHETMXbWJI4OBpihg9rmz2Vyg==", "license": "MIT" }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -2974,6 +3174,18 @@ } } }, + "node_modules/pkg-types": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz", + "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.1", + "exsolve": "^1.1.3", + "pathe": "^2.0.3" + } + }, "node_modules/postcss": { "version": "8.5.28", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", @@ -3023,6 +3235,37 @@ "node": ">=10" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/rolldown": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", @@ -3238,6 +3481,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -3245,6 +3495,115 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unplugin": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.4.0.tgz", + "integrity": "sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.7", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rsbuild/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rsbuild/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "32.1.0", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz", + "integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "local-pkg": "^1.2.0", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, "node_modules/vite": { "version": "8.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", @@ -3671,6 +4030,13 @@ "typescript": ">=5.0.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 172b509..4ee7d91 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,10 @@ "dependencies": { "@tailwindcss/vite": "^4.3.3", "@uswriting/exiftool": "1.0.9", + "altcha-lib": "^2.7.0", "axios": "^1.20.0", + "dayjs": "^1.11.23", + "escape-html": "^1.0.3", "hash-wasm": "^4.12.0", "pinia": "^4.0.3", "pretty-bytes": "^7.2.0", @@ -22,10 +25,12 @@ "vue-router": "^4.6.4" }, "devDependencies": { + "@types/escape-html": "^1.0.4", "@types/node": "^22.10.7", "@vitejs/plugin-vue": "^6.0.5", "@vue/tsconfig": "^0.9.1", "typescript": "~6.0.2", + "unplugin-vue-components": "^32.1.0", "vite": "^8.0.4", "vitest": "^3.2.7", "vue-tsc": "^3.2.6" diff --git a/frontend/src/assets/css/main.css b/frontend/src/assets/css/main.css index 683fd9a..f0d965b 100644 --- a/frontend/src/assets/css/main.css +++ b/frontend/src/assets/css/main.css @@ -20,6 +20,7 @@ --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); + --color-success: var(--success); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -40,6 +41,7 @@ --accent: oklch(0.94 0.02 260); --accent-foreground: oklch(0.28 0.03 260); --destructive: oklch(0.55 0.2 25); + --success: oklch(0.45 0.14 155); --border: oklch(0.9 0.01 260); --input: oklch(0.9 0.01 260); --ring: oklch(0.42 0.08 260); @@ -59,6 +61,7 @@ --accent: oklch(0.28 0.03 260); --accent-foreground: oklch(0.96 0.01 260); --destructive: oklch(0.62 0.2 25); + --success: oklch(0.68 0.14 155); --border: oklch(0.32 0.02 260); --input: oklch(0.32 0.02 260); --ring: oklch(0.72 0.1 260); diff --git a/frontend/src/components.d.ts b/frontend/src/components.d.ts new file mode 100644 index 0000000..1e40178 --- /dev/null +++ b/frontend/src/components.d.ts @@ -0,0 +1,26 @@ +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + InlineTabList: typeof import('./components/InlineTabList.vue')['default'] + MetadataInspector: typeof import('./components/upload/MetadataInspector.vue')['default'] + MetadataOutputPanel: typeof import('./components/upload/MetadataOutputPanel.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + ShareLinkField: typeof import('./components/upload/ShareLinkField.vue')['default'] + UploadConfigureModal: typeof import('./components/upload/UploadConfigureModal.vue')['default'] + UploadDropzone: typeof import('./components/upload/UploadDropzone.vue')['default'] + UploadQueueList: typeof import('./components/upload/UploadQueueList.vue')['default'] + UploadSettingsPanel: typeof import('./components/upload/UploadSettingsPanel.vue')['default'] + UploadShareResults: typeof import('./components/upload/UploadShareResults.vue')['default'] + } +} diff --git a/frontend/src/components/InlineTabList.vue b/frontend/src/components/InlineTabList.vue new file mode 100644 index 0000000..25944fd --- /dev/null +++ b/frontend/src/components/InlineTabList.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/components/upload/MetadataInspector.vue b/frontend/src/components/upload/MetadataInspector.vue index 6c78537..8e6a862 100644 --- a/frontend/src/components/upload/MetadataInspector.vue +++ b/frontend/src/components/upload/MetadataInspector.vue @@ -1,58 +1,25 @@ diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index aa316f0..2fca688 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -2,8 +2,11 @@ interface ImportMetaEnv { readonly VITE_API_BASE_URL: string; + readonly VITE_CLEARNET_SCHEME: string; readonly VITE_CLEARNET_DOMAIN: string; readonly VITE_ONION_HOSTNAME: string; + readonly VITE_ALTCHA_ENABLED: string; + readonly VITE_ALTCHA_MAX_NUMBER: string; readonly VITE_UPLOAD_MAX_QUEUE_SIZE: string; readonly VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX: string; readonly VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES: string; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 332e329..a5fd8b4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'; import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; +import Components from 'unplugin-vue-components/vite'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -12,5 +13,11 @@ export default defineConfig({ '@': path.resolve(__dirname, 'src') } }, - plugins: [vue(), tailwindcss()] + plugins: [ + vue(), + Components({ + dts: 'src/components.d.ts' + }), + tailwindcss() + ] });