@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.
Files changed (61) hide show
  1. package/README.md +76 -0
  2. package/dist/commands/migrate.d.ts.map +1 -1
  3. package/dist/commands/migrate.js +91 -265
  4. package/dist/commands/migrate.js.map +1 -1
  5. package/package.json +7 -2
  6. package/plugin/.claude-plugin/plugin.json +15 -0
  7. package/plugin/README.md +112 -0
  8. package/plugin/agents/migration-assistant.md +150 -0
  9. package/plugin/commands/analyze-schema.md +34 -0
  10. package/plugin/commands/generate-config.md +33 -0
  11. package/plugin/commands/validate-migration.md +34 -0
  12. package/plugin/skills/opensaas-migration/SKILL.md +192 -0
  13. package/.turbo/turbo-build.log +0 -4
  14. package/CHANGELOG.md +0 -462
  15. package/CLAUDE.md +0 -298
  16. package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
  17. package/src/commands/dev.test.ts +0 -215
  18. package/src/commands/dev.ts +0 -48
  19. package/src/commands/generate.test.ts +0 -282
  20. package/src/commands/generate.ts +0 -182
  21. package/src/commands/init.ts +0 -34
  22. package/src/commands/mcp.ts +0 -135
  23. package/src/commands/migrate.ts +0 -534
  24. package/src/generator/__snapshots__/context.test.ts.snap +0 -361
  25. package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
  26. package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
  27. package/src/generator/context.test.ts +0 -139
  28. package/src/generator/context.ts +0 -227
  29. package/src/generator/index.ts +0 -7
  30. package/src/generator/lists.test.ts +0 -335
  31. package/src/generator/lists.ts +0 -140
  32. package/src/generator/plugin-types.ts +0 -147
  33. package/src/generator/prisma-config.ts +0 -46
  34. package/src/generator/prisma-extensions.ts +0 -159
  35. package/src/generator/prisma.test.ts +0 -211
  36. package/src/generator/prisma.ts +0 -161
  37. package/src/generator/types.test.ts +0 -268
  38. package/src/generator/types.ts +0 -537
  39. package/src/index.ts +0 -46
  40. package/src/mcp/lib/documentation-provider.ts +0 -710
  41. package/src/mcp/lib/features/catalog.ts +0 -301
  42. package/src/mcp/lib/generators/feature-generator.ts +0 -598
  43. package/src/mcp/lib/types.ts +0 -89
  44. package/src/mcp/lib/wizards/migration-wizard.ts +0 -584
  45. package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
  46. package/src/mcp/server/index.ts +0 -361
  47. package/src/mcp/server/stack-mcp-server.ts +0 -544
  48. package/src/migration/generators/migration-generator.ts +0 -675
  49. package/src/migration/introspectors/index.ts +0 -12
  50. package/src/migration/introspectors/keystone-introspector.ts +0 -296
  51. package/src/migration/introspectors/nextjs-introspector.ts +0 -209
  52. package/src/migration/introspectors/prisma-introspector.ts +0 -233
  53. package/src/migration/types.ts +0 -92
  54. package/tests/introspectors/keystone-introspector.test.ts +0 -255
  55. package/tests/introspectors/nextjs-introspector.test.ts +0 -302
  56. package/tests/introspectors/prisma-introspector.test.ts +0 -268
  57. package/tests/migration-generator.test.ts +0 -592
  58. package/tests/migration-wizard.test.ts +0 -442
  59. package/tsconfig.json +0 -13
  60. package/tsconfig.tsbuildinfo +0 -1
  61. package/vitest.config.ts +0 -26
@@ -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
- })