@0x0501/mystack 0.0.0-stage → 0.1.0-bootstrap.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 (211) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -2
  3. package/assets/auth.sqlite.schema.ts +479 -0
  4. package/assets/bun-sqlite.test.ts +46 -0
  5. package/assets/d1.test.ts +41 -0
  6. package/assets/drizzle.config.ts +2 -0
  7. package/assets/generate-auth-schema.mjs +25 -0
  8. package/assets/platform.sqlite.schema.ts +46 -0
  9. package/assets/polar-checkout.ts +10 -0
  10. package/assets/polar-route.ts +19 -0
  11. package/assets/polar.test.ts +30 -0
  12. package/assets/polar.ts +12 -0
  13. package/assets/purchase.bun-sqlite.ts +38 -0
  14. package/assets/purchase.sqlite.ts +39 -0
  15. package/assets/sqlite-migrations/0000_thin_phalanx.sql +295 -0
  16. package/assets/sqlite-migrations/meta/0000_snapshot.json +2058 -0
  17. package/assets/sqlite-migrations/meta/_journal.json +13 -0
  18. package/assets/turso.test.ts +40 -0
  19. package/package.json +44 -5
  20. package/src/adapters/database.mjs +448 -0
  21. package/src/adapters/deployment.mjs +101 -0
  22. package/src/adapters/email.mjs +75 -0
  23. package/src/adapters/payments.mjs +270 -0
  24. package/src/catalog.mjs +128 -0
  25. package/src/cli.mjs +294 -0
  26. package/src/generate.mjs +183 -0
  27. package/src/template.mjs +93 -0
  28. package/template/.env.docker +16 -0
  29. package/template/.env.example +67 -0
  30. package/template/DESIGN.md +58 -0
  31. package/template/LICENSE +21 -0
  32. package/template/biome.json +46 -0
  33. package/template/docker-compose.yml +47 -0
  34. package/template/docs/project-guide.md +61 -0
  35. package/template/drizzle/0000_create-app-role.sql +54 -0
  36. package/template/drizzle/0001_add-rls-context.sql +21 -0
  37. package/template/drizzle/0002_platform_tables.sql +320 -0
  38. package/template/drizzle/meta/0002_snapshot.json +2125 -0
  39. package/template/drizzle/meta/_journal.json +27 -0
  40. package/template/drizzle.config.ts +25 -0
  41. package/template/e2e/authenticated-ui.spec.ts +40 -0
  42. package/template/e2e/headless-design.spec.ts +66 -0
  43. package/template/e2e/navigation-loading.spec.ts +70 -0
  44. package/template/e2e/theme-responsive.spec.ts +260 -0
  45. package/template/gitignore +24 -0
  46. package/template/messages/de.json +212 -0
  47. package/template/messages/en.json +212 -0
  48. package/template/package.json +101 -0
  49. package/template/paraglide.config.ts +49 -0
  50. package/template/playwright.config.ts +58 -0
  51. package/template/project.inlang/settings.json +12 -0
  52. package/template/public/drizzle.svg +1 -0
  53. package/template/public/favicon.ico +0 -0
  54. package/template/public/logo192.png +0 -0
  55. package/template/public/logo512.png +0 -0
  56. package/template/public/manifest.json +25 -0
  57. package/template/public/robots.txt +3 -0
  58. package/template/scripts/check-client-bundle.ts +150 -0
  59. package/template/scripts/check-server-fns.ts +290 -0
  60. package/template/scripts/check-sql-raw.ts +50 -0
  61. package/template/scripts/compile-paraglide.ts +12 -0
  62. package/template/scripts/postgres/README.md +76 -0
  63. package/template/scripts/postgres/create-app-role.sh +60 -0
  64. package/template/scripts/seed-local.ts +331 -0
  65. package/template/scripts/validate-prod-env.ts +53 -0
  66. package/template/src/__tests__/README.md +9 -0
  67. package/template/src/__tests__/account-security.test.ts +664 -0
  68. package/template/src/__tests__/admin-http-surface.test.ts +311 -0
  69. package/template/src/__tests__/admin-peer-protection.test.ts +178 -0
  70. package/template/src/__tests__/auth-harness.ts +104 -0
  71. package/template/src/__tests__/auth-rate-limit.test.ts +43 -0
  72. package/template/src/__tests__/auth-redirect.test.ts +120 -0
  73. package/template/src/__tests__/body-limit.test.ts +47 -0
  74. package/template/src/__tests__/creem.test.ts +62 -0
  75. package/template/src/__tests__/demo-checkout.test.ts +141 -0
  76. package/template/src/__tests__/email-locale.test.ts +110 -0
  77. package/template/src/__tests__/global-setup.ts +80 -0
  78. package/template/src/__tests__/input-rules.test.ts +0 -0
  79. package/template/src/__tests__/message-parity.test.ts +38 -0
  80. package/template/src/__tests__/navigation-loading.test.tsx +252 -0
  81. package/template/src/__tests__/nowpayments.test.ts +106 -0
  82. package/template/src/__tests__/paraglide-url-contract.test.ts +87 -0
  83. package/template/src/__tests__/purchase-fulfillment.test.ts +204 -0
  84. package/template/src/__tests__/query-unauthorized-redirect.test.ts +95 -0
  85. package/template/src/__tests__/rls-policy.test.ts +120 -0
  86. package/template/src/__tests__/same-page-locale-href.test.ts +47 -0
  87. package/template/src/__tests__/search-params-oauth.test.ts +57 -0
  88. package/template/src/__tests__/security-headers.test.ts +12 -0
  89. package/template/src/__tests__/server-fn-error-contract.test.ts +173 -0
  90. package/template/src/__tests__/stripe.test.ts +84 -0
  91. package/template/src/__tests__/test-db.ts +32 -0
  92. package/template/src/__tests__/test-rls-db.ts +23 -0
  93. package/template/src/__tests__/user-update-guard.test.ts +109 -0
  94. package/template/src/components/LocaleSwitcher.tsx +79 -0
  95. package/template/src/components/ThemeToggle.tsx +47 -0
  96. package/template/src/components/auth/social.tsx +90 -0
  97. package/template/src/components/auth/turnstile.tsx +67 -0
  98. package/template/src/components/root/navigation-progress.tsx +32 -0
  99. package/template/src/components/root/not-found.tsx +20 -0
  100. package/template/src/components/root/pending.tsx +9 -0
  101. package/template/src/components/root/root-document.tsx +37 -0
  102. package/template/src/components/root/root-error.tsx +34 -0
  103. package/template/src/components/shells.tsx +201 -0
  104. package/template/src/components/ui/styles.ts +9 -0
  105. package/template/src/db/auth.schema.ts +455 -0
  106. package/template/src/db/helper.ts +38 -0
  107. package/template/src/db/index.ts +15 -0
  108. package/template/src/db/platform.schema.ts +122 -0
  109. package/template/src/db/schema.ts +2 -0
  110. package/template/src/env.ts +65 -0
  111. package/template/src/hooks/use-theme.ts +32 -0
  112. package/template/src/integrations/better-auth/header-user.tsx +36 -0
  113. package/template/src/integrations/better-auth/session-query.ts +11 -0
  114. package/template/src/integrations/react-email/PasswordOtpEmail.tsx +62 -0
  115. package/template/src/integrations/react-email/ResetPasswordEmail.tsx +64 -0
  116. package/template/src/integrations/react-email/VerifyEmail.tsx +72 -0
  117. package/template/src/integrations/react-email/theme.ts +75 -0
  118. package/template/src/integrations/tanstack-query/devtools.tsx +6 -0
  119. package/template/src/integrations/tanstack-query/root-provider.tsx +60 -0
  120. package/template/src/lib/auth-client.ts +27 -0
  121. package/template/src/lib/auth-paths.ts +94 -0
  122. package/template/src/lib/auth-plugins/existing-email-signup.ts +86 -0
  123. package/template/src/lib/auth-plugins/field-rules.ts +84 -0
  124. package/template/src/lib/auth-plugins/social-two-factor.ts +194 -0
  125. package/template/src/lib/auth-timing.ts +9 -0
  126. package/template/src/lib/auth.ts +279 -0
  127. package/template/src/lib/captcha.ts +11 -0
  128. package/template/src/lib/email.tsx +162 -0
  129. package/template/src/lib/github.ts +18 -0
  130. package/template/src/lib/locale-cookie.ts +12 -0
  131. package/template/src/lib/localized-pages.ts +25 -0
  132. package/template/src/lib/oauth-metadata.ts +19 -0
  133. package/template/src/lib/permissions.ts +29 -0
  134. package/template/src/lib/security-headers.ts +20 -0
  135. package/template/src/lib/site.ts +59 -0
  136. package/template/src/lib/theme.ts +42 -0
  137. package/template/src/lib/user-update-guard.ts +35 -0
  138. package/template/src/middlewares/admin.ts +46 -0
  139. package/template/src/middlewares/auth-rate-limit.ts +37 -0
  140. package/template/src/middlewares/better-auth.ts +15 -0
  141. package/template/src/middlewares/body-limit.ts +62 -0
  142. package/template/src/middlewares/database.ts +13 -0
  143. package/template/src/middlewares/protected.ts +45 -0
  144. package/template/src/routeTree.gen.ts +536 -0
  145. package/template/src/router.tsx +53 -0
  146. package/template/src/routes/[.]well-known/oauth-authorization-server/api/auth.ts +18 -0
  147. package/template/src/routes/[.]well-known/oauth-protected-resource/index.ts +32 -0
  148. package/template/src/routes/__root.tsx +175 -0
  149. package/template/src/routes/admin/audit.tsx +74 -0
  150. package/template/src/routes/admin/index.tsx +7 -0
  151. package/template/src/routes/admin/oauth-apps.tsx +164 -0
  152. package/template/src/routes/admin/route.tsx +27 -0
  153. package/template/src/routes/admin/users.tsx +215 -0
  154. package/template/src/routes/api/auth/$.ts +14 -0
  155. package/template/src/routes/api/auth/[.]well-known/openid-configuration.ts +15 -0
  156. package/template/src/routes/api/webhooks/creem.ts +69 -0
  157. package/template/src/routes/api/webhooks/nowpayments.ts +62 -0
  158. package/template/src/routes/api/webhooks/stripe.ts +74 -0
  159. package/template/src/routes/dashboard/account.tsx +430 -0
  160. package/template/src/routes/dashboard/index.tsx +128 -0
  161. package/template/src/routes/dashboard/route.tsx +17 -0
  162. package/template/src/routes/forgot-password.tsx +65 -0
  163. package/template/src/routes/index.tsx +85 -0
  164. package/template/src/routes/oauth/consent.tsx +77 -0
  165. package/template/src/routes/reset-password.tsx +78 -0
  166. package/template/src/routes/sign-in.tsx +174 -0
  167. package/template/src/routes/sign-up.tsx +135 -0
  168. package/template/src/routes/two-factor.tsx +74 -0
  169. package/template/src/server/README.md +3 -0
  170. package/template/src/server/account-security.ts +163 -0
  171. package/template/src/server/admin-audit.ts +37 -0
  172. package/template/src/server/admin-users.ts +112 -0
  173. package/template/src/server/auth.ts +44 -0
  174. package/template/src/server/demo-checkout.ts +67 -0
  175. package/template/src/server/oauth-clients.ts +83 -0
  176. package/template/src/server/payment-toggles.ts +39 -0
  177. package/template/src/server.ts +20 -0
  178. package/template/src/services/README.md +3 -0
  179. package/template/src/services/account-security.ts +893 -0
  180. package/template/src/services/admin-security.ts +164 -0
  181. package/template/src/services/admin-users.ts +181 -0
  182. package/template/src/services/auth-rate-limit.ts +32 -0
  183. package/template/src/services/creem.ts +83 -0
  184. package/template/src/services/demo-checkout.ts +100 -0
  185. package/template/src/services/nowpayments.ts +154 -0
  186. package/template/src/services/oauth-client.ts +85 -0
  187. package/template/src/services/oauth-clients.ts +15 -0
  188. package/template/src/services/payment-providers.ts +157 -0
  189. package/template/src/services/payment-toggles.ts +48 -0
  190. package/template/src/services/purchase.ts +233 -0
  191. package/template/src/services/stripe.ts +106 -0
  192. package/template/src/start.ts +35 -0
  193. package/template/src/styles/layout.css +93 -0
  194. package/template/src/styles/theme.css +84 -0
  195. package/template/src/styles/ui.css +233 -0
  196. package/template/src/styles.css +90 -0
  197. package/template/src/utils/api-error.ts +129 -0
  198. package/template/src/utils/auth-errors.ts +157 -0
  199. package/template/src/utils/auth-redirect.ts +61 -0
  200. package/template/src/utils/body-limit.ts +84 -0
  201. package/template/src/utils/cn.ts +6 -0
  202. package/template/src/utils/input-rules.ts +172 -0
  203. package/template/src/utils/locale-paths.ts +18 -0
  204. package/template/src/utils/same-page-locale-href.ts +20 -0
  205. package/template/src/utils/search-params.ts +71 -0
  206. package/template/tsconfig.json +30 -0
  207. package/template/tsr.config.json +6 -0
  208. package/template/vite.config.ts +24 -0
  209. package/template/vitest.config.ts +26 -0
  210. package/template/worker-configuration.d.ts +14660 -0
  211. package/template/wrangler.jsonc +39 -0
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Local-only seed: verified email/password users for sign-in.
3
+ *
4
+ * Usage:
5
+ * bun run db:seed
6
+ * bun run db:seed -- --admin-only
7
+ * bun run db:seed -- --user-only
8
+ *
9
+ * Env (optional overrides — defaults are fine for local dev):
10
+ * DATABASE_MIGRATION_URL required direct Postgres URL
11
+ * SEED_ADMIN_EMAIL default admin@localhost.dev
12
+ * SEED_ADMIN_PASSWORD default AdminPass1!
13
+ * SEED_ADMIN_NAME default Local Admin
14
+ * SEED_ADMIN_ROLE default superadmin (admin | superadmin)
15
+ * SEED_USER_EMAIL default user@localhost.dev
16
+ * SEED_USER_PASSWORD default UserPass1!
17
+ * SEED_USER_NAME default Local User
18
+ * SEED_FORCE=1 allow non-localhost URLs (still refuse prod markers)
19
+ */
20
+ import { randomUUID } from "node:crypto";
21
+ import { hashPassword } from "better-auth/crypto";
22
+ import { config } from "dotenv";
23
+ import { and, eq } from "drizzle-orm";
24
+ import { drizzle } from "drizzle-orm/node-postgres";
25
+ import { Pool } from "pg";
26
+ import { account, user } from "../src/db/auth.schema";
27
+ import {
28
+ assertEmailLimits,
29
+ assertPasswordRules,
30
+ } from "../src/utils/input-rules";
31
+
32
+ config({ path: [".env.local", ".env"] });
33
+
34
+ const DEFAULTS = {
35
+ admin: {
36
+ email: "admin@localhost.dev",
37
+ password: "AdminPass1!",
38
+ name: "Local Admin",
39
+ role: "superadmin" as const,
40
+ },
41
+ user: {
42
+ email: "user@localhost.dev",
43
+ password: "UserPass1!",
44
+ name: "Local User",
45
+ role: "user" as const,
46
+ },
47
+ } as const;
48
+
49
+ type SeedRole = "user" | "admin" | "superadmin";
50
+
51
+ type SeedSpec = {
52
+ email: string;
53
+ password: string;
54
+ name: string;
55
+ role: SeedRole;
56
+ label: string;
57
+ };
58
+
59
+ function env(name: string, fallback: string): string {
60
+ const v = process.env[name]?.trim();
61
+ return v && v.length > 0 ? v : fallback;
62
+ }
63
+
64
+ function parseArgs(argv: string[]) {
65
+ const flags = new Set(argv.filter((a) => a.startsWith("--")));
66
+ return {
67
+ adminOnly: flags.has("--admin-only"),
68
+ userOnly: flags.has("--user-only"),
69
+ help: flags.has("--help") || flags.has("-h"),
70
+ };
71
+ }
72
+
73
+ function assertLocalDbUrl(url: string): void {
74
+ let parsed: URL;
75
+ try {
76
+ parsed = new URL(url);
77
+ } catch {
78
+ throw new Error(`DATABASE_MIGRATION_URL is not a valid URL: ${url}`);
79
+ }
80
+ const host = parsed.hostname.toLowerCase();
81
+ const local =
82
+ host === "localhost" ||
83
+ host === "127.0.0.1" ||
84
+ host === "::1" ||
85
+ host === "0.0.0.0" ||
86
+ host.endsWith(".local");
87
+ const forced = process.env.SEED_FORCE === "1";
88
+ if (!local && !forced) {
89
+ throw new Error(
90
+ `Refusing to seed non-local database host "${host}".\n` +
91
+ `Use a local Postgres URL, or set SEED_FORCE=1 if you really mean it.`,
92
+ );
93
+ }
94
+ const prodMarkers = ["prod", "production", "neon.tech", "supabase.co"];
95
+ const hay = url.toLowerCase();
96
+ if (prodMarkers.some((m) => hay.includes(m)) && process.env.SEED_FORCE !== "1") {
97
+ throw new Error(
98
+ `DATABASE_MIGRATION_URL looks production-like. Refusing without SEED_FORCE=1.`,
99
+ );
100
+ }
101
+ }
102
+
103
+ function parseRole(raw: string): SeedRole {
104
+ if (raw === "user" || raw === "admin" || raw === "superadmin") return raw;
105
+ throw new Error(
106
+ `Invalid SEED_ADMIN_ROLE "${raw}". Use user | admin | superadmin.`,
107
+ );
108
+ }
109
+
110
+ function validateCredentials(email: string, password: string, who: string) {
111
+ try {
112
+ assertEmailLimits(email);
113
+ assertPasswordRules(password);
114
+ } catch (e) {
115
+ const msg = e instanceof Error ? e.message : String(e);
116
+ throw new Error(`${who}: ${msg}`);
117
+ }
118
+ if (!email.includes("@")) {
119
+ throw new Error(`${who}: email must look like an address.`);
120
+ }
121
+ }
122
+
123
+ async function upsertUser(
124
+ db: ReturnType<typeof drizzle>,
125
+ spec: SeedSpec,
126
+ ): Promise<"created" | "updated"> {
127
+ const email = spec.email.toLowerCase();
128
+ const hashed = await hashPassword(spec.password);
129
+ const now = new Date();
130
+
131
+ const [existing] = await db
132
+ .select({ id: user.id })
133
+ .from(user)
134
+ .where(eq(user.email, email))
135
+ .limit(1);
136
+
137
+ if (existing) {
138
+ await db
139
+ .update(user)
140
+ .set({
141
+ name: spec.name,
142
+ role: spec.role,
143
+ emailVerified: true,
144
+ banned: false,
145
+ banReason: null,
146
+ banExpires: null,
147
+ updatedAt: now,
148
+ })
149
+ .where(eq(user.id, existing.id));
150
+
151
+ const [cred] = await db
152
+ .select({ id: account.id })
153
+ .from(account)
154
+ .where(
155
+ and(
156
+ eq(account.userId, existing.id),
157
+ eq(account.providerId, "credential"),
158
+ ),
159
+ )
160
+ .limit(1);
161
+
162
+ if (cred) {
163
+ await db
164
+ .update(account)
165
+ .set({ password: hashed, updatedAt: now })
166
+ .where(eq(account.id, cred.id));
167
+ } else {
168
+ await db.insert(account).values({
169
+ id: randomUUID(),
170
+ accountId: existing.id,
171
+ providerId: "credential",
172
+ userId: existing.id,
173
+ password: hashed,
174
+ createdAt: now,
175
+ updatedAt: now,
176
+ });
177
+ }
178
+ return "updated";
179
+ }
180
+
181
+ const id = randomUUID();
182
+ await db.insert(user).values({
183
+ id,
184
+ name: spec.name,
185
+ email,
186
+ emailVerified: true,
187
+ role: spec.role,
188
+ banned: false,
189
+ twoFactorEnabled: false,
190
+ locale: "en",
191
+ createdAt: now,
192
+ updatedAt: now,
193
+ });
194
+ await db.insert(account).values({
195
+ id: randomUUID(),
196
+ accountId: id,
197
+ providerId: "credential",
198
+ userId: id,
199
+ password: hashed,
200
+ createdAt: now,
201
+ updatedAt: now,
202
+ });
203
+ return "created";
204
+ }
205
+
206
+ function printHelp() {
207
+ console.log(`seed-local — insert verified local users for sign-in
208
+
209
+ Usage:
210
+ bun run db:seed
211
+ bun run db:seed -- --admin-only
212
+ bun run db:seed -- --user-only
213
+
214
+ Defaults:
215
+ Superadmin ${DEFAULTS.admin.email} / ${DEFAULTS.admin.password}
216
+ User ${DEFAULTS.user.email} / ${DEFAULTS.user.password}
217
+
218
+ Requires DATABASE_MIGRATION_URL (direct Postgres, same as drizzle migrate).
219
+ Passwords must satisfy Starter rules: 8–32 chars, letter + digit + symbol.
220
+ `);
221
+ }
222
+
223
+ async function main() {
224
+ const args = parseArgs(process.argv.slice(2));
225
+ if (args.help) {
226
+ printHelp();
227
+ process.exit(0);
228
+ }
229
+ if (args.adminOnly && args.userOnly) {
230
+ throw new Error("Use only one of --admin-only or --user-only.");
231
+ }
232
+
233
+ const databaseUrl = process.env.DATABASE_MIGRATION_URL?.trim();
234
+ if (!databaseUrl) {
235
+ throw new Error(
236
+ "DATABASE_MIGRATION_URL is required (direct Postgres URL for local seed).",
237
+ );
238
+ }
239
+ assertLocalDbUrl(databaseUrl);
240
+
241
+ const adminSpec: SeedSpec = {
242
+ label: "admin",
243
+ email: env("SEED_ADMIN_EMAIL", DEFAULTS.admin.email),
244
+ password: env("SEED_ADMIN_PASSWORD", DEFAULTS.admin.password),
245
+ name: env("SEED_ADMIN_NAME", DEFAULTS.admin.name),
246
+ role: parseRole(env("SEED_ADMIN_ROLE", DEFAULTS.admin.role)),
247
+ };
248
+ const userSpec: SeedSpec = {
249
+ label: "user",
250
+ email: env("SEED_USER_EMAIL", DEFAULTS.user.email),
251
+ password: env("SEED_USER_PASSWORD", DEFAULTS.user.password),
252
+ name: env("SEED_USER_NAME", DEFAULTS.user.name),
253
+ role: "user",
254
+ };
255
+
256
+ const specs: SeedSpec[] = [];
257
+ if (!args.userOnly) specs.push(adminSpec);
258
+ if (!args.adminOnly) specs.push(userSpec);
259
+
260
+ for (const s of specs) {
261
+ validateCredentials(s.email, s.password, s.label);
262
+ }
263
+
264
+ const pool = new Pool({ connectionString: databaseUrl });
265
+ const db = drizzle(pool);
266
+
267
+ try {
268
+ // Fail fast with a clear message if the DB is down or migrations missing.
269
+ await pool.query('select 1 from "user" limit 0');
270
+ } catch (e) {
271
+ await pool.end().catch(() => undefined);
272
+ const msg = e instanceof Error ? e.message : String(e);
273
+ if (/password authentication failed|ECONNREFUSED|ENOTFOUND/i.test(msg)) {
274
+ throw new Error(
275
+ `Cannot connect to Postgres (${msg}).\n` +
276
+ `Check DATABASE_MIGRATION_URL and that Postgres is running.`,
277
+ );
278
+ }
279
+ if (/relation ["']?user["']? does not exist/i.test(msg)) {
280
+ throw new Error(
281
+ `Table "user" not found. Run migrations first:\n bun run db:migrate`,
282
+ );
283
+ }
284
+ throw e;
285
+ }
286
+
287
+ try {
288
+ console.log("Seeding local users…\n");
289
+ const rows: {
290
+ label: string;
291
+ email: string;
292
+ password: string;
293
+ role: string;
294
+ status: string;
295
+ }[] = [];
296
+
297
+ for (const spec of specs) {
298
+ const status = await upsertUser(db, spec);
299
+ rows.push({
300
+ label: spec.label,
301
+ email: spec.email.toLowerCase(),
302
+ password: spec.password,
303
+ role: spec.role,
304
+ status,
305
+ });
306
+ console.log(
307
+ ` [${status}] ${spec.role.padEnd(11)} ${spec.email.toLowerCase()}`,
308
+ );
309
+ }
310
+
311
+ console.log(
312
+ "\nSign-in credentials (local only — change before any shared env):\n",
313
+ );
314
+ console.table(
315
+ rows.map((r) => ({
316
+ role: r.role,
317
+ email: r.email,
318
+ password: r.password,
319
+ status: r.status,
320
+ })),
321
+ );
322
+ console.log("Done. Open /sign-in and use an email above.");
323
+ } finally {
324
+ await pool.end();
325
+ }
326
+ }
327
+
328
+ main().catch((err) => {
329
+ console.error(err instanceof Error ? err.message : err);
330
+ process.exit(1);
331
+ });
@@ -0,0 +1,53 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse } from "dotenv";
3
+
4
+ /**
5
+ * Platform-required secrets for production deploy. Optional rails (GitHub,
6
+ * Turnstile, payments) are not listed — clones add them when enabled.
7
+ */
8
+ const REQUIRED_PRODUCTION_ENV = [
9
+ "APP_ORIGIN",
10
+ "BETTER_AUTH_URL",
11
+ "BETTER_AUTH_SECRET",
12
+ "EMAIL_FROM",
13
+ "EMAIL_FROM_NAME",
14
+ ] as const;
15
+
16
+ /**
17
+ * Values `.env.example` ships so a fresh clone boots. Present is not the same as
18
+ * set: copying the example forward would otherwise deploy a secret published in
19
+ * this repository, and a presence check alone cannot tell the two apart.
20
+ */
21
+ const DEV_PLACEHOLDERS: Partial<Record<string, string>> = {
22
+ BETTER_AUTH_SECRET: "dev-only-insecure-secret-change-before-deploy",
23
+ };
24
+
25
+ export async function validateProductionEnv(file = ".env.prod") {
26
+ const values = parse(await readFile(file, "utf8"));
27
+ const missing = REQUIRED_PRODUCTION_ENV.filter(
28
+ (key) => !values[key]?.trim(),
29
+ ).sort();
30
+
31
+ if (missing.length) {
32
+ throw new Error(
33
+ `Production deployment input is missing required values: ${missing.join(", ")}`,
34
+ );
35
+ }
36
+
37
+ const placeholders = REQUIRED_PRODUCTION_ENV.filter(
38
+ (key) => values[key]?.trim() === DEV_PLACEHOLDERS[key],
39
+ ).sort();
40
+
41
+ if (placeholders.length) {
42
+ throw new Error(
43
+ `Production deployment input still carries .env.example dev placeholders: ${placeholders.join(", ")}`,
44
+ );
45
+ }
46
+
47
+ return values;
48
+ }
49
+
50
+ if (import.meta.main) {
51
+ await validateProductionEnv();
52
+ console.log("Production deployment input is complete.");
53
+ }
@@ -0,0 +1,9 @@
1
+ # Tests
2
+
3
+ Application and CLI Vitest tests live here (`*.test.ts` / `*.test.tsx`). Browser tests live in `e2e/`; database/provider test assets for generated projects live in `packages/mystack/assets/`.
4
+
5
+ Test observable behavior: authorization refusals, state changes, transaction rollback, payment idempotency, protocol round-trips, routing, rendering and filesystem safety. Use real database/runtime tests where the driver behavior matters.
6
+
7
+ Do not test README wording, copied constant values, export existence, source formatting or third-party library defaults. Remove duplicate assertions when a retained test exercises the same behavior. A test must invoke the behavior its name claims to cover.
8
+
9
+ Run `bun run test` with Docker for the complete application suite, or `bun run test:unit` without database integration. `bun run test:e2e` runs Playwright. In the generator repository, `bun run test:cli` runs CLI tests; generated-project and packaged-CLI verification are documented in `docs/contributing.md`.