@opensaas/stack-cli 0.3.0 → 0.5.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +193 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +4 -13
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +473 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +20 -5
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/lists.d.ts.map +1 -1
- package/dist/generator/lists.js +33 -1
- package/dist/generator/lists.js.map +1 -1
- package/dist/generator/prisma-extensions.d.ts +11 -0
- package/dist/generator/prisma-extensions.d.ts.map +1 -0
- package/dist/generator/prisma-extensions.js +134 -0
- package/dist/generator/prisma-extensions.js.map +1 -0
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/generator/prisma.js +4 -0
- package/dist/generator/prisma.js.map +1 -1
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +151 -17
- package/dist/generator/types.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/lib/documentation-provider.d.ts +23 -0
- package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
- package/dist/mcp/lib/documentation-provider.js +471 -0
- package/dist/mcp/lib/documentation-provider.js.map +1 -1
- package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
- package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
- package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
- package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
- package/dist/mcp/server/index.d.ts.map +1 -1
- package/dist/mcp/server/index.js +103 -0
- package/dist/mcp/server/index.js.map +1 -1
- package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
- package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
- package/dist/mcp/server/stack-mcp-server.js +219 -0
- package/dist/mcp/server/stack-mcp-server.js.map +1 -1
- package/dist/migration/generators/migration-generator.d.ts +60 -0
- package/dist/migration/generators/migration-generator.d.ts.map +1 -0
- package/dist/migration/generators/migration-generator.js +510 -0
- package/dist/migration/generators/migration-generator.js.map +1 -0
- package/dist/migration/introspectors/index.d.ts +12 -0
- package/dist/migration/introspectors/index.d.ts.map +1 -0
- package/dist/migration/introspectors/index.js +10 -0
- package/dist/migration/introspectors/index.js.map +1 -0
- package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
- package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/keystone-introspector.js +229 -0
- package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
- package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
- package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/nextjs-introspector.js +159 -0
- package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
- package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
- package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
- package/dist/migration/introspectors/prisma-introspector.js +190 -0
- package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
- package/dist/migration/types.d.ts +86 -0
- package/dist/migration/types.d.ts.map +1 -0
- package/dist/migration/types.js +5 -0
- package/dist/migration/types.js.map +1 -0
- package/package.json +12 -9
- package/src/commands/__snapshots__/generate.test.ts.snap +92 -21
- package/src/commands/generate.ts +8 -19
- package/src/commands/migrate.ts +534 -0
- package/src/generator/__snapshots__/context.test.ts.snap +60 -15
- package/src/generator/__snapshots__/types.test.ts.snap +689 -95
- package/src/generator/context.test.ts +3 -1
- package/src/generator/context.ts +20 -5
- package/src/generator/index.ts +1 -1
- package/src/generator/lists.ts +39 -1
- package/src/generator/prisma-extensions.ts +159 -0
- package/src/generator/prisma.ts +5 -0
- package/src/generator/types.ts +204 -17
- package/src/index.ts +4 -0
- package/src/mcp/lib/documentation-provider.ts +507 -0
- package/src/mcp/lib/wizards/migration-wizard.ts +584 -0
- package/src/mcp/server/index.ts +121 -0
- package/src/mcp/server/stack-mcp-server.ts +243 -0
- package/src/migration/generators/migration-generator.ts +675 -0
- package/src/migration/introspectors/index.ts +12 -0
- package/src/migration/introspectors/keystone-introspector.ts +296 -0
- package/src/migration/introspectors/nextjs-introspector.ts +209 -0
- package/src/migration/introspectors/prisma-introspector.ts +233 -0
- package/src/migration/types.ts +92 -0
- package/tests/introspectors/keystone-introspector.test.ts +255 -0
- package/tests/introspectors/nextjs-introspector.test.ts +302 -0
- package/tests/introspectors/prisma-introspector.test.ts +268 -0
- package/tests/migration-generator.test.ts +592 -0
- package/tests/migration-wizard.test.ts +442 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/generator/type-patcher.d.ts +0 -13
- package/dist/generator/type-patcher.d.ts.map +0 -1
- package/dist/generator/type-patcher.js +0 -68
- package/dist/generator/type-patcher.js.map +0 -1
- package/src/generator/type-patcher.ts +0 -93
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Generator Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
6
|
+
import { MigrationGenerator } from '../src/migration/generators/migration-generator.js'
|
|
7
|
+
import type { MigrationSession } from '../src/migration/types.js'
|
|
8
|
+
|
|
9
|
+
describe('MigrationGenerator', () => {
|
|
10
|
+
let generator: MigrationGenerator
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
generator = new MigrationGenerator()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('Basic Config Generation', () => {
|
|
17
|
+
it('should generate basic config for SQLite', async () => {
|
|
18
|
+
const session: MigrationSession = {
|
|
19
|
+
id: 'test',
|
|
20
|
+
projectType: 'prisma',
|
|
21
|
+
analysis: {
|
|
22
|
+
projectTypes: ['prisma'],
|
|
23
|
+
cwd: '/tmp',
|
|
24
|
+
provider: 'sqlite',
|
|
25
|
+
},
|
|
26
|
+
currentQuestionIndex: 0,
|
|
27
|
+
answers: {
|
|
28
|
+
preserve_database: true,
|
|
29
|
+
db_provider: 'sqlite',
|
|
30
|
+
enable_auth: false,
|
|
31
|
+
default_access: 'public-read-auth-write',
|
|
32
|
+
admin_base_path: '/admin',
|
|
33
|
+
},
|
|
34
|
+
isComplete: true,
|
|
35
|
+
createdAt: new Date(),
|
|
36
|
+
updatedAt: new Date(),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const output = await generator.generate(session)
|
|
40
|
+
|
|
41
|
+
expect(output.configContent).toContain("import { config, list } from '@opensaas/stack-core'")
|
|
42
|
+
expect(output.configContent).toContain("provider: 'sqlite'")
|
|
43
|
+
expect(output.configContent).toContain('PrismaBetterSqlite3')
|
|
44
|
+
expect(output.dependencies).toContain('@opensaas/stack-core')
|
|
45
|
+
expect(output.dependencies).toContain('@prisma/adapter-better-sqlite3')
|
|
46
|
+
expect(output.steps.length).toBeGreaterThan(0)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should generate config for PostgreSQL', async () => {
|
|
50
|
+
const session: MigrationSession = {
|
|
51
|
+
id: 'test',
|
|
52
|
+
projectType: 'prisma',
|
|
53
|
+
analysis: {
|
|
54
|
+
projectTypes: ['prisma'],
|
|
55
|
+
cwd: '/tmp',
|
|
56
|
+
},
|
|
57
|
+
currentQuestionIndex: 0,
|
|
58
|
+
answers: {
|
|
59
|
+
db_provider: 'postgresql',
|
|
60
|
+
enable_auth: false,
|
|
61
|
+
},
|
|
62
|
+
isComplete: true,
|
|
63
|
+
createdAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const output = await generator.generate(session)
|
|
68
|
+
|
|
69
|
+
expect(output.configContent).toContain("provider: 'postgresql'")
|
|
70
|
+
expect(output.configContent).toContain('PrismaPg')
|
|
71
|
+
expect(output.configContent).toContain('pg.Pool')
|
|
72
|
+
expect(output.dependencies).toContain('@prisma/adapter-pg')
|
|
73
|
+
expect(output.dependencies).toContain('pg')
|
|
74
|
+
expect(output.dependencies).toContain('@types/pg')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should generate config for MySQL', async () => {
|
|
78
|
+
const session: MigrationSession = {
|
|
79
|
+
id: 'test',
|
|
80
|
+
projectType: 'prisma',
|
|
81
|
+
analysis: {
|
|
82
|
+
projectTypes: ['prisma'],
|
|
83
|
+
cwd: '/tmp',
|
|
84
|
+
},
|
|
85
|
+
currentQuestionIndex: 0,
|
|
86
|
+
answers: {
|
|
87
|
+
db_provider: 'mysql',
|
|
88
|
+
enable_auth: false,
|
|
89
|
+
},
|
|
90
|
+
isComplete: true,
|
|
91
|
+
createdAt: new Date(),
|
|
92
|
+
updatedAt: new Date(),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const output = await generator.generate(session)
|
|
96
|
+
|
|
97
|
+
expect(output.configContent).toContain("provider: 'mysql'")
|
|
98
|
+
expect(output.configContent).toContain('PrismaPlanetScale')
|
|
99
|
+
expect(output.dependencies).toContain('@prisma/adapter-planetscale')
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('Auth Integration', () => {
|
|
104
|
+
it('should include auth when enabled', async () => {
|
|
105
|
+
const session: MigrationSession = {
|
|
106
|
+
id: 'test',
|
|
107
|
+
projectType: 'prisma',
|
|
108
|
+
analysis: {
|
|
109
|
+
projectTypes: ['prisma'],
|
|
110
|
+
cwd: '/tmp',
|
|
111
|
+
},
|
|
112
|
+
currentQuestionIndex: 0,
|
|
113
|
+
answers: {
|
|
114
|
+
enable_auth: true,
|
|
115
|
+
auth_methods: ['email-password'],
|
|
116
|
+
db_provider: 'sqlite',
|
|
117
|
+
},
|
|
118
|
+
isComplete: true,
|
|
119
|
+
createdAt: new Date(),
|
|
120
|
+
updatedAt: new Date(),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const output = await generator.generate(session)
|
|
124
|
+
|
|
125
|
+
expect(output.configContent).toContain('authPlugin')
|
|
126
|
+
expect(output.configContent).toContain('emailAndPassword')
|
|
127
|
+
expect(output.configContent).toContain('plugins:')
|
|
128
|
+
expect(output.dependencies).toContain('@opensaas/stack-auth')
|
|
129
|
+
expect(output.dependencies).toContain('better-auth')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should include magic link auth', async () => {
|
|
133
|
+
const session: MigrationSession = {
|
|
134
|
+
id: 'test',
|
|
135
|
+
projectType: 'prisma',
|
|
136
|
+
analysis: {
|
|
137
|
+
projectTypes: ['prisma'],
|
|
138
|
+
cwd: '/tmp',
|
|
139
|
+
},
|
|
140
|
+
currentQuestionIndex: 0,
|
|
141
|
+
answers: {
|
|
142
|
+
enable_auth: true,
|
|
143
|
+
auth_methods: ['magic-link'],
|
|
144
|
+
db_provider: 'sqlite',
|
|
145
|
+
},
|
|
146
|
+
isComplete: true,
|
|
147
|
+
createdAt: new Date(),
|
|
148
|
+
updatedAt: new Date(),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const output = await generator.generate(session)
|
|
152
|
+
|
|
153
|
+
expect(output.configContent).toContain('magicLink')
|
|
154
|
+
expect(output.configContent).toContain('enabled: true')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should include OAuth providers as comments', async () => {
|
|
158
|
+
const session: MigrationSession = {
|
|
159
|
+
id: 'test',
|
|
160
|
+
projectType: 'prisma',
|
|
161
|
+
analysis: {
|
|
162
|
+
projectTypes: ['prisma'],
|
|
163
|
+
cwd: '/tmp',
|
|
164
|
+
},
|
|
165
|
+
currentQuestionIndex: 0,
|
|
166
|
+
answers: {
|
|
167
|
+
enable_auth: true,
|
|
168
|
+
auth_methods: ['email-password', 'google', 'github'],
|
|
169
|
+
db_provider: 'sqlite',
|
|
170
|
+
},
|
|
171
|
+
isComplete: true,
|
|
172
|
+
createdAt: new Date(),
|
|
173
|
+
updatedAt: new Date(),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const output = await generator.generate(session)
|
|
177
|
+
|
|
178
|
+
expect(output.configContent).toContain('// Uncomment and configure Google OAuth')
|
|
179
|
+
expect(output.configContent).toContain('GOOGLE_CLIENT_ID')
|
|
180
|
+
expect(output.configContent).toContain('// Uncomment and configure GitHub OAuth')
|
|
181
|
+
expect(output.configContent).toContain('GITHUB_CLIENT_ID')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('should generate BETTER_AUTH_SECRET step when auth enabled', async () => {
|
|
185
|
+
const session: MigrationSession = {
|
|
186
|
+
id: 'test',
|
|
187
|
+
projectType: 'prisma',
|
|
188
|
+
analysis: {
|
|
189
|
+
projectTypes: ['prisma'],
|
|
190
|
+
cwd: '/tmp',
|
|
191
|
+
},
|
|
192
|
+
currentQuestionIndex: 0,
|
|
193
|
+
answers: {
|
|
194
|
+
enable_auth: true,
|
|
195
|
+
auth_methods: ['email-password'],
|
|
196
|
+
db_provider: 'sqlite',
|
|
197
|
+
},
|
|
198
|
+
isComplete: true,
|
|
199
|
+
createdAt: new Date(),
|
|
200
|
+
updatedAt: new Date(),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const output = await generator.generate(session)
|
|
204
|
+
|
|
205
|
+
const secretStep = output.steps.find((step) => step.includes('BETTER_AUTH_SECRET'))
|
|
206
|
+
expect(secretStep).toBeDefined()
|
|
207
|
+
expect(secretStep).toContain('openssl rand -base64 32')
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('Access Control', () => {
|
|
212
|
+
it('should generate owner access control', async () => {
|
|
213
|
+
const session: MigrationSession = {
|
|
214
|
+
id: 'test',
|
|
215
|
+
projectType: 'prisma',
|
|
216
|
+
analysis: {
|
|
217
|
+
projectTypes: ['prisma'],
|
|
218
|
+
cwd: '/tmp',
|
|
219
|
+
models: [{ name: 'Post', fieldCount: 5 }],
|
|
220
|
+
},
|
|
221
|
+
currentQuestionIndex: 0,
|
|
222
|
+
answers: {
|
|
223
|
+
models_with_owner: ['Post'],
|
|
224
|
+
default_access: 'public-read-auth-write',
|
|
225
|
+
enable_auth: false,
|
|
226
|
+
},
|
|
227
|
+
isComplete: true,
|
|
228
|
+
createdAt: new Date(),
|
|
229
|
+
updatedAt: new Date(),
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const output = await generator.generate(session)
|
|
233
|
+
|
|
234
|
+
// Should have isOwner helper
|
|
235
|
+
expect(output.configContent).toContain('const isOwner')
|
|
236
|
+
expect(output.configContent).toContain('session.userId')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should respect default access patterns', async () => {
|
|
240
|
+
// Test that different access patterns are used based on answers
|
|
241
|
+
const authOnlySession: MigrationSession = {
|
|
242
|
+
id: 'test',
|
|
243
|
+
projectType: 'prisma',
|
|
244
|
+
analysis: {
|
|
245
|
+
projectTypes: ['prisma'],
|
|
246
|
+
cwd: '/tmp',
|
|
247
|
+
},
|
|
248
|
+
currentQuestionIndex: 0,
|
|
249
|
+
answers: {
|
|
250
|
+
default_access: 'authenticated-only',
|
|
251
|
+
enable_auth: false,
|
|
252
|
+
},
|
|
253
|
+
isComplete: true,
|
|
254
|
+
createdAt: new Date(),
|
|
255
|
+
updatedAt: new Date(),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const output = await generator.generate(authOnlySession)
|
|
259
|
+
|
|
260
|
+
// Verify the default_access answer is captured (it will be used when actual schema is provided)
|
|
261
|
+
expect(output.configContent).toBeDefined()
|
|
262
|
+
expect(output.dependencies).toContain('@opensaas/stack-core')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should handle different access control strategies', async () => {
|
|
266
|
+
// Test that admin-only access pattern is registered
|
|
267
|
+
const adminOnlySession: MigrationSession = {
|
|
268
|
+
id: 'test',
|
|
269
|
+
projectType: 'prisma',
|
|
270
|
+
analysis: {
|
|
271
|
+
projectTypes: ['prisma'],
|
|
272
|
+
cwd: '/tmp',
|
|
273
|
+
},
|
|
274
|
+
currentQuestionIndex: 0,
|
|
275
|
+
answers: {
|
|
276
|
+
default_access: 'admin-only',
|
|
277
|
+
enable_auth: false,
|
|
278
|
+
},
|
|
279
|
+
isComplete: true,
|
|
280
|
+
createdAt: new Date(),
|
|
281
|
+
updatedAt: new Date(),
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const output = await generator.generate(adminOnlySession)
|
|
285
|
+
|
|
286
|
+
// Verify the config is generated
|
|
287
|
+
expect(output.configContent).toBeDefined()
|
|
288
|
+
expect(output.dependencies).toContain('@opensaas/stack-core')
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('Additional Files', () => {
|
|
293
|
+
it('should generate .env.example with database URL', async () => {
|
|
294
|
+
const session: MigrationSession = {
|
|
295
|
+
id: 'test',
|
|
296
|
+
projectType: 'prisma',
|
|
297
|
+
analysis: {
|
|
298
|
+
projectTypes: ['prisma'],
|
|
299
|
+
cwd: '/tmp',
|
|
300
|
+
},
|
|
301
|
+
currentQuestionIndex: 0,
|
|
302
|
+
answers: {
|
|
303
|
+
db_provider: 'sqlite',
|
|
304
|
+
enable_auth: false,
|
|
305
|
+
},
|
|
306
|
+
isComplete: true,
|
|
307
|
+
createdAt: new Date(),
|
|
308
|
+
updatedAt: new Date(),
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const output = await generator.generate(session)
|
|
312
|
+
|
|
313
|
+
const envFile = output.files.find((f) => f.path === '.env.example')
|
|
314
|
+
expect(envFile).toBeDefined()
|
|
315
|
+
expect(envFile?.content).toContain('DATABASE_URL')
|
|
316
|
+
expect(envFile?.content).toContain('file:./dev.db')
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should include auth vars in .env.example when auth enabled', async () => {
|
|
320
|
+
const session: MigrationSession = {
|
|
321
|
+
id: 'test',
|
|
322
|
+
projectType: 'prisma',
|
|
323
|
+
analysis: {
|
|
324
|
+
projectTypes: ['prisma'],
|
|
325
|
+
cwd: '/tmp',
|
|
326
|
+
},
|
|
327
|
+
currentQuestionIndex: 0,
|
|
328
|
+
answers: {
|
|
329
|
+
enable_auth: true,
|
|
330
|
+
auth_methods: ['email-password', 'google'],
|
|
331
|
+
db_provider: 'sqlite',
|
|
332
|
+
},
|
|
333
|
+
isComplete: true,
|
|
334
|
+
createdAt: new Date(),
|
|
335
|
+
updatedAt: new Date(),
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const output = await generator.generate(session)
|
|
339
|
+
|
|
340
|
+
const envFile = output.files.find((f) => f.path === '.env.example')
|
|
341
|
+
expect(envFile?.content).toContain('BETTER_AUTH_SECRET')
|
|
342
|
+
expect(envFile?.content).toContain('BETTER_AUTH_URL')
|
|
343
|
+
expect(envFile?.content).toContain('GOOGLE_CLIENT_ID')
|
|
344
|
+
expect(envFile?.content).toContain('GOOGLE_CLIENT_SECRET')
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('should generate PostgreSQL connection string in .env.example', async () => {
|
|
348
|
+
const session: MigrationSession = {
|
|
349
|
+
id: 'test',
|
|
350
|
+
projectType: 'prisma',
|
|
351
|
+
analysis: {
|
|
352
|
+
projectTypes: ['prisma'],
|
|
353
|
+
cwd: '/tmp',
|
|
354
|
+
},
|
|
355
|
+
currentQuestionIndex: 0,
|
|
356
|
+
answers: {
|
|
357
|
+
db_provider: 'postgresql',
|
|
358
|
+
enable_auth: false,
|
|
359
|
+
},
|
|
360
|
+
isComplete: true,
|
|
361
|
+
createdAt: new Date(),
|
|
362
|
+
updatedAt: new Date(),
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const output = await generator.generate(session)
|
|
366
|
+
|
|
367
|
+
const envFile = output.files.find((f) => f.path === '.env.example')
|
|
368
|
+
expect(envFile?.content).toContain('postgresql://')
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
describe('Next Steps', () => {
|
|
373
|
+
it('should provide complete next steps', async () => {
|
|
374
|
+
const session: MigrationSession = {
|
|
375
|
+
id: 'test',
|
|
376
|
+
projectType: 'prisma',
|
|
377
|
+
analysis: {
|
|
378
|
+
projectTypes: ['prisma'],
|
|
379
|
+
cwd: '/tmp',
|
|
380
|
+
},
|
|
381
|
+
currentQuestionIndex: 0,
|
|
382
|
+
answers: {
|
|
383
|
+
db_provider: 'sqlite',
|
|
384
|
+
enable_auth: false,
|
|
385
|
+
},
|
|
386
|
+
isComplete: true,
|
|
387
|
+
createdAt: new Date(),
|
|
388
|
+
updatedAt: new Date(),
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const output = await generator.generate(session)
|
|
392
|
+
|
|
393
|
+
expect(output.steps).toContain('Save the generated config to `opensaas.config.ts`')
|
|
394
|
+
expect(output.steps).toContain('Copy `.env.example` to `.env` and fill in values')
|
|
395
|
+
expect(output.steps).toContain('Install dependencies: `pnpm add <dependencies>`')
|
|
396
|
+
expect(output.steps).toContain('Generate Prisma schema: `pnpm opensaas generate`')
|
|
397
|
+
expect(output.steps).toContain('Generate Prisma client: `npx prisma generate`')
|
|
398
|
+
expect(output.steps).toContain('Push schema to database: `npx prisma db push`')
|
|
399
|
+
expect(output.steps).toContain('Start development server: `pnpm dev`')
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
it('should include admin UI path in steps', async () => {
|
|
403
|
+
const session: MigrationSession = {
|
|
404
|
+
id: 'test',
|
|
405
|
+
projectType: 'prisma',
|
|
406
|
+
analysis: {
|
|
407
|
+
projectTypes: ['prisma'],
|
|
408
|
+
cwd: '/tmp',
|
|
409
|
+
},
|
|
410
|
+
currentQuestionIndex: 0,
|
|
411
|
+
answers: {
|
|
412
|
+
db_provider: 'sqlite',
|
|
413
|
+
enable_auth: false,
|
|
414
|
+
admin_base_path: '/admin',
|
|
415
|
+
},
|
|
416
|
+
isComplete: true,
|
|
417
|
+
createdAt: new Date(),
|
|
418
|
+
updatedAt: new Date(),
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const output = await generator.generate(session)
|
|
422
|
+
|
|
423
|
+
const adminStep = output.steps.find((step) => step.includes('Visit admin UI'))
|
|
424
|
+
expect(adminStep).toContain('/admin')
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
describe('Custom Admin Path', () => {
|
|
429
|
+
it('should use custom admin base path', async () => {
|
|
430
|
+
const session: MigrationSession = {
|
|
431
|
+
id: 'test',
|
|
432
|
+
projectType: 'prisma',
|
|
433
|
+
analysis: {
|
|
434
|
+
projectTypes: ['prisma'],
|
|
435
|
+
cwd: '/tmp',
|
|
436
|
+
},
|
|
437
|
+
currentQuestionIndex: 0,
|
|
438
|
+
answers: {
|
|
439
|
+
db_provider: 'sqlite',
|
|
440
|
+
enable_auth: false,
|
|
441
|
+
admin_base_path: '/dashboard',
|
|
442
|
+
},
|
|
443
|
+
isComplete: true,
|
|
444
|
+
createdAt: new Date(),
|
|
445
|
+
updatedAt: new Date(),
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const output = await generator.generate(session)
|
|
449
|
+
|
|
450
|
+
expect(output.configContent).toContain("basePath: '/dashboard'")
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
describe('Empty Schema', () => {
|
|
455
|
+
it('should generate example config when no schema available', async () => {
|
|
456
|
+
const session: MigrationSession = {
|
|
457
|
+
id: 'test',
|
|
458
|
+
projectType: 'nextjs',
|
|
459
|
+
analysis: {
|
|
460
|
+
projectTypes: ['nextjs'],
|
|
461
|
+
cwd: '/tmp',
|
|
462
|
+
},
|
|
463
|
+
currentQuestionIndex: 0,
|
|
464
|
+
answers: {
|
|
465
|
+
db_provider: 'sqlite',
|
|
466
|
+
enable_auth: false,
|
|
467
|
+
},
|
|
468
|
+
isComplete: true,
|
|
469
|
+
createdAt: new Date(),
|
|
470
|
+
updatedAt: new Date(),
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const output = await generator.generate(session)
|
|
474
|
+
|
|
475
|
+
expect(output.configContent).toContain('// Add your lists here')
|
|
476
|
+
expect(output.configContent).toContain('// Example:')
|
|
477
|
+
})
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
describe('Dependencies', () => {
|
|
481
|
+
it('should include all core dependencies', async () => {
|
|
482
|
+
const session: MigrationSession = {
|
|
483
|
+
id: 'test',
|
|
484
|
+
projectType: 'prisma',
|
|
485
|
+
analysis: {
|
|
486
|
+
projectTypes: ['prisma'],
|
|
487
|
+
cwd: '/tmp',
|
|
488
|
+
},
|
|
489
|
+
currentQuestionIndex: 0,
|
|
490
|
+
answers: {
|
|
491
|
+
db_provider: 'sqlite',
|
|
492
|
+
enable_auth: false,
|
|
493
|
+
},
|
|
494
|
+
isComplete: true,
|
|
495
|
+
createdAt: new Date(),
|
|
496
|
+
updatedAt: new Date(),
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const output = await generator.generate(session)
|
|
500
|
+
|
|
501
|
+
expect(output.dependencies).toContain('@opensaas/stack-core')
|
|
502
|
+
expect(output.dependencies).toContain('@opensaas/stack-ui')
|
|
503
|
+
expect(output.dependencies).toContain('@prisma/client')
|
|
504
|
+
expect(output.dependencies).toContain('prisma')
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('should include database-specific dependencies', async () => {
|
|
508
|
+
const postgresSession: MigrationSession = {
|
|
509
|
+
id: 'test',
|
|
510
|
+
projectType: 'prisma',
|
|
511
|
+
analysis: {
|
|
512
|
+
projectTypes: ['prisma'],
|
|
513
|
+
cwd: '/tmp',
|
|
514
|
+
},
|
|
515
|
+
currentQuestionIndex: 0,
|
|
516
|
+
answers: {
|
|
517
|
+
db_provider: 'postgresql',
|
|
518
|
+
enable_auth: false,
|
|
519
|
+
},
|
|
520
|
+
isComplete: true,
|
|
521
|
+
createdAt: new Date(),
|
|
522
|
+
updatedAt: new Date(),
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const postgresOutput = await generator.generate(postgresSession)
|
|
526
|
+
expect(postgresOutput.dependencies).toContain('@prisma/adapter-pg')
|
|
527
|
+
expect(postgresOutput.dependencies).toContain('pg')
|
|
528
|
+
|
|
529
|
+
const sqliteSession: MigrationSession = {
|
|
530
|
+
...postgresSession,
|
|
531
|
+
answers: {
|
|
532
|
+
db_provider: 'sqlite',
|
|
533
|
+
enable_auth: false,
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const sqliteOutput = await generator.generate(sqliteSession)
|
|
538
|
+
expect(sqliteOutput.dependencies).toContain('@prisma/adapter-better-sqlite3')
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
describe('Import Generation', () => {
|
|
543
|
+
it('should include AccessControl type when auth enabled', async () => {
|
|
544
|
+
const session: MigrationSession = {
|
|
545
|
+
id: 'test',
|
|
546
|
+
projectType: 'prisma',
|
|
547
|
+
analysis: {
|
|
548
|
+
projectTypes: ['prisma'],
|
|
549
|
+
cwd: '/tmp',
|
|
550
|
+
},
|
|
551
|
+
currentQuestionIndex: 0,
|
|
552
|
+
answers: {
|
|
553
|
+
enable_auth: true,
|
|
554
|
+
auth_methods: ['email-password'],
|
|
555
|
+
db_provider: 'sqlite',
|
|
556
|
+
},
|
|
557
|
+
isComplete: true,
|
|
558
|
+
createdAt: new Date(),
|
|
559
|
+
updatedAt: new Date(),
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const output = await generator.generate(session)
|
|
563
|
+
|
|
564
|
+
expect(output.configContent).toContain(
|
|
565
|
+
"import type { AccessControl } from '@opensaas/stack-core'",
|
|
566
|
+
)
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
it('should not include AccessControl type when auth disabled', async () => {
|
|
570
|
+
const session: MigrationSession = {
|
|
571
|
+
id: 'test',
|
|
572
|
+
projectType: 'prisma',
|
|
573
|
+
analysis: {
|
|
574
|
+
projectTypes: ['prisma'],
|
|
575
|
+
cwd: '/tmp',
|
|
576
|
+
},
|
|
577
|
+
currentQuestionIndex: 0,
|
|
578
|
+
answers: {
|
|
579
|
+
enable_auth: false,
|
|
580
|
+
db_provider: 'sqlite',
|
|
581
|
+
},
|
|
582
|
+
isComplete: true,
|
|
583
|
+
createdAt: new Date(),
|
|
584
|
+
updatedAt: new Date(),
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const output = await generator.generate(session)
|
|
588
|
+
|
|
589
|
+
expect(output.configContent).not.toContain('AccessControl')
|
|
590
|
+
})
|
|
591
|
+
})
|
|
592
|
+
})
|