@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.
- package/package.json +2 -2
- package/skills/maestro-examples/references/cadence/cadence-web/package.json +6 -6
- package/skills/maestro-examples/references/cadence/package.json +4 -4
- package/skills/maestro-examples/references/cadence/packages/schemas/package.json +1 -1
- package/skills/maestro-examples/references/cadence/packages/services/package.json +19 -19
- package/skills/maestro-examples/references/cadence/packages/services/src/auth/auth-db-provider.ts +14 -9
- package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/forgot-password.tsx +3 -2
- package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/reset-password.tsx +4 -3
- package/skills/maestro-examples/references/chorus/chorus-web/package.json +15 -9
- package/skills/maestro-examples/references/chorus/chorus-web/react-router.config.ts +5 -1
- package/skills/maestro-examples/references/chorus/chorus-web/server/index.ts +89 -0
- package/skills/maestro-examples/references/chorus/chorus-web/tsconfig.json +1 -1
- package/skills/maestro-examples/references/chorus/chorus-web/vite.config.ts +29 -11
- package/skills/maestro-examples/references/chorus/config/services.ts +4 -0
- package/skills/maestro-examples/references/chorus/infrastructure/cdk/index.ts +160 -26
- package/skills/maestro-examples/references/chorus/infrastructure/package.json +2 -2
- package/skills/maestro-examples/references/chorus/package.json +5 -4
- package/skills/maestro-examples/references/chorus/packages/schemas/package.json +1 -1
- package/skills/maestro-examples/references/chorus/packages/services/package.json +21 -21
- package/skills/maestro-examples/references/chorus/packages/services/src/auth/auth-use-cases.ts +23 -6
- package/skills/maestro-examples/references/chorus/packages/services/src/auth/password-reset-token-db-provider.ts +9 -1
- package/skills/maestro-examples/references/chorus/packages/services/src/auth/remember-me-token-db-provider.ts +14 -9
- package/skills/maestro-examples/references/chorus/packages/services/src/maestro.ts +4 -1
- package/skills/maestro-examples/references/chorus/scripts/deploy/artifact.ts +89 -0
- package/skills/maestro-examples/references/chorus/scripts/deploy/aws.ts +39 -0
- package/skills/maestro-examples/references/chorus/scripts/deploy/deploy-chorus-web.ts +503 -0
- package/skills/maestro-examples/references/chorus/scripts/deploy/migrate-entry.ts +53 -0
- package/skills/maestro-examples/references/chorus/scripts/deploy/migrations.ts +241 -0
- package/skills/maestro-examples/references/chorus/scripts/deploy/tarball.ts +40 -0
- package/skills/maestro-examples/references/setlist/infrastructure/package.json +2 -2
- package/skills/maestro-examples/references/setlist/package.json +4 -4
- package/skills/maestro-examples/references/setlist/packages/schemas/package.json +1 -1
- package/skills/maestro-examples/references/setlist/packages/services/package.json +15 -15
- package/skills/maestro-examples/references/setlist/packages/services/src/project/project-use-cases.ts +17 -0
- package/skills/maestro-examples/references/setlist/packages/services/src/task/task-use-cases.ts +25 -0
- package/skills/maestro-examples/references/setlist/queue-processor/package.json +2 -2
- package/skills/maestro-examples/references/setlist/setlist-web/package.json +3 -3
- package/skills/maestro-packages/config.md +22 -8
- package/skills/maestro-packages/react-router-file-routes.md +1 -1
package/package.json
CHANGED
|
@@ -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.
|
|
18
|
-
"@maestro-js/log": "^1.0.0-alpha.
|
|
19
|
-
"@maestro-js/form": "^1.0.0-alpha.
|
|
20
|
-
"@maestro-js/permissions": "^1.0.0-alpha.
|
|
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.
|
|
45
|
-
"@maestro-js/react-router-file-routes": "^1.0.0-alpha.
|
|
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.
|
|
9
|
+
"@maestro-js/config": "^1.0.0-alpha.25"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@maestro-js/cli": "^1.0.0-alpha.
|
|
13
|
-
"@maestro-js/db-migrate": "^1.0.0-alpha.
|
|
14
|
-
"@maestro-js/db": "^1.0.0-alpha.
|
|
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
|
}
|
|
@@ -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.
|
|
16
|
-
"@maestro-js/cache": "^1.0.0-alpha.
|
|
17
|
-
"@maestro-js/config": "^1.0.0-alpha.
|
|
18
|
-
"@maestro-js/crypt": "^1.0.0-alpha.
|
|
19
|
-
"@maestro-js/custom-errors": "^1.0.0-alpha.
|
|
20
|
-
"@maestro-js/db": "^1.0.0-alpha.
|
|
21
|
-
"@maestro-js/exceptions": "^1.0.0-alpha.
|
|
22
|
-
"@maestro-js/hash": "^1.0.0-alpha.
|
|
23
|
-
"@maestro-js/helpers": "^1.0.0-alpha.
|
|
24
|
-
"@maestro-js/iso-zod": "^1.0.0-alpha.
|
|
25
|
-
"@maestro-js/log": "^1.0.0-alpha.
|
|
26
|
-
"@maestro-js/mail": "^1.0.0-alpha.
|
|
27
|
-
"@maestro-js/password": "^1.0.0-alpha.
|
|
28
|
-
"@maestro-js/permissions": "^1.0.0-alpha.
|
|
29
|
-
"@maestro-js/queue": "^1.0.0-alpha.
|
|
30
|
-
"@maestro-js/rate-limiting": "^1.0.0-alpha.
|
|
31
|
-
"@maestro-js/recurring-jobs": "^1.0.0-alpha.
|
|
32
|
-
"@maestro-js/session": "^1.0.0-alpha.
|
|
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.
|
|
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"
|
package/skills/maestro-examples/references/cadence/packages/services/src/auth/auth-db-provider.ts
CHANGED
|
@@ -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(
|
|
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<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
selector
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/forgot-password.tsx
CHANGED
|
@@ -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
|
-
|
|
48
|
-
|
|
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)
|
package/skills/maestro-examples/references/chorus/chorus-web/app/routes/_auth/reset-password.tsx
CHANGED
|
@@ -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
|
-
|
|
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'
|
|
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.
|
|
17
|
-
"@maestro-js/permissions": "^1.0.0-alpha.
|
|
18
|
-
"@maestro-js/custom-errors": "^1.0.0-alpha.
|
|
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": "
|
|
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
|
-
"@
|
|
40
|
-
"@maestro-js/
|
|
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
|
-
"
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 ?? ''
|