Files
TREK/server/package.json
T
Maurice ba7d55f999 Finish the contract ratchet, the upload bridge and the client leftovers
BODY_CONTRACT_ALLOW_LIST is empty. The last seventeen handlers type their
body with a createZodDto class, so the fail-closed boot gate has nothing left
to forgive; it still refuses a boot for a new handler that reads @Body()
without one.

Several of those contracts describe rather than constrain, and that is the
whole care in this change. The pipe runs before the handler, so a schema
stricter than the handler moves a user-visible answer:

- TagsController.create answers { error: 'Tag name is required' }, pinned
  byte-for-byte in two tests, and the pipe formats as `field: message`. The
  DTO leaves name optional and the handler keeps the check that owns the
  message. Same shape for the booking-import confirm body, which additionally
  gains per-item validation the handler never did.
- PluginsController.activate and install answer 503 when plugins are disabled,
  and check that BEFORE looking at the body. A schema that rejected first
  would turn that into a 400 for an operator whose server has plugins off.
- setEgressHosts reads a non-array `hosts` as "clear the list", which is the
  documented reset. Typing it as string[] would have removed a working admin
  action rather than tightened anything.
- PluginRoutesController.route answers { route: null } at 200 for everything
  it dislikes, so the client falls back to straight lines. A 400 there would
  turn a graceful degradation into a visible error.

files.bridge is gone. The multer options are built by
MulterModule.registerAsync from an AllowedFileTypesService leaf, so the
fileFilter reads the operator's extension list from the container instead of
from a second FilesService built outside it. Nest injects
MULTER_MODULE_OPTIONS with @Optional(), so a token that fails to resolve is
not a boot error: it silently falls back to defaults, with no filter and no
size cap. FILE-020 already proved that for trip files by uploading an .svg
over HTTP and expecting 400; journey uploads had no such case, and now do.
VIDEO_UPLOAD is untouched, including the filename callback that forces .jpg
regardless of what the client sends, which guards the public share proxy.

PluginFrame gets an onError. A frame whose document fails to load left a
blank element at full height, which reads as a hung app rather than as a
plugin that is gone.

typecheck:tests runs in CI now, with the test tree at zero errors. tz-lookup
is left as it is: the build puts it in a lazy helpers chunk, not the entry
chunk, and the remaining split would trade a synchronous render for an async
one to move 172 kB that is already off the critical path.
2026-08-10 19:54:46 +02:00

117 lines
3.6 KiB
JSON

{
"name": "@trek/server",
"version": "3.4.1",
"main": "src/index.ts",
"scripts": {
"start": "node --require tsconfig-paths/register dist/index.js",
"dev": "node scripts/dev.mjs",
"build": "node scripts/build.mjs",
"start:prod": "node --require tsconfig-paths/register dist/index.js",
"reset-admin": "node reset-admin.js",
"typecheck": "tsc --noEmit",
"typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
"gen:plugin-facts": "node --import tsx scripts/gen-plugin-facts.ts",
"check:plugin-facts": "node --import tsx scripts/gen-plugin-facts.ts --check",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint:check": "eslint .",
"test": "vitest run",
"test:watch": "vitest",
"test:unit": "vitest run tests/unit",
"test:integration": "vitest run tests/integration",
"test:ws": "vitest run tests/websocket",
"test:e2e": "vitest run tests/e2e",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.30.0",
"@nestjs/common": "^11.1.24",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.1.24",
"@nestjs/platform-express": "^11.1.24",
"@nestjs/swagger": "^11.4.5",
"@simplewebauthn/server": "^13.1.2",
"@trek/nest-mcp": "*",
"@trek/shared": "*",
"archiver": "^6.0.1",
"bcryptjs": "^2.4.3",
"better-sqlite3": "^12.8.0",
"compression": "^1.8.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.4.1",
"express": "^4.18.3",
"fast-xml-parser": "^5.5.10",
"helmet": "^8.1.0",
"jimp": "^1.6.1",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.2",
"nestjs-zod": "^5.5.0",
"node-cron": "^4.2.1",
"nodemailer": "^9.0.1",
"otplib": "^12.0.1",
"pdf-parse": "^2.4.5",
"qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"semver": "^7.7.4",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.21.0",
"typescript": "^6.0.2",
"tz-lookup": "^6.1.25",
"undici": "^7.29.0",
"unzipper": "^0.12.3",
"uuid": "^14.0.0",
"ws": "^8.21.0",
"zod": "^4.3.6"
},
"overrides": {
"hono": "^4.12.16",
"@hono/node-server": "^1.19.13",
"picomatch": "^4.0.4",
"ip-address": "^10.1.1",
"multer": "^2.2.0",
"ws": "^8.21.0",
"qs": "^6.15.2",
"file-type": "^21.3.4"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@nestjs/testing": "^11.1.24",
"@swc/core": "^1.15.40",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/archiver": "^7.0.0",
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.13",
"@types/compression": "^1.8.0",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^4.17.25",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.1.0",
"@types/node": "^25.5.0",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^8.0.1",
"@types/qrcode": "^1.5.5",
"@types/semver": "^7.7.1",
"@types/supertest": "^6.0.3",
"@types/unzipper": "^0.10.11",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@vitest/coverage-istanbul": "^4.1.9",
"@vitest/coverage-v8": "^4.1.9",
"eslint": "^9.18.0",
"eslint-config-flat-gitignore": "^2.3.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"nodemon": "^3.1.0",
"prettier": "^3.8.3",
"prettier-plugin-organize-imports": "^4.3.0",
"supertest": "^7.2.2",
"typescript-eslint": "^8.58.2",
"unplugin-swc": "^1.5.9",
"vitest": "^4.1.9"
}
}