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.
Files changed (152) hide show
  1. package/dist/src/catalog.cjs +76 -11
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +13 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs +13 -7
  7. package/dist/src/doctor.cjs.map +1 -1
  8. package/dist/src/fs.cjs +10 -5
  9. package/dist/src/fs.cjs.map +1 -1
  10. package/dist/src/prompts.cjs +32 -0
  11. package/dist/src/prompts.cjs.map +1 -1
  12. package/dist/src/prompts.d.cts +7 -1
  13. package/dist/src/template.cjs +23 -4
  14. package/dist/src/template.cjs.map +1 -1
  15. package/dist/src/template.d.cts +17 -3
  16. package/dist/src/types.d.cts +11 -4
  17. package/package.json +4 -1
  18. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  19. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  20. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  21. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  22. package/templates/minimal-app/.dockerignore.tpl +31 -0
  23. package/templates/minimal-app/.gitignore.tpl +1 -0
  24. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  25. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  26. package/templates/minimal-app/README.md.tpl +50 -19
  27. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  28. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  29. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  30. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  31. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  32. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  33. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  34. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  35. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  36. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  37. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  38. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  39. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  40. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  41. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  42. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  43. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  44. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  45. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  46. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  47. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  48. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  49. package/templates/minimal-app/apps/expo/package.json.tpl +59 -59
  50. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  51. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  52. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  53. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  54. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  55. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  56. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  57. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  58. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  59. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  60. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  61. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  62. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  63. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  64. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  65. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  67. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  68. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  69. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  70. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  71. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  72. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  73. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  74. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  75. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  76. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  77. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  78. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  79. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  80. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  81. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  82. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  83. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  84. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  85. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  86. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +3 -0
  87. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  88. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  89. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  90. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -0
  91. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  92. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +15 -1
  93. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  94. package/templates/minimal-app/apps/server/package.json.tpl +57 -56
  95. package/templates/minimal-app/apps/server/src/app.ts.tpl +149 -141
  96. package/templates/minimal-app/apps/server/src/index.ts.tpl +12 -65
  97. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  98. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  99. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  100. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -30
  103. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  104. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  105. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -115
  106. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  107. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  108. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  109. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -1
  110. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  111. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  112. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  113. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  114. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  115. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  116. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  117. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  118. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  119. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  120. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  121. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  122. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  123. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  124. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  125. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  126. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  127. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  128. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  129. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  130. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  131. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  132. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  133. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  134. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  135. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  136. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  137. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  138. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  139. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  140. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  141. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  142. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  143. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  144. package/templates/minimal-app/biome.json.tpl +17 -12
  145. package/templates/minimal-app/package.json.tpl +9 -5
  146. package/templates/minimal-app/pnpm-workspace.yaml.tpl +67 -58
  147. package/templates/minimal-app/template.manifest.json +92 -2
  148. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  149. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  150. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  151. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
  152. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +0 -12
@@ -4,9 +4,10 @@
4
4
 
5
5
  ## Workspace
6
6
 
7
- - `apps/shared` contains shared contracts and constants for the app.
7
+ - `apps/shared` contains shared contracts, constants, and the product Deploy home (Dockerfile, `fly.toml`).
8
8
  - `apps/server` contains the Express, Better Auth, Drizzle, and tRPC backend.
9
9
  - `apps/webapp` contains the Vite, React Router, HeroUI, and `nuqs` frontend.
10
+ - `apps/landing` contains the public marketing site (one page) and its Fly adapter.
10
11
  - `apps/email` contains the email templates and local delivery registry used by the starter.
11
12
 
12
13
  ## Getting Started
@@ -26,26 +27,56 @@ Database tables are registered by hand in `apps/server/src/schema.ts`; after cha
26
27
  - Email: `admin@{{APP_SLUG}}.local`
27
28
  - Password: `password1234`
28
29
 
29
- ## Typical Commands
30
+ ## Typical Commands
30
31
 
31
32
  ```sh
32
33
  pnpm dev
33
34
  pnpm build
34
35
  pnpm check-types
35
- pnpm lint
36
- ```
37
-
38
- ## Managed framework updates
39
-
40
- This app records its generated baseline in `.m5kdev.json`. The updater is run
41
- explicitly from the package registry and is not installed as an app dependency:
42
-
43
- ```sh
44
- pnpm dlx create-m5kdev@<version> doctor
45
- pnpm dlx create-m5kdev@<version> update --dry-run
46
- pnpm dlx create-m5kdev@<version> update
47
- ```
48
-
49
- Use an exact target version in CI and when coordinating an upgrade. Existing
50
- projects without `.m5kdev.json` can enroll at their current compatible baseline
51
- with `pnpm dlx create-m5kdev@<version> init`.
36
+ pnpm lint
37
+ ```
38
+
39
+ ## Managed framework updates
40
+
41
+ This app records its generated baseline in `.m5kdev.json`. The updater is run
42
+ explicitly from the package registry and is not installed as an app dependency:
43
+
44
+ ```sh
45
+ pnpm dlx create-m5kdev@<version> doctor
46
+ pnpm dlx create-m5kdev@<version> update --dry-run
47
+ pnpm dlx create-m5kdev@<version> update
48
+ ```
49
+
50
+ Use an exact target version in CI and when coordinating an upgrade. Existing
51
+ projects without `.m5kdev.json` can enroll at their current compatible baseline
52
+ with `pnpm dlx create-m5kdev@<version> init`.
53
+
54
+ ## Deploy (Fly.io)
55
+
56
+ The Dockerfile is the portable image. Fly is the adapter this starter ships —
57
+ `fly.toml` is ignored by `create-m5kdev update` so your app name and region stay
58
+ yours.
59
+
60
+ `.env*` files are excluded from the image. Copy the examples, then pass values
61
+ as Fly **build secrets** (baked into the SPA at image build) and **runtime
62
+ secrets** (`REDIS_URL`, `DATABASE_URL`, auth).
63
+
64
+ ```sh
65
+ cp apps/shared/.env.production.example apps/shared/.env.production
66
+ cp apps/landing/.env.production.example apps/landing/.env.production
67
+ # fill in URLs, BETTER_AUTH_SECRET, REDIS_URL, …
68
+
69
+ fly apps create {{APP_SLUG}}-app
70
+ fly apps create {{APP_SLUG}}-landing
71
+ fly volumes create libsql_data --app {{APP_SLUG}}-app --region iad --size 1
72
+ pnpm app:secrets
73
+ pnpm landing:secrets
74
+ pnpm app:deploy
75
+ pnpm landing:deploy
76
+ ```
77
+
78
+ `app:deploy` / `landing:deploy` read that app’s `.env.production` and forward
79
+ every key as `fly deploy --build-secret`. A missing env file errors with a
80
+ copy-the-example message. Product data lives on the `libsql_data` volume at
81
+ `/app/data` (`DATABASE_URL=file:/app/data/local.db`). Redis is a runtime secret,
82
+ not an image layer.
@@ -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:",
15
- "@m5kdev/commons": "catalog:",
16
- "@m5kdev/config": "catalog:",
17
- "@playwright/test": "catalog:",
18
- "{{PACKAGE_SCOPE}}/shared": "workspace:*",
19
- "@trpc/client": "catalog:",
20
- "@types/node": "catalog:",
21
- "typescript": "catalog:"
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
+ }
@@ -1,39 +1,51 @@
1
- import { defineConfig, devices } from "@playwright/test";
2
- import { BASE_URLS, serverWebServer, webappWebServer } from "./servers";
3
-
4
- export default defineConfig({
5
- testDir: "./tests",
6
- fullyParallel: false,
7
- workers: 1,
8
- forbidOnly: Boolean(process.env.CI),
9
- retries: process.env.CI ? 2 : 0,
10
- reporter: [["list"], ["html", { open: "never" }]],
11
- use: {
12
- trace: "on-first-retry",
13
- },
14
- webServer: [
15
- serverWebServer("standard"),
16
- webappWebServer("standard"),
17
- serverWebServer("waitlist"),
18
- webappWebServer("waitlist"),
19
- ],
20
- projects: [
21
- {
22
- name: "standard",
23
- // exact filenames — a broad regex would also match the expo-*.spec files
24
- testMatch: ["standard.spec.ts", "email.spec.ts"],
25
- use: {
26
- ...devices["Desktop Chrome"],
27
- baseURL: BASE_URLS.standard,
28
- },
29
- },
30
- {
31
- name: "waitlist",
32
- testMatch: ["waitlist.spec.ts", "email.spec.ts"],
33
- use: {
34
- ...devices["Desktop Chrome"],
35
- baseURL: BASE_URLS.waitlist,
36
- },
37
- },
38
- ],
39
- });
1
+ import { defineConfig, devices } from "@playwright/test";
2
+ import { BASE_URLS, serverWebServer, webappWebServer } from "./servers";
3
+
4
+ export default defineConfig({
5
+ testDir: "./tests",
6
+ fullyParallel: false,
7
+ workers: 1,
8
+ forbidOnly: Boolean(process.env.CI),
9
+ retries: process.env.CI ? 2 : 0,
10
+ reporter: [["list"], ["html", { open: "never" }]],
11
+ use: {
12
+ trace: "on-first-retry",
13
+ },
14
+ webServer: [
15
+ serverWebServer("standard"),
16
+ webappWebServer("standard"),
17
+ serverWebServer("waitlist"),
18
+ webappWebServer("waitlist"),
19
+ ],
20
+ projects: [
21
+ {
22
+ name: "standard",
23
+ // exact filenames — a broad regex would also match the expo-*.spec files
24
+ testMatch: [
25
+ "standard.spec.ts",
26
+ "email.spec.ts",
27
+ // m5k:ai:start
28
+ "conversation.spec.ts",
29
+ // m5k:ai:end
30
+ // m5k:files:start
31
+ "files.spec.ts",
32
+ // m5k:files:end
33
+ // m5k:workflows:start
34
+ "workflow.spec.ts",
35
+ // m5k:workflows:end
36
+ ],
37
+ use: {
38
+ ...devices["Desktop Chrome"],
39
+ baseURL: BASE_URLS.standard,
40
+ },
41
+ },
42
+ {
43
+ name: "waitlist",
44
+ testMatch: ["waitlist.spec.ts", "email.spec.ts"],
45
+ use: {
46
+ ...devices["Desktop Chrome"],
47
+ baseURL: BASE_URLS.waitlist,
48
+ },
49
+ },
50
+ ],
51
+ });
@@ -1,38 +1,38 @@
1
- import { defineConfig, devices } from "@playwright/test";
2
- import { EXPO_BASE_URLS, expoWebServer, serverWebServer } from "./servers";
3
-
4
- export default defineConfig({
5
- testDir: "./tests",
6
- fullyParallel: false,
7
- workers: 1,
8
- forbidOnly: Boolean(process.env.CI),
9
- retries: process.env.CI ? 2 : 0,
10
- reporter: [["list"], ["html", { open: "never" }]],
11
- use: {
12
- trace: "on-first-retry",
13
- },
14
- webServer: [
15
- serverWebServer("standard", { expo: true }),
16
- expoWebServer("standard"),
17
- serverWebServer("waitlist", { expo: true }),
18
- expoWebServer("waitlist"),
19
- ],
20
- projects: [
21
- {
22
- name: "expo-standard",
23
- testMatch: /.*expo-standard\.spec\.ts/,
24
- use: {
25
- ...devices["Desktop Chrome"],
26
- baseURL: EXPO_BASE_URLS.standard,
27
- },
28
- },
29
- {
30
- name: "expo-waitlist",
31
- testMatch: /.*expo-waitlist\.spec\.ts/,
32
- use: {
33
- ...devices["Desktop Chrome"],
34
- baseURL: EXPO_BASE_URLS.waitlist,
35
- },
36
- },
37
- ],
38
- });
1
+ import { defineConfig, devices } from "@playwright/test";
2
+ import { EXPO_BASE_URLS, expoWebServer, serverWebServer } from "./servers";
3
+
4
+ export default defineConfig({
5
+ testDir: "./tests",
6
+ fullyParallel: false,
7
+ workers: 1,
8
+ forbidOnly: Boolean(process.env.CI),
9
+ retries: process.env.CI ? 2 : 0,
10
+ reporter: [["list"], ["html", { open: "never" }]],
11
+ use: {
12
+ trace: "on-first-retry",
13
+ },
14
+ webServer: [
15
+ serverWebServer("standard", { expo: true }),
16
+ expoWebServer("standard"),
17
+ serverWebServer("waitlist", { expo: true }),
18
+ expoWebServer("waitlist"),
19
+ ],
20
+ projects: [
21
+ {
22
+ name: "expo-standard",
23
+ testMatch: /.*expo-standard\.spec\.ts/,
24
+ use: {
25
+ ...devices["Desktop Chrome"],
26
+ baseURL: EXPO_BASE_URLS.standard,
27
+ },
28
+ },
29
+ {
30
+ name: "expo-waitlist",
31
+ testMatch: /.*expo-waitlist\.spec\.ts/,
32
+ use: {
33
+ ...devices["Desktop Chrome"],
34
+ baseURL: EXPO_BASE_URLS.waitlist,
35
+ },
36
+ },
37
+ ],
38
+ });
@@ -1,112 +1,136 @@
1
- /**
2
- * Shared webServer definitions for the Playwright configs. Each profile runs
3
- * an isolated stack: own port pair, own SQLite file, own email output dir, and
4
- * own Redis DB index (queue names are identical across profiles — sharing a
5
- * Redis DB would let one profile's workers consume the other's jobs).
6
- */
7
-
8
- export type E2EProfile = "standard" | "waitlist";
9
-
10
- const SERVER_FILTER = "pnpm --filter {{PACKAGE_SCOPE}}/server exec";
11
- const WEBAPP_FILTER = "pnpm --filter {{PACKAGE_SCOPE}}/webapp exec";
12
-
13
- const SIGNOZ_OTLP_ENDPOINT =
14
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
15
- "https://signoz-ingester-production-9090.up.railway.app";
16
-
17
- const PORTS: Record<E2EProfile, { web: number; server: number; expoWeb: number; expoServer: number; redisDb: number; expoRedisDb: number }> = {
18
- standard: { web: 15173, server: 18180, expoWeb: 15183, expoServer: 18182, redisDb: 1, expoRedisDb: 3 },
19
- waitlist: { web: 15174, server: 18181, expoWeb: 15184, expoServer: 18183, redisDb: 2, expoRedisDb: 4 },
20
- };
21
-
22
- function serverEnv(profile: E2EProfile, expo: boolean): Record<string, string> {
23
- const ports = PORTS[profile];
24
- const serverPort = expo ? ports.expoServer : ports.server;
25
- const webPort = expo ? ports.expoWeb : ports.web;
26
- const suffix = expo ? `${profile}-expo` : profile;
27
- return {
28
- PORT: String(serverPort),
29
- VITE_SERVER_URL: `http://127.0.0.1:${serverPort}`,
30
- VITE_APP_URL: `http://127.0.0.1:${webPort}`,
31
- DATABASE_URL: `file:./.e2e/${suffix}.db`,
32
- EMAIL_OUTPUT_DIRECTORY: `.e2e/emails-${suffix}`,
33
- PROVISIONED_EMAIL_DOMAIN: "provisioned.auth-e2e.local",
34
- REDIS_URL: `redis://127.0.0.1:6379/${expo ? PORTS[profile].expoRedisDb : PORTS[profile].redisDb}`,
35
- VITE_ENABLE_WAITLIST: profile === "waitlist" ? "true" : "false",
36
- AUTH_E2E_PROFILE: profile,
37
- BETTER_AUTH_SECRET: "auth-e2e-local-secret-auth-e2e-local-secret",
38
- SYSTEM_NOTIFICATION_EMAIL: "ops@auth-e2e.local",
39
- NODE_ENV: "development",
40
- // hermetic: a real key in the invoking shell would flip email mode to
41
- // "send" and the stored-email assertions would find nothing
42
- RESEND_API_KEY: "",
43
- ...(process.env.OTEL_SDK_DISABLED === "true"
44
- ? {}
45
- : {
46
- OTEL_EXPORTER_OTLP_ENDPOINT: SIGNOZ_OTLP_ENDPOINT,
47
- OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
48
- OTEL_SERVICE_NAME: `starter-server-e2e-${suffix}`,
49
- OTEL_RESOURCE_ATTRIBUTES: `deployment.environment=e2e,e2e.profile=${profile},e2e.platform=${expo ? "expo" : "web"}`,
50
- }),
51
- };
52
- }
53
-
54
- export function serverWebServer(profile: E2EProfile, { expo = false } = {}) {
55
- const env = serverEnv(profile, expo);
56
- return {
57
- command: [
58
- `${SERVER_FILTER} tsx drizzle/reset.ts`,
59
- `${SERVER_FILTER} drizzle-kit migrate`,
60
- `${SERVER_FILTER} tsx drizzle/seed.e2e.ts`,
61
- `${SERVER_FILTER} tsx src/index.ts`,
62
- ].join(" && "),
63
- url: `${env.VITE_SERVER_URL}/__emails`,
64
- reuseExistingServer: !process.env.CI,
65
- timeout: 120_000,
66
- env,
67
- };
68
- }
69
-
70
- export function webappWebServer(profile: E2EProfile) {
71
- const ports = PORTS[profile];
72
- const env = serverEnv(profile, false);
73
- return {
74
- command: `${WEBAPP_FILTER} vite --host 127.0.0.1 --port ${ports.web}`,
75
- url: `http://127.0.0.1:${ports.web}`,
76
- reuseExistingServer: !process.env.CI,
77
- timeout: 120_000,
78
- env: {
79
- VITE_SERVER_URL: env.VITE_SERVER_URL,
80
- VITE_APP_URL: env.VITE_APP_URL,
81
- VITE_ENABLE_WAITLIST: env.VITE_ENABLE_WAITLIST,
82
- },
83
- };
84
- }
85
-
86
- export function expoWebServer(profile: E2EProfile) {
87
- const ports = PORTS[profile];
88
- const env = serverEnv(profile, true);
89
- return {
90
- command: `pnpm --filter {{PACKAGE_SCOPE}}/expo exec expo start --web --port ${ports.expoWeb} --host localhost`,
91
- url: `http://127.0.0.1:${ports.expoWeb}`,
92
- reuseExistingServer: !process.env.CI,
93
- timeout: 180_000,
94
- env: {
95
- EXPO_PUBLIC_SERVER_URL: env.VITE_SERVER_URL,
96
- EXPO_PUBLIC_APP_URL: `http://127.0.0.1:${ports.expoWeb}`,
97
- EXPO_PUBLIC_ENABLE_WAITLIST: env.VITE_ENABLE_WAITLIST,
98
- EXPO_NO_TELEMETRY: "1",
99
- BROWSER: "none",
100
- },
101
- };
102
- }
103
-
104
- export const BASE_URLS: Record<E2EProfile, string> = {
105
- standard: `http://127.0.0.1:${PORTS.standard.web}`,
106
- waitlist: `http://127.0.0.1:${PORTS.waitlist.web}`,
107
- };
108
-
109
- export const EXPO_BASE_URLS: Record<E2EProfile, string> = {
110
- standard: `http://127.0.0.1:${PORTS.standard.expoWeb}`,
111
- waitlist: `http://127.0.0.1:${PORTS.waitlist.expoWeb}`,
112
- };
1
+ /**
2
+ * Shared webServer definitions for the Playwright configs. Each profile runs
3
+ * an isolated stack: own port pair, own SQLite file, own email output dir, and
4
+ * own Redis DB index (queue names are identical across profiles — sharing a
5
+ * Redis DB would let one profile's workers consume the other's jobs).
6
+ */
7
+
8
+ export type E2EProfile = "standard" | "waitlist";
9
+
10
+ const SERVER_FILTER = "pnpm --filter {{PACKAGE_SCOPE}}/server exec";
11
+ const WEBAPP_FILTER = "pnpm --filter {{PACKAGE_SCOPE}}/webapp exec";
12
+
13
+ const SIGNOZ_OTLP_ENDPOINT =
14
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT ??
15
+ "https://signoz-ingester-production-9090.up.railway.app";
16
+
17
+ const PORTS: Record<
18
+ E2EProfile,
19
+ {
20
+ web: number;
21
+ server: number;
22
+ expoWeb: number;
23
+ expoServer: number;
24
+ redisDb: number;
25
+ expoRedisDb: number;
26
+ }
27
+ > = {
28
+ standard: {
29
+ web: 15173,
30
+ server: 18180,
31
+ expoWeb: 15183,
32
+ expoServer: 18182,
33
+ redisDb: 1,
34
+ expoRedisDb: 3,
35
+ },
36
+ waitlist: {
37
+ web: 15174,
38
+ server: 18181,
39
+ expoWeb: 15184,
40
+ expoServer: 18183,
41
+ redisDb: 2,
42
+ expoRedisDb: 4,
43
+ },
44
+ };
45
+
46
+ function serverEnv(profile: E2EProfile, expo: boolean): Record<string, string> {
47
+ const ports = PORTS[profile];
48
+ const serverPort = expo ? ports.expoServer : ports.server;
49
+ const webPort = expo ? ports.expoWeb : ports.web;
50
+ const suffix = expo ? `${profile}-expo` : profile;
51
+ return {
52
+ PORT: String(serverPort),
53
+ VITE_SERVER_URL: `http://127.0.0.1:${serverPort}`,
54
+ VITE_APP_URL: `http://127.0.0.1:${webPort}`,
55
+ DATABASE_URL: `file:./.e2e/${suffix}.db`,
56
+ EMAIL_OUTPUT_DIRECTORY: `.e2e/emails-${suffix}`,
57
+ PROVISIONED_EMAIL_DOMAIN: "provisioned.auth-e2e.local",
58
+ REDIS_URL: `redis://127.0.0.1:6379/${expo ? PORTS[profile].expoRedisDb : PORTS[profile].redisDb}`,
59
+ VITE_ENABLE_WAITLIST: profile === "waitlist" ? "true" : "false",
60
+ AUTH_E2E_PROFILE: profile,
61
+ BETTER_AUTH_SECRET: "auth-e2e-local-secret-auth-e2e-local-secret",
62
+ SYSTEM_NOTIFICATION_EMAIL: "ops@auth-e2e.local",
63
+ NODE_ENV: "development",
64
+ // hermetic: a real key in the invoking shell would flip email mode to
65
+ // "send" and the stored-email assertions would find nothing
66
+ RESEND_API_KEY: "",
67
+ ...(process.env.OTEL_SDK_DISABLED === "true"
68
+ ? {}
69
+ : {
70
+ OTEL_EXPORTER_OTLP_ENDPOINT: SIGNOZ_OTLP_ENDPOINT,
71
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
72
+ OTEL_SERVICE_NAME: `starter-server-e2e-${suffix}`,
73
+ OTEL_RESOURCE_ATTRIBUTES: `deployment.environment=e2e,e2e.profile=${profile},e2e.platform=${expo ? "expo" : "web"}`,
74
+ }),
75
+ };
76
+ }
77
+
78
+ export function serverWebServer(profile: E2EProfile, { expo = false } = {}) {
79
+ const env = serverEnv(profile, expo);
80
+ return {
81
+ command: [
82
+ `${SERVER_FILTER} tsx db.ts reset`,
83
+ `${SERVER_FILTER} drizzle-kit migrate`,
84
+ `${SERVER_FILTER} tsx db.e2e.ts seed`,
85
+ `${SERVER_FILTER} tsx src/index.ts`,
86
+ ].join(" && "),
87
+ url: `${env.VITE_SERVER_URL}/__emails`,
88
+ reuseExistingServer: !process.env.CI,
89
+ timeout: 120_000,
90
+ env,
91
+ };
92
+ }
93
+
94
+ export function webappWebServer(profile: E2EProfile) {
95
+ const ports = PORTS[profile];
96
+ const env = serverEnv(profile, false);
97
+ return {
98
+ command: `${WEBAPP_FILTER} vite --host 127.0.0.1 --port ${ports.web}`,
99
+ url: `http://127.0.0.1:${ports.web}`,
100
+ reuseExistingServer: !process.env.CI,
101
+ timeout: 120_000,
102
+ env: {
103
+ VITE_SERVER_URL: env.VITE_SERVER_URL,
104
+ VITE_APP_URL: env.VITE_APP_URL,
105
+ VITE_ENABLE_WAITLIST: env.VITE_ENABLE_WAITLIST,
106
+ },
107
+ };
108
+ }
109
+
110
+ export function expoWebServer(profile: E2EProfile) {
111
+ const ports = PORTS[profile];
112
+ const env = serverEnv(profile, true);
113
+ return {
114
+ command: `pnpm --filter {{PACKAGE_SCOPE}}/expo exec expo start --web --port ${ports.expoWeb} --host localhost`,
115
+ url: `http://127.0.0.1:${ports.expoWeb}`,
116
+ reuseExistingServer: !process.env.CI,
117
+ timeout: 180_000,
118
+ env: {
119
+ EXPO_PUBLIC_SERVER_URL: env.VITE_SERVER_URL,
120
+ EXPO_PUBLIC_APP_URL: `http://127.0.0.1:${ports.expoWeb}`,
121
+ EXPO_PUBLIC_ENABLE_WAITLIST: env.VITE_ENABLE_WAITLIST,
122
+ EXPO_NO_TELEMETRY: "1",
123
+ BROWSER: "none",
124
+ },
125
+ };
126
+ }
127
+
128
+ export const BASE_URLS: Record<E2EProfile, string> = {
129
+ standard: `http://127.0.0.1:${PORTS.standard.web}`,
130
+ waitlist: `http://127.0.0.1:${PORTS.waitlist.web}`,
131
+ };
132
+
133
+ export const EXPO_BASE_URLS: Record<E2EProfile, string> = {
134
+ standard: `http://127.0.0.1:${PORTS.standard.expoWeb}`,
135
+ waitlist: `http://127.0.0.1:${PORTS.waitlist.expoWeb}`,
136
+ };
@@ -0,0 +1,22 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { clearEmails, login, signUp, verifyLatestEmail } from "./helpers";
3
+
4
+ const profile = "standard" as const;
5
+
6
+ test.beforeEach(async ({ request }) => {
7
+ await clearEmails(request, profile);
8
+ });
9
+
10
+ test("open the agent conversation and see an empty transcript", async ({ page, request }) => {
11
+ const email = `conversation.${Date.now()}@auth-e2e.local`;
12
+ const password = "password1234";
13
+
14
+ await signUp(page, email, password);
15
+ await verifyLatestEmail(request, profile, email);
16
+ await login(page, email, password);
17
+
18
+ await page.goto("/conversation");
19
+ await expect(page.getByTestId("conversation-route")).toBeVisible();
20
+ await expect(page.getByText("No messages yet")).toBeVisible();
21
+ await expect(page.getByRole("button", { name: "Send" })).toBeVisible();
22
+ });