create-m5kdev 0.33.0 → 0.34.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/module-dto-guide.mdc +1 -1
  18. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  19. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  20. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  21. package/templates/minimal-app/.dockerignore.tpl +31 -0
  22. package/templates/minimal-app/.gitignore.tpl +1 -0
  23. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  24. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  25. package/templates/minimal-app/README.md.tpl +50 -19
  26. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  27. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  28. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  29. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  30. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  31. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  32. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  33. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  34. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  35. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  36. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  37. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  38. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  39. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  40. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  41. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  42. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  43. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  44. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  45. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  46. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  47. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  48. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  49. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  50. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  51. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  52. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  53. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  54. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  55. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  56. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  57. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  58. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  59. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  60. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  61. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  62. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  63. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  64. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  65. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  67. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  68. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  69. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  70. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  71. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  72. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  73. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -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 +54 -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 +47 -0
  95. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -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 +54 -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 +25 -24
  114. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  115. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  116. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  117. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  118. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  119. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  120. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  121. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  122. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  123. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  124. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  125. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  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 +6 -2
  142. package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
  143. package/templates/minimal-app/template.manifest.json +92 -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,75 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { parseArgs } from "node:util";
5
+
6
+ function missingEnvMessage(envPath) {
7
+ return `Missing ${envPath}. Copy the .env.production.example next to it, fill in values, then retry.`;
8
+ }
9
+
10
+ function parseDotenv(source) {
11
+ const vars = {};
12
+ for (const rawLine of source.split(/\r?\n/)) {
13
+ const line = rawLine.trim();
14
+ if (!line || line.startsWith("#")) continue;
15
+ const eq = line.indexOf("=");
16
+ if (eq === -1) continue;
17
+ const key = line.slice(0, eq).trim();
18
+ let value = line.slice(eq + 1);
19
+ if (
20
+ (value.startsWith('"') && value.endsWith('"')) ||
21
+ (value.startsWith("'") && value.endsWith("'"))
22
+ ) {
23
+ value = value.slice(1, -1);
24
+ }
25
+ vars[key] = value;
26
+ }
27
+ return vars;
28
+ }
29
+
30
+ function shellSingleQuote(value) {
31
+ return `'${value.replaceAll("'", `'\\''`)}'`;
32
+ }
33
+
34
+ function toExportScript(vars) {
35
+ return Object.entries(vars)
36
+ .map(([key, value]) => `export ${key}=${shellSingleQuote(value)}`)
37
+ .join("\n");
38
+ }
39
+
40
+ const { values } = parseArgs({
41
+ options: {
42
+ config: { type: "string" },
43
+ dockerfile: { type: "string" },
44
+ env: { type: "string" },
45
+ },
46
+ });
47
+
48
+ if (!values.config || !values.dockerfile || !values.env) {
49
+ console.error(
50
+ "Usage: fly-deploy.mjs --config <fly.toml> --dockerfile <Dockerfile> --env <.env.production>"
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ if (!existsSync(values.env)) {
56
+ console.error(missingEnvMessage(values.env));
57
+ process.exit(1);
58
+ }
59
+
60
+ const vars = parseDotenv(readFileSync(values.env, "utf8"));
61
+ const args = [
62
+ "deploy",
63
+ "-c",
64
+ values.config,
65
+ "--dockerfile",
66
+ values.dockerfile,
67
+ "--build-secret",
68
+ `ALL_SECRETS=${toExportScript(vars)}`,
69
+ ];
70
+ for (const [key, value] of Object.entries(vars)) {
71
+ args.push("--build-secret", `${key}=${value}`);
72
+ }
73
+
74
+ const result = spawnSync("fly", args, { stdio: "inherit" });
75
+ process.exit(result.status ?? 1);
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { parseArgs } from "node:util";
5
+
6
+ function missingEnvMessage(envPath) {
7
+ return `Missing ${envPath}. Copy the .env.production.example next to it, fill in values, then retry.`;
8
+ }
9
+
10
+ const { values } = parseArgs({
11
+ options: {
12
+ config: { type: "string" },
13
+ env: { type: "string" },
14
+ },
15
+ });
16
+
17
+ if (!values.config || !values.env) {
18
+ console.error("Usage: fly-secrets.mjs --config <fly.toml> --env <.env.production>");
19
+ process.exit(1);
20
+ }
21
+
22
+ if (!existsSync(values.env)) {
23
+ console.error(missingEnvMessage(values.env));
24
+ process.exit(1);
25
+ }
26
+
27
+ const result = spawnSync("fly", ["secrets", "import", "-c", values.config], {
28
+ input: readFileSync(values.env),
29
+ stdio: ["pipe", "inherit", "inherit"],
30
+ });
31
+ process.exit(result.status ?? 1);
@@ -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,54 @@
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
+ // m5k:workflows:end
49
+ - `index.ts` calls `builtBackendApp.start()`, which listens and handles SIGINT/SIGTERM. Extra shutdown work (telemetry) is `onShutdown` on `createBackendApp`.
50
+ - After changing Drizzle tables, run `pnpm --filter ./apps/server drizzle:generate` then `drizzle:migrate` — do not hand-edit SQL migrations in this repo.
51
+
52
+ // m5k:notifications:start
53
+ - `NotificationModule` is registered in `app.ts` when this feature is selected. Push-related server env vars are documented in `apps/shared/.env.example`.
54
+ // m5k:notifications:end