@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
|
@@ -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
|
*/
|