create-m5kdev 0.32.8 → 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.
- package/dist/src/catalog.cjs +76 -11
- package/dist/src/catalog.cjs.map +1 -1
- package/dist/src/catalog.d.cts +13 -1
- package/dist/src/create.cjs +6 -2
- package/dist/src/create.cjs.map +1 -1
- package/dist/src/doctor.cjs +13 -7
- 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/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/.dockerignore.tpl +31 -0
- package/templates/minimal-app/.gitignore.tpl +1 -0
- package/templates/minimal-app/AGENTS.md.tpl +13 -10
- package/templates/minimal-app/CLAUDE.md.tpl +132 -131
- package/templates/minimal-app/README.md.tpl +50 -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/package.json.tpl +59 -59
- 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/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -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 +54 -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/0002_concerned_proudstar.sql +3 -0
- 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 -0
- 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 +15 -1
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
- package/templates/minimal-app/apps/server/package.json.tpl +57 -56
- package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
- package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
- 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 +47 -0
- package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -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 -30
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -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 -115
- 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 -1
- 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 +25 -24
- package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
- package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
- 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/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 +46 -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/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 +9 -5
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
- package/templates/minimal-app/template.manifest.json +92 -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
- package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
|
@@ -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,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
|
|
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
|
+
// 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
|