@factiii/auth 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/init.mjs +449 -64
- package/dist/{chunk-EHI4P63M.mjs → chunk-KUYH4DBN.mjs} +8 -0
- package/dist/index.d.mts +285 -19
- package/dist/index.d.ts +285 -19
- package/dist/index.js +726 -390
- package/dist/index.mjs +724 -391
- package/dist/validators.d.mts +1 -1
- package/dist/validators.d.ts +1 -1
- package/dist/validators.mjs +1 -1
- package/package.json +29 -21
- package/dist/{hooks-BXNxNK4S.d.mts → hooks-yHGJ7C6_.d.mts} +2 -2
- package/dist/{hooks-BXNxNK4S.d.ts → hooks-yHGJ7C6_.d.ts} +2 -2
package/bin/init.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
@@ -17,21 +17,215 @@ function printHelp() {
|
|
|
17
17
|
@factiii/auth CLI
|
|
18
18
|
|
|
19
19
|
Usage:
|
|
20
|
-
npx @factiii/auth <command>
|
|
20
|
+
npx @factiii/auth <command> [--prisma | --drizzle]
|
|
21
21
|
|
|
22
22
|
Commands:
|
|
23
|
-
init
|
|
23
|
+
init Generate the auth schema for your ORM (auto-detected)
|
|
24
24
|
schema Print the schema path (for manual copying)
|
|
25
25
|
doctor Check your project setup for common issues
|
|
26
26
|
help Show this help message
|
|
27
27
|
|
|
28
|
+
Flags:
|
|
29
|
+
--prisma Force Prisma mode (skip auto-detection)
|
|
30
|
+
--drizzle Force Drizzle mode (skip auto-detection)
|
|
31
|
+
|
|
28
32
|
Examples:
|
|
29
|
-
npx @factiii/auth init
|
|
33
|
+
npx @factiii/auth init # auto-detects your ORM
|
|
34
|
+
npx @factiii/auth init --drizzle # force Drizzle schema
|
|
30
35
|
npx @factiii/auth doctor
|
|
31
36
|
`);
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Read package.json deps and detect ORM.
|
|
41
|
+
* Returns 'prisma' | 'drizzle' | 'both' | 'none'.
|
|
42
|
+
*/
|
|
43
|
+
function detectORMFromPackageJson() {
|
|
44
|
+
const packageJsonPath = resolve(process.cwd(), 'package.json');
|
|
45
|
+
if (!existsSync(packageJsonPath)) return 'none';
|
|
46
|
+
try {
|
|
47
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
48
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
49
|
+
return detectORM(deps);
|
|
50
|
+
} catch {
|
|
51
|
+
return 'none';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolve which ORM to use for init, considering CLI flags and auto-detection.
|
|
57
|
+
*/
|
|
58
|
+
function resolveORMForInit() {
|
|
59
|
+
if (args.includes('--prisma')) return 'prisma';
|
|
60
|
+
if (args.includes('--drizzle')) return 'drizzle';
|
|
61
|
+
|
|
62
|
+
const detected = detectORMFromPackageJson();
|
|
63
|
+
|
|
64
|
+
if (detected === 'both') {
|
|
65
|
+
console.log('Both @prisma/client and drizzle-orm detected.');
|
|
66
|
+
console.log('Use --prisma or --drizzle to specify which schema to generate:');
|
|
67
|
+
console.log(' npx @factiii/auth init --prisma');
|
|
68
|
+
console.log(' npx @factiii/auth init --drizzle');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (detected === 'none') {
|
|
73
|
+
console.log('No ORM detected in package.json.');
|
|
74
|
+
console.log('Install one first, or use a flag to force:');
|
|
75
|
+
console.log(' npm install @prisma/client prisma # then: npx @factiii/auth init');
|
|
76
|
+
console.log(' npm install drizzle-orm # then: npx @factiii/auth init');
|
|
77
|
+
console.log(' npx @factiii/auth init --prisma # force without installing first');
|
|
78
|
+
console.log(' npx @factiii/auth init --drizzle # force without installing first');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return detected; // 'prisma' or 'drizzle'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Drizzle schema template ─────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
const DRIZZLE_SCHEMA_TEMPLATE = `import { pgTable, pgEnum, serial, text, integer, boolean, timestamp, uniqueIndex } from 'drizzle-orm/pg-core';
|
|
88
|
+
import { relations } from 'drizzle-orm';
|
|
89
|
+
|
|
90
|
+
// ==============================================================================
|
|
91
|
+
// Enums
|
|
92
|
+
// ==============================================================================
|
|
93
|
+
|
|
94
|
+
export const userStatusEnum = pgEnum('UserStatus', ['ACTIVE', 'DEACTIVATED', 'BANNED']);
|
|
95
|
+
export const userTagEnum = pgEnum('UserTag', ['HUMAN', 'BOT']);
|
|
96
|
+
export const emailVerificationStatusEnum = pgEnum('EmailVerificationStatus', ['UNVERIFIED', 'PENDING', 'VERIFIED']);
|
|
97
|
+
export const oauthProviderEnum = pgEnum('OAuthProvider', ['GOOGLE', 'APPLE']);
|
|
98
|
+
|
|
99
|
+
// ==============================================================================
|
|
100
|
+
// Users
|
|
101
|
+
// ==============================================================================
|
|
102
|
+
|
|
103
|
+
export const users = pgTable('User', {
|
|
104
|
+
id: serial('id').primaryKey(),
|
|
105
|
+
createdAt: timestamp('createdAt').defaultNow().notNull(),
|
|
106
|
+
status: userStatusEnum('status').default('ACTIVE').notNull(),
|
|
107
|
+
email: text('email').unique().notNull(),
|
|
108
|
+
emailVerificationStatus: emailVerificationStatusEnum('emailVerificationStatus').default('UNVERIFIED').notNull(),
|
|
109
|
+
password: text('password'),
|
|
110
|
+
username: text('username').unique().notNull(),
|
|
111
|
+
twoFaEnabled: boolean('twoFaEnabled').default(false).notNull(),
|
|
112
|
+
oauthProvider: oauthProviderEnum('oauthProvider'),
|
|
113
|
+
oauthId: text('oauthId'),
|
|
114
|
+
tag: userTagEnum('tag').default('HUMAN').notNull(),
|
|
115
|
+
isActive: boolean('isActive').default(false).notNull(),
|
|
116
|
+
verifiedHumanAt: timestamp('verifiedHumanAt'),
|
|
117
|
+
otpForEmailVerification: text('otpForEmailVerification'),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ==============================================================================
|
|
121
|
+
// Sessions
|
|
122
|
+
// ==============================================================================
|
|
123
|
+
|
|
124
|
+
export const sessions = pgTable('Session', {
|
|
125
|
+
id: serial('id').primaryKey(),
|
|
126
|
+
socketId: text('socketId').unique(),
|
|
127
|
+
twoFaSecret: text('twoFaSecret').unique(),
|
|
128
|
+
issuedAt: timestamp('issuedAt').defaultNow().notNull(),
|
|
129
|
+
browserName: text('browserName').default('Unknown').notNull(),
|
|
130
|
+
lastUsed: timestamp('lastUsed').defaultNow().notNull(),
|
|
131
|
+
deviceId: integer('deviceId').references(() => devices.id),
|
|
132
|
+
userId: integer('userId').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
|
133
|
+
revokedAt: timestamp('revokedAt'),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ==============================================================================
|
|
137
|
+
// Admins
|
|
138
|
+
// ==============================================================================
|
|
139
|
+
|
|
140
|
+
export const admins = pgTable('Admin', {
|
|
141
|
+
userId: integer('userId').primaryKey().references(() => users.id, { onDelete: 'cascade' }),
|
|
142
|
+
ip: text('ip').notNull(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ==============================================================================
|
|
146
|
+
// Password Resets
|
|
147
|
+
// ==============================================================================
|
|
148
|
+
|
|
149
|
+
export const passwordResets = pgTable('PasswordReset', {
|
|
150
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
151
|
+
createdAt: timestamp('createdAt').defaultNow().notNull(),
|
|
152
|
+
userId: integer('userId').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
|
153
|
+
invalidatedAt: timestamp('invalidatedAt'),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ==============================================================================
|
|
157
|
+
// OTPs
|
|
158
|
+
// ==============================================================================
|
|
159
|
+
|
|
160
|
+
export const otps = pgTable('OTP', {
|
|
161
|
+
id: serial('id').primaryKey(),
|
|
162
|
+
code: integer('code').notNull(),
|
|
163
|
+
expiresAt: timestamp('expiresAt').notNull(),
|
|
164
|
+
userId: integer('userId').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ==============================================================================
|
|
168
|
+
// Devices
|
|
169
|
+
// ==============================================================================
|
|
170
|
+
|
|
171
|
+
export const devices = pgTable('Device', {
|
|
172
|
+
id: serial('id').primaryKey(),
|
|
173
|
+
pushToken: text('pushToken').unique().notNull(),
|
|
174
|
+
createdAt: timestamp('createdAt').defaultNow().notNull(),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ==============================================================================
|
|
178
|
+
// Join Tables (many-to-many)
|
|
179
|
+
// ==============================================================================
|
|
180
|
+
|
|
181
|
+
export const devicesToUsers = pgTable('_devices', {
|
|
182
|
+
deviceId: integer('A').references(() => devices.id, { onDelete: 'cascade' }).notNull(),
|
|
183
|
+
userId: integer('B').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ==============================================================================
|
|
187
|
+
// Relations
|
|
188
|
+
// ==============================================================================
|
|
189
|
+
|
|
190
|
+
export const usersRelations = relations(users, ({ many, one }) => ({
|
|
191
|
+
sessions: many(sessions),
|
|
192
|
+
passwordResets: many(passwordResets),
|
|
193
|
+
otps: many(otps),
|
|
194
|
+
devices: many(devicesToUsers),
|
|
195
|
+
admin: one(admins),
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
export const sessionsRelations = relations(sessions, ({ one }) => ({
|
|
199
|
+
user: one(users, { fields: [sessions.userId], references: [users.id] }),
|
|
200
|
+
device: one(devices, { fields: [sessions.deviceId], references: [devices.id] }),
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
export const adminsRelations = relations(admins, ({ one }) => ({
|
|
204
|
+
user: one(users, { fields: [admins.userId], references: [users.id] }),
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
export const passwordResetsRelations = relations(passwordResets, ({ one }) => ({
|
|
208
|
+
user: one(users, { fields: [passwordResets.userId], references: [users.id] }),
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
export const otpsRelations = relations(otps, ({ one }) => ({
|
|
212
|
+
user: one(users, { fields: [otps.userId], references: [users.id] }),
|
|
213
|
+
}));
|
|
214
|
+
|
|
215
|
+
export const devicesRelations = relations(devices, ({ many }) => ({
|
|
216
|
+
sessions: many(sessions),
|
|
217
|
+
users: many(devicesToUsers),
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
export const devicesToUsersRelations = relations(devicesToUsers, ({ one }) => ({
|
|
221
|
+
device: one(devices, { fields: [devicesToUsers.deviceId], references: [devices.id] }),
|
|
222
|
+
user: one(users, { fields: [devicesToUsers.userId], references: [users.id] }),
|
|
223
|
+
}));
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
// ── Init commands ───────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
function initPrisma() {
|
|
35
229
|
// Ensure prisma directory exists
|
|
36
230
|
if (!existsSync(targetDir)) {
|
|
37
231
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -48,7 +242,7 @@ function init() {
|
|
|
48
242
|
// Copy schema
|
|
49
243
|
try {
|
|
50
244
|
copyFileSync(schemaSource, targetFile);
|
|
51
|
-
console.log(`✓ Copied schema to ${targetFile}`);
|
|
245
|
+
console.log(`✓ Copied Prisma schema to ${targetFile}`);
|
|
52
246
|
console.log('');
|
|
53
247
|
console.log('Next steps:');
|
|
54
248
|
console.log(' 1. Review and customize the schema for your database provider');
|
|
@@ -61,12 +255,92 @@ function init() {
|
|
|
61
255
|
}
|
|
62
256
|
}
|
|
63
257
|
|
|
258
|
+
function initDrizzle() {
|
|
259
|
+
// Determine target location
|
|
260
|
+
const candidates = [
|
|
261
|
+
{ dir: 'src/db', file: 'src/db/auth-schema.ts' },
|
|
262
|
+
{ dir: 'drizzle', file: 'drizzle/auth-schema.ts' },
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
// Pick the first directory that already exists, or default to src/db
|
|
266
|
+
let target = candidates[0];
|
|
267
|
+
for (const c of candidates) {
|
|
268
|
+
if (existsSync(resolve(process.cwd(), c.dir))) {
|
|
269
|
+
target = c;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const targetDirDrizzle = resolve(process.cwd(), target.dir);
|
|
275
|
+
const targetFileDrizzle = resolve(process.cwd(), target.file);
|
|
276
|
+
|
|
277
|
+
// Ensure directory exists
|
|
278
|
+
if (!existsSync(targetDirDrizzle)) {
|
|
279
|
+
mkdirSync(targetDirDrizzle, { recursive: true });
|
|
280
|
+
console.log(`Created ${target.dir}/ directory`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check if file already exists
|
|
284
|
+
if (existsSync(targetFileDrizzle)) {
|
|
285
|
+
console.log(`⚠️ ${target.file} already exists`);
|
|
286
|
+
console.log(' To overwrite, delete it first and run again.');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
writeFileSync(targetFileDrizzle, DRIZZLE_SCHEMA_TEMPLATE);
|
|
292
|
+
console.log(`✓ Generated Drizzle schema at ${target.file}`);
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log('Next steps:');
|
|
295
|
+
console.log(' 1. Review and customize the schema (table names, column types, etc.)');
|
|
296
|
+
console.log(' 2. Import and merge into your existing schema (if you have one)');
|
|
297
|
+
console.log(' 3. Create a drizzle.config.ts if you haven\'t already');
|
|
298
|
+
console.log(' 4. Run: npx drizzle-kit generate');
|
|
299
|
+
console.log(' 5. Run: npx drizzle-kit migrate');
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log('Usage with @factiii/auth:');
|
|
302
|
+
console.log(' import { createDrizzleAdapter } from \'@factiii/auth\';');
|
|
303
|
+
console.log(` import * as schema from './${target.file.replace(/\.ts$/, '')}';`);
|
|
304
|
+
console.log(' const adapter = createDrizzleAdapter(db, {');
|
|
305
|
+
console.log(' users: schema.users,');
|
|
306
|
+
console.log(' sessions: schema.sessions,');
|
|
307
|
+
console.log(' otps: schema.otps,');
|
|
308
|
+
console.log(' passwordResets: schema.passwordResets,');
|
|
309
|
+
console.log(' devices: schema.devices,');
|
|
310
|
+
console.log(' admins: schema.admins,');
|
|
311
|
+
console.log(' devicesToUsers: schema.devicesToUsers,');
|
|
312
|
+
console.log(' });');
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.error('Failed to generate schema:', err.message);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function init() {
|
|
320
|
+
const orm = resolveORMForInit();
|
|
321
|
+
console.log(`Detected ORM: ${orm}\n`);
|
|
322
|
+
|
|
323
|
+
if (orm === 'prisma') {
|
|
324
|
+
initPrisma();
|
|
325
|
+
} else if (orm === 'drizzle') {
|
|
326
|
+
initDrizzle();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
64
330
|
function printSchemaPath() {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
331
|
+
const orm = resolveORMForInit();
|
|
332
|
+
|
|
333
|
+
if (orm === 'prisma') {
|
|
334
|
+
console.log('Prisma schema location:');
|
|
335
|
+
console.log(` ${schemaSource}`);
|
|
336
|
+
console.log('');
|
|
337
|
+
console.log('Copy manually:');
|
|
338
|
+
console.log(` cp "${schemaSource}" ./prisma/auth-schema.prisma`);
|
|
339
|
+
} else if (orm === 'drizzle') {
|
|
340
|
+
console.log('Drizzle schema:');
|
|
341
|
+
console.log(' Run: npx @factiii/auth init --drizzle');
|
|
342
|
+
console.log(' This generates a TypeScript schema file with all required tables.');
|
|
343
|
+
}
|
|
70
344
|
}
|
|
71
345
|
|
|
72
346
|
// ANSI color codes
|
|
@@ -182,6 +456,56 @@ function parseReferenceSchema() {
|
|
|
182
456
|
return { models, enums, modelFields };
|
|
183
457
|
}
|
|
184
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Detect which ORM the consumer's project uses.
|
|
461
|
+
* Returns 'prisma' | 'drizzle' | 'both' | 'none'.
|
|
462
|
+
*/
|
|
463
|
+
function detectORM(deps) {
|
|
464
|
+
const hasPrisma = Boolean(deps['@prisma/client']);
|
|
465
|
+
const hasDrizzle = Boolean(deps['drizzle-orm']);
|
|
466
|
+
if (hasPrisma && hasDrizzle) return 'both';
|
|
467
|
+
if (hasPrisma) return 'prisma';
|
|
468
|
+
if (hasDrizzle) return 'drizzle';
|
|
469
|
+
return 'none';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Look for Drizzle schema files (*.ts files exporting table definitions).
|
|
474
|
+
* Common patterns: src/db/schema.ts, drizzle/schema.ts, src/schema.ts
|
|
475
|
+
*/
|
|
476
|
+
function findDrizzleSchema() {
|
|
477
|
+
const candidates = [
|
|
478
|
+
'src/db/schema.ts',
|
|
479
|
+
'src/db/schema/index.ts',
|
|
480
|
+
'src/schema.ts',
|
|
481
|
+
'drizzle/schema.ts',
|
|
482
|
+
'db/schema.ts',
|
|
483
|
+
'src/db/schema.js',
|
|
484
|
+
'drizzle/schema.js',
|
|
485
|
+
];
|
|
486
|
+
|
|
487
|
+
for (const candidate of candidates) {
|
|
488
|
+
const fullPath = resolve(process.cwd(), candidate);
|
|
489
|
+
if (existsSync(fullPath)) {
|
|
490
|
+
return { found: true, location: candidate };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return { found: false, location: '' };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Check for drizzle config file (drizzle.config.ts or drizzle.config.js)
|
|
498
|
+
*/
|
|
499
|
+
function findDrizzleConfig() {
|
|
500
|
+
const candidates = ['drizzle.config.ts', 'drizzle.config.js', 'drizzle.config.mjs'];
|
|
501
|
+
for (const candidate of candidates) {
|
|
502
|
+
if (existsSync(resolve(process.cwd(), candidate))) {
|
|
503
|
+
return { found: true, location: candidate };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return { found: false, location: '' };
|
|
507
|
+
}
|
|
508
|
+
|
|
185
509
|
function doctor() {
|
|
186
510
|
console.log(`${colors.bold}${colors.cyan}Running diagnostics...${colors.reset}\n`);
|
|
187
511
|
|
|
@@ -197,24 +521,38 @@ function doctor() {
|
|
|
197
521
|
process.exit(1);
|
|
198
522
|
}
|
|
199
523
|
|
|
200
|
-
// Check 1: package.json exists
|
|
524
|
+
// Check 1: package.json exists and detect ORM
|
|
201
525
|
const packageJsonPath = resolve(process.cwd(), 'package.json');
|
|
526
|
+
let orm = 'none';
|
|
527
|
+
|
|
202
528
|
if (!existsSync(packageJsonPath)) {
|
|
203
529
|
fail('No package.json found in current directory');
|
|
204
530
|
issues++;
|
|
205
531
|
} else {
|
|
206
532
|
ok('package.json found');
|
|
207
533
|
|
|
208
|
-
// Check for @prisma/client dependency
|
|
209
534
|
try {
|
|
210
535
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
211
536
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
537
|
+
orm = detectORM(deps);
|
|
538
|
+
|
|
539
|
+
switch (orm) {
|
|
540
|
+
case 'prisma':
|
|
541
|
+
ok('@prisma/client found — using Prisma adapter');
|
|
542
|
+
break;
|
|
543
|
+
case 'drizzle':
|
|
544
|
+
ok('drizzle-orm found — using Drizzle adapter');
|
|
545
|
+
break;
|
|
546
|
+
case 'both':
|
|
547
|
+
ok('@prisma/client and drizzle-orm both found');
|
|
548
|
+
hint('You can use either createPrismaAdapter() or createDrizzleAdapter()');
|
|
549
|
+
break;
|
|
550
|
+
case 'none':
|
|
551
|
+
fail('No ORM detected — install @prisma/client or drizzle-orm');
|
|
552
|
+
hint('Prisma: npm install @prisma/client prisma');
|
|
553
|
+
hint('Drizzle: npm install drizzle-orm');
|
|
554
|
+
issues++;
|
|
555
|
+
break;
|
|
218
556
|
}
|
|
219
557
|
} catch (e) {
|
|
220
558
|
warn('Could not parse package.json');
|
|
@@ -222,70 +560,117 @@ function doctor() {
|
|
|
222
560
|
}
|
|
223
561
|
}
|
|
224
562
|
|
|
225
|
-
// Check 2:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
563
|
+
// Check 2: ORM-specific schema checks
|
|
564
|
+
if (orm === 'prisma' || orm === 'both') {
|
|
565
|
+
console.log(`\n${colors.bold}Prisma checks:${colors.reset}`);
|
|
566
|
+
|
|
567
|
+
const { found: schemaFound, schemaContent: schema, location: schemaLocation } = findPrismaSchema();
|
|
568
|
+
if (!schemaFound) {
|
|
569
|
+
fail('No Prisma schema found');
|
|
570
|
+
hint('Checked: prisma/schema.prisma, prisma/schema/*.prisma, prisma/*.prisma');
|
|
571
|
+
hint('Run: npx @factiii/auth init');
|
|
572
|
+
issues++;
|
|
573
|
+
} else {
|
|
574
|
+
ok(`Prisma schema found (${schemaLocation})`);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
// Check models
|
|
578
|
+
for (const model of reference.models) {
|
|
579
|
+
const regex = new RegExp(`model\\s+${model}\\s*\\{`, 'm');
|
|
580
|
+
if (regex.test(schema)) {
|
|
581
|
+
ok(`Model ${colors.cyan}${model}${colors.reset} found`);
|
|
582
|
+
} else {
|
|
583
|
+
fail(`Model ${colors.cyan}${model}${colors.reset} not found in schema`);
|
|
584
|
+
issues++;
|
|
585
|
+
}
|
|
245
586
|
}
|
|
246
|
-
}
|
|
247
587
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
588
|
+
// Check enums
|
|
589
|
+
for (const enumName of reference.enums) {
|
|
590
|
+
const regex = new RegExp(`enum\\s+${enumName}\\s*\\{`, 'm');
|
|
591
|
+
if (regex.test(schema)) {
|
|
592
|
+
ok(`Enum ${colors.cyan}${enumName}${colors.reset} found`);
|
|
593
|
+
} else {
|
|
594
|
+
fail(`Enum ${colors.cyan}${enumName}${colors.reset} not found in schema`);
|
|
595
|
+
issues++;
|
|
596
|
+
}
|
|
256
597
|
}
|
|
257
|
-
}
|
|
258
598
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
599
|
+
// Check fields for each model
|
|
600
|
+
for (const [model, fields] of Object.entries(reference.modelFields)) {
|
|
601
|
+
const modelMatch = schema.match(new RegExp(`model\\s+${model}\\s*\\{([^}]+)\\}`, 'm'));
|
|
602
|
+
if (modelMatch) {
|
|
603
|
+
const block = modelMatch[1];
|
|
604
|
+
for (const field of fields) {
|
|
605
|
+
const fieldRegex = new RegExp(`\\b${field}\\b`, 'm');
|
|
606
|
+
if (!fieldRegex.test(block)) {
|
|
607
|
+
warn(`${model}.${colors.cyan}${field}${colors.reset} field not found`);
|
|
608
|
+
warnings++;
|
|
609
|
+
}
|
|
269
610
|
}
|
|
270
611
|
}
|
|
271
612
|
}
|
|
613
|
+
} catch (e) {
|
|
614
|
+
warn('Could not parse Prisma schema');
|
|
615
|
+
warnings++;
|
|
272
616
|
}
|
|
273
|
-
}
|
|
274
|
-
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (orm === 'drizzle' || orm === 'both') {
|
|
621
|
+
console.log(`\n${colors.bold}Drizzle checks:${colors.reset}`);
|
|
622
|
+
|
|
623
|
+
const drizzleConfig = findDrizzleConfig();
|
|
624
|
+
if (drizzleConfig.found) {
|
|
625
|
+
ok(`Drizzle config found (${drizzleConfig.location})`);
|
|
626
|
+
} else {
|
|
627
|
+
warn('No drizzle.config.ts found (optional but recommended for migrations)');
|
|
628
|
+
warnings++;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const drizzleSchema = findDrizzleSchema();
|
|
632
|
+
if (drizzleSchema.found) {
|
|
633
|
+
ok(`Drizzle schema found (${drizzleSchema.location})`);
|
|
634
|
+
} else {
|
|
635
|
+
warn('No Drizzle schema file found');
|
|
636
|
+
hint('Expected in: src/db/schema.ts, drizzle/schema.ts, or src/schema.ts');
|
|
637
|
+
hint('See SETUP.md for required table definitions');
|
|
275
638
|
warnings++;
|
|
276
639
|
}
|
|
640
|
+
|
|
641
|
+
// Check that required tables are referenced (basic text scan of schema file)
|
|
642
|
+
if (drizzleSchema.found) {
|
|
643
|
+
try {
|
|
644
|
+
const schemaContent = readFileSync(resolve(process.cwd(), drizzleSchema.location), 'utf-8');
|
|
645
|
+
const requiredTables = ['users', 'sessions', 'admins', 'passwordResets', 'otps', 'devices'];
|
|
646
|
+
for (const table of requiredTables) {
|
|
647
|
+
// Look for pgTable/mysqlTable/sqliteTable calls or export names
|
|
648
|
+
const tableRegex = new RegExp(`(?:Table|export).*${table}`, 'i');
|
|
649
|
+
if (tableRegex.test(schemaContent)) {
|
|
650
|
+
ok(`Table ${colors.cyan}${table}${colors.reset} found`);
|
|
651
|
+
} else {
|
|
652
|
+
warn(`Table ${colors.cyan}${table}${colors.reset} not found in schema`);
|
|
653
|
+
hint(`createDrizzleAdapter() expects a '${table}' table reference`);
|
|
654
|
+
warnings++;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} catch (e) {
|
|
658
|
+
warn('Could not read Drizzle schema file');
|
|
659
|
+
warnings++;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
277
662
|
}
|
|
278
663
|
|
|
279
664
|
// Summary
|
|
280
665
|
console.log(`\n${colors.bold}--- Summary ---${colors.reset}`);
|
|
281
666
|
if (issues === 0 && warnings === 0) {
|
|
282
|
-
console.log(`${colors.green}${colors.bold}
|
|
667
|
+
console.log(`${colors.green}${colors.bold}All checks passed!${colors.reset} Your setup looks good.`);
|
|
283
668
|
} else {
|
|
284
669
|
if (issues > 0) {
|
|
285
|
-
console.log(`${colors.red}${colors.bold}
|
|
670
|
+
console.log(`${colors.red}${colors.bold}${issues} issue(s) found${colors.reset}`);
|
|
286
671
|
}
|
|
287
672
|
if (warnings > 0) {
|
|
288
|
-
console.log(`${colors.yellow}${colors.bold}
|
|
673
|
+
console.log(`${colors.yellow}${colors.bold}${warnings} warning(s) found${colors.reset}`);
|
|
289
674
|
}
|
|
290
675
|
}
|
|
291
676
|
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/validators.ts
|
|
2
9
|
import { z } from "zod";
|
|
3
10
|
var usernameValidationRegex = /^[a-zA-Z0-9_]+$/;
|
|
@@ -77,6 +84,7 @@ function createSchemas(extensions) {
|
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
export {
|
|
87
|
+
__require,
|
|
80
88
|
signupSchema,
|
|
81
89
|
loginSchema,
|
|
82
90
|
oAuthLoginSchema,
|