@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.
Files changed (105) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +193 -0
  3. package/dist/commands/generate.d.ts.map +1 -1
  4. package/dist/commands/generate.js +4 -13
  5. package/dist/commands/generate.js.map +1 -1
  6. package/dist/commands/migrate.d.ts +9 -0
  7. package/dist/commands/migrate.d.ts.map +1 -0
  8. package/dist/commands/migrate.js +473 -0
  9. package/dist/commands/migrate.js.map +1 -0
  10. package/dist/generator/context.d.ts.map +1 -1
  11. package/dist/generator/context.js +20 -5
  12. package/dist/generator/context.js.map +1 -1
  13. package/dist/generator/index.d.ts +1 -1
  14. package/dist/generator/index.d.ts.map +1 -1
  15. package/dist/generator/index.js +1 -1
  16. package/dist/generator/index.js.map +1 -1
  17. package/dist/generator/lists.d.ts.map +1 -1
  18. package/dist/generator/lists.js +33 -1
  19. package/dist/generator/lists.js.map +1 -1
  20. package/dist/generator/prisma-extensions.d.ts +11 -0
  21. package/dist/generator/prisma-extensions.d.ts.map +1 -0
  22. package/dist/generator/prisma-extensions.js +134 -0
  23. package/dist/generator/prisma-extensions.js.map +1 -0
  24. package/dist/generator/prisma.d.ts.map +1 -1
  25. package/dist/generator/prisma.js +4 -0
  26. package/dist/generator/prisma.js.map +1 -1
  27. package/dist/generator/types.d.ts.map +1 -1
  28. package/dist/generator/types.js +151 -17
  29. package/dist/generator/types.js.map +1 -1
  30. package/dist/index.js +3 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/mcp/lib/documentation-provider.d.ts +23 -0
  33. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
  34. package/dist/mcp/lib/documentation-provider.js +471 -0
  35. package/dist/mcp/lib/documentation-provider.js.map +1 -1
  36. package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
  37. package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
  38. package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
  39. package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
  40. package/dist/mcp/server/index.d.ts.map +1 -1
  41. package/dist/mcp/server/index.js +103 -0
  42. package/dist/mcp/server/index.js.map +1 -1
  43. package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
  44. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
  45. package/dist/mcp/server/stack-mcp-server.js +219 -0
  46. package/dist/mcp/server/stack-mcp-server.js.map +1 -1
  47. package/dist/migration/generators/migration-generator.d.ts +60 -0
  48. package/dist/migration/generators/migration-generator.d.ts.map +1 -0
  49. package/dist/migration/generators/migration-generator.js +510 -0
  50. package/dist/migration/generators/migration-generator.js.map +1 -0
  51. package/dist/migration/introspectors/index.d.ts +12 -0
  52. package/dist/migration/introspectors/index.d.ts.map +1 -0
  53. package/dist/migration/introspectors/index.js +10 -0
  54. package/dist/migration/introspectors/index.js.map +1 -0
  55. package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
  56. package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
  57. package/dist/migration/introspectors/keystone-introspector.js +229 -0
  58. package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
  59. package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
  60. package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
  61. package/dist/migration/introspectors/nextjs-introspector.js +159 -0
  62. package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
  63. package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
  64. package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
  65. package/dist/migration/introspectors/prisma-introspector.js +190 -0
  66. package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
  67. package/dist/migration/types.d.ts +86 -0
  68. package/dist/migration/types.d.ts.map +1 -0
  69. package/dist/migration/types.js +5 -0
  70. package/dist/migration/types.js.map +1 -0
  71. package/package.json +12 -9
  72. package/src/commands/__snapshots__/generate.test.ts.snap +92 -21
  73. package/src/commands/generate.ts +8 -19
  74. package/src/commands/migrate.ts +534 -0
  75. package/src/generator/__snapshots__/context.test.ts.snap +60 -15
  76. package/src/generator/__snapshots__/types.test.ts.snap +689 -95
  77. package/src/generator/context.test.ts +3 -1
  78. package/src/generator/context.ts +20 -5
  79. package/src/generator/index.ts +1 -1
  80. package/src/generator/lists.ts +39 -1
  81. package/src/generator/prisma-extensions.ts +159 -0
  82. package/src/generator/prisma.ts +5 -0
  83. package/src/generator/types.ts +204 -17
  84. package/src/index.ts +4 -0
  85. package/src/mcp/lib/documentation-provider.ts +507 -0
  86. package/src/mcp/lib/wizards/migration-wizard.ts +584 -0
  87. package/src/mcp/server/index.ts +121 -0
  88. package/src/mcp/server/stack-mcp-server.ts +243 -0
  89. package/src/migration/generators/migration-generator.ts +675 -0
  90. package/src/migration/introspectors/index.ts +12 -0
  91. package/src/migration/introspectors/keystone-introspector.ts +296 -0
  92. package/src/migration/introspectors/nextjs-introspector.ts +209 -0
  93. package/src/migration/introspectors/prisma-introspector.ts +233 -0
  94. package/src/migration/types.ts +92 -0
  95. package/tests/introspectors/keystone-introspector.test.ts +255 -0
  96. package/tests/introspectors/nextjs-introspector.test.ts +302 -0
  97. package/tests/introspectors/prisma-introspector.test.ts +268 -0
  98. package/tests/migration-generator.test.ts +592 -0
  99. package/tests/migration-wizard.test.ts +442 -0
  100. package/tsconfig.tsbuildinfo +1 -1
  101. package/dist/generator/type-patcher.d.ts +0 -13
  102. package/dist/generator/type-patcher.d.ts.map +0 -1
  103. package/dist/generator/type-patcher.js +0 -68
  104. package/dist/generator/type-patcher.js.map +0 -1
  105. package/src/generator/type-patcher.ts +0 -93
@@ -3,16 +3,26 @@
3
3
  */
4
4
 
5
5
  import { WizardEngine } from '../lib/wizards/wizard-engine.js'
6
+ import { MigrationWizard } from '../lib/wizards/migration-wizard.js'
6
7
  import { OpenSaasDocumentationProvider } from '../lib/documentation-provider.js'
7
8
  import { getAllFeatures, getFeature } from '../lib/features/catalog.js'
9
+ import { PrismaIntrospector } from '../../migration/introspectors/prisma-introspector.js'
10
+ import { KeystoneIntrospector } from '../../migration/introspectors/keystone-introspector.js'
11
+ import type { ProjectType } from '../../migration/types.js'
8
12
 
9
13
  export class StackMCPServer {
10
14
  private wizardEngine: WizardEngine
15
+ private migrationWizard: MigrationWizard
11
16
  private docsProvider: OpenSaasDocumentationProvider
17
+ private prismaIntrospector: PrismaIntrospector
18
+ private keystoneIntrospector: KeystoneIntrospector
12
19
 
13
20
  constructor() {
14
21
  this.wizardEngine = new WizardEngine()
22
+ this.migrationWizard = new MigrationWizard()
15
23
  this.docsProvider = new OpenSaasDocumentationProvider()
24
+ this.prismaIntrospector = new PrismaIntrospector()
25
+ this.keystoneIntrospector = new KeystoneIntrospector()
16
26
  }
17
27
 
18
28
  /**
@@ -291,6 +301,239 @@ ${featureDefinition.dependsOn && featureDefinition.dependsOn.length > 0 ? `\n##
291
301
  }
292
302
  }
293
303
 
304
+ /**
305
+ * Start a migration wizard session
306
+ */
307
+ async startMigration({ projectType }: { projectType: ProjectType }) {
308
+ return this.migrationWizard.startMigration(projectType)
309
+ }
310
+
311
+ /**
312
+ * Answer a migration wizard question
313
+ */
314
+ async answerMigration({
315
+ sessionId,
316
+ answer,
317
+ }: {
318
+ sessionId: string
319
+ answer: string | boolean | string[]
320
+ }) {
321
+ return this.migrationWizard.answerQuestion(sessionId, answer)
322
+ }
323
+
324
+ /**
325
+ * Introspect a Prisma schema
326
+ */
327
+ async introspectPrisma({ schemaPath }: { schemaPath?: string }) {
328
+ const cwd = process.cwd()
329
+ const path = schemaPath || 'prisma/schema.prisma'
330
+
331
+ try {
332
+ const schema = await this.prismaIntrospector.introspect(cwd, path)
333
+
334
+ const modelList = schema.models
335
+ .map((m) => {
336
+ const fields = m.fields
337
+ .map((f) => {
338
+ let type = f.type
339
+ if (f.relation) type = `→ ${f.relation.model}`
340
+ if (f.isList) type = `${type}[]`
341
+ if (!f.isRequired) type = `${type}?`
342
+ return ` - ${f.name}: ${type}`
343
+ })
344
+ .join('\n')
345
+ return `### ${m.name}\n${fields}`
346
+ })
347
+ .join('\n\n')
348
+
349
+ const enumList =
350
+ schema.enums.length > 0
351
+ ? `\n## Enums\n\n${schema.enums.map((e) => `- **${e.name}**: ${e.values.join(', ')}`).join('\n')}`
352
+ : ''
353
+
354
+ return {
355
+ content: [
356
+ {
357
+ type: 'text' as const,
358
+ text: `# Prisma Schema Analysis
359
+
360
+ **Provider:** ${schema.provider}
361
+ **Models:** ${schema.models.length}
362
+ **Enums:** ${schema.enums.length}
363
+
364
+ ## Models
365
+
366
+ ${modelList}
367
+ ${enumList}
368
+
369
+ ---
370
+
371
+ **Ready to migrate?** Use \`opensaas_start_migration({ projectType: "prisma" })\` to begin the wizard.`,
372
+ },
373
+ ],
374
+ }
375
+ } catch (error) {
376
+ const message = error instanceof Error ? error.message : String(error)
377
+ return {
378
+ content: [
379
+ {
380
+ type: 'text' as const,
381
+ text: `❌ Failed to introspect Prisma schema: ${message}\n\nMake sure the file exists at: ${path}`,
382
+ },
383
+ ],
384
+ isError: true,
385
+ }
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Introspect a KeystoneJS config
391
+ */
392
+ async introspectKeystone({ configPath }: { configPath?: string }) {
393
+ const cwd = process.cwd()
394
+ const path = configPath || 'keystone.config.ts'
395
+
396
+ try {
397
+ const config = await this.keystoneIntrospector.introspect(cwd, path)
398
+
399
+ const listInfo = config.models
400
+ .map((m) => {
401
+ const fields = m.fields.map((f) => ` - ${f.name}: ${f.type}`).join('\n')
402
+ return `### ${m.name}\n${fields}`
403
+ })
404
+ .join('\n\n')
405
+
406
+ return {
407
+ content: [
408
+ {
409
+ type: 'text' as const,
410
+ text: `# KeystoneJS Config Analysis
411
+
412
+ **Lists:** ${config.models.length}
413
+
414
+ ## Lists
415
+
416
+ ${listInfo}
417
+
418
+ ---
419
+
420
+ **Note:** KeystoneJS → OpenSaaS migration is mostly 1:1. Field types and access control patterns map directly.
421
+
422
+ **Ready to migrate?** Use \`opensaas_start_migration({ projectType: "keystone" })\` to begin.`,
423
+ },
424
+ ],
425
+ }
426
+ } catch (error) {
427
+ const message = error instanceof Error ? error.message : String(error)
428
+ return {
429
+ content: [
430
+ {
431
+ type: 'text' as const,
432
+ text: `❌ Failed to introspect KeystoneJS config: ${message}\n\nMake sure the file exists at: ${path}`,
433
+ },
434
+ ],
435
+ isError: true,
436
+ }
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Search migration documentation
442
+ */
443
+ async searchMigrationDocs({ query }: { query: string }) {
444
+ // First try local CLAUDE.md files
445
+ const localDocs = await this.docsProvider.searchLocalDocs(query)
446
+
447
+ // Then try hosted docs
448
+ const hostedDocs = await this.docsProvider.searchDocs(query)
449
+
450
+ const sections: string[] = []
451
+
452
+ if (localDocs.content) {
453
+ sections.push(`## Local Documentation\n\n${localDocs.content}`)
454
+ }
455
+
456
+ if (hostedDocs.content && hostedDocs.content !== 'No documentation found for this query.') {
457
+ sections.push(`## Online Documentation\n\n${hostedDocs.content}`)
458
+ }
459
+
460
+ if (sections.length === 0) {
461
+ return {
462
+ content: [
463
+ {
464
+ type: 'text' as const,
465
+ text: `No documentation found for "${query}".
466
+
467
+ Try these searches:
468
+ - "access control" - How to restrict access to data
469
+ - "field types" - Available field types in OpenSaaS
470
+ - "authentication" - Setting up auth with Better-auth
471
+ - "hooks" - Data transformation and side effects
472
+
473
+ Or visit: https://stack.opensaas.au/`,
474
+ },
475
+ ],
476
+ }
477
+ }
478
+
479
+ return {
480
+ content: [
481
+ {
482
+ type: 'text' as const,
483
+ text: `# Documentation: ${query}\n\n${sections.join('\n\n---\n\n')}`,
484
+ },
485
+ ],
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Get example code for a feature
491
+ */
492
+ async getExample({ feature }: { feature: string }) {
493
+ const example = await this.docsProvider.getExampleConfig(feature)
494
+
495
+ if (!example) {
496
+ return {
497
+ content: [
498
+ {
499
+ type: 'text' as const,
500
+ text: `No example found for "${feature}".
501
+
502
+ Available examples:
503
+ - **blog-with-auth** - Blog with user authentication
504
+ - **access-control** - Access control patterns
505
+ - **relationships** - Model relationships
506
+ - **hooks** - Data transformation hooks
507
+ - **custom-fields** - Custom field types
508
+
509
+ Use: \`opensaas_get_example({ feature: "example-name" })\``,
510
+ },
511
+ ],
512
+ }
513
+ }
514
+
515
+ return {
516
+ content: [
517
+ {
518
+ type: 'text' as const,
519
+ text: `# Example: ${feature}
520
+
521
+ ${example.description}
522
+
523
+ \`\`\`typescript
524
+ ${example.code}
525
+ \`\`\`
526
+
527
+ ${example.notes ? `\n## Notes\n\n${example.notes}` : ''}
528
+
529
+ ---
530
+
531
+ 📚 Full example at: ${example.sourcePath}`,
532
+ },
533
+ ],
534
+ }
535
+ }
536
+
294
537
  /**
295
538
  * Cleanup - clear wizard sessions and caches
296
539
  */