Files
crewli/apps/app/vite.config.ts
bert.hausmans bc477837eb feat: install @sentry/vue + observability module skeleton
WS-7 PR-3 commit 1. Frontend mirror of the backend SDK install
(commits bdb89a2..adab3be), wired against the existing apps/app SPA.

- pnpm add @sentry/vue@10.52.0 (pinned).
- src/observability/sentry.ts: initSentry() — empty DSN no-op (RFC §3.3),
  errors-only (tracesSampleRate=0, profilesSampleRate=0; RFC §2 amend.B),
  sendDefaultPii=false, Console integration off, beforeSend wired to the
  scrubber, initial scope tag app=app for GlitchTip filtering.
- src/observability/scrubber.ts: TypeScript port of backend
  SentryEventScrubber. RFC §3.7 frontend block — body / header / query
  scrubbing, form_values wholesale replacement, cookies wholesale,
  defensive strip of contexts.storage and user.cookies, max-depth guard.
- src/observability/contextBinding.ts: Vue Router beforeEach guard that
  binds RFC §3.6 auth-scope tags per navigation. Three zones via
  route.meta.public + route.path matching:
    - portal token zone (meta.public + meta.context=portal) → actor_scope=
      portal, no user_id (RFC §3.6 explicit)
    - /platform/* with super_admin → actor_scope=platform, no org tag
    - default authenticated → actor_scope=organisation when an active
      organisation is selected (useOrganisationStore.activeOrganisationId),
      otherwise actor_scope=user
    - unauthenticated public pages → actor_scope=anonymous
  Reads useAuthStore (user, appRoles, isSuperAdmin) and
  useOrganisationStore (activeOrganisationId) — corrected vs. RFC's
  speculative auth-store API.
- src/observability/index.ts: barrel.
- src/main.ts: initSentry runs before registerPlugins so Sentry's Vue
  errorHandler hooks before any plugin or component initialises;
  installContextBinding runs after registerPlugins so pinia is up.
- env.d.ts: VITE_SENTRY_DSN_FRONTEND + VITE_SENTRY_RELEASE typed.
- .env.example: new file (didn't exist before) documenting all SPA env
  vars including the new Sentry pair.
- vite.config.ts: build.sourcemap=true (RFC §3.5 — generated, uploaded
  to GlitchTip by deploy.sh, then stripped before nginx serves dist/).

Typecheck: green. Build: green, *.map files emitted alongside *.js
chunks as expected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:56:21 +02:00

142 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fileURLToPath } from 'node:url'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import {
VueRouterAutoImports,
getPascalCaseRouteName,
} from 'unplugin-vue-router'
import VueRouter from 'unplugin-vue-router/vite'
import { defineConfig } from 'vite'
import VueDevTools from 'vite-plugin-vue-devtools'
import MetaLayouts from 'vite-plugin-vue-meta-layouts'
import vuetify from 'vite-plugin-vuetify'
import svgLoader from 'vite-svg-loader'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
// Docs: https://github.com/posva/unplugin-vue-router
// This plugin should be placed before vue plugin
VueRouter({
getRouteName: routeNode => {
// Convert pascal case to kebab case
return getPascalCaseRouteName(routeNode)
.replace(/([a-z\d])([A-Z])/g, '$1-$2')
.toLowerCase()
},
}),
vue(),
VueDevTools(),
vueJsx(),
// Docs: https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin
vuetify({
styles: {
// Absolute URL so resolution does not depend on process cwd (fixes common SASS 404s).
configFile: fileURLToPath(
new URL('./src/styles/settings.scss', import.meta.url),
),
},
}),
// Docs: https://github.com/dishait/vite-plugin-vue-meta-layouts?tab=readme-ov-file
MetaLayouts({
target: './src/layouts',
defaultLayout: 'default',
}),
// Docs: https://github.com/antfu/unplugin-vue-components#unplugin-vue-components
Components({
dirs: ['src/@core/components', 'src/views/demos', 'src/components'],
dts: true,
resolvers: [
componentName => {
// Auto import `VueApexCharts`
if (componentName === 'VueApexCharts') {
return {
name: 'default',
from: 'vue3-apexcharts',
as: 'VueApexCharts',
}
}
},
],
}),
// Docs: https://github.com/antfu/unplugin-auto-import#unplugin-auto-import
AutoImport({
imports: [
'vue',
VueRouterAutoImports,
'@vueuse/core',
'@vueuse/math',
'vue-i18n',
'pinia',
],
dirs: [
'./src/@core/utils',
'./src/@core/composable/',
'./src/composables/',
'./src/utils/',
'./src/plugins/*/composables/*',
],
vueTemplate: true,
// Disabled to avoid confusion & accidental usage
ignore: ['useCookies', 'useStorage'],
}),
svgLoader(),
],
define: { 'process.env': {} },
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@themeConfig': fileURLToPath(
new URL('./themeConfig.ts', import.meta.url),
),
'@core': fileURLToPath(new URL('./src/@core', import.meta.url)),
'@layouts': fileURLToPath(new URL('./src/@layouts', import.meta.url)),
'@images': fileURLToPath(
new URL('./src/assets/images/', import.meta.url),
),
'@styles': fileURLToPath(
new URL('./src/assets/styles/', import.meta.url),
),
'@configured-variables': fileURLToPath(
new URL(
'./src/assets/styles/variables/_template.scss',
import.meta.url,
),
),
},
},
server: {
port: 5174,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
warmup: {
clientFiles: ['./src/pages/**/*.vue', './src/components/**/*.vue'],
},
},
build: {
chunkSizeWarningLimit: 5000,
// RFC-WS-7 §3.5 — sourcemaps generated at build, uploaded to GlitchTip
// by deploy.sh, then `find dist -name '*.map' -delete` strips them
// before nginx serves dist/. No public-mapped sources on production.
sourcemap: true,
},
optimizeDeps: {
exclude: ['vuetify'],
entries: ['./src/**/*.vue'],
force: true,
},
})