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
package/dist/src/prompts.d.cts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { CreateCommandOptions } from "./types.cjs";
|
|
2
|
+
import { BackendModuleChoice } from "./template.cjs";
|
|
2
3
|
|
|
3
4
|
//#region src/prompts.d.ts
|
|
5
|
+
declare function formatBackendModulesPrompt(choices: readonly BackendModuleChoice[]): string;
|
|
6
|
+
declare function parseBackendModulesAnswer(options: {
|
|
7
|
+
answer: string;
|
|
8
|
+
choices: readonly BackendModuleChoice[];
|
|
9
|
+
}): string[];
|
|
4
10
|
declare function resolveCreateCommandOptions(options: CreateCommandOptions): Promise<CreateCommandOptions>;
|
|
5
11
|
//#endregion
|
|
6
|
-
export { resolveCreateCommandOptions };
|
|
12
|
+
export { formatBackendModulesPrompt, parseBackendModulesAnswer, resolveCreateCommandOptions };
|
|
7
13
|
//# sourceMappingURL=prompts.d.cts.map
|
package/dist/src/template.cjs
CHANGED
|
@@ -26,11 +26,28 @@ function getTemplateFilePolicy(manifest, relativePath) {
|
|
|
26
26
|
for (const rule of manifest.sync.rules) if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;
|
|
27
27
|
return manifest.sync.defaultPolicy;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function getFeatureKind(options) {
|
|
30
|
+
if (options.config.kind) return options.config.kind;
|
|
31
|
+
if (options.id === "webapp" || options.id === "expo") return "platform";
|
|
32
|
+
if (options.id === "test-harness") return "harness";
|
|
33
|
+
return "module";
|
|
34
|
+
}
|
|
35
|
+
function listBackendModuleChoices(manifest) {
|
|
36
|
+
return Object.entries(manifest.features).filter(([id, config]) => getFeatureKind({
|
|
37
|
+
id,
|
|
38
|
+
config
|
|
39
|
+
}) === "module").map(([id, config]) => ({
|
|
40
|
+
id,
|
|
41
|
+
label: config.label ?? id,
|
|
42
|
+
experimental: Boolean(config.experimental)
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
function getEnabledFeatures(options) {
|
|
30
46
|
const enabled = /* @__PURE__ */ new Set();
|
|
31
|
-
if (platform !== "expo") enabled.add("webapp");
|
|
32
|
-
if (platform !== "web") enabled.add("expo");
|
|
33
|
-
if (testHarness) enabled.add("test-harness");
|
|
47
|
+
if (options.platform !== "expo") enabled.add("webapp");
|
|
48
|
+
if (options.platform !== "web") enabled.add("expo");
|
|
49
|
+
if (options.testHarness) enabled.add("test-harness");
|
|
50
|
+
for (const id of options.modules ?? []) enabled.add(id);
|
|
34
51
|
return enabled;
|
|
35
52
|
}
|
|
36
53
|
function getExcludedFeaturePaths(manifest, enabledFeatures) {
|
|
@@ -39,7 +56,9 @@ function getExcludedFeaturePaths(manifest, enabledFeatures) {
|
|
|
39
56
|
//#endregion
|
|
40
57
|
exports.getEnabledFeatures = getEnabledFeatures;
|
|
41
58
|
exports.getExcludedFeaturePaths = getExcludedFeaturePaths;
|
|
59
|
+
exports.getFeatureKind = getFeatureKind;
|
|
42
60
|
exports.getTemplateFilePolicy = getTemplateFilePolicy;
|
|
61
|
+
exports.listBackendModuleChoices = listBackendModuleChoices;
|
|
43
62
|
exports.loadTemplateManifest = loadTemplateManifest;
|
|
44
63
|
|
|
45
64
|
//# sourceMappingURL=template.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.cjs","names":["path","fs"],"sources":["../../src/template.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type { TemplateFeatureManifest, TemplateFilePolicy } from \"./types\";\r\n\r\nexport function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest {\r\n const manifestPath = path.join(templateDirectory, \"template.manifest.json\");\r\n if (!fs.existsSync(manifestPath)) {\r\n throw new Error(`Template manifest is missing: ${manifestPath}`);\r\n }\r\n return JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as TemplateFeatureManifest;\r\n}\r\n\r\nfunction globToRegExp(pattern: string): RegExp {\r\n let source = \"^\";\r\n for (let index = 0; index < pattern.length; index += 1) {\r\n const character = pattern[index];\r\n if (character === \"*\" && pattern[index + 1] === \"*\") {\r\n source += \".*\";\r\n index += 1;\r\n } else if (character === \"*\") {\r\n source += \"[^/]*\";\r\n } else {\r\n source += character.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n }\r\n }\r\n return new RegExp(`${source}$`);\r\n}\r\n\r\nexport function getTemplateFilePolicy(\r\n manifest: TemplateFeatureManifest,\r\n relativePath: string\r\n): TemplateFilePolicy {\r\n for (const rule of manifest.sync.rules) {\r\n if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;\r\n }\r\n return manifest.sync.defaultPolicy;\r\n}\r\n\r\nexport function getEnabledFeatures(\r\n platform: \"web\" | \"expo\" | \"both\"
|
|
1
|
+
{"version":3,"file":"template.cjs","names":["path","fs"],"sources":["../../src/template.ts"],"sourcesContent":["import fs from \"node:fs\";\r\nimport path from \"node:path\";\r\nimport type { TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from \"./types\";\r\n\r\nexport function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest {\r\n const manifestPath = path.join(templateDirectory, \"template.manifest.json\");\r\n if (!fs.existsSync(manifestPath)) {\r\n throw new Error(`Template manifest is missing: ${manifestPath}`);\r\n }\r\n return JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as TemplateFeatureManifest;\r\n}\r\n\r\nfunction globToRegExp(pattern: string): RegExp {\r\n let source = \"^\";\r\n for (let index = 0; index < pattern.length; index += 1) {\r\n const character = pattern[index];\r\n if (character === \"*\" && pattern[index + 1] === \"*\") {\r\n source += \".*\";\r\n index += 1;\r\n } else if (character === \"*\") {\r\n source += \"[^/]*\";\r\n } else {\r\n source += character.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\r\n }\r\n }\r\n return new RegExp(`${source}$`);\r\n}\r\n\r\nexport function getTemplateFilePolicy(\r\n manifest: TemplateFeatureManifest,\r\n relativePath: string\r\n): TemplateFilePolicy {\r\n for (const rule of manifest.sync.rules) {\r\n if (globToRegExp(rule.pattern).test(relativePath)) return rule.policy;\r\n }\r\n return manifest.sync.defaultPolicy;\r\n}\r\n\r\nexport interface BackendModuleChoice {\r\n id: string;\r\n label: string;\r\n experimental: boolean;\r\n}\r\n\r\nexport function getFeatureKind(options: {\r\n id: string;\r\n config: TemplateFeatureManifest[\"features\"][string];\r\n}): TemplateFeatureKind {\r\n if (options.config.kind) return options.config.kind;\r\n if (options.id === \"webapp\" || options.id === \"expo\") return \"platform\";\r\n if (options.id === \"test-harness\") return \"harness\";\r\n return \"module\";\r\n}\r\n\r\nexport function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[] {\r\n return Object.entries(manifest.features)\r\n .filter(([id, config]) => getFeatureKind({ id, config }) === \"module\")\r\n .map(([id, config]) => ({\r\n id,\r\n label: config.label ?? id,\r\n experimental: Boolean(config.experimental),\r\n }));\r\n}\r\n\r\nexport function getEnabledFeatures(options: {\r\n platform: \"web\" | \"expo\" | \"both\";\r\n testHarness: boolean;\r\n modules?: readonly string[];\r\n}): Set<string> {\r\n const enabled = new Set<string>();\r\n if (options.platform !== \"expo\") enabled.add(\"webapp\");\r\n if (options.platform !== \"web\") enabled.add(\"expo\");\r\n if (options.testHarness) enabled.add(\"test-harness\");\r\n for (const id of options.modules ?? []) enabled.add(id);\r\n return enabled;\r\n}\r\n\r\nexport function getExcludedFeaturePaths(\r\n manifest: TemplateFeatureManifest,\r\n enabledFeatures: ReadonlySet<string>\r\n): string[] {\r\n return Object.entries(manifest.features)\r\n .filter(([feature]) => !enabledFeatures.has(feature))\r\n .flatMap(([, config]) => [...config.paths]);\r\n}\r\n"],"mappings":";;;;;;;AAIA,SAAgB,qBAAqB,mBAAoD;CACvF,MAAM,eAAeA,UAAAA,QAAK,KAAK,mBAAmB,yBAAyB;AAC3E,KAAI,CAACC,QAAAA,QAAG,WAAW,aAAa,CAC9B,OAAM,IAAI,MAAM,iCAAiC,eAAe;AAElE,QAAO,KAAK,MAAMA,QAAAA,QAAG,aAAa,cAAc,OAAO,CAAC;;AAG1D,SAAS,aAAa,SAAyB;CAC7C,IAAI,SAAS;AACb,MAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,YAAY,QAAQ;AAC1B,MAAI,cAAc,OAAO,QAAQ,QAAQ,OAAO,KAAK;AACnD,aAAU;AACV,YAAS;aACA,cAAc,IACvB,WAAU;MAEV,WAAU,UAAU,QAAQ,uBAAuB,OAAO;;AAG9D,QAAO,IAAI,OAAO,GAAG,OAAO,GAAG;;AAGjC,SAAgB,sBACd,UACA,cACoB;AACpB,MAAK,MAAM,QAAQ,SAAS,KAAK,MAC/B,KAAI,aAAa,KAAK,QAAQ,CAAC,KAAK,aAAa,CAAE,QAAO,KAAK;AAEjE,QAAO,SAAS,KAAK;;AASvB,SAAgB,eAAe,SAGP;AACtB,KAAI,QAAQ,OAAO,KAAM,QAAO,QAAQ,OAAO;AAC/C,KAAI,QAAQ,OAAO,YAAY,QAAQ,OAAO,OAAQ,QAAO;AAC7D,KAAI,QAAQ,OAAO,eAAgB,QAAO;AAC1C,QAAO;;AAGT,SAAgB,yBAAyB,UAA0D;AACjG,QAAO,OAAO,QAAQ,SAAS,SAAS,CACrC,QAAQ,CAAC,IAAI,YAAY,eAAe;EAAE;EAAI;EAAQ,CAAC,KAAK,SAAS,CACrE,KAAK,CAAC,IAAI,aAAa;EACtB;EACA,OAAO,OAAO,SAAS;EACvB,cAAc,QAAQ,OAAO,aAAa;EAC3C,EAAE;;AAGP,SAAgB,mBAAmB,SAInB;CACd,MAAM,0BAAU,IAAI,KAAa;AACjC,KAAI,QAAQ,aAAa,OAAQ,SAAQ,IAAI,SAAS;AACtD,KAAI,QAAQ,aAAa,MAAO,SAAQ,IAAI,OAAO;AACnD,KAAI,QAAQ,YAAa,SAAQ,IAAI,eAAe;AACpD,MAAK,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAE,SAAQ,IAAI,GAAG;AACvD,QAAO;;AAGT,SAAgB,wBACd,UACA,iBACU;AACV,QAAO,OAAO,QAAQ,SAAS,SAAS,CACrC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,IAAI,QAAQ,CAAC,CACpD,SAAS,GAAG,YAAY,CAAC,GAAG,OAAO,MAAM,CAAC"}
|
package/dist/src/template.d.cts
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import { TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
|
|
1
|
+
import { TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy } from "./types.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/template.d.ts
|
|
4
4
|
declare function loadTemplateManifest(templateDirectory: string): TemplateFeatureManifest;
|
|
5
5
|
declare function getTemplateFilePolicy(manifest: TemplateFeatureManifest, relativePath: string): TemplateFilePolicy;
|
|
6
|
-
|
|
6
|
+
interface BackendModuleChoice {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
experimental: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function getFeatureKind(options: {
|
|
12
|
+
id: string;
|
|
13
|
+
config: TemplateFeatureManifest["features"][string];
|
|
14
|
+
}): TemplateFeatureKind;
|
|
15
|
+
declare function listBackendModuleChoices(manifest: TemplateFeatureManifest): BackendModuleChoice[];
|
|
16
|
+
declare function getEnabledFeatures(options: {
|
|
17
|
+
platform: "web" | "expo" | "both";
|
|
18
|
+
testHarness: boolean;
|
|
19
|
+
modules?: readonly string[];
|
|
20
|
+
}): Set<string>;
|
|
7
21
|
declare function getExcludedFeaturePaths(manifest: TemplateFeatureManifest, enabledFeatures: ReadonlySet<string>): string[];
|
|
8
22
|
//#endregion
|
|
9
|
-
export { getEnabledFeatures, getExcludedFeaturePaths, getTemplateFilePolicy, loadTemplateManifest };
|
|
23
|
+
export { BackendModuleChoice, getEnabledFeatures, getExcludedFeaturePaths, getFeatureKind, getTemplateFilePolicy, listBackendModuleChoices, loadTemplateManifest };
|
|
10
24
|
//# sourceMappingURL=template.d.cts.map
|
package/dist/src/types.d.cts
CHANGED
|
@@ -6,6 +6,8 @@ interface PromptValues {
|
|
|
6
6
|
appDescription?: string;
|
|
7
7
|
platform?: AppPlatform;
|
|
8
8
|
testHarness?: boolean;
|
|
9
|
+
/** Optional Backend Module feature ids (billing, workflows, …). `--yes` defaults to none. */
|
|
10
|
+
modules?: string[];
|
|
9
11
|
}
|
|
10
12
|
interface CreateCommandOptions extends PromptValues {
|
|
11
13
|
yes: boolean;
|
|
@@ -20,11 +22,16 @@ interface TemplateContext {
|
|
|
20
22
|
packageScope: string;
|
|
21
23
|
betterAuthSecret: string;
|
|
22
24
|
}
|
|
25
|
+
type TemplateFeatureKind = "platform" | "harness" | "module";
|
|
26
|
+
interface TemplateFeatureConfig {
|
|
27
|
+
paths: readonly string[];
|
|
28
|
+
kind?: TemplateFeatureKind;
|
|
29
|
+
experimental?: boolean;
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
23
32
|
interface TemplateFeatureManifest {
|
|
24
33
|
schemaVersion: number;
|
|
25
|
-
features: Record<string,
|
|
26
|
-
paths: readonly string[];
|
|
27
|
-
}>;
|
|
34
|
+
features: Record<string, TemplateFeatureConfig>;
|
|
28
35
|
requiredPaths: readonly string[];
|
|
29
36
|
sync: {
|
|
30
37
|
defaultPolicy: TemplateFilePolicy;
|
|
@@ -45,5 +52,5 @@ interface RenderedTemplateFile {
|
|
|
45
52
|
relativePath: string;
|
|
46
53
|
}
|
|
47
54
|
//#endregion
|
|
48
|
-
export { AppPlatform, CreateCommandOptions, PromptValues, RenderedTemplateFile, TemplateContext, TemplateFeatureManifest, TemplateFilePolicy };
|
|
55
|
+
export { AppPlatform, CreateCommandOptions, PromptValues, RenderedTemplateFile, TemplateContext, TemplateFeatureConfig, TemplateFeatureKind, TemplateFeatureManifest, TemplateFilePolicy };
|
|
49
56
|
//# sourceMappingURL=types.d.cts.map
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ database schema first. Services, repositories, and tRPC files import the DTO
|
|
|
13
13
|
## The pattern
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
import { createZodSchemas } from "@m5kdev/backend/
|
|
16
|
+
import { createZodSchemas } from "@m5kdev/backend/base/base.dto";
|
|
17
17
|
import {
|
|
18
18
|
postCreateInputSchema,
|
|
19
19
|
postUpdateInputSchema,
|
|
@@ -22,10 +22,10 @@ apps/<app>/server/src/modules/<module>/<module>.grants.ts
|
|
|
22
22
|
|
|
23
23
|
## Grant Structure
|
|
24
24
|
|
|
25
|
-
App modules use flat `ResourceGrant[]` arrays imported from `@m5kdev/backend/
|
|
25
|
+
App modules use flat `ResourceGrant[]` arrays imported from `@m5kdev/backend/base/base.grants`:
|
|
26
26
|
|
|
27
27
|
```typescript
|
|
28
|
-
import type { ResourceGrant } from "@m5kdev/backend/
|
|
28
|
+
import type { ResourceGrant } from "@m5kdev/backend/base/base.grants";
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Each `ResourceGrant` has four fields:
|
|
@@ -47,7 +47,7 @@ Each `ResourceGrant` has four fields:
|
|
|
47
47
|
## Example
|
|
48
48
|
|
|
49
49
|
```typescript
|
|
50
|
-
import type { ResourceGrant } from "@m5kdev/backend/
|
|
50
|
+
import type { ResourceGrant } from "@m5kdev/backend/base/base.grants";
|
|
51
51
|
|
|
52
52
|
export const postsGrants: ResourceGrant[] = [
|
|
53
53
|
{
|
|
@@ -15,13 +15,14 @@ apps/<app>/server/src/modules/<module>/<module>.module.ts
|
|
|
15
15
|
|
|
16
16
|
## Conventions
|
|
17
17
|
|
|
18
|
-
- Extend `BaseModule<Deps, Tables, Repositories, Services, Routers>` from `@m5kdev/backend/
|
|
18
|
+
- Extend `BaseModule<Deps, Tables, Repositories, Services, Routers>` from `@m5kdev/backend/base/base.module`.
|
|
19
19
|
- Declare local type aliases for each generic slot (`*ModuleDeps`, `*ModuleTables`, `*ModuleRepositories`, `*ModuleServices`, `*ModuleRouters`).
|
|
20
20
|
- Set `readonly id` to match the module directory name (e.g. `readonly id = "posts"`).
|
|
21
21
|
- Declare `dbDependsOn` or `dependsOn` as a `readonly` tuple with `as const`. App modules that reference auth tables typically use `dbDependsOn: ["auth"]`.
|
|
22
22
|
- Override only the hooks the module actually needs; omit the rest.
|
|
23
23
|
- Keep hook bodies as pure wiring — no business logic.
|
|
24
24
|
- Use `createBackendRouterMap(namespace, router)` from `@m5kdev/backend/app` in the `trpc()` hook.
|
|
25
|
+
- Register the module with `createBackendApp(config, [modules])`. Do not use `backendApp.use`.
|
|
25
26
|
- Export a singleton instance at the bottom of the file.
|
|
26
27
|
|
|
27
28
|
## Generic Parameters
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
type ModuleRepositoriesContext,
|
|
61
62
|
type ModuleServicesContext,
|
|
62
63
|
type ModuleTRPCContext,
|
|
63
|
-
} from "@m5kdev/backend/
|
|
64
|
+
} from "@m5kdev/backend/base/base.module";
|
|
64
65
|
import type * as postsTables from "./posts.db";
|
|
65
66
|
import { postsGrants } from "./posts.grants";
|
|
66
67
|
import { PostsRepository } from "./posts.repository";
|
|
@@ -298,7 +298,7 @@ readonly validateCode = this.procedure("validateCode")
|
|
|
298
298
|
## Example Skeleton
|
|
299
299
|
|
|
300
300
|
```typescript
|
|
301
|
-
import { BasePermissionService } from "@m5kdev/backend/
|
|
301
|
+
import { BasePermissionService } from "@m5kdev/backend/base/base.service";
|
|
302
302
|
import { postSchemas } from "./posts.dto";
|
|
303
303
|
import type { PostsRepository } from "./posts.repository";
|
|
304
304
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules
|
|
3
|
+
**/node_modules
|
|
4
|
+
.pnpm-store
|
|
5
|
+
.pnp
|
|
6
|
+
.pnp.js
|
|
7
|
+
|
|
8
|
+
# Git
|
|
9
|
+
.git
|
|
10
|
+
|
|
11
|
+
# Local env files — never bake secrets into the image
|
|
12
|
+
.env*
|
|
13
|
+
|
|
14
|
+
# Testing
|
|
15
|
+
coverage
|
|
16
|
+
|
|
17
|
+
# Turbo
|
|
18
|
+
.turbo
|
|
19
|
+
|
|
20
|
+
# Build outputs
|
|
21
|
+
build
|
|
22
|
+
dist
|
|
23
|
+
|
|
24
|
+
# Debug
|
|
25
|
+
npm-debug.log*
|
|
26
|
+
yarn-debug.log*
|
|
27
|
+
yarn-error.log*
|
|
28
|
+
|
|
29
|
+
# Misc
|
|
30
|
+
.DS_Store
|
|
31
|
+
*.pem
|
|
@@ -8,20 +8,23 @@
|
|
|
8
8
|
|
|
9
9
|
## Repository Structure
|
|
10
10
|
|
|
11
|
-
- `apps/shared` holds shared Zod schemas and
|
|
11
|
+
- `apps/shared` holds shared Zod schemas, constants, and the product Deploy home.
|
|
12
12
|
- `apps/server` holds the backend composition root, modules, auth wiring, and database scripts.
|
|
13
13
|
- `apps/webapp` holds the React app shell, routes, providers, and the `posts` feature.
|
|
14
|
+
- `apps/landing` holds the public one-page site and its Fly adapter.
|
|
14
15
|
- `apps/email` holds the email templates used by auth flows and local email delivery.
|
|
15
16
|
|
|
16
|
-
## Backend Conventions
|
|
17
|
-
|
|
18
|
-
- Keep repository, service, and transport layers separate inside each module.
|
|
19
|
-
- Register modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- Do not
|
|
17
|
+
## Backend Conventions
|
|
18
|
+
|
|
19
|
+
- Keep repository, service, and transport layers separate inside each module.
|
|
20
|
+
- Register modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`.
|
|
21
|
+
- Import Kernel infrastructure (`BaseModule`, Grants, DTOs) from `@m5kdev/backend/base/*`. The `modules/base` path still re-exports.
|
|
22
|
+
- Clay, Docx, Pdf, Social, and Video are Optional Backend Module packages (`@m5kdev/module-<name>`). They are not scaffolded; add them only when the product needs them.
|
|
23
|
+
- Use `BaseModule` subclasses for app modules; the kernel wires repositories, services, and tRPC.
|
|
24
|
+
- Keep tRPC files focused on input/output wiring and delegate logic to services.
|
|
25
|
+
- Do not add service methods that merely call another service. Call the owning service directly unless the method adds a business rule, authorization, validation, orchestration, a transaction boundary, or meaningful domain translation.
|
|
26
|
+
- Renaming a dependency method, repackaging arguments, constructing a prompt, or forwarding actor/context data alone does not justify a service wrapper.
|
|
27
|
+
- Do not create Drizzle migrations by hand. Use the scaffolded config and your project migration workflow later if you need generated migrations.
|
|
25
28
|
|
|
26
29
|
## Frontend Conventions
|
|
27
30
|
|
|
@@ -1,131 +1,132 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
Guidance for Claude Code when working in this repository.
|
|
4
|
-
|
|
5
|
-
## About This App
|
|
6
|
-
|
|
7
|
-
**{{APP_NAME}}** — {{APP_DESCRIPTION}}
|
|
8
|
-
|
|
9
|
-
This is a pnpm + Turborepo monorepo built on the **m5kdev framework**:
|
|
10
|
-
|
|
11
|
-
- `@m5kdev/backend` — backend kernel: Express, Better Auth, Drizzle (LibSQL), and tRPC. Apps compose it via `createBackendApp(config, [modules])`.
|
|
12
|
-
- `@m5kdev/commons` — shared runtime utilities and contracts.
|
|
13
|
-
- `@m5kdev/config` — configuration loading and validation.
|
|
14
|
-
- `@m5kdev/email` — email rendering and local delivery used by auth flows.
|
|
15
|
-
// m5k:webapp:start
|
|
16
|
-
- `@m5kdev/frontend` — framework-level React providers (`AppConfigProvider`, `AppTrpcQueryProvider`) and hooks.
|
|
17
|
-
- `@m5kdev/web-ui` — web UI components built on HeroUI v3.
|
|
18
|
-
// m5k:webapp:end
|
|
19
|
-
|
|
20
|
-
The framework owns the plumbing (auth, database wiring, tRPC composition, permissions); app code supplies modules and screens that follow the conventions below.
|
|
21
|
-
|
|
22
|
-
## Workspace
|
|
23
|
-
|
|
24
|
-
- `apps/shared` — shared Zod schemas, constants, and the `.env` file every app reads.
|
|
25
|
-
- `apps/server` — backend composition root (`src/app.ts`), app modules (`src/modules/`), hand-registered table list (`src/schema.ts`), and Drizzle scripts.
|
|
26
|
-
// m5k:webapp:start
|
|
27
|
-
- `apps/webapp` — Vite + React 19 frontend: app shell, routes, providers, and feature modules.
|
|
28
|
-
// m5k:webapp:end
|
|
29
|
-
// m5k:expo:start
|
|
30
|
-
- `apps/expo` — Expo (React Native) app sharing the same server and shared contracts.
|
|
31
|
-
// m5k:expo:end
|
|
32
|
-
- `apps/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
pnpm
|
|
42
|
-
pnpm
|
|
43
|
-
pnpm lint
|
|
44
|
-
pnpm
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
pnpm --filter ./apps/server drizzle:
|
|
52
|
-
pnpm --filter ./apps/server drizzle:
|
|
53
|
-
pnpm --filter ./apps/server drizzle:
|
|
54
|
-
pnpm --filter ./apps/server drizzle:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
├─ <name>.
|
|
78
|
-
|
|
79
|
-
<name>.
|
|
80
|
-
<name>.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- `apps/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
| `.cursor/rules/module-
|
|
117
|
-
| `.cursor/rules/module-
|
|
118
|
-
| `.cursor/rules/module-
|
|
119
|
-
| `.cursor/rules/module-
|
|
120
|
-
| `.cursor/rules/module-
|
|
121
|
-
| `.cursor/rules/module-
|
|
122
|
-
| `.cursor/rules/module-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| `.cursor/rules/frontend-
|
|
126
|
-
| `.cursor/rules/frontend-
|
|
127
|
-
| `.cursor/rules/frontend-
|
|
128
|
-
| `.cursor/rules/frontend-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for Claude Code when working in this repository.
|
|
4
|
+
|
|
5
|
+
## About This App
|
|
6
|
+
|
|
7
|
+
**{{APP_NAME}}** — {{APP_DESCRIPTION}}
|
|
8
|
+
|
|
9
|
+
This is a pnpm + Turborepo monorepo built on the **m5kdev framework**:
|
|
10
|
+
|
|
11
|
+
- `@m5kdev/backend` — backend kernel: Express, Better Auth, Drizzle (LibSQL), and tRPC. Apps compose it via `createBackendApp(config, [modules])`.
|
|
12
|
+
- `@m5kdev/commons` — shared runtime utilities and contracts.
|
|
13
|
+
- `@m5kdev/config` — configuration loading and validation.
|
|
14
|
+
- `@m5kdev/email` — email rendering and local delivery used by auth flows.
|
|
15
|
+
// m5k:webapp:start
|
|
16
|
+
- `@m5kdev/frontend` — framework-level React providers (`AppConfigProvider`, `AppTrpcQueryProvider`) and hooks.
|
|
17
|
+
- `@m5kdev/web-ui` — web UI components built on HeroUI v3.
|
|
18
|
+
// m5k:webapp:end
|
|
19
|
+
|
|
20
|
+
The framework owns the plumbing (auth, database wiring, tRPC composition, permissions); app code supplies modules and screens that follow the conventions below.
|
|
21
|
+
|
|
22
|
+
## Workspace
|
|
23
|
+
|
|
24
|
+
- `apps/shared` — shared Zod schemas, constants, and the `.env` file every app reads.
|
|
25
|
+
- `apps/server` — backend composition root (`src/app.ts`), app modules (`src/modules/`), hand-registered table list (`src/schema.ts`), and Drizzle scripts.
|
|
26
|
+
// m5k:webapp:start
|
|
27
|
+
- `apps/webapp` — Vite + React 19 frontend: app shell, routes, providers, and feature modules.
|
|
28
|
+
// m5k:webapp:end
|
|
29
|
+
// m5k:expo:start
|
|
30
|
+
- `apps/expo` — Expo (React Native) app sharing the same server and shared contracts.
|
|
31
|
+
// m5k:expo:end
|
|
32
|
+
- `apps/landing` — public one-page site (app name, pitch, CTA) with its own Dockerfile and `fly.toml`.
|
|
33
|
+
- `apps/email` — email templates and the local delivery registry.
|
|
34
|
+
// m5k:test-harness:start
|
|
35
|
+
- `apps/e2e` — Playwright end-to-end tests; the server exposes a test-harness module for them.
|
|
36
|
+
// m5k:test-harness:end
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm dev # run all apps in dev (turbo)
|
|
42
|
+
pnpm build # build all apps
|
|
43
|
+
pnpm lint # biome check
|
|
44
|
+
pnpm lint:fix # biome check --write
|
|
45
|
+
pnpm check-types # tsc --noEmit everywhere
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Database (Drizzle, run from the repo root):
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pnpm --filter ./apps/server drizzle:generate # generate migrations from *.db.ts changes
|
|
52
|
+
pnpm --filter ./apps/server drizzle:migrate # apply migrations
|
|
53
|
+
pnpm --filter ./apps/server drizzle:seed # seed the local database
|
|
54
|
+
pnpm --filter ./apps/server drizzle:reset # reset the local database
|
|
55
|
+
pnpm --filter ./apps/server drizzle:studio # inspect the database
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
First-time setup: `pnpm install`, then `drizzle:migrate` and `drizzle:seed`, then `pnpm dev`.
|
|
59
|
+
// m5k:test-harness:start
|
|
60
|
+
|
|
61
|
+
End-to-end tests: `pnpm --filter ./apps/e2e test:e2e` (Playwright).
|
|
62
|
+
// m5k:test-harness:end
|
|
63
|
+
|
|
64
|
+
The local database is a LibSQL file; local auth emails are written to `apps/server/.emails`. Demo login: `admin@{{APP_SLUG}}.local` / `password1234`.
|
|
65
|
+
|
|
66
|
+
## Framework Architecture
|
|
67
|
+
|
|
68
|
+
### Backend: kernel + modules
|
|
69
|
+
|
|
70
|
+
The backend kernel orchestrates modules. Each app module is a `BaseModule` subclass registered in `apps/server/src/app.ts` via `createBackendApp(config, [modules])` — the kernel wires its repositories, services, and tRPC routers. Import Kernel infrastructure from `@m5kdev/backend/base/*`. Clay, Docx, Pdf, Social, and Video are Optional Backend Module packages (`@m5kdev/module-<name>`); they are not scaffolded.
|
|
71
|
+
|
|
72
|
+
A module lives in `apps/server/src/modules/<name>/` and follows a strict file anatomy where **the Drizzle table is the source of truth** and types flow one way:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
<name>.db.ts table definition (source of truth; register it in src/schema.ts)
|
|
76
|
+
└─ <name>.dto.ts Zod schemas derived from the table — the module's API surface
|
|
77
|
+
├─ <name>.repository.ts data access, consumes the DTO object
|
|
78
|
+
├─ <name>.service.ts business logic, consumes the DTO object
|
|
79
|
+
└─ <name>.trpc.ts thin router: attaches DTO schemas, delegates to the service
|
|
80
|
+
<name>.grants.ts permission grants for the module
|
|
81
|
+
<name>.module.ts composition unit wiring the above into one class
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Clients infer all row/list types from `AppRouter` (`inferRouterOutputs`) — never hand-mirror server types anywhere. Never write Drizzle migrations by hand; change `*.db.ts`, register new tables in `src/schema.ts`, then run `drizzle:generate` + `drizzle:migrate`.
|
|
85
|
+
|
|
86
|
+
Roles are defined in `apps/shared/src/modules/app/` and passed through both `createBackendApp` metadata and the frontend config provider; when adding roles, extend the module `*.grants.ts` files to match.
|
|
87
|
+
// m5k:webapp:start
|
|
88
|
+
|
|
89
|
+
### Frontend: fixed stack, hooks own the logic
|
|
90
|
+
|
|
91
|
+
The webapp stack is fixed — React 19, HeroUI v3, Tailwind CSS v4, react-router v7, `nuqs` for URL state, TanStack Query over tRPC, i18next. Do not introduce alternatives (no fetch/axios, no form libraries, no other UI kits).
|
|
92
|
+
|
|
93
|
+
- App shell shape: `NuqsAdapter` + `BrowserRouter` + `Providers`; global providers are composed in one place only.
|
|
94
|
+
- Components stay display-focused; data fetching, mutations, and effect chains live in custom hooks under `modules/<feature>/hooks/`, composed into one screen-level hook (e.g. `usePostsRoute`).
|
|
95
|
+
- All server data goes through `useTRPC()` + `queryOptions`/`mutationOptions`; invalidate with the matching `queryFilter()` after mutations.
|
|
96
|
+
- Forms are uncontrolled HeroUI `Form` components with native HTML validation; read values from `FormData` on submit.
|
|
97
|
+
- All user-facing copy goes through i18next; keys live in `apps/webapp/translations/`.
|
|
98
|
+
// m5k:webapp:end
|
|
99
|
+
|
|
100
|
+
### Reference implementation
|
|
101
|
+
|
|
102
|
+
The **posts** feature is the canonical example of every convention. When adding a feature, mirror its structure:
|
|
103
|
+
|
|
104
|
+
- `apps/shared/src/modules/posts/`
|
|
105
|
+
- `apps/server/src/modules/posts/`
|
|
106
|
+
// m5k:webapp:start
|
|
107
|
+
- `apps/webapp/src/modules/posts/`
|
|
108
|
+
// m5k:webapp:end
|
|
109
|
+
|
|
110
|
+
## Detailed Style Guides
|
|
111
|
+
|
|
112
|
+
The framework's detailed conventions live in `.cursor/rules/*.mdc`. Cursor applies them by glob automatically; Claude does not — so **before creating or editing a file that matches a pattern below, read the matching guide first**. They describe how the framework is meant to be used, with correct/incorrect examples.
|
|
113
|
+
|
|
114
|
+
| Guide | Read before touching |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `.cursor/rules/module-db-guide.mdc` | `apps/server/src/modules/**/*.db.ts` |
|
|
117
|
+
| `.cursor/rules/module-dto-guide.mdc` | `apps/server/src/modules/**/*.dto.ts` |
|
|
118
|
+
| `.cursor/rules/module-repository-guide.mdc` | `apps/server/src/modules/**/*.repository.ts` |
|
|
119
|
+
| `.cursor/rules/module-service-guide.mdc` | `apps/server/src/modules/**/*.service.ts` |
|
|
120
|
+
| `.cursor/rules/module-trpc-guide.mdc` | `apps/server/src/modules/**/*.trpc.ts` |
|
|
121
|
+
| `.cursor/rules/module-grants-guide.mdc` | `apps/server/src/modules/**/*.grants.ts` |
|
|
122
|
+
| `.cursor/rules/module-module-guide.mdc` | `apps/server/src/modules/**/*.module.ts` |
|
|
123
|
+
| `.cursor/rules/module-schema-guide.mdc` | `apps/shared/src/modules/**/*.schema.ts` |
|
|
124
|
+
// m5k:webapp:start
|
|
125
|
+
| `.cursor/rules/frontend-component-guide.mdc` | webapp `*.tsx` components |
|
|
126
|
+
| `.cursor/rules/frontend-hook-guide.mdc` | webapp `hooks/**` |
|
|
127
|
+
| `.cursor/rules/frontend-data-guide.mdc` | webapp data fetching / tRPC usage |
|
|
128
|
+
| `.cursor/rules/frontend-form-guide.mdc` | webapp forms |
|
|
129
|
+
| `.cursor/rules/frontend-i18n-guide.mdc` | webapp user-facing copy |
|
|
130
|
+
// m5k:webapp:end
|
|
131
|
+
|
|
132
|
+
`AGENTS.md` (repo root and per-app) carries a compact summary of the same conventions for other agents; this file plus the guides above are the authoritative version for Claude.
|