@opensaas/stack-cli 0.5.0 → 0.6.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/README.md +76 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +91 -265
- package/dist/commands/migrate.js.map +1 -1
- package/package.json +7 -2
- package/plugin/.claude-plugin/plugin.json +15 -0
- package/plugin/README.md +112 -0
- package/plugin/agents/migration-assistant.md +150 -0
- package/plugin/commands/analyze-schema.md +34 -0
- package/plugin/commands/generate-config.md +33 -0
- package/plugin/commands/validate-migration.md +34 -0
- package/plugin/skills/opensaas-migration/SKILL.md +192 -0
- 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
package/src/generator/prisma.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import type { OpenSaasConfig, FieldConfig, RelationshipField } from '@opensaas/stack-core'
|
|
2
|
-
import * as fs from 'fs'
|
|
3
|
-
import * as path from 'path'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Map OpenSaas field types to Prisma field types
|
|
7
|
-
*/
|
|
8
|
-
function mapFieldTypeToPrisma(fieldName: string, field: FieldConfig): string | null {
|
|
9
|
-
// Relationships are handled separately
|
|
10
|
-
if (field.type === 'relationship') {
|
|
11
|
-
return null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Use field's own Prisma type generator if available
|
|
15
|
-
if (field.getPrismaType) {
|
|
16
|
-
const result = field.getPrismaType(fieldName)
|
|
17
|
-
return result.type
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Fallback for fields without generator methods
|
|
21
|
-
throw new Error(`Field type "${field.type}" does not implement getPrismaType method`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get field modifiers (?, @default, @unique, etc.)
|
|
26
|
-
*/
|
|
27
|
-
function getFieldModifiers(fieldName: string, field: FieldConfig): string {
|
|
28
|
-
// Handle relationships separately
|
|
29
|
-
if (field.type === 'relationship') {
|
|
30
|
-
const relField = field as RelationshipField
|
|
31
|
-
if (relField.many) {
|
|
32
|
-
return '[]'
|
|
33
|
-
} else {
|
|
34
|
-
return '?'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Use field's own Prisma type generator if available
|
|
39
|
-
if (field.getPrismaType) {
|
|
40
|
-
const result = field.getPrismaType(fieldName)
|
|
41
|
-
return result.modifiers || ''
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Fallback for fields without generator methods
|
|
45
|
-
return ''
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Parse relationship ref to get target list and field
|
|
50
|
-
*/
|
|
51
|
-
function parseRelationshipRef(ref: string): { list: string; field: string } {
|
|
52
|
-
const [list, field] = ref.split('.')
|
|
53
|
-
if (!list || !field) {
|
|
54
|
-
throw new Error(`Invalid relationship ref: ${ref}`)
|
|
55
|
-
}
|
|
56
|
-
return { list, field }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Generate Prisma schema from OpenSaas config
|
|
61
|
-
*/
|
|
62
|
-
export function generatePrismaSchema(config: OpenSaasConfig): string {
|
|
63
|
-
const lines: string[] = []
|
|
64
|
-
|
|
65
|
-
const opensaasPath = config.opensaasPath || '.opensaas'
|
|
66
|
-
|
|
67
|
-
// Generator and datasource
|
|
68
|
-
lines.push('generator client {')
|
|
69
|
-
lines.push(' provider = "prisma-client"')
|
|
70
|
-
lines.push(` output = "../${opensaasPath}/prisma-client"`)
|
|
71
|
-
lines.push('}')
|
|
72
|
-
lines.push('')
|
|
73
|
-
lines.push('datasource db {')
|
|
74
|
-
lines.push(` provider = "${config.db.provider}"`)
|
|
75
|
-
lines.push('}')
|
|
76
|
-
lines.push('')
|
|
77
|
-
|
|
78
|
-
// Generate models for each list
|
|
79
|
-
for (const [listName, listConfig] of Object.entries(config.lists)) {
|
|
80
|
-
lines.push(`model ${listName} {`)
|
|
81
|
-
|
|
82
|
-
// Always add id field
|
|
83
|
-
lines.push(' id String @id @default(cuid())')
|
|
84
|
-
|
|
85
|
-
// Track relationship fields for later processing
|
|
86
|
-
const relationshipFields: Array<{
|
|
87
|
-
name: string
|
|
88
|
-
field: RelationshipField
|
|
89
|
-
}> = []
|
|
90
|
-
|
|
91
|
-
// Add regular fields
|
|
92
|
-
for (const [fieldName, fieldConfig] of Object.entries(listConfig.fields)) {
|
|
93
|
-
// Skip virtual fields - they don't create database columns
|
|
94
|
-
if (fieldConfig.virtual) {
|
|
95
|
-
continue
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (fieldConfig.type === 'relationship') {
|
|
99
|
-
relationshipFields.push({
|
|
100
|
-
name: fieldName,
|
|
101
|
-
field: fieldConfig as RelationshipField,
|
|
102
|
-
})
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const prismaType = mapFieldTypeToPrisma(fieldName, fieldConfig)
|
|
107
|
-
if (!prismaType) continue // Skip if no type returned
|
|
108
|
-
|
|
109
|
-
const modifiers = getFieldModifiers(fieldName, fieldConfig)
|
|
110
|
-
|
|
111
|
-
// Format with proper spacing
|
|
112
|
-
const paddedName = fieldName.padEnd(12)
|
|
113
|
-
lines.push(` ${paddedName} ${prismaType}${modifiers}`)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Add relationship fields
|
|
117
|
-
for (const { name: fieldName, field: relField } of relationshipFields) {
|
|
118
|
-
const { list: targetList } = parseRelationshipRef(relField.ref)
|
|
119
|
-
const _modifiers = getFieldModifiers(fieldName, relField)
|
|
120
|
-
const paddedName = fieldName.padEnd(12)
|
|
121
|
-
|
|
122
|
-
if (relField.many) {
|
|
123
|
-
// One-to-many relationship
|
|
124
|
-
lines.push(` ${paddedName} ${targetList}[]`)
|
|
125
|
-
} else {
|
|
126
|
-
// Many-to-one relationship (add foreign key field)
|
|
127
|
-
const foreignKeyField = `${fieldName}Id`
|
|
128
|
-
const fkPaddedName = foreignKeyField.padEnd(12)
|
|
129
|
-
|
|
130
|
-
lines.push(` ${fkPaddedName} String?`)
|
|
131
|
-
lines.push(
|
|
132
|
-
` ${paddedName} ${targetList}? @relation(fields: [${foreignKeyField}], references: [id])`,
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Always add timestamps
|
|
138
|
-
lines.push(' createdAt DateTime @default(now())')
|
|
139
|
-
lines.push(' updatedAt DateTime @updatedAt')
|
|
140
|
-
|
|
141
|
-
lines.push('}')
|
|
142
|
-
lines.push('')
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return lines.join('\n')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Write Prisma schema to file
|
|
150
|
-
*/
|
|
151
|
-
export function writePrismaSchema(config: OpenSaasConfig, outputPath: string): void {
|
|
152
|
-
const schema = generatePrismaSchema(config)
|
|
153
|
-
|
|
154
|
-
// Ensure directory exists
|
|
155
|
-
const dir = path.dirname(outputPath)
|
|
156
|
-
if (!fs.existsSync(dir)) {
|
|
157
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
fs.writeFileSync(outputPath, schema, 'utf-8')
|
|
161
|
-
}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { generateTypes } from './types.js'
|
|
3
|
-
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
|
-
import { text, integer, relationship, checkbox } from '@opensaas/stack-core/fields'
|
|
5
|
-
|
|
6
|
-
describe('Types Generator', () => {
|
|
7
|
-
describe('generateTypes', () => {
|
|
8
|
-
it('should generate type definitions for basic model', () => {
|
|
9
|
-
const config: OpenSaasConfig = {
|
|
10
|
-
db: {
|
|
11
|
-
provider: 'sqlite',
|
|
12
|
-
},
|
|
13
|
-
lists: {
|
|
14
|
-
User: {
|
|
15
|
-
fields: {
|
|
16
|
-
name: text({ validation: { isRequired: true } }),
|
|
17
|
-
email: text({ validation: { isRequired: true } }),
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const types = generateTypes(config)
|
|
24
|
-
|
|
25
|
-
expect(types).toMatchSnapshot()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('should generate CreateInput type', () => {
|
|
29
|
-
const config: OpenSaasConfig = {
|
|
30
|
-
db: {
|
|
31
|
-
provider: 'sqlite',
|
|
32
|
-
},
|
|
33
|
-
lists: {
|
|
34
|
-
Post: {
|
|
35
|
-
fields: {
|
|
36
|
-
title: text({ validation: { isRequired: true } }),
|
|
37
|
-
content: text(),
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const types = generateTypes(config)
|
|
44
|
-
|
|
45
|
-
expect(types).toMatchSnapshot()
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('should generate UpdateInput type', () => {
|
|
49
|
-
const config: OpenSaasConfig = {
|
|
50
|
-
db: {
|
|
51
|
-
provider: 'sqlite',
|
|
52
|
-
},
|
|
53
|
-
lists: {
|
|
54
|
-
Post: {
|
|
55
|
-
fields: {
|
|
56
|
-
title: text({ validation: { isRequired: true } }),
|
|
57
|
-
content: text(),
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const types = generateTypes(config)
|
|
64
|
-
|
|
65
|
-
expect(types).toMatchSnapshot()
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('should generate WhereInput type', () => {
|
|
69
|
-
const config: OpenSaasConfig = {
|
|
70
|
-
db: {
|
|
71
|
-
provider: 'sqlite',
|
|
72
|
-
},
|
|
73
|
-
lists: {
|
|
74
|
-
User: {
|
|
75
|
-
fields: {
|
|
76
|
-
name: text(),
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const types = generateTypes(config)
|
|
83
|
-
|
|
84
|
-
expect(types).toMatchSnapshot()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('should generate Context type with all operations', () => {
|
|
88
|
-
const config: OpenSaasConfig = {
|
|
89
|
-
db: {
|
|
90
|
-
provider: 'sqlite',
|
|
91
|
-
},
|
|
92
|
-
lists: {
|
|
93
|
-
User: {
|
|
94
|
-
fields: {
|
|
95
|
-
name: text(),
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const types = generateTypes(config)
|
|
102
|
-
|
|
103
|
-
expect(types).toMatchSnapshot()
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('should handle relationship fields in types', () => {
|
|
107
|
-
const config: OpenSaasConfig = {
|
|
108
|
-
db: {
|
|
109
|
-
provider: 'sqlite',
|
|
110
|
-
},
|
|
111
|
-
lists: {
|
|
112
|
-
User: {
|
|
113
|
-
fields: {
|
|
114
|
-
name: text(),
|
|
115
|
-
posts: relationship({ ref: 'Post.author', many: true }),
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
Post: {
|
|
119
|
-
fields: {
|
|
120
|
-
title: text(),
|
|
121
|
-
author: relationship({ ref: 'User.posts' }),
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const types = generateTypes(config)
|
|
128
|
-
|
|
129
|
-
expect(types).toMatchSnapshot()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should handle relationship fields in CreateInput', () => {
|
|
133
|
-
const config: OpenSaasConfig = {
|
|
134
|
-
db: {
|
|
135
|
-
provider: 'sqlite',
|
|
136
|
-
},
|
|
137
|
-
lists: {
|
|
138
|
-
Post: {
|
|
139
|
-
fields: {
|
|
140
|
-
title: text(),
|
|
141
|
-
author: relationship({ ref: 'User.posts' }),
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
User: {
|
|
145
|
-
fields: {
|
|
146
|
-
name: text(),
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const types = generateTypes(config)
|
|
153
|
-
|
|
154
|
-
expect(types).toMatchSnapshot()
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('should handle relationship fields in UpdateInput', () => {
|
|
158
|
-
const config: OpenSaasConfig = {
|
|
159
|
-
db: {
|
|
160
|
-
provider: 'sqlite',
|
|
161
|
-
},
|
|
162
|
-
lists: {
|
|
163
|
-
Post: {
|
|
164
|
-
fields: {
|
|
165
|
-
title: text(),
|
|
166
|
-
author: relationship({ ref: 'User.posts' }),
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
User: {
|
|
170
|
-
fields: {
|
|
171
|
-
name: text(),
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const types = generateTypes(config)
|
|
178
|
-
|
|
179
|
-
expect(types).toMatchSnapshot()
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('should generate types for multiple lists', () => {
|
|
183
|
-
const config: OpenSaasConfig = {
|
|
184
|
-
db: {
|
|
185
|
-
provider: 'sqlite',
|
|
186
|
-
},
|
|
187
|
-
lists: {
|
|
188
|
-
User: {
|
|
189
|
-
fields: {
|
|
190
|
-
name: text(),
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
Post: {
|
|
194
|
-
fields: {
|
|
195
|
-
title: text(),
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
Comment: {
|
|
199
|
-
fields: {
|
|
200
|
-
content: text(),
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const types = generateTypes(config)
|
|
207
|
-
|
|
208
|
-
expect(types).toMatchSnapshot()
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('should handle integer fields correctly', () => {
|
|
212
|
-
const config: OpenSaasConfig = {
|
|
213
|
-
db: {
|
|
214
|
-
provider: 'sqlite',
|
|
215
|
-
},
|
|
216
|
-
lists: {
|
|
217
|
-
Product: {
|
|
218
|
-
fields: {
|
|
219
|
-
name: text(),
|
|
220
|
-
price: integer(),
|
|
221
|
-
},
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const types = generateTypes(config)
|
|
227
|
-
|
|
228
|
-
expect(types).toContain('price:')
|
|
229
|
-
expect(types).toContain('number')
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
it('should handle checkbox fields correctly', () => {
|
|
233
|
-
const config: OpenSaasConfig = {
|
|
234
|
-
db: {
|
|
235
|
-
provider: 'sqlite',
|
|
236
|
-
},
|
|
237
|
-
lists: {
|
|
238
|
-
Post: {
|
|
239
|
-
fields: {
|
|
240
|
-
title: text(),
|
|
241
|
-
isPublished: checkbox(),
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const types = generateTypes(config)
|
|
248
|
-
|
|
249
|
-
expect(types).toContain('isPublished:')
|
|
250
|
-
expect(types).toContain('boolean')
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('should include header comment', () => {
|
|
254
|
-
const config: OpenSaasConfig = {
|
|
255
|
-
db: {
|
|
256
|
-
provider: 'sqlite',
|
|
257
|
-
},
|
|
258
|
-
lists: {},
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const types = generateTypes(config)
|
|
262
|
-
|
|
263
|
-
expect(types).toContain('/**')
|
|
264
|
-
expect(types).toContain('Generated types from OpenSaas configuration')
|
|
265
|
-
expect(types).toContain('DO NOT EDIT')
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
})
|