@maestro-js/agent-skills 1.0.0-alpha.23 → 1.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +2 -2
  2. package/skills/maestro-examples/references/cadence/cadence-web/package.json +6 -6
  3. package/skills/maestro-examples/references/cadence/package.json +4 -4
  4. package/skills/maestro-examples/references/cadence/packages/schemas/package.json +1 -1
  5. package/skills/maestro-examples/references/cadence/packages/services/package.json +19 -19
  6. package/skills/maestro-examples/references/cadence/packages/services/src/auth/auth-db-provider.ts +14 -9
  7. package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/forgot-password.tsx +3 -2
  8. package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/reset-password.tsx +4 -3
  9. package/skills/maestro-examples/references/chorus/chorus-web/package.json +15 -9
  10. package/skills/maestro-examples/references/chorus/chorus-web/react-router.config.ts +5 -1
  11. package/skills/maestro-examples/references/chorus/chorus-web/server/index.ts +89 -0
  12. package/skills/maestro-examples/references/chorus/chorus-web/tsconfig.json +1 -1
  13. package/skills/maestro-examples/references/chorus/chorus-web/vite.config.ts +29 -11
  14. package/skills/maestro-examples/references/chorus/config/services.ts +4 -0
  15. package/skills/maestro-examples/references/chorus/infrastructure/cdk/index.ts +160 -26
  16. package/skills/maestro-examples/references/chorus/infrastructure/package.json +2 -2
  17. package/skills/maestro-examples/references/chorus/package.json +5 -4
  18. package/skills/maestro-examples/references/chorus/packages/schemas/package.json +1 -1
  19. package/skills/maestro-examples/references/chorus/packages/services/package.json +21 -21
  20. package/skills/maestro-examples/references/chorus/packages/services/src/auth/auth-use-cases.ts +23 -6
  21. package/skills/maestro-examples/references/chorus/packages/services/src/auth/password-reset-token-db-provider.ts +9 -1
  22. package/skills/maestro-examples/references/chorus/packages/services/src/auth/remember-me-token-db-provider.ts +14 -9
  23. package/skills/maestro-examples/references/chorus/packages/services/src/maestro.ts +4 -1
  24. package/skills/maestro-examples/references/chorus/scripts/deploy/artifact.ts +89 -0
  25. package/skills/maestro-examples/references/chorus/scripts/deploy/aws.ts +39 -0
  26. package/skills/maestro-examples/references/chorus/scripts/deploy/deploy-chorus-web.ts +503 -0
  27. package/skills/maestro-examples/references/chorus/scripts/deploy/migrate-entry.ts +53 -0
  28. package/skills/maestro-examples/references/chorus/scripts/deploy/migrations.ts +241 -0
  29. package/skills/maestro-examples/references/chorus/scripts/deploy/tarball.ts +40 -0
  30. package/skills/maestro-examples/references/setlist/infrastructure/package.json +2 -2
  31. package/skills/maestro-examples/references/setlist/package.json +4 -4
  32. package/skills/maestro-examples/references/setlist/packages/schemas/package.json +1 -1
  33. package/skills/maestro-examples/references/setlist/packages/services/package.json +15 -15
  34. package/skills/maestro-examples/references/setlist/packages/services/src/project/project-use-cases.ts +17 -0
  35. package/skills/maestro-examples/references/setlist/packages/services/src/task/task-use-cases.ts +25 -0
  36. package/skills/maestro-examples/references/setlist/queue-processor/package.json +2 -2
  37. package/skills/maestro-examples/references/setlist/setlist-web/package.json +3 -3
  38. package/skills/maestro-packages/config.md +22 -8
  39. package/skills/maestro-packages/react-router-file-routes.md +1 -1
package/package.json CHANGED
@@ -11,9 +11,9 @@
11
11
  "devDependencies": {
12
12
  "@types/node": "^22.19.11"
13
13
  },
14
- "version": "1.0.0-alpha.23",
14
+ "version": "1.0.0-alpha.25",
15
15
  "publishConfig": {
16
- "access": "restricted"
16
+ "access": "public"
17
17
  },
18
18
  "files": [
19
19
  "dist",
@@ -14,10 +14,10 @@
14
14
  "@cadence/schemas": "workspace:*",
15
15
  "@cadence/services": "workspace:*",
16
16
  "@internationalized/date": "^3.11.0",
17
- "@maestro-js/custom-errors": "^1.0.0-alpha.23",
18
- "@maestro-js/log": "^1.0.0-alpha.23",
19
- "@maestro-js/form": "^1.0.0-alpha.23",
20
- "@maestro-js/permissions": "^1.0.0-alpha.23",
17
+ "@maestro-js/custom-errors": "^1.0.0-alpha.25",
18
+ "@maestro-js/log": "^1.0.0-alpha.25",
19
+ "@maestro-js/form": "^1.0.0-alpha.25",
20
+ "@maestro-js/permissions": "^1.0.0-alpha.25",
21
21
  "@react-aria/interactions": "^3.27.0",
22
22
  "@react-aria/utils": "^3.33.0",
23
23
  "@react-router/node": "^7.9.0",
@@ -41,8 +41,8 @@
41
41
  "zod": "^4.0.0"
42
42
  },
43
43
  "devDependencies": {
44
- "@maestro-js/cli": "^1.0.0-alpha.23",
45
- "@maestro-js/react-router-file-routes": "^1.0.0-alpha.23",
44
+ "@maestro-js/cli": "^1.0.0-alpha.25",
45
+ "@maestro-js/react-router-file-routes": "^1.0.0-alpha.25",
46
46
  "@playwright/test": "^1.52.0",
47
47
  "@react-router/dev": "^7.9.0",
48
48
  "@tailwindcss/typography": "^0.5.19",
@@ -6,12 +6,12 @@
6
6
  "fks-restrict": "maestro config:run --config services -- node ./scripts/foreign-key-delete-behavior.ts restrict"
7
7
  },
8
8
  "dependencies": {
9
- "@maestro-js/config": "^1.0.0-alpha.23"
9
+ "@maestro-js/config": "^1.0.0-alpha.25"
10
10
  },
11
11
  "devDependencies": {
12
- "@maestro-js/cli": "^1.0.0-alpha.23",
13
- "@maestro-js/db-migrate": "^1.0.0-alpha.23",
14
- "@maestro-js/db": "^1.0.0-alpha.23",
12
+ "@maestro-js/cli": "^1.0.0-alpha.25",
13
+ "@maestro-js/db-migrate": "^1.0.0-alpha.25",
14
+ "@maestro-js/db": "^1.0.0-alpha.25",
15
15
  "@types/node": "^22.19.11"
16
16
  }
17
17
  }
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "iso-fns": "npm:iso-fns@2.0.0-alpha.26",
13
13
  "zod": "^4.3.0",
14
- "@maestro-js/iso-zod": "^1.0.0-alpha.23"
14
+ "@maestro-js/iso-zod": "^1.0.0-alpha.25"
15
15
  },
16
16
  "devDependencies": {}
17
17
  }
@@ -12,30 +12,30 @@
12
12
  "dependencies": {
13
13
  "@aws-sdk/client-ses": "^3.1004.0",
14
14
  "@cadence/schemas": "workspace:*",
15
- "@maestro-js/auth": "^1.0.0-alpha.23",
16
- "@maestro-js/cache": "^1.0.0-alpha.23",
17
- "@maestro-js/config": "^1.0.0-alpha.23",
18
- "@maestro-js/crypt": "^1.0.0-alpha.23",
19
- "@maestro-js/custom-errors": "^1.0.0-alpha.23",
20
- "@maestro-js/db": "^1.0.0-alpha.23",
21
- "@maestro-js/exceptions": "^1.0.0-alpha.23",
22
- "@maestro-js/hash": "^1.0.0-alpha.23",
23
- "@maestro-js/helpers": "^1.0.0-alpha.23",
24
- "@maestro-js/iso-zod": "^1.0.0-alpha.23",
25
- "@maestro-js/log": "^1.0.0-alpha.23",
26
- "@maestro-js/mail": "^1.0.0-alpha.23",
27
- "@maestro-js/password": "^1.0.0-alpha.23",
28
- "@maestro-js/permissions": "^1.0.0-alpha.23",
29
- "@maestro-js/queue": "^1.0.0-alpha.23",
30
- "@maestro-js/rate-limiting": "^1.0.0-alpha.23",
31
- "@maestro-js/recurring-jobs": "^1.0.0-alpha.23",
32
- "@maestro-js/session": "^1.0.0-alpha.23",
15
+ "@maestro-js/auth": "^1.0.0-alpha.25",
16
+ "@maestro-js/cache": "^1.0.0-alpha.25",
17
+ "@maestro-js/config": "^1.0.0-alpha.25",
18
+ "@maestro-js/crypt": "^1.0.0-alpha.25",
19
+ "@maestro-js/custom-errors": "^1.0.0-alpha.25",
20
+ "@maestro-js/db": "^1.0.0-alpha.25",
21
+ "@maestro-js/exceptions": "^1.0.0-alpha.25",
22
+ "@maestro-js/hash": "^1.0.0-alpha.25",
23
+ "@maestro-js/helpers": "^1.0.0-alpha.25",
24
+ "@maestro-js/iso-zod": "^1.0.0-alpha.25",
25
+ "@maestro-js/log": "^1.0.0-alpha.25",
26
+ "@maestro-js/mail": "^1.0.0-alpha.25",
27
+ "@maestro-js/password": "^1.0.0-alpha.25",
28
+ "@maestro-js/permissions": "^1.0.0-alpha.25",
29
+ "@maestro-js/queue": "^1.0.0-alpha.25",
30
+ "@maestro-js/rate-limiting": "^1.0.0-alpha.25",
31
+ "@maestro-js/recurring-jobs": "^1.0.0-alpha.25",
32
+ "@maestro-js/session": "^1.0.0-alpha.25",
33
33
  "iso-fns": "npm:iso-fns@2.0.0-alpha.26",
34
34
  "sanitize-html": "^2.17.2",
35
35
  "zod": "^4.3.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@maestro-js/cli": "^1.0.0-alpha.23",
38
+ "@maestro-js/cli": "^1.0.0-alpha.25",
39
39
  "@types/node": "^22.0.0",
40
40
  "@types/sanitize-html": "^2.16.1",
41
41
  "beartest-js": "^8.0.2"
@@ -69,7 +69,8 @@ const rememberMeTokenRecordModel = z.object({
69
69
  userAgent: z.string().nullable()
70
70
  }),
71
71
  expirationDate: isoZ.instant().nullable(),
72
- rotatedDate: isoZ.instant().nullable()
72
+ rotatedDate: isoZ.instant().nullable(),
73
+ createdDate: isoZ.instant()
73
74
  })
74
75
 
75
76
  type RememberMeTokenRecord = z.infer<typeof rememberMeTokenRecordModel>
@@ -84,7 +85,7 @@ async function createRememberMeToken(options: RememberMeTokenRecord): Promise<vo
84
85
  JSON.stringify(options.deviceContext),
85
86
  options.expirationDate ? instantFns.formatISO9075(options.expirationDate) : null,
86
87
  options.rotatedDate ? instantFns.formatISO9075(options.rotatedDate) : null,
87
- instantFns.formatISO9075(instantFns.now())
88
+ instantFns.formatISO9075(options.createdDate)
88
89
  ]
89
90
  )
90
91
  }
@@ -103,19 +104,23 @@ async function getRememberMeTokenBySelector(selector: string): Promise<RememberM
103
104
  async function rotateRememberMeTokenBySelector(
104
105
  selector: string,
105
106
  token: { verifierHash: string; selector: string }
106
- ): Promise<void> {
107
- await Db.transaction(async () => {
108
- await Db.update('UPDATE rememberMeTokens SET rotatedDate = ? WHERE selector = ?', [
109
- instantFns.formatISO9075(instantFns.now()),
110
- selector
111
- ])
112
- await Db.update(
107
+ ): Promise<boolean> {
108
+ return Db.transaction(async () => {
109
+ const { affectedRows } = await Db.update(
110
+ 'UPDATE rememberMeTokens SET rotatedDate = ? WHERE selector = ? AND rotatedDate IS NULL',
111
+ [instantFns.formatISO9075(instantFns.now()), selector]
112
+ )
113
+ if (affectedRows !== 1) {
114
+ return false
115
+ }
116
+ await Db.insert(
113
117
  `INSERT INTO rememberMeTokens (userId, selector, verifierHash, deviceContext, expirationDate, rotatedDate, createdDate)
114
118
  SELECT userId, ? as selector, ? as verifierHash, deviceContext, expirationDate, NULL as rotatedDate, createdDate
115
119
  FROM rememberMeTokens
116
120
  WHERE selector = ?`,
117
121
  [token.selector, token.verifierHash, selector]
118
122
  )
123
+ return true
119
124
  })
120
125
  }
121
126
 
@@ -44,8 +44,9 @@ export async function action({ request, context }: ActionFunctionArgs) {
44
44
  const parsed = await Form.serverSafeParse(formSchema, formData)
45
45
  if (parsed.success) {
46
46
  const { email } = parsed.data
47
- const resetUrl = new URL('/reset-password', request.url).toString()
48
- await AuthService.ForgotPassword({ email, url: resetUrl })
47
+ // The reset link origin is derived from a trusted server config inside the service
48
+ // (APP_URL), not from `request.url` / the Host header, to prevent reset poisoning.
49
+ await AuthService.ForgotPassword({ email })
49
50
  return redirect('/forgot-password?sent=true')
50
51
  } else {
51
52
  return Form.validationError(parsed)
@@ -46,9 +46,11 @@ export async function action({ request, context }: ActionFunctionArgs) {
46
46
 
47
47
  const searchParams = new URL(request.url).searchParams
48
48
  const token = searchParams.get('token')
49
- const id = searchParams.get('id')
50
49
 
51
- if (!token || !id) {
50
+ // Re-verify the signed URL on the action (POST) path, not just the loader. Otherwise the
51
+ // signed query params (token/id) could be tampered with on submit. The account to reset
52
+ // is derived from the token by the service — the `id` query param is never trusted.
53
+ if (!token || !(await SignedUrl.hasValidSignature(searchParams))) {
52
54
  return data({ error: 'Invalid reset link' }, { status: 400 })
53
55
  }
54
56
 
@@ -61,7 +63,6 @@ export async function action({ request, context }: ActionFunctionArgs) {
61
63
  try {
62
64
  const { cookie } = await AuthService.ResetPassword({
63
65
  token,
64
- authenticatableId: Number(id),
65
66
  newPassword: parsed.data.newPassword,
66
67
  deviceContext: requestContext.deviceContext,
67
68
  session: requestContext.session
@@ -6,45 +6,51 @@
6
6
  "dev": "NODE_OPTIONS='--import ./instrument.server.mjs' maestro config:run --config chorus-web --root $PWD/.. -- react-router dev",
7
7
  "dev-test": "IS_TESTING=true pnpm dev",
8
8
  "build": "react-router build",
9
- "start": "NODE_OPTIONS='--import ./instrument.server.mjs' react-router-serve ./build/server/index.js",
9
+ "start": "NODE_ENV=production NODE_OPTIONS='--import ./instrument.server.mjs' node ./server/index.ts",
10
10
  "typecheck": "react-router typegen && tsc --noEmit",
11
11
  "test:e2e": "maestro config:run --config chorus-web --root $PWD/.. -- playwright test"
12
12
  },
13
13
  "dependencies": {
14
14
  "@bundled-es-modules/pdfjs-dist": "3.6.172-alpha.1",
15
15
  "@internationalized/date": "^3.11.0",
16
- "@maestro-js/form": "^1.0.0-alpha.23",
17
- "@maestro-js/permissions": "^1.0.0-alpha.23",
18
- "@maestro-js/custom-errors": "^1.0.0-alpha.23",
16
+ "@maestro-js/form": "^1.0.0-alpha.25",
17
+ "@maestro-js/permissions": "^1.0.0-alpha.25",
18
+ "@maestro-js/custom-errors": "^1.0.0-alpha.25",
19
+ "@maestro-js/config": "^1.0.0-alpha.25",
20
+ "@maestro-js/log": "^1.0.0-alpha.25",
21
+ "@hono/node-server": "^2.0.5",
19
22
  "@react-aria/interactions": "^3.27.0",
20
23
  "@react-aria/utils": "^3.33.0",
21
24
  "@react-router/node": "^7.9.0",
22
- "@react-router/serve": "^7.9.0",
23
25
  "@chorus/schemas": "workspace:*",
24
26
  "@chorus/services": "workspace:*",
27
+ "hono": "^4.12.26",
25
28
  "isbot": "^5",
26
29
  "iso-fns": "npm:iso-fns@2.0.0-alpha.26",
27
30
  "match-sorter": "^8.2.0",
28
31
  "react": "^19.0.0",
29
32
  "react-aria-components": "^1.15.1",
30
33
  "react-dom": "^19.0.0",
31
- "react-pdf-hook": "^1.0.4",
34
+ "react-pdf-hook": "2.0.0",
32
35
  "react-router": "^7.9.0",
36
+ "remix-hono": "^0.0.18",
33
37
  "tailwind-merge": "^3.5.0",
38
+ "tailwindcss": "^4.2.1",
34
39
  "vaul": "^1.1.2",
35
40
  "zod": "^4.0.0",
36
41
  "@sentry/react-router": "^9"
37
42
  },
38
43
  "devDependencies": {
39
- "@maestro-js/cli": "^1.0.0-alpha.23",
40
- "@maestro-js/react-router-file-routes": "^1.0.0-alpha.23",
44
+ "@hono/vite-dev-server": "^0.26.0",
45
+ "@maestro-js/cli": "^1.0.0-alpha.25",
46
+ "@maestro-js/react-router-file-routes": "^1.0.0-alpha.25",
41
47
  "@playwright/test": "^1.58.2",
42
48
  "@react-router/dev": "^7.9.0",
43
49
  "@tailwindcss/vite": "^4.2.0",
44
50
  "@types/node": "^22.19.11",
45
51
  "@types/react": "^19.0.0",
46
52
  "@types/react-dom": "^19.0.0",
47
- "tailwindcss": "^4.2.1",
53
+ "prettier": "^3.8.0",
48
54
  "typescript": "^5.7.0",
49
55
  "vite": "^8.0.0",
50
56
  "vite-env-only": "^3.0.2",
@@ -1,12 +1,16 @@
1
1
  import type { Config } from '@react-router/dev/config'
2
2
  import { sentryOnBuildEnd } from '@sentry/react-router'
3
3
 
4
+ // Matches the guard in vite.config.ts: without upload credentials sentryOnBuildEnd shells out to the
5
+ // Sentry CLI, which aborts with "An organization ID or slug is required". Skip it unless CI set them.
6
+ const sentryEnabled = Boolean(process.env.SENTRY_ORG && process.env.SENTRY_PROJECT && process.env.SENTRY_AUTH_TOKEN)
7
+
4
8
  export default {
5
9
  ssr: true,
6
10
  future: {
7
11
  v8_middleware: true
8
12
  },
9
13
  async buildEnd({ viteConfig, reactRouterConfig, buildManifest }) {
10
- await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest })
14
+ if (sentryEnabled) await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest })
11
15
  }
12
16
  } satisfies Config
@@ -0,0 +1,89 @@
1
+ import { join } from 'node:path'
2
+ import { pathToFileURL } from 'node:url'
3
+ import { Hono } from 'hono'
4
+ import { Log } from '@maestro-js/log'
5
+ import { type AppLoadContext, RouterContextProvider, type ServerBuild } from 'react-router'
6
+ import { reactRouter } from 'remix-hono/handler'
7
+
8
+ const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development'
9
+
10
+ const app = new Hono()
11
+
12
+ // Log every request once it has been handled, capturing the response status and
13
+ // how long it took. Registered first so it wraps the full middleware chain —
14
+ // health checks, static assets, and React Router routes alike.
15
+ app.use('*', async (c, next) => {
16
+ const start = performance.now()
17
+ await next()
18
+ Log.info({
19
+ channel: 'http',
20
+ action: 'request',
21
+ method: c.req.method,
22
+ path: c.req.path,
23
+ status: c.res.status,
24
+ durationMs: Math.round(performance.now() - start)
25
+ })
26
+ })
27
+
28
+ // Liveness probe for the ALB target group. Answer before serveStatic and React
29
+ // Router so the health check proves only that the process can accept a request —
30
+ // never coupled to the on-disk server build, the root middleware chain, or
31
+ // session/auth. A slow or throwing app must not read as an unhealthy target.
32
+ app.get('/health', (c) => c.json({ status: 'ok' }))
33
+
34
+ // In production the built client assets are served directly by Hono. In
35
+ // development Vite (via @hono/vite-dev-server) serves modules and assets.
36
+ if (mode === 'production') {
37
+ const { serveStatic } = await import('@hono/node-server/serve-static')
38
+ app.use('/assets/*', serveStatic({ root: './build/client' }))
39
+ app.use('*', serveStatic({ root: './build/client' }))
40
+ }
41
+
42
+ // Hand every remaining request to React Router. The server build comes from the
43
+ // Vite virtual module in development and from the on-disk build in production.
44
+ app.use('*', async (c, next) => {
45
+ const build = await loadServerBuild()
46
+ return reactRouter({
47
+ build,
48
+ mode,
49
+ // With `future.v8_middleware` enabled the load context must be a fresh
50
+ // `RouterContextProvider` per request; the app's middlewares populate it
51
+ // via `context.set(...)`.
52
+ getLoadContext: () => new RouterContextProvider() as unknown as AppLoadContext
53
+ })(c, next)
54
+ })
55
+
56
+ export default app
57
+
58
+ // Only start a long-lived Node server in production; in development the request
59
+ // handling is driven by Vite's dev server.
60
+ if (mode === 'production') {
61
+ const { serve } = await import('@hono/node-server')
62
+ const port = Number(process.env.PORT ?? 3001)
63
+ const server = serve({ fetch: app.fetch, port }, (info) => {
64
+ console.log(`Chorus web listening on http://localhost:${info.port}`)
65
+ })
66
+
67
+ // Exit promptly on the signal ECS sends to roll the service. This process is PID 1 (the bootstrap
68
+ // `exec`s it), so SIGTERM arrives here directly. By the time it does, the ALB has already drained this
69
+ // task's connections (target-group deregistration delay), so stop accepting requests and exit instead
70
+ // of idling until ECS's 30s stopTimeout forces a SIGKILL — that wait is dead time on every deploy.
71
+ // Force-exit if close() stalls on a lingering keep-alive socket.
72
+ const shutdown = () => {
73
+ server.close(() => process.exit(0))
74
+ setTimeout(() => process.exit(0), 5000).unref()
75
+ }
76
+ process.on('SIGTERM', shutdown)
77
+ process.on('SIGINT', shutdown)
78
+ }
79
+
80
+ async function loadServerBuild(): Promise<ServerBuild> {
81
+ if (mode === 'production') {
82
+ // Resolve the built server relative to the working directory (the artifact
83
+ // root at runtime); the URL is dynamic so Vite never tries to resolve it.
84
+ const buildUrl = pathToFileURL(join(process.cwd(), 'build/server/index.js')).href
85
+ return (await import(/* @vite-ignore */ buildUrl)) as unknown as ServerBuild
86
+ }
87
+ // @ts-expect-error - virtual module provided by the React Router Vite plugin in development
88
+ return (await import('virtual:react-router/server-build')) as unknown as ServerBuild
89
+ }
@@ -10,5 +10,5 @@
10
10
  "~/*": ["./app/*"]
11
11
  }
12
12
  },
13
- "include": ["app", "vite.config.ts", "react-router.config.ts", ".react-router", "./environment.d.ts"]
13
+ "include": ["app", "server", "vite.config.ts", "react-router.config.ts", ".react-router", "./environment.d.ts"]
14
14
  }
@@ -1,4 +1,5 @@
1
1
  import { reactRouter } from '@react-router/dev/vite'
2
+ import devServer from '@hono/vite-dev-server'
2
3
  import { sentryReactRouter } from '@sentry/react-router'
3
4
  import tailwindcss from '@tailwindcss/vite'
4
5
  import { defineConfig } from 'vite'
@@ -6,20 +7,40 @@ import { iconsSpritesheet } from 'vite-plugin-icons-spritesheet'
6
7
  import tsconfigPaths from 'vite-tsconfig-paths'
7
8
  import { envOnlyMacros } from 'vite-env-only'
8
9
 
10
+ // Only wire up Sentry when its upload credentials are present. The plugin otherwise (a) aborts every
11
+ // build with "An organization ID or slug is required" and (b) still forces `sourcemap: 'hidden'`, so it
12
+ // emits ~10MB of .map files that — because the failed upload never deletes them — get packed into the
13
+ // deploy artifact, inflating the S3 upload and every cold task's download + extract. CI sets these vars
14
+ // to ship real source maps; local/dev deploys skip the plugin and build leaner.
15
+ const sentryEnabled = Boolean(process.env.SENTRY_ORG && process.env.SENTRY_PROJECT && process.env.SENTRY_AUTH_TOKEN)
16
+
9
17
  export default defineConfig((config) => ({
10
18
  plugins: [
11
19
  envOnlyMacros(),
12
20
  tsconfigPaths(),
13
21
  tailwindcss(),
14
22
  reactRouter(),
15
- sentryReactRouter(
16
- {
17
- org: process.env.SENTRY_ORG,
18
- project: process.env.SENTRY_PROJECT,
19
- authToken: process.env.SENTRY_AUTH_TOKEN
20
- },
21
- config
22
- ),
23
+ devServer({
24
+ entry: 'server/index.ts',
25
+ // Vite serves app source modules, its internals and deps; everything else
26
+ // (document and React Router `.data` requests) goes to the Hono server.
27
+ exclude: [/^\/(app)\/.+/, /^\/@.+$/, /^\/node_modules\/.*/],
28
+ // React Router injects its own dev/HMR scripts via <Scripts/>, so the Vite
29
+ // client must not be injected a second time.
30
+ injectClientScript: false
31
+ }),
32
+ ...(sentryEnabled
33
+ ? [
34
+ sentryReactRouter(
35
+ {
36
+ org: process.env.SENTRY_ORG,
37
+ project: process.env.SENTRY_PROJECT,
38
+ authToken: process.env.SENTRY_AUTH_TOKEN
39
+ },
40
+ config
41
+ )
42
+ ]
43
+ : []),
23
44
  iconsSpritesheet({
24
45
  inputDir: './other/svg-icons',
25
46
  outputDir: './app/icons',
@@ -31,8 +52,5 @@ export default defineConfig((config) => ({
31
52
  server: {
32
53
  port: 3001,
33
54
  strictPort: true
34
- },
35
- ssr: {
36
- noExternal: ['react-pdf-hook']
37
55
  }
38
56
  }))
@@ -8,6 +8,10 @@ export default async function (env: Record<string, string | undefined>) {
8
8
  CRYPT_KEY: env.CRYPT_KEY,
9
9
  HASH_KEY: env.HASH_KEY,
10
10
  ENV_NAME: env.ENV_NAME ?? 'local',
11
+ // Trusted canonical origin used to build absolute links (e.g. password-reset emails).
12
+ // Never derive these from the incoming request / Host header. Production deployments
13
+ // must set APP_URL to the real origin.
14
+ APP_URL: env.APP_URL ?? 'http://localhost:3001',
11
15
  AWS_REGION: 'us-east-1',
12
16
  SENTRY_DSN: env.SENTRY_DSN ?? '',
13
17
  VITE_SENTRY_DSN: env.SENTRY_DSN ?? ''