@opensaas/stack-cli 0.4.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.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +52 -0
  3. package/dist/commands/migrate.d.ts +9 -0
  4. package/dist/commands/migrate.d.ts.map +1 -0
  5. package/dist/commands/migrate.js +473 -0
  6. package/dist/commands/migrate.js.map +1 -0
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp/lib/documentation-provider.d.ts +23 -0
  10. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
  11. package/dist/mcp/lib/documentation-provider.js +471 -0
  12. package/dist/mcp/lib/documentation-provider.js.map +1 -1
  13. package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
  14. package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
  15. package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
  16. package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
  17. package/dist/mcp/server/index.d.ts.map +1 -1
  18. package/dist/mcp/server/index.js +103 -0
  19. package/dist/mcp/server/index.js.map +1 -1
  20. package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
  21. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
  22. package/dist/mcp/server/stack-mcp-server.js +219 -0
  23. package/dist/mcp/server/stack-mcp-server.js.map +1 -1
  24. package/dist/migration/generators/migration-generator.d.ts +60 -0
  25. package/dist/migration/generators/migration-generator.d.ts.map +1 -0
  26. package/dist/migration/generators/migration-generator.js +510 -0
  27. package/dist/migration/generators/migration-generator.js.map +1 -0
  28. package/dist/migration/introspectors/index.d.ts +12 -0
  29. package/dist/migration/introspectors/index.d.ts.map +1 -0
  30. package/dist/migration/introspectors/index.js +10 -0
  31. package/dist/migration/introspectors/index.js.map +1 -0
  32. package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
  33. package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
  34. package/dist/migration/introspectors/keystone-introspector.js +229 -0
  35. package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
  36. package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
  37. package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
  38. package/dist/migration/introspectors/nextjs-introspector.js +159 -0
  39. package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
  40. package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
  41. package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
  42. package/dist/migration/introspectors/prisma-introspector.js +190 -0
  43. package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
  44. package/dist/migration/types.d.ts +86 -0
  45. package/dist/migration/types.d.ts.map +1 -0
  46. package/dist/migration/types.js +5 -0
  47. package/dist/migration/types.js.map +1 -0
  48. package/package.json +5 -2
  49. package/src/commands/migrate.ts +534 -0
  50. package/src/index.ts +4 -0
  51. package/src/mcp/lib/documentation-provider.ts +507 -0
  52. package/src/mcp/lib/wizards/migration-wizard.ts +584 -0
  53. package/src/mcp/server/index.ts +121 -0
  54. package/src/mcp/server/stack-mcp-server.ts +243 -0
  55. package/src/migration/generators/migration-generator.ts +675 -0
  56. package/src/migration/introspectors/index.ts +12 -0
  57. package/src/migration/introspectors/keystone-introspector.ts +296 -0
  58. package/src/migration/introspectors/nextjs-introspector.ts +209 -0
  59. package/src/migration/introspectors/prisma-introspector.ts +233 -0
  60. package/src/migration/types.ts +92 -0
  61. package/tests/introspectors/keystone-introspector.test.ts +255 -0
  62. package/tests/introspectors/nextjs-introspector.test.ts +302 -0
  63. package/tests/introspectors/prisma-introspector.test.ts +268 -0
  64. package/tests/migration-generator.test.ts +592 -0
  65. package/tests/migration-wizard.test.ts +442 -0
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Migration command - Helps migrate existing projects to OpenSaaS Stack
3
+ */
4
+
5
+ import { Command } from 'commander'
6
+ import * as fs from 'fs'
7
+ import * as path from 'path'
8
+ import chalk from 'chalk'
9
+ import ora from 'ora'
10
+ import type { ProjectAnalysis, ProjectType } from '../migration/types.js'
11
+
12
+ interface MigrateOptions {
13
+ withAi?: boolean
14
+ type?: 'prisma' | 'nextjs' | 'keystone'
15
+ }
16
+
17
+ /**
18
+ * Detect what type of project this is
19
+ */
20
+ async function detectProjectType(cwd: string): Promise<ProjectType[]> {
21
+ const types: ProjectType[] = []
22
+
23
+ // Check for Prisma
24
+ const prismaSchemaPath = path.join(cwd, 'prisma', 'schema.prisma')
25
+ if (fs.existsSync(prismaSchemaPath)) {
26
+ types.push('prisma')
27
+ }
28
+
29
+ // Check for KeystoneJS
30
+ const keystoneConfigPath = path.join(cwd, 'keystone.config.ts')
31
+ const keystoneAltPath = path.join(cwd, 'keystone.ts')
32
+ if (fs.existsSync(keystoneConfigPath) || fs.existsSync(keystoneAltPath)) {
33
+ types.push('keystone')
34
+ }
35
+
36
+ // Check for Next.js
37
+ const packageJsonPath = path.join(cwd, 'package.json')
38
+ if (fs.existsSync(packageJsonPath)) {
39
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
40
+ if (pkg.dependencies?.next || pkg.devDependencies?.next) {
41
+ types.push('nextjs')
42
+ }
43
+ }
44
+
45
+ return types
46
+ }
47
+
48
+ /**
49
+ * Analyze a Prisma schema
50
+ */
51
+ async function analyzePrismaSchema(cwd: string): Promise<{
52
+ models: Array<{ name: string; fieldCount: number }>
53
+ provider: string
54
+ }> {
55
+ const schemaPath = path.join(cwd, 'prisma', 'schema.prisma')
56
+ const schema = fs.readFileSync(schemaPath, 'utf-8')
57
+
58
+ // Extract models
59
+ const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g
60
+ const models: Array<{ name: string; fieldCount: number }> = []
61
+ let match
62
+
63
+ while ((match = modelRegex.exec(schema)) !== null) {
64
+ const name = match[1]
65
+ const body = match[2]
66
+ const fieldCount = body
67
+ .split('\n')
68
+ .filter(
69
+ (line) => line.trim() && !line.trim().startsWith('@@') && !line.trim().startsWith('//'),
70
+ ).length
71
+ models.push({ name, fieldCount })
72
+ }
73
+
74
+ // Extract provider
75
+ const providerMatch = schema.match(/provider\s*=\s*"(\w+)"/)
76
+ const provider = providerMatch ? providerMatch[1] : 'unknown'
77
+
78
+ return { models, provider }
79
+ }
80
+
81
+ /**
82
+ * Ensure directory exists
83
+ */
84
+ function ensureDir(dirPath: string): void {
85
+ if (!fs.existsSync(dirPath)) {
86
+ fs.mkdirSync(dirPath, { recursive: true })
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Generate template content with placeholders replaced
92
+ */
93
+ function generateTemplateContent(template: string, data: ProjectAnalysis): string {
94
+ const projectType = data.projectTypes[0] || 'unknown'
95
+ const projectTypeLower = projectType.toLowerCase()
96
+ const modelCount = data.models?.length || 0
97
+ const modelList = data.models?.map((m) => `- ${m.name} (${m.fieldCount} fields)`).join('\n') || ''
98
+ const modelDetails =
99
+ data.models?.map((m) => `- **${m.name}**: ${m.fieldCount} fields`).join('\n') || ''
100
+
101
+ return template
102
+ .replace(/\{\{PROJECT_TYPES\}\}/g, data.projectTypes.join(', '))
103
+ .replace(/\{\{PROJECT_TYPE\}\}/g, projectType)
104
+ .replace(/\{\{PROJECT_TYPE_LOWER\}\}/g, projectTypeLower)
105
+ .replace(/\{\{PROVIDER\}\}/g, data.provider || 'sqlite')
106
+ .replace(/\{\{MODEL_COUNT\}\}/g, String(modelCount))
107
+ .replace(/\{\{HAS_AUTH\}\}/g, String(data.hasAuth || false))
108
+ .replace(/\{\{MODEL_LIST\}\}/g, modelList)
109
+ .replace(/\{\{MODEL_DETAILS\}\}/g, modelDetails)
110
+ }
111
+
112
+ /**
113
+ * Setup Claude Code integration
114
+ */
115
+ async function setupClaudeCode(cwd: string, analysis: ProjectAnalysis): Promise<void> {
116
+ const claudeDir = path.join(cwd, '.claude')
117
+ const agentsDir = path.join(claudeDir, 'agents')
118
+ const commandsDir = path.join(claudeDir, 'commands')
119
+
120
+ // Create directories
121
+ ensureDir(agentsDir)
122
+ ensureDir(commandsDir)
123
+
124
+ // Create settings.json
125
+ const settings = {
126
+ mcpServers: {
127
+ 'opensaas-migration': {
128
+ command: 'npx',
129
+ args: ['@opensaas/stack-cli', 'mcp', 'start'],
130
+ disabled: false,
131
+ },
132
+ },
133
+ }
134
+ fs.writeFileSync(path.join(claudeDir, 'settings.json'), JSON.stringify(settings, null, 2))
135
+
136
+ // Create README template
137
+ const readmeTemplate = `# OpenSaaS Stack Migration
138
+
139
+ This project is being migrated to OpenSaaS Stack.
140
+
141
+ ## Project Summary
142
+
143
+ - **Project Type:** {{PROJECT_TYPES}}
144
+ - **Database Provider:** {{PROVIDER}}
145
+ - **Models Detected:** {{MODEL_COUNT}}
146
+
147
+ ### Models
148
+
149
+ {{MODEL_LIST}}
150
+
151
+ ## Quick Start
152
+
153
+ Ask Claude: **"Help me migrate to OpenSaaS Stack"**
154
+
155
+ Claude will guide you through:
156
+ 1. Reviewing your current schema
157
+ 2. Configuring access control
158
+ 3. Setting up authentication (optional)
159
+ 4. Generating \`opensaas.config.ts\`
160
+
161
+ ## Available Commands
162
+
163
+ | Command | Description |
164
+ |---------|-------------|
165
+ | \`/analyze-schema\` | View detailed schema analysis |
166
+ | \`/generate-config\` | Generate the config file |
167
+ | \`/validate-migration\` | Validate generated config |
168
+
169
+ ## Resources
170
+
171
+ - [OpenSaaS Stack Documentation](https://stack.opensaas.au/)
172
+ - [Migration Guide](https://stack.opensaas.au/guides/migration)
173
+ - [GitHub Repository](https://github.com/OpenSaasAU/stack)
174
+
175
+ ## Generated By
176
+
177
+ This migration was set up using:
178
+ \`\`\`bash
179
+ npx @opensaas/stack-cli migrate --with-ai
180
+ \`\`\`
181
+ `
182
+
183
+ fs.writeFileSync(
184
+ path.join(claudeDir, 'README.md'),
185
+ generateTemplateContent(readmeTemplate, analysis),
186
+ )
187
+
188
+ // Create migration assistant agent template
189
+ const agentTemplate = `You are the OpenSaaS Stack Migration Assistant, helping users migrate their existing projects to OpenSaaS Stack.
190
+
191
+ ## Project Context
192
+
193
+ **Project Type:** {{PROJECT_TYPES}}
194
+ **Database Provider:** {{PROVIDER}}
195
+ **Total Models:** {{MODEL_COUNT}}
196
+
197
+ ### Detected Models
198
+
199
+ {{MODEL_DETAILS}}
200
+
201
+ ## Your Role
202
+
203
+ Guide the user through a complete migration to OpenSaaS Stack:
204
+
205
+ 1. **Analyze** their current project structure
206
+ 2. **Explain** what OpenSaaS Stack offers (access control, admin UI, type safety)
207
+ 3. **Guide** them through the migration wizard
208
+ 4. **Generate** a working \`opensaas.config.ts\`
209
+ 5. **Validate** the generated configuration
210
+ 6. **Provide** clear next steps
211
+
212
+ ## Available MCP Tools
213
+
214
+ ### Schema Analysis
215
+ - \`opensaas_introspect_prisma\` - Analyze Prisma schema in detail
216
+ - \`opensaas_introspect_keystone\` - Analyze KeystoneJS config
217
+
218
+ ### Migration Wizard
219
+ - \`opensaas_start_migration\` - Start the interactive wizard
220
+ - \`opensaas_answer_migration\` - Answer wizard questions
221
+
222
+ ### Documentation
223
+ - \`opensaas_search_migration_docs\` - Search migration documentation
224
+ - \`opensaas_get_example\` - Get example code patterns
225
+
226
+ ### Validation
227
+ - \`opensaas_validate_feature\` - Validate implementation
228
+
229
+ ## Conversation Guidelines
230
+
231
+ ### When the user says "help me migrate" or similar:
232
+
233
+ 1. **Acknowledge** their project:
234
+ > "I can see you have a {{PROJECT_TYPE}} project with {{MODEL_COUNT}} models. Let me help you migrate to OpenSaaS Stack!"
235
+
236
+ 2. **Start the wizard** by calling:
237
+ \`\`\`
238
+ opensaas_start_migration({ projectType: "{{PROJECT_TYPE_LOWER}}" })
239
+ \`\`\`
240
+
241
+ 3. **Present questions naturally** - don't mention session IDs or technical details to the user
242
+
243
+ 4. **Explain choices** - help them understand what each option means:
244
+ - Access control patterns
245
+ - Authentication options
246
+ - Database configuration
247
+
248
+ 5. **Show progress** - let them know how far along they are
249
+
250
+ 6. **Generate the config** when complete and explain what was created
251
+
252
+ ### When explaining OpenSaaS Stack:
253
+
254
+ Highlight these benefits:
255
+ - **Built-in access control** - Secure by default
256
+ - **Admin UI** - Auto-generated from your schema
257
+ - **Type safety** - Full TypeScript support
258
+ - **Prisma integration** - Uses familiar ORM
259
+ - **Plugin system** - Easy to extend
260
+
261
+ ### When answering questions:
262
+
263
+ - Use \`opensaas_search_migration_docs\` to find accurate information
264
+ - Use \`opensaas_get_example\` to show code patterns
265
+ - Be honest if something isn't supported
266
+
267
+ ### Tone
268
+
269
+ - Be encouraging and helpful
270
+ - Explain technical concepts simply
271
+ - Celebrate progress ("Great choice!", "Almost there!")
272
+ - Don't overwhelm with information
273
+
274
+ ## Example Conversation
275
+
276
+ **User:** Help me migrate to OpenSaaS Stack
277
+
278
+ **You:** I can see you have a {{PROJECT_TYPE}} project with {{MODEL_COUNT}} models. OpenSaaS Stack will give you:
279
+
280
+ - Automatic admin UI for managing your data
281
+ - Built-in access control to secure your API
282
+ - Type-safe database operations
283
+
284
+ Let me start the migration wizard to configure your project...
285
+
286
+ [Call opensaas_start_migration]
287
+
288
+ **User:** [answers questions]
289
+
290
+ **You:** [Continue through wizard, explain each choice, generate final config]
291
+
292
+ ## Error Handling
293
+
294
+ If something goes wrong:
295
+ 1. Explain what happened in simple terms
296
+ 2. Suggest alternatives or manual steps
297
+ 3. Link to documentation for more help
298
+
299
+ ## After Migration
300
+
301
+ Once the config is generated, guide them through:
302
+ 1. Installing dependencies
303
+ 2. Running \`opensaas generate\`
304
+ 3. Running \`prisma db push\`
305
+ 4. Starting their dev server
306
+ 5. Visiting the admin UI
307
+ `
308
+
309
+ fs.writeFileSync(
310
+ path.join(agentsDir, 'migration-assistant.md'),
311
+ generateTemplateContent(agentTemplate, analysis),
312
+ )
313
+
314
+ // Create analyze-schema command
315
+ const analyzeSchemaTemplate = `Analyze the current project schema and provide a detailed breakdown.
316
+
317
+ ## Instructions
318
+
319
+ 1. Use \`opensaas_introspect_prisma\` or \`opensaas_introspect_keystone\` based on project type
320
+ 2. Present the results in a clear, organized format
321
+ 3. Highlight:
322
+ - All models and their fields
323
+ - Relationships between models
324
+ - Potential access control patterns
325
+ - Any issues or warnings
326
+
327
+ ## Output Format
328
+
329
+ Present like this:
330
+
331
+ ### Models Summary
332
+
333
+ | Model | Fields | Has Relations | Suggested Access |
334
+ |-------|--------|---------------|------------------|
335
+ | ... | ... | ... | ... |
336
+
337
+ ### Detailed Analysis
338
+
339
+ [For each model, show fields and relationships]
340
+
341
+ ### Recommendations
342
+
343
+ [Based on the schema, suggest access control patterns]
344
+ `
345
+
346
+ fs.writeFileSync(path.join(commandsDir, 'analyze-schema.md'), analyzeSchemaTemplate)
347
+
348
+ // Create generate-config command
349
+ const generateConfigTemplate = `Generate the opensaas.config.ts file for this project.
350
+
351
+ ## Instructions
352
+
353
+ 1. If migration wizard hasn't been started, start it:
354
+ \`\`\`
355
+ opensaas_start_migration({ projectType: "{{PROJECT_TYPE_LOWER}}" })
356
+ \`\`\`
357
+
358
+ 2. Guide the user through any remaining questions
359
+
360
+ 3. When complete, display:
361
+ - The generated config file
362
+ - Dependencies to install
363
+ - Next steps to run
364
+
365
+ 4. Offer to explain any part of the generated config
366
+
367
+ ## Quick Mode
368
+
369
+ If the user wants defaults, use these answers:
370
+ - preserve_database: true
371
+ - db_provider: {{PROVIDER}}
372
+ - enable_auth: {{HAS_AUTH}}
373
+ - default_access: "public-read-auth-write"
374
+ - admin_base_path: "/admin"
375
+ `
376
+
377
+ fs.writeFileSync(
378
+ path.join(commandsDir, 'generate-config.md'),
379
+ generateTemplateContent(generateConfigTemplate, analysis),
380
+ )
381
+
382
+ // Create validate-migration command
383
+ const validateMigrationTemplate = `Validate the generated opensaas.config.ts file.
384
+
385
+ ## Instructions
386
+
387
+ 1. Check if opensaas.config.ts exists in the project root
388
+
389
+ 2. If it exists, verify:
390
+ - Syntax is valid TypeScript
391
+ - All imports are correct
392
+ - Database config is complete
393
+ - Lists match original schema
394
+
395
+ 3. Try running:
396
+ \`\`\`bash
397
+ npx @opensaas/stack-cli generate
398
+ \`\`\`
399
+
400
+ 4. Report any errors and suggest fixes
401
+
402
+ 5. If validation passes, confirm next steps:
403
+ - \`npx prisma generate\`
404
+ - \`npx prisma db push\`
405
+ - \`pnpm dev\`
406
+
407
+ ## Common Issues
408
+
409
+ - Missing dependencies → suggest \`pnpm add ...\`
410
+ - Database URL not set → remind about .env file
411
+ - Type errors → suggest specific fixes
412
+ `
413
+
414
+ fs.writeFileSync(path.join(commandsDir, 'validate-migration.md'), validateMigrationTemplate)
415
+ }
416
+
417
+ /**
418
+ * Main migrate command
419
+ */
420
+ async function migrateCommand(options: MigrateOptions): Promise<void> {
421
+ const cwd = process.cwd()
422
+
423
+ console.log(chalk.bold.cyan('\n🚀 OpenSaaS Stack Migration\n'))
424
+
425
+ // Step 1: Detect project type
426
+ const spinner = ora('Detecting project type...').start()
427
+
428
+ let projectTypes: ProjectType[]
429
+ if (options.type) {
430
+ projectTypes = [options.type]
431
+ } else {
432
+ projectTypes = await detectProjectType(cwd)
433
+ }
434
+
435
+ if (projectTypes.length === 0) {
436
+ spinner.fail(chalk.red('No recognizable project found'))
437
+ console.log(chalk.dim('\nThis command works with:'))
438
+ console.log(chalk.dim(' - Prisma projects (prisma/schema.prisma)'))
439
+ console.log(chalk.dim(' - KeystoneJS projects (keystone.config.ts)'))
440
+ console.log(chalk.dim(' - Next.js projects (package.json with next)'))
441
+ console.log(chalk.dim('\nUse --type to force a project type.'))
442
+ process.exit(1)
443
+ }
444
+
445
+ spinner.succeed(chalk.green(`Detected: ${projectTypes.join(', ')}`))
446
+
447
+ // Step 2: Analyze schema
448
+ const analysisSpinner = ora('Analyzing schema...').start()
449
+
450
+ const analysis: ProjectAnalysis = {
451
+ projectTypes,
452
+ cwd,
453
+ }
454
+
455
+ if (projectTypes.includes('prisma')) {
456
+ try {
457
+ const prismaAnalysis = await analyzePrismaSchema(cwd)
458
+ analysis.models = prismaAnalysis.models
459
+ analysis.provider = prismaAnalysis.provider
460
+ } catch (_error) {
461
+ // Prisma analysis failed, continue without it
462
+ }
463
+ }
464
+
465
+ if (analysis.models && analysis.models.length > 0) {
466
+ analysisSpinner.succeed(chalk.green(`Found ${analysis.models.length} models`))
467
+
468
+ // Display model tree
469
+ const lastIndex = analysis.models.length - 1
470
+ analysis.models.forEach((model, index) => {
471
+ const prefix = index === lastIndex ? '└─' : '├─'
472
+ console.log(chalk.dim(` ${prefix} ${model.name} (${model.fieldCount} fields)`))
473
+ })
474
+ } else {
475
+ analysisSpinner.succeed(chalk.yellow('No models found (will create from scratch)'))
476
+ }
477
+
478
+ // Step 3: Setup Claude Code (if --with-ai)
479
+ if (options.withAi) {
480
+ const claudeSpinner = ora('Setting up Claude Code...').start()
481
+
482
+ try {
483
+ await setupClaudeCode(cwd, analysis)
484
+ claudeSpinner.succeed(chalk.green('Claude Code ready'))
485
+
486
+ console.log(chalk.dim(' ├─ Created .claude directory'))
487
+ console.log(chalk.dim(' ├─ Generated migration assistant'))
488
+ console.log(chalk.dim(' └─ Registered MCP server'))
489
+ } catch (error) {
490
+ claudeSpinner.fail(chalk.red('Failed to setup Claude Code'))
491
+ console.error(error)
492
+ }
493
+ }
494
+
495
+ // Step 4: Display next steps
496
+ console.log(chalk.green('\n✅ Analysis complete!\n'))
497
+
498
+ if (options.withAi) {
499
+ console.log(chalk.bold('🤖 Next Steps:\n'))
500
+ console.log(chalk.cyan(' 1. Open this project in Claude Code'))
501
+ console.log(chalk.cyan(' 2. Ask: "Help me migrate to OpenSaaS Stack"'))
502
+ console.log(chalk.cyan(' 3. Follow the interactive wizard'))
503
+ } else {
504
+ console.log(chalk.bold('📝 Next Steps:\n'))
505
+ console.log(chalk.cyan(' 1. Run with --with-ai for AI-guided migration'))
506
+ console.log(chalk.cyan(' 2. Or manually create opensaas.config.ts'))
507
+ console.log(chalk.dim('\n See: https://stack.opensaas.au/guides/migration'))
508
+ }
509
+
510
+ console.log(chalk.dim(`\n📚 Documentation: https://stack.opensaas.au/guides/migration\n`))
511
+ }
512
+
513
+ /**
514
+ * Create the migrate command for Commander
515
+ */
516
+ export function createMigrateCommand(): Command {
517
+ const migrate = new Command('migrate')
518
+ migrate.description('Migrate an existing project to OpenSaaS Stack')
519
+
520
+ migrate
521
+ .option('--with-ai', 'Enable AI-guided migration with Claude Code')
522
+ .option('--type <type>', 'Force project type (prisma, nextjs, keystone)')
523
+ .action(async (options: MigrateOptions) => {
524
+ try {
525
+ await migrateCommand(options)
526
+ process.exit(0)
527
+ } catch (error) {
528
+ console.error(chalk.red('\n❌ Migration failed:'), error)
529
+ process.exit(1)
530
+ }
531
+ })
532
+
533
+ return migrate
534
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { generateCommand } from './commands/generate.js'
5
5
  import { initCommand } from './commands/init.js'
6
6
  import { devCommand } from './commands/dev.js'
7
7
  import { createMCPCommand } from './commands/mcp.js'
8
+ import { createMigrateCommand } from './commands/migrate.js'
8
9
 
9
10
  const program = new Command()
10
11
 
@@ -39,4 +40,7 @@ program
39
40
  // Add MCP command group
40
41
  program.addCommand(createMCPCommand())
41
42
 
43
+ // Add migrate command
44
+ program.addCommand(createMigrateCommand())
45
+
42
46
  program.parse()