create-m5kdev 0.40.3 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/package.json +1 -1
  2. package/templates/minimal-app/.cursor/rules/frontend-component-guide.mdc +95 -95
  3. package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +103 -103
  4. package/templates/minimal-app/.cursor/rules/frontend-form-guide.mdc +80 -80
  5. package/templates/minimal-app/.cursor/rules/frontend-hook-guide.mdc +79 -79
  6. package/templates/minimal-app/.cursor/rules/frontend-i18n-guide.mdc +45 -45
  7. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +60 -60
  8. package/templates/minimal-app/.cursor/rules/module-prompt-guide.mdc +214 -214
  9. package/templates/minimal-app/.cursor/rules/module-repository-guide.mdc +167 -167
  10. package/templates/minimal-app/.cursor/rules/module-schema-guide.mdc +53 -53
  11. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +367 -367
  12. package/templates/minimal-app/.cursor/rules/module-trpc-guide.mdc +71 -71
  13. package/templates/minimal-app/apps/.gitignore.tpl +13 -13
  14. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  15. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +11 -22
  16. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  17. package/templates/minimal-app/apps/expo/.gitignore.tpl +44 -44
  18. package/templates/minimal-app/apps/expo/.prettierignore +13 -13
  19. package/templates/minimal-app/apps/expo/.prettierrc.json.tpl +9 -9
  20. package/templates/minimal-app/apps/expo/CHANGELOG.md.tpl +156 -156
  21. package/templates/minimal-app/apps/expo/README.md.tpl +44 -44
  22. package/templates/minimal-app/apps/expo/package.json.tpl +63 -63
  23. package/templates/minimal-app/apps/landing/package.json.tpl +34 -34
  24. package/templates/minimal-app/apps/server/drizzle/0000_whole_echo.sql +366 -366
  25. package/templates/minimal-app/apps/server/drizzle/0002_crazy_lilith.sql +46 -46
  26. package/templates/minimal-app/apps/server/drizzle/0003_freezing_fantastic_four.sql +3 -3
  27. package/templates/minimal-app/apps/server/drizzle/0004_slow_ezekiel_stane.sql +98 -98
  28. package/templates/minimal-app/apps/server/drizzle/0005_wonderful_the_professor.sql +1 -1
  29. package/templates/minimal-app/apps/server/drizzle/0006_lazy_blue_marvel.sql +158 -158
  30. package/templates/minimal-app/apps/server/drizzle/0007_abnormal_ultimo.sql +1 -1
  31. package/templates/minimal-app/apps/server/drizzle/0008_steady_nekra.sql +86 -86
  32. package/templates/minimal-app/apps/server/drizzle/0009_empty_starjammers.sql +45 -0
  33. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +2624 -2624
  34. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +2670 -2670
  35. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +2715 -2715
  36. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2687 -2687
  37. package/templates/minimal-app/apps/server/drizzle/meta/0004_snapshot.json.tpl +2497 -2497
  38. package/templates/minimal-app/apps/server/drizzle/meta/0005_snapshot.json.tpl +2483 -2483
  39. package/templates/minimal-app/apps/server/drizzle/meta/0006_snapshot.json.tpl +3602 -3602
  40. package/templates/minimal-app/apps/server/drizzle/meta/0007_snapshot.json.tpl +3619 -3619
  41. package/templates/minimal-app/apps/server/drizzle/meta/0008_snapshot.json.tpl +3619 -3619
  42. package/templates/minimal-app/apps/server/drizzle/meta/0009_snapshot.json.tpl +3687 -0
  43. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
  44. package/templates/minimal-app/apps/server/package.json.tpl +57 -57
  45. package/templates/minimal-app/apps/shared/package.json.tpl +27 -27
  46. package/templates/minimal-app/apps/webapp/AGENTS.md.tpl +24 -24
  47. package/templates/minimal-app/apps/webapp/index.html.tpl +22 -22
  48. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  49. package/templates/minimal-app/apps/webapp/public/logo.svg.tpl +6 -6
  50. package/templates/minimal-app/pnpm-workspace.yaml.tpl +15 -15
  51. package/templates/minimal-app/apps/expo/expo-env.d.ts.tpl +0 -3
  52. package/templates/minimal-app/apps/expo/uniwind-types.d.ts.tpl +0 -10
@@ -1,71 +1,71 @@
1
- ---
2
- globs: apps/**/server/src/modules/**/*.trpc.ts
3
- alwaysApply: false
4
- ---
5
-
6
- # Module tRPC File Style Guide
7
-
8
- Conventions for tRPC router files in app modules. Routers are thin wiring:
9
- they attach the module DTO schemas and delegate to the service. The router is
10
- also the type source for the client — the webapp infers all row/list types
11
- from `AppRouter`, so accurate `.input()`/`.output()` here is what makes
12
- end-to-end types work.
13
-
14
- ## File Location
15
-
16
- ```
17
- apps/<app>/server/src/modules/<module>/<module>.trpc.ts
18
- ```
19
-
20
- ## Conventions
21
-
22
- - Export a factory function named `create<Module>TRPC`.
23
- - Import the module DTO object (`postSchemas`) from `./<module>.dto` — not
24
- shared schemas, not hand-written types.
25
- - Use `handleTRPCResult` from `@m5kdev/backend/utils/trpc` to unwrap `Result`s.
26
- - Keep resolvers one-liners that delegate to the service; no business logic or
27
- DB access here.
28
- - Always pass `ctx` to service methods as the second argument. The first
29
- argument (`input`) can be `undefined` for optional-input queries.
30
-
31
- ## Factory Pattern
32
-
33
- ```typescript
34
- import { handleTRPCResult, type TRPCMethods } from "@m5kdev/backend/utils/trpc";
35
- import { postSchemas } from "./posts.dto";
36
- import type { PostsService } from "./posts.service";
37
-
38
- export function createPostsTRPC(
39
- { router, privateProcedure: procedure }: TRPCMethods,
40
- postsService: PostsService
41
- ) {
42
- return router({
43
- list: procedure
44
- .input(postSchemas.input.list)
45
- .output(postSchemas.output.list)
46
- .query(async ({ ctx, input }) => handleTRPCResult(await postsService.list(input ?? {}, ctx))),
47
-
48
- create: procedure
49
- .input(postSchemas.input.create)
50
- .output(postSchemas.output.single)
51
- .mutation(async ({ ctx, input }) => handleTRPCResult(await postsService.create(input, ctx))),
52
-
53
- softDelete: procedure
54
- .input(postSchemas.input.delete)
55
- .output(postSchemas.output.uuid)
56
- .mutation(async ({ ctx, input }) =>
57
- handleTRPCResult(await postsService.softDelete(input, ctx))
58
- ),
59
- });
60
- }
61
- ```
62
-
63
- ## Procedure Selection
64
-
65
- - `privateProcedure` — authenticated user (most module routes).
66
- - `organizationProcedure` — requires an active organization.
67
- - `adminProcedure` — admin role.
68
- - `publicProcedure` — unauthenticated; use sparingly.
69
-
70
- The service's own `.requireAuth()`/`.access()` still run — the tRPC procedure
71
- choice is transport-level gating, not a replacement for service checks.
1
+ ---
2
+ globs: apps/**/server/src/modules/**/*.trpc.ts
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Module tRPC File Style Guide
7
+
8
+ Conventions for tRPC router files in app modules. Routers are thin wiring:
9
+ they attach the module DTO schemas and delegate to the service. The router is
10
+ also the type source for the client — the webapp infers all row/list types
11
+ from `AppRouter`, so accurate `.input()`/`.output()` here is what makes
12
+ end-to-end types work.
13
+
14
+ ## File Location
15
+
16
+ ```
17
+ apps/<app>/server/src/modules/<module>/<module>.trpc.ts
18
+ ```
19
+
20
+ ## Conventions
21
+
22
+ - Export a factory function named `create<Module>TRPC`.
23
+ - Import the module DTO object (`postSchemas`) from `./<module>.dto` — not
24
+ shared schemas, not hand-written types.
25
+ - Use `handleTRPCResult` from `@m5kdev/backend/utils/trpc` to unwrap `Result`s.
26
+ - Keep resolvers one-liners that delegate to the service; no business logic or
27
+ DB access here.
28
+ - Always pass `ctx` to service methods as the second argument. The first
29
+ argument (`input`) can be `undefined` for optional-input queries.
30
+
31
+ ## Factory Pattern
32
+
33
+ ```typescript
34
+ import { handleTRPCResult, type TRPCMethods } from "@m5kdev/backend/utils/trpc";
35
+ import { postSchemas } from "./posts.dto";
36
+ import type { PostsService } from "./posts.service";
37
+
38
+ export function createPostsTRPC(
39
+ { router, privateProcedure: procedure }: TRPCMethods,
40
+ postsService: PostsService
41
+ ) {
42
+ return router({
43
+ list: procedure
44
+ .input(postSchemas.input.list)
45
+ .output(postSchemas.output.list)
46
+ .query(async ({ ctx, input }) => handleTRPCResult(await postsService.list(input ?? {}, ctx))),
47
+
48
+ create: procedure
49
+ .input(postSchemas.input.create)
50
+ .output(postSchemas.output.single)
51
+ .mutation(async ({ ctx, input }) => handleTRPCResult(await postsService.create(input, ctx))),
52
+
53
+ softDelete: procedure
54
+ .input(postSchemas.input.delete)
55
+ .output(postSchemas.output.uuid)
56
+ .mutation(async ({ ctx, input }) =>
57
+ handleTRPCResult(await postsService.softDelete(input, ctx))
58
+ ),
59
+ });
60
+ }
61
+ ```
62
+
63
+ ## Procedure Selection
64
+
65
+ - `privateProcedure` — authenticated user (most module routes).
66
+ - `organizationProcedure` — requires an active organization.
67
+ - `adminProcedure` — admin role.
68
+ - `publicProcedure` — unauthenticated; use sparingly.
69
+
70
+ The service's own `.requireAuth()`/`.access()` still run — the tRPC procedure
71
+ choice is transport-level gating, not a replacement for service checks.
@@ -1,13 +1,13 @@
1
- # runtime artifacts
2
- server/local.db*
3
- server/.e2e/
4
- server/.emails/
5
- server/uploads/
6
-
7
- # e2e output
8
- e2e/test-results/
9
- e2e/playwright-report/
10
-
11
- # expo
12
- expo/.expo/
13
- expo/dist/
1
+ # runtime artifacts
2
+ server/local.db*
3
+ server/.e2e/
4
+ server/.emails/
5
+ server/uploads/
6
+
7
+ # e2e output
8
+ e2e/test-results/
9
+ e2e/playwright-report/
10
+
11
+ # expo
12
+ expo/.expo/
13
+ expo/dist/
@@ -1,23 +1,23 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/e2e",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "build": "tsc --noEmit",
7
- "lint": "biome check .",
8
- "lint:fix": "biome check . --write",
9
- "check-types": "tsc --noEmit",
10
- "test:e2e": "playwright test",
11
- "test:e2e:expo": "playwright test --config playwright.expo.config.ts --headed"
12
- },
13
- "devDependencies": {
14
- "@m5kdev/backend": "catalog:m5kdev",
15
- "@m5kdev/commons": "catalog:m5kdev",
16
- "@m5kdev/config": "catalog:m5kdev",
17
- "@playwright/test": "catalog:m5kdev",
18
- "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
- "@trpc/client": "catalog:m5kdev",
20
- "@types/node": "catalog:m5kdev",
21
- "typescript": "catalog:m5kdev"
22
- }
23
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/e2e",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "tsc --noEmit",
7
+ "lint": "biome check .",
8
+ "lint:fix": "biome check . --write",
9
+ "check-types": "tsc --noEmit",
10
+ "test:e2e": "playwright test",
11
+ "test:e2e:expo": "playwright test --config playwright.expo.config.ts --headed"
12
+ },
13
+ "devDependencies": {
14
+ "@m5kdev/backend": "catalog:m5kdev",
15
+ "@m5kdev/commons": "catalog:m5kdev",
16
+ "@m5kdev/config": "catalog:m5kdev",
17
+ "@playwright/test": "catalog:m5kdev",
18
+ "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
+ "@trpc/client": "catalog:m5kdev",
20
+ "@types/node": "catalog:m5kdev",
21
+ "typescript": "catalog:m5kdev"
22
+ }
23
+ }
@@ -58,7 +58,7 @@ async function saveMemberEdit(page: Page, row: Locator, changes: { name?: string
58
58
  await dialog.getByLabel(/^name$/i).fill(changes.name);
59
59
  }
60
60
  if (changes.role !== undefined) {
61
- await dialog.getByRole("button", { name: /^role$/i }).click();
61
+ await dialog.getByRole("button", { name: /role/i }).click();
62
62
  await page.getByRole("option", { name: new RegExp(`^${changes.role}$`) }).click();
63
63
  }
64
64
 
@@ -211,7 +211,10 @@ test("duplicate signup with an existing email is rejected", async ({ page, reque
211
211
  expect(
212
212
  response.ok(),
213
213
  `${response.status()} ${response.statusText()}: ${await response.text()}`
214
- ).toBe(false);
214
+ ).toBe(true);
215
+ expect(await response.json()).toMatchObject({ token: null });
216
+ const state = await getUserState(request, profile, email);
217
+ expect(state.user.emailVerified).toBe(true);
215
218
  });
216
219
 
217
220
  test("organization invite email lets a new user join without creating a separate org", async ({
@@ -357,7 +360,7 @@ test("organization members page lets an admin edit a member name", async ({ page
357
360
  expect(afterName?.id).toBe(invitedMemberId);
358
361
  expect(afterName?.name).toBe("Kitchen lead");
359
362
  expect(afterName?.userId).toBeNull();
360
- await expect(invitedRow.getByRole("cell").first()).toHaveText("Kitchen lead");
363
+ await expect(page.getByRole("row").filter({ hasText: "Kitchen lead" })).toBeVisible();
361
364
  });
362
365
 
363
366
  test("organization members page resends, updates, cancels, and revives an invited seat", async ({
@@ -491,9 +494,11 @@ test("admin creates, bans, unbans, and authenticates a user", async ({ page, req
491
494
  await dialog.getByPlaceholder(/enter user's email/i).fill(email);
492
495
  await dialog.getByPlaceholder(/enter password/i).fill(password);
493
496
  await dialog.getByRole("button", { name: /^create user$/i }).click();
497
+ await expect(dialog).toBeHidden();
494
498
 
499
+ await page.locator('input[name="search"]').fill(email);
495
500
  const createdRow = page.getByRole("row").filter({ hasText: email });
496
- await expect(createdRow).toBeVisible();
501
+ await expect(createdRow).toBeVisible({ timeout: 10_000 });
497
502
  await expect(createdRow.getByText(/^verified$/i)).toBeVisible();
498
503
  await createdRow.getByLabel("User actions").click();
499
504
  await page.getByRole("menuitem", { name: /^ban$/i }).click();
@@ -529,21 +534,6 @@ test("admin creates, bans, unbans, and authenticates a user", async ({ page, req
529
534
  .toBe("admin");
530
535
 
531
536
  await logout(page);
532
- const verificationResponse = await request.post(
533
- `${profiles.standard.serverUrl}/api/auth/send-verification-email`,
534
- {
535
- data: {
536
- email,
537
- callbackURL: `${profiles.standard.webUrl}/login`,
538
- },
539
- }
540
- );
541
- expect(verificationResponse.ok()).toBe(true);
542
- const verification = await latestEmail(request, profile, {
543
- to: email,
544
- templateId: "verification",
545
- });
546
- await request.get(emailUrl(verification));
547
537
  await login(page, email, password);
548
538
  const adminListResponse = await authFetch(page, profile, "/api/auth/admin/list-users");
549
539
  expect(
@@ -561,6 +551,7 @@ test("admin verifies email, changes user role, and sets password from users list
561
551
  const newPassword = "password5678";
562
552
 
563
553
  await signUp(page, email, password);
554
+ await expect(page.getByText(/verification email sent/i)).toBeVisible();
564
555
  const before = await getUserState(request, profile, email);
565
556
  expect(before.user.emailVerified).toBe(false);
566
557
  expect(before.user.role).toBe("user");
@@ -603,9 +594,7 @@ test("admin verifies email, changes user role, and sets password from users list
603
594
  await page.locator('input[name="search"]').fill(profiles.standard.adminEmail);
604
595
  const adminRow = page.getByRole("row").filter({ hasText: profiles.standard.adminEmail });
605
596
  await expect(adminRow).toBeVisible();
606
- await expect(
607
- adminRow.getByRole("button", { name: `Role for ${profiles.standard.adminEmail}` })
608
- ).toBeDisabled();
597
+ await expect(adminRow.locator('[data-slot="select-trigger"]')).toBeDisabled();
609
598
 
610
599
  await logout(page);
611
600
  await login(page, email, newPassword);
@@ -1,34 +1,34 @@
1
- {
2
- "name": "{{PACKAGE_SCOPE}}/email",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "build": "tsc --noEmit",
7
- "lint": "biome check .",
8
- "lint:fix": "biome check . --write",
9
- "check-types": "tsc --noEmit",
10
- "email:dev": "react-email dev --dir src/emails"
11
- },
12
- "dependencies": {
13
- "@m5kdev/commons": "catalog:m5kdev",
14
- "@m5kdev/email": "catalog:m5kdev",
15
- "@react-email/components": "catalog:m5kdev",
16
- "react": "catalog:m5kdev",
17
- "react-dom": "catalog:m5kdev"
18
- },
19
- "devDependencies": {
20
- "@m5kdev/backend": "catalog:m5kdev",
21
- "@m5kdev/config": "catalog:m5kdev",
22
- "@types/node": "catalog:m5kdev",
23
- "@types/react": "catalog:m5kdev",
24
- "@types/react-dom": "catalog:m5kdev",
25
- "react-email": "catalog:m5kdev",
26
- "tslib": "catalog:m5kdev",
27
- "typescript": "catalog:m5kdev"
28
- },
29
- "main": "./src/index.ts",
30
- "exports": {
31
- ".": "./src/index.ts",
32
- "./resources": "./src/resources.ts"
33
- }
34
- }
1
+ {
2
+ "name": "{{PACKAGE_SCOPE}}/email",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "tsc --noEmit",
7
+ "lint": "biome check .",
8
+ "lint:fix": "biome check . --write",
9
+ "check-types": "tsc --noEmit",
10
+ "email:dev": "react-email dev --dir src/emails"
11
+ },
12
+ "dependencies": {
13
+ "@m5kdev/commons": "catalog:m5kdev",
14
+ "@m5kdev/email": "catalog:m5kdev",
15
+ "@react-email/components": "catalog:m5kdev",
16
+ "react": "catalog:m5kdev",
17
+ "react-dom": "catalog:m5kdev"
18
+ },
19
+ "devDependencies": {
20
+ "@m5kdev/backend": "catalog:m5kdev",
21
+ "@m5kdev/config": "catalog:m5kdev",
22
+ "@types/node": "catalog:m5kdev",
23
+ "@types/react": "catalog:m5kdev",
24
+ "@types/react-dom": "catalog:m5kdev",
25
+ "react-email": "catalog:m5kdev",
26
+ "tslib": "catalog:m5kdev",
27
+ "typescript": "catalog:m5kdev"
28
+ },
29
+ "main": "./src/index.ts",
30
+ "exports": {
31
+ ".": "./src/index.ts",
32
+ "./resources": "./src/resources.ts"
33
+ }
34
+ }
@@ -1,44 +1,44 @@
1
- # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
-
3
- # Dependencies
4
- node_modules/
5
-
6
- # Expo
7
- .expo/
8
- dist/
9
- web-build/
10
- expo-env.d.ts
11
-
12
- # Uniwind (generated on first `expo start`)
13
- uniwind-types.d.ts
14
-
15
- # Native
16
- .kotlin/
17
- *.orig.*
18
- *.jks
19
- *.p8
20
- *.p12
21
- *.key
22
- *.mobileprovision
23
-
24
- # Metro
25
- .metro-health-check*
26
-
27
- # Debug
28
- npm-debug.*
29
- yarn-debug.*
30
- yarn-error.*
31
-
32
- # macOS
33
- .DS_Store
34
- *.pem
35
-
36
- # Local env files
37
- .env*.local
38
-
39
- # TypeScript
40
- *.tsbuildinfo
41
-
42
- # Generated native folders
43
- /ios
44
- /android
1
+ # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2
+
3
+ # Dependencies
4
+ node_modules/
5
+
6
+ # Expo
7
+ .expo/
8
+ dist/
9
+ web-build/
10
+ expo-env.d.ts
11
+
12
+ # Uniwind (generated on first `expo start`)
13
+ uniwind-types.d.ts
14
+
15
+ # Native
16
+ .kotlin/
17
+ *.orig.*
18
+ *.jks
19
+ *.p8
20
+ *.p12
21
+ *.key
22
+ *.mobileprovision
23
+
24
+ # Metro
25
+ .metro-health-check*
26
+
27
+ # Debug
28
+ npm-debug.*
29
+ yarn-debug.*
30
+ yarn-error.*
31
+
32
+ # macOS
33
+ .DS_Store
34
+ *.pem
35
+
36
+ # Local env files
37
+ .env*.local
38
+
39
+ # TypeScript
40
+ *.tsbuildinfo
41
+
42
+ # Generated native folders
43
+ /ios
44
+ /android
@@ -1,13 +1,13 @@
1
- node_modules
2
- .expo
3
- dist
4
- ios
5
- android
6
- *.lock
7
- pnpm-lock.yaml
8
- yarn.lock
9
- package-lock.json
10
- bun.lockb
11
- expo-env.d.ts
12
- uniwind-types.d.ts
13
- .DS_Store
1
+ node_modules
2
+ .expo
3
+ dist
4
+ ios
5
+ android
6
+ *.lock
7
+ pnpm-lock.yaml
8
+ yarn.lock
9
+ package-lock.json
10
+ bun.lockb
11
+ expo-env.d.ts
12
+ uniwind-types.d.ts
13
+ .DS_Store
@@ -1,9 +1,9 @@
1
- {
2
- "semi": true,
3
- "singleQuote": false,
4
- "trailingComma": "es5",
5
- "printWidth": 100,
6
- "tabWidth": 2,
7
- "arrowParens": "always",
8
- "endOfLine": "lf"
9
- }
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "es5",
5
+ "printWidth": 100,
6
+ "tabWidth": 2,
7
+ "arrowParens": "always",
8
+ "endOfLine": "lf"
9
+ }