@opensaas/stack-cli 0.5.0 → 0.6.1
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/README.md +76 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +94 -268
- package/dist/commands/migrate.js.map +1 -1
- package/package.json +7 -2
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -462
- package/CLAUDE.md +0 -298
- package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
- package/src/commands/dev.test.ts +0 -215
- package/src/commands/dev.ts +0 -48
- package/src/commands/generate.test.ts +0 -282
- package/src/commands/generate.ts +0 -182
- package/src/commands/init.ts +0 -34
- package/src/commands/mcp.ts +0 -135
- package/src/commands/migrate.ts +0 -534
- package/src/generator/__snapshots__/context.test.ts.snap +0 -361
- package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
- package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
- package/src/generator/context.test.ts +0 -139
- package/src/generator/context.ts +0 -227
- package/src/generator/index.ts +0 -7
- package/src/generator/lists.test.ts +0 -335
- package/src/generator/lists.ts +0 -140
- package/src/generator/plugin-types.ts +0 -147
- package/src/generator/prisma-config.ts +0 -46
- package/src/generator/prisma-extensions.ts +0 -159
- package/src/generator/prisma.test.ts +0 -211
- package/src/generator/prisma.ts +0 -161
- package/src/generator/types.test.ts +0 -268
- package/src/generator/types.ts +0 -537
- package/src/index.ts +0 -46
- package/src/mcp/lib/documentation-provider.ts +0 -710
- package/src/mcp/lib/features/catalog.ts +0 -301
- package/src/mcp/lib/generators/feature-generator.ts +0 -598
- package/src/mcp/lib/types.ts +0 -89
- package/src/mcp/lib/wizards/migration-wizard.ts +0 -584
- package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
- package/src/mcp/server/index.ts +0 -361
- package/src/mcp/server/stack-mcp-server.ts +0 -544
- package/src/migration/generators/migration-generator.ts +0 -675
- package/src/migration/introspectors/index.ts +0 -12
- package/src/migration/introspectors/keystone-introspector.ts +0 -296
- package/src/migration/introspectors/nextjs-introspector.ts +0 -209
- package/src/migration/introspectors/prisma-introspector.ts +0 -233
- package/src/migration/types.ts +0 -92
- package/tests/introspectors/keystone-introspector.test.ts +0 -255
- package/tests/introspectors/nextjs-introspector.test.ts +0 -302
- package/tests/introspectors/prisma-introspector.test.ts +0 -268
- package/tests/migration-generator.test.ts +0 -592
- package/tests/migration-wizard.test.ts +0 -442
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -26
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { NextjsIntrospector } from '../../src/migration/introspectors/nextjs-introspector.js'
|
|
3
|
-
import fs from 'fs-extra'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import os from 'os'
|
|
6
|
-
|
|
7
|
-
describe('NextjsIntrospector', () => {
|
|
8
|
-
let introspector: NextjsIntrospector
|
|
9
|
-
let tempDir: string
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
introspector = new NextjsIntrospector()
|
|
13
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'nextjs-test-'))
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
afterEach(async () => {
|
|
17
|
-
await fs.remove(tempDir)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('should detect Next.js version', async () => {
|
|
21
|
-
const pkg = {
|
|
22
|
-
dependencies: {
|
|
23
|
-
next: '^14.0.0',
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
27
|
-
|
|
28
|
-
const result = await introspector.introspect(tempDir)
|
|
29
|
-
|
|
30
|
-
expect(result.version).toBe('14.0.0')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should detect app router', async () => {
|
|
34
|
-
const pkg = {
|
|
35
|
-
dependencies: {
|
|
36
|
-
next: '14.0.0',
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
40
|
-
await fs.ensureDir(path.join(tempDir, 'app'))
|
|
41
|
-
|
|
42
|
-
const result = await introspector.introspect(tempDir)
|
|
43
|
-
|
|
44
|
-
expect(result.routerType).toBe('app')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('should detect pages router', async () => {
|
|
48
|
-
const pkg = {
|
|
49
|
-
dependencies: {
|
|
50
|
-
next: '14.0.0',
|
|
51
|
-
},
|
|
52
|
-
}
|
|
53
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
54
|
-
await fs.ensureDir(path.join(tempDir, 'pages'))
|
|
55
|
-
|
|
56
|
-
const result = await introspector.introspect(tempDir)
|
|
57
|
-
|
|
58
|
-
expect(result.routerType).toBe('pages')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('should detect both routers', async () => {
|
|
62
|
-
const pkg = {
|
|
63
|
-
dependencies: {
|
|
64
|
-
next: '14.0.0',
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
68
|
-
await fs.ensureDir(path.join(tempDir, 'app'))
|
|
69
|
-
await fs.ensureDir(path.join(tempDir, 'pages'))
|
|
70
|
-
|
|
71
|
-
const result = await introspector.introspect(tempDir)
|
|
72
|
-
|
|
73
|
-
expect(result.routerType).toBe('both')
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('should detect TypeScript usage', async () => {
|
|
77
|
-
const pkg = {
|
|
78
|
-
dependencies: {
|
|
79
|
-
next: '14.0.0',
|
|
80
|
-
},
|
|
81
|
-
}
|
|
82
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
83
|
-
await fs.writeJSON(path.join(tempDir, 'tsconfig.json'), {
|
|
84
|
-
compilerOptions: {
|
|
85
|
-
target: 'es2015',
|
|
86
|
-
},
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
const result = await introspector.introspect(tempDir)
|
|
90
|
-
|
|
91
|
-
expect(result.typescript).toBe(true)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('should detect auth libraries', async () => {
|
|
95
|
-
const pkg = {
|
|
96
|
-
dependencies: {
|
|
97
|
-
next: '14.0.0',
|
|
98
|
-
'next-auth': '^4.0.0',
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
102
|
-
|
|
103
|
-
const result = await introspector.introspect(tempDir)
|
|
104
|
-
|
|
105
|
-
expect(result.authLibrary).toBe('next-auth')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('should detect better-auth', async () => {
|
|
109
|
-
const pkg = {
|
|
110
|
-
dependencies: {
|
|
111
|
-
next: '14.0.0',
|
|
112
|
-
'better-auth': '^1.0.0',
|
|
113
|
-
},
|
|
114
|
-
}
|
|
115
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
116
|
-
|
|
117
|
-
const result = await introspector.introspect(tempDir)
|
|
118
|
-
|
|
119
|
-
expect(result.authLibrary).toBe('better-auth')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('should detect database libraries', async () => {
|
|
123
|
-
const pkg = {
|
|
124
|
-
dependencies: {
|
|
125
|
-
next: '14.0.0',
|
|
126
|
-
'@prisma/client': '^5.0.0',
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
130
|
-
|
|
131
|
-
const result = await introspector.introspect(tempDir)
|
|
132
|
-
|
|
133
|
-
expect(result.databaseLibrary).toBe('prisma')
|
|
134
|
-
expect(result.hasPrisma).toBe(true)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('should detect Prisma directory', async () => {
|
|
138
|
-
const pkg = {
|
|
139
|
-
dependencies: {
|
|
140
|
-
next: '14.0.0',
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
144
|
-
await fs.ensureDir(path.join(tempDir, 'prisma'))
|
|
145
|
-
|
|
146
|
-
const result = await introspector.introspect(tempDir)
|
|
147
|
-
|
|
148
|
-
expect(result.hasPrisma).toBe(true)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('should detect .env file', async () => {
|
|
152
|
-
const pkg = {
|
|
153
|
-
dependencies: {
|
|
154
|
-
next: '14.0.0',
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
158
|
-
await fs.writeFile(path.join(tempDir, '.env'), 'DATABASE_URL=...')
|
|
159
|
-
|
|
160
|
-
const result = await introspector.introspect(tempDir)
|
|
161
|
-
|
|
162
|
-
expect(result.hasEnvFile).toBe(true)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('should list all dependencies', async () => {
|
|
166
|
-
const pkg = {
|
|
167
|
-
dependencies: {
|
|
168
|
-
next: '14.0.0',
|
|
169
|
-
react: '18.0.0',
|
|
170
|
-
},
|
|
171
|
-
devDependencies: {
|
|
172
|
-
typescript: '5.0.0',
|
|
173
|
-
},
|
|
174
|
-
}
|
|
175
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
176
|
-
|
|
177
|
-
const result = await introspector.introspect(tempDir)
|
|
178
|
-
|
|
179
|
-
expect(result.existingDependencies).toContain('next')
|
|
180
|
-
expect(result.existingDependencies).toContain('react')
|
|
181
|
-
expect(result.existingDependencies).toContain('typescript')
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('should generate recommendations for pages router', async () => {
|
|
185
|
-
const pkg = {
|
|
186
|
-
dependencies: {
|
|
187
|
-
next: '14.0.0',
|
|
188
|
-
},
|
|
189
|
-
}
|
|
190
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
191
|
-
await fs.ensureDir(path.join(tempDir, 'pages'))
|
|
192
|
-
|
|
193
|
-
const result = await introspector.introspect(tempDir)
|
|
194
|
-
const recommendations = introspector.getRecommendations(result)
|
|
195
|
-
|
|
196
|
-
expect(recommendations.some((r) => r.includes('App Router'))).toBe(true)
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('should recommend Better-auth for non-better-auth projects', async () => {
|
|
200
|
-
const pkg = {
|
|
201
|
-
dependencies: {
|
|
202
|
-
next: '14.0.0',
|
|
203
|
-
'next-auth': '4.0.0',
|
|
204
|
-
},
|
|
205
|
-
}
|
|
206
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
207
|
-
|
|
208
|
-
const result = await introspector.introspect(tempDir)
|
|
209
|
-
const recommendations = introspector.getRecommendations(result)
|
|
210
|
-
|
|
211
|
-
expect(recommendations.some((r) => r.includes('Better-auth'))).toBe(true)
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('should recommend Prisma setup if missing', async () => {
|
|
215
|
-
const pkg = {
|
|
216
|
-
dependencies: {
|
|
217
|
-
next: '14.0.0',
|
|
218
|
-
},
|
|
219
|
-
}
|
|
220
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
221
|
-
|
|
222
|
-
const result = await introspector.introspect(tempDir)
|
|
223
|
-
const recommendations = introspector.getRecommendations(result)
|
|
224
|
-
|
|
225
|
-
expect(recommendations.some((r) => r.includes('Prisma'))).toBe(true)
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('should generate warnings for old Next.js versions', async () => {
|
|
229
|
-
const pkg = {
|
|
230
|
-
dependencies: {
|
|
231
|
-
next: '12.0.0',
|
|
232
|
-
},
|
|
233
|
-
}
|
|
234
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
235
|
-
|
|
236
|
-
const result = await introspector.introspect(tempDir)
|
|
237
|
-
const warnings = introspector.getWarnings(result)
|
|
238
|
-
|
|
239
|
-
expect(warnings.some((w) => w.includes('12.0.0'))).toBe(true)
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
it('should warn about MongoDB/Mongoose', async () => {
|
|
243
|
-
const pkg = {
|
|
244
|
-
dependencies: {
|
|
245
|
-
next: '14.0.0',
|
|
246
|
-
mongoose: '7.0.0',
|
|
247
|
-
},
|
|
248
|
-
}
|
|
249
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
250
|
-
|
|
251
|
-
const result = await introspector.introspect(tempDir)
|
|
252
|
-
const warnings = introspector.getWarnings(result)
|
|
253
|
-
|
|
254
|
-
expect(warnings.some((w) => w.includes('MongoDB'))).toBe(true)
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('should throw for missing package.json', async () => {
|
|
258
|
-
await expect(introspector.introspect(tempDir)).rejects.toThrow('package.json not found')
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
it('should detect router in src directory', async () => {
|
|
262
|
-
const pkg = {
|
|
263
|
-
dependencies: {
|
|
264
|
-
next: '14.0.0',
|
|
265
|
-
},
|
|
266
|
-
}
|
|
267
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
268
|
-
await fs.ensureDir(path.join(tempDir, 'src', 'app'))
|
|
269
|
-
|
|
270
|
-
const result = await introspector.introspect(tempDir)
|
|
271
|
-
|
|
272
|
-
expect(result.routerType).toBe('app')
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
it('should handle multiple auth libraries', async () => {
|
|
276
|
-
const pkg = {
|
|
277
|
-
dependencies: {
|
|
278
|
-
next: '14.0.0',
|
|
279
|
-
'@clerk/nextjs': '4.0.0',
|
|
280
|
-
},
|
|
281
|
-
}
|
|
282
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
283
|
-
|
|
284
|
-
const result = await introspector.introspect(tempDir)
|
|
285
|
-
|
|
286
|
-
expect(result.authLibrary).toBe('clerk')
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('should detect drizzle', async () => {
|
|
290
|
-
const pkg = {
|
|
291
|
-
dependencies: {
|
|
292
|
-
next: '14.0.0',
|
|
293
|
-
'drizzle-orm': '0.28.0',
|
|
294
|
-
},
|
|
295
|
-
}
|
|
296
|
-
await fs.writeJSON(path.join(tempDir, 'package.json'), pkg)
|
|
297
|
-
|
|
298
|
-
const result = await introspector.introspect(tempDir)
|
|
299
|
-
|
|
300
|
-
expect(result.databaseLibrary).toBe('drizzle')
|
|
301
|
-
})
|
|
302
|
-
})
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { PrismaIntrospector } from '../../src/migration/introspectors/prisma-introspector.js'
|
|
3
|
-
import fs from 'fs-extra'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import os from 'os'
|
|
6
|
-
|
|
7
|
-
describe('PrismaIntrospector', () => {
|
|
8
|
-
let introspector: PrismaIntrospector
|
|
9
|
-
let tempDir: string
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
introspector = new PrismaIntrospector()
|
|
13
|
-
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prisma-test-'))
|
|
14
|
-
await fs.ensureDir(path.join(tempDir, 'prisma'))
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
afterEach(async () => {
|
|
18
|
-
await fs.remove(tempDir)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('should parse a simple schema', async () => {
|
|
22
|
-
const schema = `
|
|
23
|
-
datasource db {
|
|
24
|
-
provider = "sqlite"
|
|
25
|
-
url = "file:./dev.db"
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
model User {
|
|
29
|
-
id String @id @default(cuid())
|
|
30
|
-
email String @unique
|
|
31
|
-
name String?
|
|
32
|
-
posts Post[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
model Post {
|
|
36
|
-
id String @id @default(cuid())
|
|
37
|
-
title String
|
|
38
|
-
author User @relation(fields: [authorId], references: [id])
|
|
39
|
-
authorId String
|
|
40
|
-
}
|
|
41
|
-
`
|
|
42
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
43
|
-
|
|
44
|
-
const result = await introspector.introspect(tempDir)
|
|
45
|
-
|
|
46
|
-
expect(result.provider).toBe('sqlite')
|
|
47
|
-
expect(result.models).toHaveLength(2)
|
|
48
|
-
|
|
49
|
-
const user = result.models.find((m) => m.name === 'User')
|
|
50
|
-
expect(user).toBeDefined()
|
|
51
|
-
expect(user!.fields).toHaveLength(4)
|
|
52
|
-
|
|
53
|
-
const post = result.models.find((m) => m.name === 'Post')
|
|
54
|
-
expect(post).toBeDefined()
|
|
55
|
-
expect(post!.hasRelations).toBe(true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should parse enums', async () => {
|
|
59
|
-
const schema = `
|
|
60
|
-
datasource db {
|
|
61
|
-
provider = "postgresql"
|
|
62
|
-
url = env("DATABASE_URL")
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
enum Role {
|
|
66
|
-
USER
|
|
67
|
-
ADMIN
|
|
68
|
-
MODERATOR
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
model User {
|
|
72
|
-
id String @id
|
|
73
|
-
role Role @default(USER)
|
|
74
|
-
}
|
|
75
|
-
`
|
|
76
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
77
|
-
|
|
78
|
-
const result = await introspector.introspect(tempDir)
|
|
79
|
-
|
|
80
|
-
expect(result.enums).toHaveLength(1)
|
|
81
|
-
expect(result.enums[0].name).toBe('Role')
|
|
82
|
-
expect(result.enums[0].values).toEqual(['USER', 'ADMIN', 'MODERATOR'])
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('should handle optional and list fields', async () => {
|
|
86
|
-
const schema = `
|
|
87
|
-
datasource db {
|
|
88
|
-
provider = "sqlite"
|
|
89
|
-
url = "file:./dev.db"
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
model User {
|
|
93
|
-
id String @id
|
|
94
|
-
name String?
|
|
95
|
-
emails String[]
|
|
96
|
-
isActive Boolean @default(true)
|
|
97
|
-
}
|
|
98
|
-
`
|
|
99
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
100
|
-
|
|
101
|
-
const result = await introspector.introspect(tempDir)
|
|
102
|
-
const user = result.models[0]
|
|
103
|
-
|
|
104
|
-
const nameField = user.fields.find((f) => f.name === 'name')
|
|
105
|
-
expect(nameField!.isRequired).toBe(false)
|
|
106
|
-
|
|
107
|
-
const emailsField = user.fields.find((f) => f.name === 'emails')
|
|
108
|
-
expect(emailsField!.isList).toBe(true)
|
|
109
|
-
|
|
110
|
-
const isActiveField = user.fields.find((f) => f.name === 'isActive')
|
|
111
|
-
expect(isActiveField!.defaultValue).toBe('true')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('should parse field attributes correctly', async () => {
|
|
115
|
-
const schema = `
|
|
116
|
-
datasource db {
|
|
117
|
-
provider = "postgresql"
|
|
118
|
-
url = env("DATABASE_URL")
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
model User {
|
|
122
|
-
id String @id @default(cuid())
|
|
123
|
-
email String @unique
|
|
124
|
-
username String
|
|
125
|
-
age Int
|
|
126
|
-
}
|
|
127
|
-
`
|
|
128
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
129
|
-
|
|
130
|
-
const result = await introspector.introspect(tempDir)
|
|
131
|
-
const user = result.models[0]
|
|
132
|
-
|
|
133
|
-
const idField = user.fields.find((f) => f.name === 'id')
|
|
134
|
-
expect(idField!.isId).toBe(true)
|
|
135
|
-
expect(idField!.defaultValue).toBe('cuid()')
|
|
136
|
-
|
|
137
|
-
const emailField = user.fields.find((f) => f.name === 'email')
|
|
138
|
-
expect(emailField!.isUnique).toBe(true)
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('should parse relationships correctly', async () => {
|
|
142
|
-
const schema = `
|
|
143
|
-
datasource db {
|
|
144
|
-
provider = "sqlite"
|
|
145
|
-
url = "file:./dev.db"
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
model Post {
|
|
149
|
-
id String @id
|
|
150
|
-
author User @relation(fields: [authorId], references: [id])
|
|
151
|
-
authorId String
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
model User {
|
|
155
|
-
id String @id
|
|
156
|
-
posts Post[]
|
|
157
|
-
}
|
|
158
|
-
`
|
|
159
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
160
|
-
|
|
161
|
-
const result = await introspector.introspect(tempDir)
|
|
162
|
-
const post = result.models.find((m) => m.name === 'Post')
|
|
163
|
-
|
|
164
|
-
const authorField = post!.fields.find((f) => f.name === 'author')
|
|
165
|
-
expect(authorField!.relation).toBeDefined()
|
|
166
|
-
expect(authorField!.relation!.model).toBe('User')
|
|
167
|
-
expect(authorField!.relation!.fields).toEqual(['authorId'])
|
|
168
|
-
expect(authorField!.relation!.references).toEqual(['id'])
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('should map Prisma types to OpenSaaS types', () => {
|
|
172
|
-
expect(introspector.mapPrismaTypeToOpenSaas('String')).toEqual({ type: 'text', import: 'text' })
|
|
173
|
-
expect(introspector.mapPrismaTypeToOpenSaas('Int')).toEqual({
|
|
174
|
-
type: 'integer',
|
|
175
|
-
import: 'integer',
|
|
176
|
-
})
|
|
177
|
-
expect(introspector.mapPrismaTypeToOpenSaas('Boolean')).toEqual({
|
|
178
|
-
type: 'checkbox',
|
|
179
|
-
import: 'checkbox',
|
|
180
|
-
})
|
|
181
|
-
expect(introspector.mapPrismaTypeToOpenSaas('DateTime')).toEqual({
|
|
182
|
-
type: 'timestamp',
|
|
183
|
-
import: 'timestamp',
|
|
184
|
-
})
|
|
185
|
-
expect(introspector.mapPrismaTypeToOpenSaas('Json')).toEqual({ type: 'json', import: 'json' })
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
it('should generate warnings for unsupported types', async () => {
|
|
189
|
-
const schema = `
|
|
190
|
-
datasource db {
|
|
191
|
-
provider = "postgresql"
|
|
192
|
-
url = env("DATABASE_URL")
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
model Data {
|
|
196
|
-
id String @id
|
|
197
|
-
bigValue BigInt
|
|
198
|
-
decimal Decimal
|
|
199
|
-
bytes Bytes
|
|
200
|
-
}
|
|
201
|
-
`
|
|
202
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
203
|
-
|
|
204
|
-
const result = await introspector.introspect(tempDir)
|
|
205
|
-
const warnings = introspector.getWarnings(result)
|
|
206
|
-
|
|
207
|
-
expect(warnings).toHaveLength(3)
|
|
208
|
-
expect(warnings[0]).toContain('BigInt')
|
|
209
|
-
expect(warnings[1]).toContain('Decimal')
|
|
210
|
-
expect(warnings[2]).toContain('Bytes')
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('should throw for missing schema', async () => {
|
|
214
|
-
await expect(introspector.introspect(tempDir, 'nonexistent.prisma')).rejects.toThrow(
|
|
215
|
-
'Schema file not found',
|
|
216
|
-
)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('should handle comments in schema', async () => {
|
|
220
|
-
const schema = `
|
|
221
|
-
datasource db {
|
|
222
|
-
provider = "sqlite"
|
|
223
|
-
url = "file:./dev.db"
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// This is a comment
|
|
227
|
-
model User {
|
|
228
|
-
id String @id // Comment after field
|
|
229
|
-
// Another comment
|
|
230
|
-
email String
|
|
231
|
-
}
|
|
232
|
-
`
|
|
233
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
234
|
-
|
|
235
|
-
const result = await introspector.introspect(tempDir)
|
|
236
|
-
const user = result.models[0]
|
|
237
|
-
|
|
238
|
-
expect(user.fields).toHaveLength(2)
|
|
239
|
-
expect(user.fields.find((f) => f.name === 'id')).toBeDefined()
|
|
240
|
-
expect(user.fields.find((f) => f.name === 'email')).toBeDefined()
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('should handle model-level attributes', async () => {
|
|
244
|
-
const schema = `
|
|
245
|
-
datasource db {
|
|
246
|
-
provider = "postgresql"
|
|
247
|
-
url = env("DATABASE_URL")
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
model User {
|
|
251
|
-
firstName String
|
|
252
|
-
lastName String
|
|
253
|
-
|
|
254
|
-
@@unique([firstName, lastName])
|
|
255
|
-
@@index([lastName])
|
|
256
|
-
}
|
|
257
|
-
`
|
|
258
|
-
await fs.writeFile(path.join(tempDir, 'prisma', 'schema.prisma'), schema)
|
|
259
|
-
|
|
260
|
-
const result = await introspector.introspect(tempDir)
|
|
261
|
-
const user = result.models[0]
|
|
262
|
-
|
|
263
|
-
// Should only include actual fields, not model-level attributes
|
|
264
|
-
expect(user.fields).toHaveLength(2)
|
|
265
|
-
expect(user.fields.find((f) => f.name === 'firstName')).toBeDefined()
|
|
266
|
-
expect(user.fields.find((f) => f.name === 'lastName')).toBeDefined()
|
|
267
|
-
})
|
|
268
|
-
})
|