@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,182 +0,0 @@
1
- import * as path from 'path'
2
- import * as fs from 'fs'
3
- import { execSync } from 'child_process'
4
- import chalk from 'chalk'
5
- import ora from 'ora'
6
- import { createJiti } from 'jiti'
7
- import {
8
- writePrismaSchema,
9
- writePrismaConfig,
10
- writeTypes,
11
- writeLists,
12
- writeContext,
13
- writePluginTypes,
14
- writePrismaExtensions,
15
- } from '../generator/index.js'
16
- import { OpenSaasConfig } from '@opensaas/stack-core'
17
-
18
- export async function generateCommand() {
19
- console.log(chalk.bold('\nšŸš€ OpenSaas Generator\n'))
20
-
21
- const cwd = process.cwd()
22
- const configPath = path.join(cwd, 'opensaas.config.ts')
23
-
24
- // Check if config exists
25
- if (!fs.existsSync(configPath)) {
26
- console.error(chalk.red('āŒ Error: opensaas.config.ts not found in current directory'))
27
- console.error(chalk.gray(' Please run this command from your project root'))
28
- process.exit(1)
29
- }
30
-
31
- const spinner = ora('Loading configuration...').start()
32
-
33
- try {
34
- // Load config using jiti (supports TypeScript)
35
- const jiti = createJiti(cwd, {
36
- interopDefault: true,
37
- })
38
-
39
- // Config may be async (if plugins are present)
40
- // jiti.import() returns a module object with 'default' export
41
- // We need to manually extract the default export since interopDefault doesn't work with async exports
42
- const module = (await jiti.import(configPath)) as { default: Promise<OpenSaasConfig> }
43
- const configOrPromise = module.default
44
-
45
- // Resolve the config if it's a Promise (from plugin execution)
46
- let config = await Promise.resolve(configOrPromise)
47
-
48
- // Log plugin count if plugins are present
49
- if (config.plugins && config.plugins.length > 0) {
50
- spinner.text = `Loading configuration with ${config.plugins.length} plugin(s)...`
51
- }
52
-
53
- spinner.succeed(chalk.green('Configuration loaded'))
54
-
55
- // Execute beforeGenerate hooks if plugins are present
56
- if (config.plugins && config.plugins.length > 0) {
57
- const pluginSpinner = ora('Running plugin beforeGenerate hooks...').start()
58
-
59
- try {
60
- // Import plugin engine (avoid circular dependency)
61
- const { executeBeforeGenerateHooks } =
62
- await import('@opensaas/stack-core/config/plugin-engine')
63
- config = await executeBeforeGenerateHooks(config)
64
- pluginSpinner.succeed(chalk.green('Plugin beforeGenerate hooks complete'))
65
- } catch (err) {
66
- pluginSpinner.fail(chalk.red('Plugin beforeGenerate hooks failed'))
67
- throw err
68
- }
69
- }
70
-
71
- // Generate Prisma schema, types, and context
72
- const generatorSpinner = ora('Generating schema and types...').start()
73
- try {
74
- const prismaSchemaPath = path.join(cwd, 'prisma', 'schema.prisma')
75
- const prismaConfigPath = path.join(cwd, 'prisma.config.ts')
76
- const typesPath = path.join(cwd, '.opensaas', 'types.ts')
77
- const listsPath = path.join(cwd, '.opensaas', 'lists.ts')
78
- const contextPath = path.join(cwd, '.opensaas', 'context.ts')
79
- const pluginTypesPath = path.join(cwd, '.opensaas', 'plugin-types.ts')
80
- const prismaExtensionsPath = path.join(cwd, '.opensaas', 'prisma-extensions.ts')
81
-
82
- writePrismaSchema(config, prismaSchemaPath)
83
- writePrismaConfig(config, prismaConfigPath)
84
- writeTypes(config, typesPath)
85
- writeLists(config, listsPath)
86
- writeContext(config, contextPath)
87
- writePluginTypes(config, pluginTypesPath)
88
- writePrismaExtensions(config, prismaExtensionsPath)
89
-
90
- generatorSpinner.succeed(chalk.green('Schema generation complete'))
91
- console.log(chalk.green('āœ… Prisma schema generated'))
92
- console.log(chalk.green('āœ… Prisma config generated'))
93
- console.log(chalk.green('āœ… TypeScript types generated'))
94
- console.log(chalk.green('āœ… Lists namespace generated'))
95
- console.log(chalk.green('āœ… Context factory generated'))
96
- console.log(chalk.green('āœ… Plugin types generated'))
97
- console.log(chalk.green('āœ… Prisma extensions generated'))
98
-
99
- // Execute afterGenerate hooks if plugins are present
100
- if (config.plugins && config.plugins.length > 0) {
101
- const afterGenSpinner = ora('Running plugin afterGenerate hooks...').start()
102
-
103
- try {
104
- // Read generated files
105
- const generatedFiles = {
106
- prismaSchema: fs.readFileSync(prismaSchemaPath, 'utf-8'),
107
- types: fs.readFileSync(typesPath, 'utf-8'),
108
- context: fs.readFileSync(contextPath, 'utf-8'),
109
- }
110
-
111
- // Execute afterGenerate hooks
112
- const { executeAfterGenerateHooks } =
113
- await import('@opensaas/stack-core/config/plugin-engine')
114
- const modifiedFiles = await executeAfterGenerateHooks(config, generatedFiles)
115
-
116
- // Write back modified files
117
- if (modifiedFiles.prismaSchema !== generatedFiles.prismaSchema) {
118
- fs.writeFileSync(prismaSchemaPath, modifiedFiles.prismaSchema)
119
- }
120
- if (modifiedFiles.types !== generatedFiles.types) {
121
- fs.writeFileSync(typesPath, modifiedFiles.types)
122
- }
123
- if (modifiedFiles.context !== generatedFiles.context) {
124
- fs.writeFileSync(contextPath, modifiedFiles.context)
125
- }
126
-
127
- // Write any additional files plugins generated
128
- for (const [filename, content] of Object.entries(modifiedFiles)) {
129
- if (!['prismaSchema', 'types', 'context'].includes(filename)) {
130
- const filePath = path.join(cwd, '.opensaas', filename)
131
- fs.writeFileSync(filePath, content)
132
- console.log(chalk.green(`āœ… Plugin generated: ${filename}`))
133
- }
134
- }
135
-
136
- afterGenSpinner.succeed(chalk.green('Plugin afterGenerate hooks complete'))
137
- } catch (err) {
138
- afterGenSpinner.fail(chalk.red('Plugin afterGenerate hooks failed'))
139
- throw err
140
- }
141
- }
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
- } catch (err: any) {
144
- generatorSpinner.fail(chalk.red('Failed to generate'))
145
- console.error(chalk.red('\nāŒ Error:'), err.message)
146
- if (err.stack) {
147
- console.error(chalk.gray('\n' + err.stack))
148
- }
149
- process.exit(1)
150
- }
151
-
152
- // Run Prisma generate to create the Prisma client
153
- const prismaSpinner = ora('Generating Prisma client...').start()
154
- try {
155
- execSync('npx prisma generate', {
156
- cwd,
157
- encoding: 'utf-8',
158
- stdio: 'pipe',
159
- })
160
- prismaSpinner.succeed(chalk.green('Prisma client generated'))
161
- console.log(chalk.green('āœ… Prisma client generated'))
162
- } catch (err) {
163
- prismaSpinner.fail(chalk.red('Failed to generate Prisma client'))
164
- const message = err instanceof Error ? err.message : String(err)
165
- console.error(chalk.red('\nāŒ Error:'), message)
166
- process.exit(1)
167
- }
168
-
169
- console.log(chalk.bold('\n✨ Generation complete!\n'))
170
- console.log(chalk.gray('Next steps:'))
171
- console.log(chalk.gray(' 1. Run: npx prisma db push'))
172
- console.log(chalk.gray(' 2. Start using your generated types!\n'))
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
- } catch (error: any) {
175
- spinner.fail(chalk.red('Generation failed'))
176
- console.error(chalk.red('\nāŒ Error:'), error.message)
177
- if (error.stack) {
178
- console.error(chalk.gray('\n' + error.stack))
179
- }
180
- process.exit(1)
181
- }
182
- }
@@ -1,34 +0,0 @@
1
- import { spawn } from 'child_process'
2
- import chalk from 'chalk'
3
-
4
- /**
5
- * Initialize a new OpenSaas Stack project.
6
- *
7
- * This command delegates to create-opensaas-app for the actual scaffolding.
8
- * It's kept here for backwards compatibility with `opensaas init`.
9
- *
10
- * @param args - Command line arguments (project name and flags)
11
- */
12
- export async function initCommand(args: string[]) {
13
- console.log(chalk.dim('Delegating to create-opensaas-app...\n'))
14
-
15
- // Forward all arguments to create-opensaas-app
16
- const child = spawn('npx', ['create-opensaas-app@latest', ...args], {
17
- stdio: 'inherit',
18
- shell: true,
19
- })
20
-
21
- return new Promise<void>((resolve, reject) => {
22
- child.on('close', (code) => {
23
- if (code !== 0) {
24
- reject(new Error(`create-opensaas-app exited with code ${code}`))
25
- } else {
26
- resolve()
27
- }
28
- })
29
-
30
- child.on('error', (err) => {
31
- reject(err)
32
- })
33
- })
34
- }
@@ -1,135 +0,0 @@
1
- /**
2
- * MCP command group for AI-assisted development
3
- */
4
-
5
- import { Command } from 'commander'
6
- import { spawn } from 'child_process'
7
- import { fileURLToPath } from 'url'
8
- import { dirname, join } from 'path'
9
-
10
- const __filename = fileURLToPath(import.meta.url)
11
- const __dirname = dirname(__filename)
12
-
13
- function getServerPath(): string {
14
- // In development: cli/dist/mcp/server/index.js
15
- // In production: same structure
16
- return join(__dirname, '..', 'mcp', 'server', 'index.js')
17
- }
18
-
19
- function installMCPServer(): Promise<void> {
20
- return new Promise((resolve, reject) => {
21
- console.log('šŸ“¦ Installing OpenSaaS Stack MCP server...\n')
22
-
23
- const serverPath = getServerPath()
24
- const claudeCommand = ['claude', 'mcp', 'add', 'opensaas-stack', '--', 'node', serverPath]
25
-
26
- console.log(`Running: ${claudeCommand.join(' ')}\n`)
27
-
28
- const child = spawn(claudeCommand[0], claudeCommand.slice(1), {
29
- stdio: 'inherit',
30
- })
31
-
32
- child.on('close', (code) => {
33
- if (code === 0) {
34
- console.log('\nāœ… OpenSaaS Stack MCP server installed successfully!')
35
- console.log('\nšŸ“– Available tools:')
36
- console.log(' - opensaas_implement_feature')
37
- console.log(' - opensaas_feature_docs')
38
- console.log(' - opensaas_list_features')
39
- console.log(' - opensaas_suggest_features')
40
- console.log(' - opensaas_validate_feature')
41
- console.log('\nšŸš€ Restart Claude Code to use the MCP tools.')
42
- resolve()
43
- } else {
44
- console.error(
45
- `\nāŒ Installation failed with code ${code}. Please ensure Claude Code is installed.`,
46
- )
47
- reject(new Error(`Installation failed with code ${code}`))
48
- }
49
- })
50
-
51
- child.on('error', (error) => {
52
- console.error('\nāŒ Error during installation:', error.message)
53
- console.error('\nMake sure Claude Code is installed and the "claude" command is available.')
54
- reject(error)
55
- })
56
- })
57
- }
58
-
59
- function uninstallMCPServer(): Promise<void> {
60
- return new Promise((resolve, reject) => {
61
- console.log('šŸ—‘ļø Uninstalling OpenSaaS Stack MCP server...\n')
62
-
63
- const child = spawn('claude', ['mcp', 'remove', 'opensaas-stack'], {
64
- stdio: 'inherit',
65
- })
66
-
67
- child.on('close', (code) => {
68
- if (code === 0) {
69
- console.log('\nāœ… OpenSaaS Stack MCP server uninstalled successfully.')
70
- resolve()
71
- } else {
72
- console.error(`\nāŒ Uninstall failed with code ${code}`)
73
- reject(new Error(`Uninstall failed with code ${code}`))
74
- }
75
- })
76
-
77
- child.on('error', (error) => {
78
- console.error('\nāŒ Error during uninstall:', error.message)
79
- reject(error)
80
- })
81
- })
82
- }
83
-
84
- function startMCPServer(): void {
85
- console.log('šŸš€ Starting OpenSaaS Stack MCP server...\n')
86
-
87
- const serverPath = getServerPath()
88
-
89
- const child = spawn('node', [serverPath], {
90
- stdio: 'inherit',
91
- })
92
-
93
- child.on('error', (error) => {
94
- console.error('āŒ Error starting server:', error.message)
95
- process.exit(1)
96
- })
97
- }
98
-
99
- export function createMCPCommand(): Command {
100
- const mcp = new Command('mcp')
101
- mcp.description('MCP server for AI-assisted development with Claude Code')
102
-
103
- mcp
104
- .command('install')
105
- .description('Install MCP server in Claude Code')
106
- .action(async () => {
107
- try {
108
- await installMCPServer()
109
- process.exit(0)
110
- } catch {
111
- process.exit(1)
112
- }
113
- })
114
-
115
- mcp
116
- .command('uninstall')
117
- .description('Remove MCP server from Claude Code')
118
- .action(async () => {
119
- try {
120
- await uninstallMCPServer()
121
- process.exit(0)
122
- } catch {
123
- process.exit(1)
124
- }
125
- })
126
-
127
- mcp
128
- .command('start')
129
- .description('Start MCP server directly (for debugging)')
130
- .action(() => {
131
- startMCPServer()
132
- })
133
-
134
- return mcp
135
- }