@opensaas/stack-cli 0.5.0 → 0.6.1

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