@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.
- package/README.md +76 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +91 -265
- package/dist/commands/migrate.js.map +1 -1
- package/package.json +7 -2
- package/plugin/.claude-plugin/plugin.json +15 -0
- package/plugin/README.md +112 -0
- package/plugin/agents/migration-assistant.md +150 -0
- package/plugin/commands/analyze-schema.md +34 -0
- package/plugin/commands/generate-config.md +33 -0
- package/plugin/commands/validate-migration.md +34 -0
- package/plugin/skills/opensaas-migration/SKILL.md +192 -0
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -462
- package/CLAUDE.md +0 -298
- package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
- package/src/commands/dev.test.ts +0 -215
- package/src/commands/dev.ts +0 -48
- package/src/commands/generate.test.ts +0 -282
- package/src/commands/generate.ts +0 -182
- package/src/commands/init.ts +0 -34
- package/src/commands/mcp.ts +0 -135
- package/src/commands/migrate.ts +0 -534
- package/src/generator/__snapshots__/context.test.ts.snap +0 -361
- package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
- package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
- package/src/generator/context.test.ts +0 -139
- package/src/generator/context.ts +0 -227
- package/src/generator/index.ts +0 -7
- package/src/generator/lists.test.ts +0 -335
- package/src/generator/lists.ts +0 -140
- package/src/generator/plugin-types.ts +0 -147
- package/src/generator/prisma-config.ts +0 -46
- package/src/generator/prisma-extensions.ts +0 -159
- package/src/generator/prisma.test.ts +0 -211
- package/src/generator/prisma.ts +0 -161
- package/src/generator/types.test.ts +0 -268
- package/src/generator/types.ts +0 -537
- package/src/index.ts +0 -46
- package/src/mcp/lib/documentation-provider.ts +0 -710
- package/src/mcp/lib/features/catalog.ts +0 -301
- package/src/mcp/lib/generators/feature-generator.ts +0 -598
- package/src/mcp/lib/types.ts +0 -89
- package/src/mcp/lib/wizards/migration-wizard.ts +0 -584
- package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
- package/src/mcp/server/index.ts +0 -361
- package/src/mcp/server/stack-mcp-server.ts +0 -544
- package/src/migration/generators/migration-generator.ts +0 -675
- package/src/migration/introspectors/index.ts +0 -12
- package/src/migration/introspectors/keystone-introspector.ts +0 -296
- package/src/migration/introspectors/nextjs-introspector.ts +0 -209
- package/src/migration/introspectors/prisma-introspector.ts +0 -233
- package/src/migration/types.ts +0 -92
- package/tests/introspectors/keystone-introspector.test.ts +0 -255
- package/tests/introspectors/nextjs-introspector.test.ts +0 -302
- package/tests/introspectors/prisma-introspector.test.ts +0 -268
- package/tests/migration-generator.test.ts +0 -592
- package/tests/migration-wizard.test.ts +0 -442
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -26
package/src/commands/generate.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/mcp.ts
DELETED
|
@@ -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
|
-
}
|