create-m5kdev 0.33.0 → 0.35.0

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 (147) hide show
  1. package/dist/src/catalog.cjs +21 -1
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +3 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs.map +1 -1
  7. package/dist/src/fs.cjs +10 -5
  8. package/dist/src/fs.cjs.map +1 -1
  9. package/dist/src/prompts.cjs +32 -0
  10. package/dist/src/prompts.cjs.map +1 -1
  11. package/dist/src/prompts.d.cts +7 -1
  12. package/dist/src/template.cjs +23 -4
  13. package/dist/src/template.cjs.map +1 -1
  14. package/dist/src/template.d.cts +17 -3
  15. package/dist/src/types.d.cts +11 -4
  16. package/package.json +4 -1
  17. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +1 -0
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.cursor/rules/server-event-emit.mdc +41 -0
  23. package/templates/minimal-app/.dockerignore.tpl +31 -0
  24. package/templates/minimal-app/.gitignore.tpl +1 -0
  25. package/templates/minimal-app/AGENTS.md.tpl +14 -10
  26. package/templates/minimal-app/CLAUDE.md.tpl +133 -131
  27. package/templates/minimal-app/README.md.tpl +51 -19
  28. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  29. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  30. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  31. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  32. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  33. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  34. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  35. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  36. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  37. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  38. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  39. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  40. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  41. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  42. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  43. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  44. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  45. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  46. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  47. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  48. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  49. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  75. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  76. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  77. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  78. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  79. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  80. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  81. package/templates/minimal-app/apps/server/AGENTS.md.tpl +55 -46
  82. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  83. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  84. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  85. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  86. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  87. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
  88. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  89. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
  90. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  91. package/templates/minimal-app/apps/server/package.json.tpl +5 -3
  92. package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
  93. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  94. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +51 -0
  95. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +51 -0
  96. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +57 -54
  100. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  103. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  104. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  105. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
  106. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  107. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  108. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  109. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  110. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  111. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  112. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  113. package/templates/minimal-app/apps/shared/package.json.tpl +26 -24
  114. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  115. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  116. package/templates/minimal-app/apps/shared/src/modules/demo-workflow/demo-workflow.constants.ts.tpl +1 -0
  117. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  118. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  119. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  120. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  121. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  122. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +49 -44
  123. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  124. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  125. package/templates/minimal-app/apps/webapp/src/StarterServerEventProvider.tsx.tpl +38 -0
  126. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  127. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  128. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  129. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  130. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  131. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  132. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  133. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  134. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  135. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  136. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  137. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  138. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  139. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  140. package/templates/minimal-app/biome.json.tpl +17 -12
  141. package/templates/minimal-app/package.json.tpl +7 -2
  142. package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
  143. package/templates/minimal-app/template.manifest.json +93 -2
  144. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  145. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  146. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  147. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
@@ -1,20 +1,20 @@
1
- import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
- import { transformer } from "@m5kdev/commons/utils/trpc";
3
- import { createTRPCClient, httpBatchLink } from "@trpc/client";
4
- import { serverUrl } from "../config";
5
-
6
- export const trpcClient = createTRPCClient<BackendTRPCRouter>({
7
- links: [
8
- httpBatchLink({
9
- url: `${serverUrl}/trpc`,
10
- fetch(url, options) {
11
- return fetch(url, {
12
- ...options,
13
- body: options?.body ? (options.body as BodyInit | null | undefined) : undefined,
14
- credentials: "include",
15
- });
16
- },
17
- transformer,
18
- }),
19
- ],
20
- });
1
+ import type { BackendTRPCRouter } from "@m5kdev/backend/types";
2
+ import { transformer } from "@m5kdev/commons/utils/trpc";
3
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
4
+ import { serverUrl } from "../config";
5
+
6
+ export const trpcClient = createTRPCClient<BackendTRPCRouter>({
7
+ links: [
8
+ httpBatchLink({
9
+ url: `${serverUrl}/trpc`,
10
+ fetch(url, options) {
11
+ return fetch(url, {
12
+ ...options,
13
+ body: options?.body ? (options.body as BodyInit | null | undefined) : undefined,
14
+ credentials: "include",
15
+ });
16
+ },
17
+ transformer,
18
+ }),
19
+ ],
20
+ });
@@ -1,11 +1,11 @@
1
- {
2
- "extends": "expo/tsconfig.base",
3
- "compilerOptions": {
4
- "strict": true,
5
- "paths": {
6
- "@/*": ["./src/*"],
7
- "@/assets/*": ["./assets/*"]
8
- }
9
- },
10
- "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "uniwind-env.d.ts"]
11
- }
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "paths": {
6
+ "@/*": ["./src/*"],
7
+ "@/assets/*": ["./assets/*"]
8
+ }
9
+ },
10
+ "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "uniwind-env.d.ts"]
11
+ }
@@ -1 +1 @@
1
- /// <reference types="uniwind/types" />
1
+ /// <reference types="uniwind/types" />
@@ -0,0 +1,7 @@
1
+ # Copy to .env.production and fill in values. This file is not copied into the
2
+ # image (.dockerignore excludes .env*). landing:deploy forwards every key as a
3
+ # Fly build secret; landing:secrets imports the same file as runtime secrets.
4
+
5
+ VITE_APP_NAME={{APP_NAME}}
6
+ VITE_APP_URL=
7
+ VITE_SERVER_URL=
@@ -0,0 +1,39 @@
1
+ # syntax = docker/dockerfile:1
2
+
3
+ FROM node:24-slim AS base
4
+
5
+ LABEL fly_launch_runtime="NodeJS"
6
+
7
+ WORKDIR /app
8
+
9
+ ENV NODE_ENV=production
10
+
11
+ FROM base AS build
12
+
13
+ RUN apt-get update -qq && \
14
+ apt-get install -y python-is-python3 pkg-config build-essential ca-certificates && \
15
+ update-ca-certificates
16
+
17
+ RUN npm install -g pnpm@10.13.1
18
+
19
+ COPY --link . .
20
+
21
+ RUN pnpm install --frozen-lockfile --prod=false
22
+
23
+ # Frontend bake needs VITE_* from the operator env file. The wrapper passes
24
+ # ALL_SECRETS (every key) plus per-key --build-secret flags.
25
+ RUN --mount=type=secret,id=ALL_SECRETS,required=false \
26
+ if [ -f /run/secrets/ALL_SECRETS ]; then . /run/secrets/ALL_SECRETS; fi && \
27
+ pnpm --filter={{PACKAGE_SCOPE}}/landing build
28
+
29
+ RUN pnpm --filter={{PACKAGE_SCOPE}}/landing deploy --prod --legacy /deploy
30
+ RUN cp -r /app/apps/landing/dist /deploy/dist
31
+
32
+ FROM base
33
+
34
+ COPY --from=build /deploy /app
35
+ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
36
+
37
+ WORKDIR /app
38
+
39
+ CMD ["npm", "run", "start"]
@@ -0,0 +1,20 @@
1
+ app = "{{APP_SLUG}}-landing"
2
+ primary_region = "iad"
3
+
4
+ [build]
5
+
6
+ [env]
7
+ PORT = "8080"
8
+
9
+ [http_service]
10
+ internal_port = 8080
11
+ force_https = true
12
+ auto_stop_machines = "stop"
13
+ auto_start_machines = true
14
+ min_machines_running = 0
15
+ processes = ["app"]
16
+
17
+ [[vm]]
18
+ memory = "1gb"
19
+ cpu_kind = "shared"
20
+ cpus = 1
@@ -0,0 +1,35 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0"
8
+ />
9
+ <meta
10
+ name="description"
11
+ content="{{APP_NAME}} — an AI SaaS workspace."
12
+ />
13
+ <link
14
+ rel="preconnect"
15
+ href="https://fonts.googleapis.com"
16
+ />
17
+ <link
18
+ rel="preconnect"
19
+ href="https://fonts.gstatic.com"
20
+ crossorigin
21
+ />
22
+ <link
23
+ href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,700&family=Figtree:wght@400;500;600&display=swap"
24
+ rel="stylesheet"
25
+ />
26
+ <title>{{APP_NAME}}</title>
27
+ </head>
28
+ <body>
29
+ <div id="root"></div>
30
+ <script
31
+ type="module"
32
+ src="/src/main.tsx"
33
+ ></script>
34
+ </body>
35
+ </html>
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/landing",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "start": "vite preview --host 0.0.0.0 --port 8080",
10
+ "lint": "biome check .",
11
+ "lint:fix": "biome check . --write",
12
+ "check-types": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@heroui/react": "catalog:m5kdev",
16
+ "@heroui/styles": "catalog:m5kdev",
17
+ "react": "catalog:m5kdev",
18
+ "react-dom": "catalog:m5kdev",
19
+ "react-router": "catalog:m5kdev",
20
+ "tailwindcss": "catalog:m5kdev",
21
+ "tw-animate-css": "catalog:m5kdev",
22
+ "vite": "catalog:m5kdev"
23
+ },
24
+ "devDependencies": {
25
+ "@m5kdev/config": "catalog:m5kdev",
26
+ "@tailwindcss/vite": "catalog:m5kdev",
27
+ "@types/react": "catalog:m5kdev",
28
+ "@types/react-dom": "catalog:m5kdev",
29
+ "@vitejs/plugin-react": "catalog:m5kdev",
30
+ "babel-plugin-react-compiler": "catalog:m5kdev",
31
+ "typescript": "catalog:m5kdev",
32
+ "vite-tsconfig-paths": "catalog:m5kdev"
33
+ }
34
+ }
@@ -0,0 +1,12 @@
1
+ import { BrowserRouter, Route, Routes } from "react-router";
2
+ import { LandingPage } from "./LandingPage";
3
+
4
+ export function App() {
5
+ return (
6
+ <BrowserRouter>
7
+ <Routes>
8
+ <Route path="*" element={<LandingPage />} />
9
+ </Routes>
10
+ </BrowserRouter>
11
+ );
12
+ }
@@ -0,0 +1,37 @@
1
+ import { Button, Card } from "@heroui/react";
2
+
3
+ export function LandingPage() {
4
+ const appName = import.meta.env.VITE_APP_NAME ?? "{{APP_NAME}}";
5
+ const appUrl = import.meta.env.VITE_APP_URL ?? "http://localhost:5173";
6
+
7
+ return (
8
+ <main className="relative flex min-h-dvh flex-col items-center justify-center overflow-hidden px-6 py-16">
9
+ <div
10
+ aria-hidden="true"
11
+ className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,oklch(0.92_0.04_85),transparent_55%)]"
12
+ />
13
+ <div
14
+ aria-hidden="true"
15
+ className="pointer-events-none absolute inset-0 opacity-[0.12] [background-image:repeating-linear-gradient(0deg,transparent,transparent_23px,var(--color-rule)_24px)]"
16
+ />
17
+ <Card className="relative max-w-xl border border-[var(--color-rule)] bg-[color-mix(in_oklch,var(--color-paper)_92%,white)] shadow-none">
18
+ <Card.Header className="grid gap-4 p-8 pb-4 sm:p-10 sm:pb-4">
19
+ <p className="text-xs font-medium tracking-[0.28em] text-[color-mix(in_oklch,var(--color-ink)_55%,transparent)] uppercase">
20
+ Public site
21
+ </p>
22
+ <Card.Title className="font-[family-name:var(--font-display)] text-5xl leading-[0.95] font-medium tracking-tight sm:text-6xl">
23
+ {appName}
24
+ </Card.Title>
25
+ <Card.Description className="max-w-md text-base leading-relaxed text-[color-mix(in_oklch,var(--color-ink)_72%,transparent)]">
26
+ An AI SaaS workspace. Sign in on the app when you are ready to write, ship, and operate.
27
+ </Card.Description>
28
+ </Card.Header>
29
+ <Card.Footer className="p-8 pt-2 sm:p-10 sm:pt-2">
30
+ <Button variant="primary" onPress={() => window.location.assign(appUrl)}>
31
+ Open app
32
+ </Button>
33
+ </Card.Footer>
34
+ </Card>
35
+ </main>
36
+ );
37
+ }
@@ -0,0 +1,24 @@
1
+ /* biome-ignore-all lint/suspicious/noUnknownAtRules: Tailwind v4 */
2
+
3
+ @import "tailwindcss";
4
+ @import "@heroui/styles";
5
+ @import "tw-animate-css";
6
+
7
+ @theme {
8
+ --font-display: "Fraunces", "Times New Roman", serif;
9
+ --font-sans: "Figtree", ui-sans-serif, system-ui, sans-serif;
10
+ --color-ink: oklch(0.22 0.03 250);
11
+ --color-paper: oklch(0.97 0.01 85);
12
+ --color-rule: oklch(0.84 0.02 85);
13
+ --accent: oklch(0.6204 0.195 253.83);
14
+ }
15
+
16
+ html {
17
+ font-family: var(--font-sans);
18
+ background: var(--color-paper);
19
+ color: var(--color-ink);
20
+ }
21
+
22
+ body {
23
+ margin: 0;
24
+ }
@@ -0,0 +1,16 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ const rootElement = document.getElementById("root");
7
+
8
+ if (!rootElement) {
9
+ throw new Error("Root element #root not found.");
10
+ }
11
+
12
+ createRoot(rootElement).render(
13
+ <StrictMode>
14
+ <App />
15
+ </StrictMode>
16
+ );
@@ -0,0 +1,11 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_APP_NAME?: string;
5
+ readonly VITE_APP_URL?: string;
6
+ readonly VITE_SERVER_URL?: string;
7
+ }
8
+
9
+ interface ImportMeta {
10
+ readonly env: ImportMetaEnv;
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "@m5kdev/config/tsconfig.vite.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "ignoreDeprecations": "6.0",
6
+ "rootDir": ".",
7
+ "outDir": "dist",
8
+ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
9
+ },
10
+ "include": ["src"],
11
+ "exclude": ["dist", "node_modules"]
12
+ }
@@ -0,0 +1,25 @@
1
+ import tailwindcss from "@tailwindcss/vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import { defineConfig } from "vite";
4
+ import tsconfigPaths from "vite-tsconfig-paths";
5
+
6
+ export default defineConfig({
7
+ root: __dirname,
8
+ plugins: [
9
+ tailwindcss(),
10
+ react({
11
+ babel: {
12
+ plugins: ["babel-plugin-react-compiler"],
13
+ },
14
+ }),
15
+ tsconfigPaths(),
16
+ ],
17
+ resolve: {
18
+ dedupe: ["react", "react-dom", "react-router"],
19
+ },
20
+ envDir: "../shared",
21
+ preview: {
22
+ host: "0.0.0.0",
23
+ port: 8080,
24
+ },
25
+ });
@@ -1,46 +1,55 @@
1
- # Server Module Structure Guide
2
-
3
- Use this for modules in `apps/server/src/modules/**`.
4
-
5
- ## Preferred Module Layout
6
-
7
- Shared contracts:
8
-
9
- ```
10
- apps/shared/src/modules/<module>/
11
- ├── <module>.constants.ts
12
- └── <module>.schema.ts
13
- ```
14
-
15
- Server module:
16
-
17
- ```
18
- apps/server/src/modules/<module>/
19
- ├── <module>.db.ts
20
- ├── <module>.repository.ts
21
- ├── <module>.service.ts
22
- ├── <module>.trpc.ts
23
- └── <module>.module.ts
24
- ```
25
-
26
- ## Layer Boundaries
27
-
28
- - Repositories own persistence and query construction.
29
- - Services own business rules, orchestration, and context-aware defaults.
30
- - tRPC files own transport only and must delegate to services.
31
- - Register modules in `apps/server/src/app.ts`; use `db.ts` only for scripts that need direct DB.
32
-
33
- ## Avoid Trivial Service Delegation
34
-
35
- - Do not add service methods that merely call another service.
36
- - Call the owning service directly unless the method adds a business rule, authorization, validation, orchestration, a transaction boundary, or meaningful domain translation.
37
- - Renaming a dependency method, repackaging arguments, constructing a prompt, or forwarding actor/context data alone does not justify a wrapper.
38
- - If removing the method and calling the dependency directly would lose no behavior or boundary, do not add it.
39
-
40
- ## Workflow, Redis, and push notifications
41
-
42
- - `WorkflowModule` and `NotificationModule` are registered in `app.ts`. Start **Redis** locally (`REDIS_URL`) before `pnpm dev` on the server, or background jobs will not run.
43
- - `index.ts` calls `builtBackendApp.start()`, which listens and handles SIGINT/SIGTERM. Extra shutdown work (telemetry) is `onShutdown` on `createBackendApp`.
44
- - After changing Drizzle tables, run `pnpm --filter ./apps/server sync` — do not hand-edit SQL migrations in this repo.
45
-
46
- Push-related server env vars are documented in `apps/shared/.env.example`.
1
+ # Server Module Structure Guide
2
+
3
+ Use this for modules in `apps/server/src/modules/**`.
4
+
5
+ ## Preferred Module Layout
6
+
7
+ Shared contracts:
8
+
9
+ ```
10
+ apps/shared/src/modules/<module>/
11
+ ├── <module>.constants.ts
12
+ └── <module>.schema.ts
13
+ ```
14
+
15
+ Server module:
16
+
17
+ ```
18
+ apps/server/src/modules/<module>/
19
+ ├── <module>.db.ts
20
+ ├── <module>.repository.ts
21
+ ├── <module>.service.ts
22
+ ├── <module>.trpc.ts
23
+ └── <module>.module.ts
24
+ ```
25
+
26
+ ## Layer Boundaries
27
+
28
+ - Repositories own persistence and query construction.
29
+ - Services own business rules, orchestration, and context-aware defaults.
30
+ - tRPC files own transport only and must delegate to services.
31
+ - Register Core Modules and app modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`. Do not use `backendApp.use`. Optional Backend Modules (`@m5kdev/module-*`) are not scaffolded; add them only when the product needs Clay, Docx, Pdf, Social, or Video.
32
+ // m5k:ai:start
33
+ - `AIModule` is registered in `app.ts` with an app-owned Mastra Agent (`assistant`). Conversation HTTP lives on `/ai`. Set `OPENROUTER_API_KEY` for live replies. Do not add a Starter service wrapper around `AIService` chat.
34
+ // m5k:ai:end
35
+ - Database commands live in server `db.ts` (`pnpm drizzle:reset`, `drizzle:sync`, `drizzle:seed`) and call Kernel `runDb` — they must not import `app.ts`.
36
+
37
+ ## Avoid Trivial Service Delegation
38
+
39
+ - Do not add service methods that merely call another service.
40
+ - Call the owning service directly unless the method adds a business rule, authorization, validation, orchestration, a transaction boundary, or meaningful domain translation.
41
+ - Renaming a dependency method, repackaging arguments, constructing a prompt, or forwarding actor/context data alone does not justify a wrapper.
42
+ - If removing the method and calling the dependency directly would lose no behavior or boundary, do not add it.
43
+
44
+ ## Runtime notes
45
+
46
+ // m5k:workflows:start
47
+ - `WorkflowModule` is registered in `app.ts`. Start **Redis** locally (`REDIS_URL`) before `pnpm dev` on the server, or background jobs will not run.
48
+ - Server event: emit through Auth from a job `.handle` when the UI must refresh a change the trigger mutation did not return. See `.cursor/rules/server-event-emit.mdc`.
49
+ // m5k:workflows:end
50
+ - `index.ts` calls `builtBackendApp.start()`, which listens and handles SIGINT/SIGTERM. Extra shutdown work (telemetry) is `onShutdown` on `createBackendApp`.
51
+ - After changing Drizzle tables, run `pnpm --filter ./apps/server drizzle:generate` then `drizzle:migrate` — do not hand-edit SQL migrations in this repo.
52
+
53
+ // m5k:notifications:start
54
+ - `NotificationModule` is registered in `app.ts` when this feature is selected. Push-related server env vars are documented in `apps/shared/.env.example`.
55
+ // m5k:notifications:end
@@ -1,18 +1,39 @@
1
1
  /**
2
2
  * E2E fixture seed — used only by the e2e package's prepare scripts, never by
3
3
  * generated apps (the CLI excludes this file). The user-facing demo seed lives
4
- * in ./seed.ts.
4
+ * in ./db.ts.
5
5
  */
6
+ import { type RunDbSeedContext, runDb } from "@m5kdev/backend/db";
6
7
  import { hashPassword } from "better-auth/crypto";
7
8
  import { eq } from "drizzle-orm";
8
9
  import { v4 as uuidv4 } from "uuid";
9
- import { orm, schema } from "./db";
10
- import { ensureDevServerStopped } from "./guard";
10
+ import * as schema from "./src/schema";
11
11
 
12
12
  const profile = process.env.AUTH_E2E_PROFILE ?? "standard";
13
13
  const ADMIN_EMAIL = `admin.${profile}@auth-e2e.local`;
14
14
  const ADMIN_PASSWORD = "password1234";
15
15
 
16
+ type SeedOrm = RunDbSeedContext<typeof schema>["orm"];
17
+
18
+ void runDb({
19
+ schema,
20
+ seed: async ({ orm }) => {
21
+ const admin = await ensureUser({
22
+ orm,
23
+ id: `auth-e2e-admin-${profile}`,
24
+ name: "Auth E2E Admin",
25
+ email: ADMIN_EMAIL,
26
+ password: ADMIN_PASSWORD,
27
+ role: "admin",
28
+ });
29
+ const { organizationId } = await ensureOrganization({ orm, userId: admin.id });
30
+ await seedPosts({ orm, userId: admin.id, organizationId });
31
+ await seedExpiredWaitlistCode(orm);
32
+ await seedProvisionedClaimUser(orm);
33
+ console.info(`Seeded ${profile}. Admin login: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}`);
34
+ },
35
+ });
36
+
16
37
  function slugify(value: string): string {
17
38
  return value
18
39
  .trim()
@@ -21,27 +42,24 @@ function slugify(value: string): string {
21
42
  .replace(/^-+|-+$/g, "");
22
43
  }
23
44
 
24
- async function ensureUser({
25
- id,
26
- name,
27
- email,
28
- password,
29
- role = "user",
30
- emailVerified = true,
31
- }: {
45
+ async function ensureUser(input: {
46
+ orm: SeedOrm;
32
47
  id: string;
33
48
  name: string;
34
49
  email: string;
35
50
  password?: string;
36
51
  role?: string;
37
52
  emailVerified?: boolean;
38
- }) {
53
+ }): Promise<typeof schema.users.$inferSelect> {
54
+ const { orm, id, name, email, password, role = "user", emailVerified = true } = input;
39
55
  const [existingUser] = await orm
40
56
  .select()
41
57
  .from(schema.users)
42
58
  .where(eq(schema.users.email, email))
43
59
  .limit(1);
44
- if (existingUser) return existingUser;
60
+ if (existingUser) {
61
+ return existingUser;
62
+ }
45
63
 
46
64
  const [user] = await orm
47
65
  .insert(schema.users)
@@ -56,7 +74,9 @@ async function ensureUser({
56
74
  })
57
75
  .returning();
58
76
 
59
- if (!user) throw new Error(`Failed to seed user ${email}`);
77
+ if (!user) {
78
+ throw new Error(`Failed to seed user ${email}`);
79
+ }
60
80
 
61
81
  if (password) {
62
82
  await orm.insert(schema.accounts).values({
@@ -73,7 +93,11 @@ async function ensureUser({
73
93
  return user;
74
94
  }
75
95
 
76
- async function ensureOrganization(userId: string) {
96
+ async function ensureOrganization(input: {
97
+ orm: SeedOrm;
98
+ userId: string;
99
+ }): Promise<{ organizationId: string; teamId: string }> {
100
+ const { orm, userId } = input;
77
101
  const organizationId = `auth-e2e-enterprise-${profile}`;
78
102
  const teamId = `auth-e2e-team-${profile}`;
79
103
 
@@ -144,9 +168,16 @@ async function ensureOrganization(userId: string) {
144
168
  return { organizationId, teamId };
145
169
  }
146
170
 
147
- async function seedPosts(userId: string, organizationId: string) {
171
+ async function seedPosts(input: {
172
+ orm: SeedOrm;
173
+ userId: string;
174
+ organizationId: string;
175
+ }): Promise<void> {
176
+ const { orm, userId, organizationId } = input;
148
177
  const existingPosts = await orm.select().from(schema.posts).limit(1);
149
- if (existingPosts.length > 0) return;
178
+ if (existingPosts.length > 0) {
179
+ return;
180
+ }
150
181
 
151
182
  const posts = [
152
183
  {
@@ -175,13 +206,15 @@ async function seedPosts(userId: string, organizationId: string) {
175
206
  );
176
207
  }
177
208
 
178
- async function seedExpiredWaitlistCode() {
209
+ async function seedExpiredWaitlistCode(orm: SeedOrm): Promise<void> {
179
210
  const [existing] = await orm
180
211
  .select()
181
212
  .from(schema.waitlist)
182
213
  .where(eq(schema.waitlist.code, "expired-waitlist-code"))
183
214
  .limit(1);
184
- if (existing) return;
215
+ if (existing) {
216
+ return;
217
+ }
185
218
 
186
219
  await orm.insert(schema.waitlist).values({
187
220
  id: uuidv4(),
@@ -196,8 +229,9 @@ async function seedExpiredWaitlistCode() {
196
229
  });
197
230
  }
198
231
 
199
- async function seedProvisionedClaimUser() {
232
+ async function seedProvisionedClaimUser(orm: SeedOrm): Promise<typeof schema.users.$inferSelect> {
200
233
  const user = await ensureUser({
234
+ orm,
201
235
  id: `auth-e2e-claim-user-${profile}`,
202
236
  name: "Provisioned Claim User",
203
237
  email: `claim.${profile}@provisioned.auth-e2e.local`,
@@ -210,7 +244,9 @@ async function seedProvisionedClaimUser() {
210
244
  .from(schema.waitlist)
211
245
  .where(eq(schema.waitlist.claimUserId, user.id))
212
246
  .limit(1);
213
- if (existingClaim) return user;
247
+ if (existingClaim) {
248
+ return user;
249
+ }
214
250
 
215
251
  await orm.insert(schema.waitlist).values({
216
252
  id: `auth-e2e-claim-${profile}`,
@@ -225,22 +261,3 @@ async function seedProvisionedClaimUser() {
225
261
 
226
262
  return user;
227
263
  }
228
-
229
- async function seed() {
230
- await ensureDevServerStopped();
231
- const admin = await ensureUser({
232
- id: `auth-e2e-admin-${profile}`,
233
- name: "Auth E2E Admin",
234
- email: ADMIN_EMAIL,
235
- password: ADMIN_PASSWORD,
236
- role: "admin",
237
- });
238
- const { organizationId } = await ensureOrganization(admin.id);
239
- await seedPosts(admin.id, organizationId);
240
- await seedExpiredWaitlistCode();
241
- await seedProvisionedClaimUser();
242
-
243
- console.info(`Seeded ${profile}. Admin login: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}`);
244
- }
245
-
246
- void seed();