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.
- package/dist/src/catalog.cjs +21 -1
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +3 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs.map +1 -1
- package/dist/src/fs.cjs +10 -5
- package/dist/src/fs.cjs.map +1 -1
- package/dist/src/prompts.cjs +32 -0
- package/dist/src/prompts.cjs.map +1 -1
- package/dist/src/prompts.d.cts +7 -1
- package/dist/src/template.cjs +23 -4
- package/dist/src/template.cjs.map +1 -1
- package/dist/src/template.d.cts +17 -3
- package/dist/src/types.d.cts +11 -4
- package/package.json +4 -1
- package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +1 -0
- package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
- package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
- package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
- package/templates/minimal-app/.cursor/rules/server-event-emit.mdc +41 -0
- package/templates/minimal-app/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +14 -10
- package/templates/minimal-app/CLAUDE.md.tpl +133 -131
- package/templates/minimal-app/README.md.tpl +51 -19
- package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
- package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
- package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
- package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
- package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
- package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
- package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
- package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
- package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
- package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
- package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
- package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
- package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
- package/templates/minimal-app/apps/email/package.json.tpl +34 -34
- package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
- package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
- package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
- package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
- package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
- package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
- package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
- package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
- package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
- package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
- package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
- package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
- package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
- package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
- package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
- package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
- package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
- package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
- package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
- package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
- package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
- package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
- package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
- package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
- package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
- package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
- package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
- package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
- package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
- package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
- package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
- package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
- package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
- package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
- package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
- package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +55 -46
- package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
- package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
- package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +5 -3
- package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +51 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +57 -54
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
- package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
- package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
- package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
- package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
- package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
- package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
- package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
- package/templates/minimal-app/apps/shared/.env.tpl +5 -0
- package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
- package/templates/minimal-app/apps/shared/package.json.tpl +26 -24
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
- package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/demo-workflow/demo-workflow.constants.ts.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
- package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
- package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
- package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
- package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +49 -44
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
- package/templates/minimal-app/apps/webapp/src/StarterServerEventProvider.tsx.tpl +38 -0
- package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
- package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
- package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
- package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
- package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
- package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
- package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
- package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
- package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
- package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
- package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
- package/templates/minimal-app/biome.json.tpl +17 -12
- package/templates/minimal-app/package.json.tpl +7 -2
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
- package/templates/minimal-app/template.manifest.json +93 -2
- package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
- package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
- package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
- 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,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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 ./
|
|
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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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(
|
|
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(
|
|
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)
|
|
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)
|
|
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)
|
|
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();
|