create-m5kdev 0.28.7 → 0.28.8

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.
@@ -1,13 +1,13 @@
1
- {
2
- "version": "7",
3
- "dialect": "sqlite",
4
- "entries": [
5
- {
6
- "idx": 0,
7
- "version": "6",
8
- "when": 1783931793066,
9
- "tag": "0000_wealthy_absorbing_man",
10
- "breakpoints": true
11
- }
12
- ]
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1783931793066,
9
+ "tag": "0000_wealthy_absorbing_man",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
13
  }
@@ -1,33 +1,40 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- /** Deletes the local SQLite database (and stored-email output) for a clean slate. */
5
- function sqlitePathFromDatabaseUrl(url: string) {
6
- if (!url.startsWith("file:")) return null;
7
- return path.resolve(process.cwd(), url.slice("file:".length));
8
- }
9
-
10
- async function removeIfExists(target: string) {
11
- await fs.rm(target, { recursive: true, force: true });
12
- }
13
-
14
- async function reset() {
15
- const dbPath = sqlitePathFromDatabaseUrl(process.env.DATABASE_URL ?? "");
16
- if (dbPath) {
17
- await removeIfExists(dbPath);
18
- await removeIfExists(`${dbPath}-shm`);
19
- await removeIfExists(`${dbPath}-wal`);
20
- await fs.mkdir(path.dirname(dbPath), { recursive: true });
21
- }
22
-
23
- const emailDirectory = process.env.EMAIL_OUTPUT_DIRECTORY
24
- ? path.resolve(process.cwd(), process.env.EMAIL_OUTPUT_DIRECTORY)
25
- : null;
26
-
27
- if (emailDirectory) {
28
- await removeIfExists(emailDirectory);
29
- await fs.mkdir(emailDirectory, { recursive: true });
30
- }
31
- }
32
-
33
- void reset();
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { ensureDevServerStopped } from "./guard";
4
+
5
+ /** Deletes the local SQLite database (and stored-email output) for a clean slate. */
6
+ function sqlitePathFromDatabaseUrl(url: string) {
7
+ if (!url.startsWith("file:")) return null;
8
+ return path.resolve(process.cwd(), url.slice("file:".length));
9
+ }
10
+
11
+ // Sidecars include the embedded-replica sync metadata (-info, -client_wal_index);
12
+ // leaving those behind next to a recreated file causes WAL/sync conflicts.
13
+ const DB_SIDECAR_SUFFIXES = ["-shm", "-wal", "-journal", "-info", "-client_wal_index"];
14
+
15
+ async function removeIfExists(target: string) {
16
+ await fs.rm(target, { recursive: true, force: true });
17
+ }
18
+
19
+ async function reset() {
20
+ await ensureDevServerStopped();
21
+ const dbPath = sqlitePathFromDatabaseUrl(process.env.DATABASE_URL ?? "");
22
+ if (dbPath) {
23
+ await removeIfExists(dbPath);
24
+ for (const suffix of DB_SIDECAR_SUFFIXES) {
25
+ await removeIfExists(`${dbPath}${suffix}`);
26
+ }
27
+ await fs.mkdir(path.dirname(dbPath), { recursive: true });
28
+ }
29
+
30
+ const emailDirectory = process.env.EMAIL_OUTPUT_DIRECTORY
31
+ ? path.resolve(process.cwd(), process.env.EMAIL_OUTPUT_DIRECTORY)
32
+ : null;
33
+
34
+ if (emailDirectory) {
35
+ await removeIfExists(emailDirectory);
36
+ await fs.mkdir(emailDirectory, { recursive: true });
37
+ }
38
+ }
39
+
40
+ void reset();
@@ -1,244 +1,246 @@
1
- /**
2
- * E2E fixture seed — used only by the e2e package's prepare scripts, never by
3
- * generated apps (the CLI excludes this file). The user-facing demo seed lives
4
- * in ./seed.ts.
5
- */
6
- import { hashPassword } from "better-auth/crypto";
7
- import { eq } from "drizzle-orm";
8
- import { v4 as uuidv4 } from "uuid";
9
- import { orm, schema } from "./db";
10
-
11
- const profile = process.env.AUTH_E2E_PROFILE ?? "standard";
12
- const ADMIN_EMAIL = `admin.${profile}@auth-e2e.local`;
13
- const ADMIN_PASSWORD = "password1234";
14
-
15
- function slugify(value: string): string {
16
- return value
17
- .trim()
18
- .toLowerCase()
19
- .replace(/[^a-z0-9]+/g, "-")
20
- .replace(/^-+|-+$/g, "");
21
- }
22
-
23
- async function ensureUser({
24
- id,
25
- name,
26
- email,
27
- password,
28
- role = "user",
29
- emailVerified = true,
30
- }: {
31
- id: string;
32
- name: string;
33
- email: string;
34
- password?: string;
35
- role?: string;
36
- emailVerified?: boolean;
37
- }) {
38
- const [existingUser] = await orm
39
- .select()
40
- .from(schema.users)
41
- .where(eq(schema.users.email, email))
42
- .limit(1);
43
- if (existingUser) return existingUser;
44
-
45
- const [user] = await orm
46
- .insert(schema.users)
47
- .values({
48
- id,
49
- name,
50
- email,
51
- emailVerified,
52
- role,
53
- createdAt: new Date(),
54
- updatedAt: new Date(),
55
- })
56
- .returning();
57
-
58
- if (!user) throw new Error(`Failed to seed user ${email}`);
59
-
60
- if (password) {
61
- await orm.insert(schema.accounts).values({
62
- id: uuidv4(),
63
- accountId: user.id,
64
- providerId: "credential",
65
- userId: user.id,
66
- password: await hashPassword(password),
67
- createdAt: new Date(),
68
- updatedAt: new Date(),
69
- });
70
- }
71
-
72
- return user;
73
- }
74
-
75
- async function ensureOrganization(userId: string) {
76
- const organizationId = `auth-e2e-enterprise-${profile}`;
77
- const teamId = `auth-e2e-team-${profile}`;
78
-
79
- const [existingOrganization] = await orm
80
- .select()
81
- .from(schema.organizations)
82
- .where(eq(schema.organizations.id, organizationId))
83
- .limit(1);
84
-
85
- if (!existingOrganization) {
86
- await orm.insert(schema.organizations).values({
87
- id: organizationId,
88
- name: `Auth E2E Enterprise ${profile}`,
89
- slug: `auth-e2e-enterprise-${profile}`,
90
- type: "enterprise",
91
- createdAt: new Date(),
92
- });
93
- }
94
-
95
- const [existingMember] = await orm
96
- .select()
97
- .from(schema.members)
98
- .where(eq(schema.members.userId, userId))
99
- .limit(1);
100
-
101
- if (!existingMember) {
102
- await orm.insert(schema.members).values({
103
- id: uuidv4(),
104
- organizationId,
105
- userId,
106
- role: "owner",
107
- createdAt: new Date(),
108
- });
109
- }
110
-
111
- const [existingTeam] = await orm
112
- .select()
113
- .from(schema.teams)
114
- .where(eq(schema.teams.id, teamId))
115
- .limit(1);
116
-
117
- if (!existingTeam) {
118
- await orm.insert(schema.teams).values({
119
- id: teamId,
120
- name: "Editorial",
121
- organizationId,
122
- createdAt: new Date(),
123
- updatedAt: new Date(),
124
- });
125
- }
126
-
127
- const [existingTeamMember] = await orm
128
- .select()
129
- .from(schema.teamMembers)
130
- .where(eq(schema.teamMembers.userId, userId))
131
- .limit(1);
132
-
133
- if (!existingTeamMember) {
134
- await orm.insert(schema.teamMembers).values({
135
- id: uuidv4(),
136
- teamId,
137
- userId,
138
- role: "owner",
139
- createdAt: new Date(),
140
- });
141
- }
142
-
143
- return { organizationId, teamId };
144
- }
145
-
146
- async function seedPosts(userId: string, organizationId: string) {
147
- const existingPosts = await orm.select().from(schema.posts).limit(1);
148
- if (existingPosts.length > 0) return;
149
-
150
- const posts = [
151
- {
152
- title: "A small blog that protects auth changes",
153
- excerpt: "The first fixture post explains why the E2E app exists.",
154
- content:
155
- "This post is intentionally simple. It gives Playwright a protected application surface after authentication succeeds.",
156
- },
157
- {
158
- title: "Organization invites should never be guessed",
159
- excerpt: "Invite links are generated by Better Auth and captured by stored email previews.",
160
- content:
161
- "The E2E suite verifies that organization invitation emails contain the correct app URL and can be accepted by a new user.",
162
- },
163
- ];
164
-
165
- await orm.insert(schema.posts).values(
166
- posts.map((post, index) => ({
167
- id: uuidv4(),
168
- ...post,
169
- slug: `${slugify(post.title)}-${profile}-${index}`,
170
- authorUserId: userId,
171
- organizationId,
172
- createdAt: new Date(Date.now() - index * 60_000),
173
- }))
174
- );
175
- }
176
-
177
- async function seedExpiredWaitlistCode() {
178
- const [existing] = await orm
179
- .select()
180
- .from(schema.waitlist)
181
- .where(eq(schema.waitlist.code, "expired-waitlist-code"))
182
- .limit(1);
183
- if (existing) return;
184
-
185
- await orm.insert(schema.waitlist).values({
186
- id: uuidv4(),
187
- email: `expired.${profile}@auth-e2e.local`,
188
- name: "Expired Invite",
189
- type: "WAITLIST",
190
- status: "INVITED",
191
- code: "expired-waitlist-code",
192
- expiresAt: new Date(Date.now() - 60_000),
193
- createdAt: new Date(),
194
- updatedAt: new Date(),
195
- });
196
- }
197
-
198
- async function seedProvisionedClaimUser() {
199
- const user = await ensureUser({
200
- id: `auth-e2e-claim-user-${profile}`,
201
- name: "Provisioned Claim User",
202
- email: `claim.${profile}@provisioned.auth-e2e.local`,
203
- role: "user",
204
- emailVerified: false,
205
- });
206
-
207
- const [existingClaim] = await orm
208
- .select()
209
- .from(schema.waitlist)
210
- .where(eq(schema.waitlist.claimUserId, user.id))
211
- .limit(1);
212
- if (existingClaim) return user;
213
-
214
- await orm.insert(schema.waitlist).values({
215
- id: `auth-e2e-claim-${profile}`,
216
- type: "ACCOUNT_CLAIM",
217
- status: "INVITED",
218
- claimUserId: user.id,
219
- code: uuidv4(),
220
- expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
221
- createdAt: new Date(),
222
- updatedAt: new Date(),
223
- });
224
-
225
- return user;
226
- }
227
-
228
- async function seed() {
229
- const admin = await ensureUser({
230
- id: `auth-e2e-admin-${profile}`,
231
- name: "Auth E2E Admin",
232
- email: ADMIN_EMAIL,
233
- password: ADMIN_PASSWORD,
234
- role: "admin",
235
- });
236
- const { organizationId } = await ensureOrganization(admin.id);
237
- await seedPosts(admin.id, organizationId);
238
- await seedExpiredWaitlistCode();
239
- await seedProvisionedClaimUser();
240
-
241
- console.info(`Seeded ${profile}. Admin login: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}`);
242
- }
243
-
244
- void seed();
1
+ /**
2
+ * E2E fixture seed — used only by the e2e package's prepare scripts, never by
3
+ * generated apps (the CLI excludes this file). The user-facing demo seed lives
4
+ * in ./seed.ts.
5
+ */
6
+ import { hashPassword } from "better-auth/crypto";
7
+ import { eq } from "drizzle-orm";
8
+ import { v4 as uuidv4 } from "uuid";
9
+ import { orm, schema } from "./db";
10
+ import { ensureDevServerStopped } from "./guard";
11
+
12
+ const profile = process.env.AUTH_E2E_PROFILE ?? "standard";
13
+ const ADMIN_EMAIL = `admin.${profile}@auth-e2e.local`;
14
+ const ADMIN_PASSWORD = "password1234";
15
+
16
+ function slugify(value: string): string {
17
+ return value
18
+ .trim()
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, "-")
21
+ .replace(/^-+|-+$/g, "");
22
+ }
23
+
24
+ async function ensureUser({
25
+ id,
26
+ name,
27
+ email,
28
+ password,
29
+ role = "user",
30
+ emailVerified = true,
31
+ }: {
32
+ id: string;
33
+ name: string;
34
+ email: string;
35
+ password?: string;
36
+ role?: string;
37
+ emailVerified?: boolean;
38
+ }) {
39
+ const [existingUser] = await orm
40
+ .select()
41
+ .from(schema.users)
42
+ .where(eq(schema.users.email, email))
43
+ .limit(1);
44
+ if (existingUser) return existingUser;
45
+
46
+ const [user] = await orm
47
+ .insert(schema.users)
48
+ .values({
49
+ id,
50
+ name,
51
+ email,
52
+ emailVerified,
53
+ role,
54
+ createdAt: new Date(),
55
+ updatedAt: new Date(),
56
+ })
57
+ .returning();
58
+
59
+ if (!user) throw new Error(`Failed to seed user ${email}`);
60
+
61
+ if (password) {
62
+ await orm.insert(schema.accounts).values({
63
+ id: uuidv4(),
64
+ accountId: user.id,
65
+ providerId: "credential",
66
+ userId: user.id,
67
+ password: await hashPassword(password),
68
+ createdAt: new Date(),
69
+ updatedAt: new Date(),
70
+ });
71
+ }
72
+
73
+ return user;
74
+ }
75
+
76
+ async function ensureOrganization(userId: string) {
77
+ const organizationId = `auth-e2e-enterprise-${profile}`;
78
+ const teamId = `auth-e2e-team-${profile}`;
79
+
80
+ const [existingOrganization] = await orm
81
+ .select()
82
+ .from(schema.organizations)
83
+ .where(eq(schema.organizations.id, organizationId))
84
+ .limit(1);
85
+
86
+ if (!existingOrganization) {
87
+ await orm.insert(schema.organizations).values({
88
+ id: organizationId,
89
+ name: `Auth E2E Enterprise ${profile}`,
90
+ slug: `auth-e2e-enterprise-${profile}`,
91
+ type: "enterprise",
92
+ createdAt: new Date(),
93
+ });
94
+ }
95
+
96
+ const [existingMember] = await orm
97
+ .select()
98
+ .from(schema.members)
99
+ .where(eq(schema.members.userId, userId))
100
+ .limit(1);
101
+
102
+ if (!existingMember) {
103
+ await orm.insert(schema.members).values({
104
+ id: uuidv4(),
105
+ organizationId,
106
+ userId,
107
+ role: "owner",
108
+ createdAt: new Date(),
109
+ });
110
+ }
111
+
112
+ const [existingTeam] = await orm
113
+ .select()
114
+ .from(schema.teams)
115
+ .where(eq(schema.teams.id, teamId))
116
+ .limit(1);
117
+
118
+ if (!existingTeam) {
119
+ await orm.insert(schema.teams).values({
120
+ id: teamId,
121
+ name: "Editorial",
122
+ organizationId,
123
+ createdAt: new Date(),
124
+ updatedAt: new Date(),
125
+ });
126
+ }
127
+
128
+ const [existingTeamMember] = await orm
129
+ .select()
130
+ .from(schema.teamMembers)
131
+ .where(eq(schema.teamMembers.userId, userId))
132
+ .limit(1);
133
+
134
+ if (!existingTeamMember) {
135
+ await orm.insert(schema.teamMembers).values({
136
+ id: uuidv4(),
137
+ teamId,
138
+ userId,
139
+ role: "owner",
140
+ createdAt: new Date(),
141
+ });
142
+ }
143
+
144
+ return { organizationId, teamId };
145
+ }
146
+
147
+ async function seedPosts(userId: string, organizationId: string) {
148
+ const existingPosts = await orm.select().from(schema.posts).limit(1);
149
+ if (existingPosts.length > 0) return;
150
+
151
+ const posts = [
152
+ {
153
+ title: "A small blog that protects auth changes",
154
+ excerpt: "The first fixture post explains why the E2E app exists.",
155
+ content:
156
+ "This post is intentionally simple. It gives Playwright a protected application surface after authentication succeeds.",
157
+ },
158
+ {
159
+ title: "Organization invites should never be guessed",
160
+ excerpt: "Invite links are generated by Better Auth and captured by stored email previews.",
161
+ content:
162
+ "The E2E suite verifies that organization invitation emails contain the correct app URL and can be accepted by a new user.",
163
+ },
164
+ ];
165
+
166
+ await orm.insert(schema.posts).values(
167
+ posts.map((post, index) => ({
168
+ id: uuidv4(),
169
+ ...post,
170
+ slug: `${slugify(post.title)}-${profile}-${index}`,
171
+ authorUserId: userId,
172
+ organizationId,
173
+ createdAt: new Date(Date.now() - index * 60_000),
174
+ }))
175
+ );
176
+ }
177
+
178
+ async function seedExpiredWaitlistCode() {
179
+ const [existing] = await orm
180
+ .select()
181
+ .from(schema.waitlist)
182
+ .where(eq(schema.waitlist.code, "expired-waitlist-code"))
183
+ .limit(1);
184
+ if (existing) return;
185
+
186
+ await orm.insert(schema.waitlist).values({
187
+ id: uuidv4(),
188
+ email: `expired.${profile}@auth-e2e.local`,
189
+ name: "Expired Invite",
190
+ type: "WAITLIST",
191
+ status: "INVITED",
192
+ code: "expired-waitlist-code",
193
+ expiresAt: new Date(Date.now() - 60_000),
194
+ createdAt: new Date(),
195
+ updatedAt: new Date(),
196
+ });
197
+ }
198
+
199
+ async function seedProvisionedClaimUser() {
200
+ const user = await ensureUser({
201
+ id: `auth-e2e-claim-user-${profile}`,
202
+ name: "Provisioned Claim User",
203
+ email: `claim.${profile}@provisioned.auth-e2e.local`,
204
+ role: "user",
205
+ emailVerified: false,
206
+ });
207
+
208
+ const [existingClaim] = await orm
209
+ .select()
210
+ .from(schema.waitlist)
211
+ .where(eq(schema.waitlist.claimUserId, user.id))
212
+ .limit(1);
213
+ if (existingClaim) return user;
214
+
215
+ await orm.insert(schema.waitlist).values({
216
+ id: `auth-e2e-claim-${profile}`,
217
+ type: "ACCOUNT_CLAIM",
218
+ status: "INVITED",
219
+ claimUserId: user.id,
220
+ code: uuidv4(),
221
+ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
222
+ createdAt: new Date(),
223
+ updatedAt: new Date(),
224
+ });
225
+
226
+ return user;
227
+ }
228
+
229
+ async function seed() {
230
+ await ensureDevServerStopped();
231
+ const admin = await ensureUser({
232
+ id: `auth-e2e-admin-${profile}`,
233
+ name: "Auth E2E Admin",
234
+ email: ADMIN_EMAIL,
235
+ password: ADMIN_PASSWORD,
236
+ role: "admin",
237
+ });
238
+ const { organizationId } = await ensureOrganization(admin.id);
239
+ await seedPosts(admin.id, organizationId);
240
+ await seedExpiredWaitlistCode();
241
+ await seedProvisionedClaimUser();
242
+
243
+ console.info(`Seeded ${profile}. Admin login: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}`);
244
+ }
245
+
246
+ void seed();