@opensaas/stack-cli 0.1.0 → 0.1.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +249 -0
- package/LICENSE +21 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +6 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +78 -3
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +1 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +1 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/mcp.d.ts +14 -0
- package/dist/generator/mcp.d.ts.map +1 -0
- package/dist/generator/mcp.js +193 -0
- package/dist/generator/mcp.js.map +1 -0
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +11 -33
- package/dist/generator/types.js.map +1 -1
- package/package.json +9 -3
- package/src/commands/__snapshots__/generate.test.ts.snap +265 -0
- package/src/commands/dev.test.ts +216 -0
- package/src/commands/generate.test.ts +272 -0
- package/src/commands/generate.ts +7 -0
- package/src/commands/init.test.ts +308 -0
- package/src/commands/init.ts +2 -2
- package/src/generator/__snapshots__/context.test.ts.snap +137 -0
- package/src/generator/__snapshots__/prisma.test.ts.snap +182 -0
- package/src/generator/__snapshots__/types.test.ts.snap +512 -0
- package/src/generator/context.test.ts +145 -0
- package/src/generator/context.ts +80 -3
- package/src/generator/index.ts +1 -0
- package/src/generator/mcp.test.ts +393 -0
- package/src/generator/mcp.ts +221 -0
- package/src/generator/prisma.test.ts +221 -0
- package/src/generator/types.test.ts +280 -0
- package/src/generator/types.ts +14 -36
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP metadata generation
|
|
3
|
+
* Generates reference files for MCP configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs'
|
|
7
|
+
import * as path from 'path'
|
|
8
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
9
|
+
import { getDbKey } from '@opensaas/stack-core'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate MCP metadata if MCP is enabled
|
|
13
|
+
*/
|
|
14
|
+
export function generateMcp(config: OpenSaasConfig, outputPath: string): boolean {
|
|
15
|
+
// Skip if MCP is not enabled
|
|
16
|
+
if (!config.mcp?.enabled) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Ensure output directory exists
|
|
21
|
+
const mcpDir = path.join(outputPath, '.opensaas', 'mcp')
|
|
22
|
+
if (!fs.existsSync(mcpDir)) {
|
|
23
|
+
fs.mkdirSync(mcpDir, { recursive: true })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Generate tool metadata for reference
|
|
27
|
+
const tools: Array<{
|
|
28
|
+
name: string
|
|
29
|
+
description: string
|
|
30
|
+
listKey: string
|
|
31
|
+
operation: string
|
|
32
|
+
}> = []
|
|
33
|
+
|
|
34
|
+
for (const [listKey, listConfig] of Object.entries(config.lists)) {
|
|
35
|
+
if (listConfig.mcp?.enabled === false) continue
|
|
36
|
+
|
|
37
|
+
const dbKey = getDbKey(listKey)
|
|
38
|
+
const defaultTools = config.mcp?.defaultTools || {
|
|
39
|
+
read: true,
|
|
40
|
+
create: true,
|
|
41
|
+
update: true,
|
|
42
|
+
delete: true,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const enabledTools = {
|
|
46
|
+
read: listConfig.mcp?.tools?.read ?? defaultTools.read ?? true,
|
|
47
|
+
create: listConfig.mcp?.tools?.create ?? defaultTools.create ?? true,
|
|
48
|
+
update: listConfig.mcp?.tools?.update ?? defaultTools.update ?? true,
|
|
49
|
+
delete: listConfig.mcp?.tools?.delete ?? defaultTools.delete ?? true,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (enabledTools.read) {
|
|
53
|
+
tools.push({
|
|
54
|
+
name: `list_${dbKey}_query`,
|
|
55
|
+
description: `Query ${listKey} records with optional filters`,
|
|
56
|
+
listKey,
|
|
57
|
+
operation: 'query',
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (enabledTools.create) {
|
|
62
|
+
tools.push({
|
|
63
|
+
name: `list_${dbKey}_create`,
|
|
64
|
+
description: `Create a new ${listKey} record`,
|
|
65
|
+
listKey,
|
|
66
|
+
operation: 'create',
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (enabledTools.update) {
|
|
71
|
+
tools.push({
|
|
72
|
+
name: `list_${dbKey}_update`,
|
|
73
|
+
description: `Update an existing ${listKey} record`,
|
|
74
|
+
listKey,
|
|
75
|
+
operation: 'update',
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (enabledTools.delete) {
|
|
80
|
+
tools.push({
|
|
81
|
+
name: `list_${dbKey}_delete`,
|
|
82
|
+
description: `Delete a ${listKey} record`,
|
|
83
|
+
listKey,
|
|
84
|
+
operation: 'delete',
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Custom tools
|
|
89
|
+
if (listConfig.mcp?.customTools) {
|
|
90
|
+
for (const customTool of listConfig.mcp.customTools) {
|
|
91
|
+
tools.push({
|
|
92
|
+
name: customTool.name,
|
|
93
|
+
description: customTool.description,
|
|
94
|
+
listKey,
|
|
95
|
+
operation: 'custom',
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Write tools.json for reference
|
|
102
|
+
const toolsPath = path.join(mcpDir, 'tools.json')
|
|
103
|
+
fs.writeFileSync(toolsPath, JSON.stringify(tools, null, 2), 'utf-8')
|
|
104
|
+
|
|
105
|
+
// Write README with usage instructions
|
|
106
|
+
const readmePath = path.join(mcpDir, 'README.md')
|
|
107
|
+
fs.writeFileSync(
|
|
108
|
+
readmePath,
|
|
109
|
+
`# MCP Tools Reference
|
|
110
|
+
|
|
111
|
+
This directory contains metadata about your MCP configuration.
|
|
112
|
+
|
|
113
|
+
## Available Tools
|
|
114
|
+
|
|
115
|
+
${tools.length} tool(s) available:
|
|
116
|
+
|
|
117
|
+
${tools
|
|
118
|
+
.map(
|
|
119
|
+
(tool) => `- **${tool.name}** (${tool.operation}): ${tool.description}
|
|
120
|
+
- List: ${tool.listKey}`,
|
|
121
|
+
)
|
|
122
|
+
.join('\n\n')}
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
Create your MCP route handler:
|
|
127
|
+
|
|
128
|
+
\`\`\`typescript
|
|
129
|
+
// app/api/mcp/[[...transport]]/route.ts
|
|
130
|
+
import { createMcpHandlers } from '@opensaas/stack-mcp'
|
|
131
|
+
import config from '@/opensaas.config'
|
|
132
|
+
import { auth } from '@/lib/auth'
|
|
133
|
+
import { getContext } from '@/.opensaas/context'
|
|
134
|
+
|
|
135
|
+
const { GET, POST, DELETE } = createMcpHandlers({
|
|
136
|
+
config,
|
|
137
|
+
auth,
|
|
138
|
+
getContext
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
export { GET, POST, DELETE }
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
## Connecting to Claude Desktop
|
|
145
|
+
|
|
146
|
+
### Option 1: Remote MCP Server (Recommended for Production)
|
|
147
|
+
|
|
148
|
+
For production use with OAuth authentication, add your server via **Claude Desktop > Settings > Connectors**.
|
|
149
|
+
|
|
150
|
+
Claude Desktop requires:
|
|
151
|
+
1. Your server must be publicly accessible (use ngrok/cloudflare tunnel for local testing)
|
|
152
|
+
2. OAuth authorization server at \`/.well-known/oauth-authorization-server\`
|
|
153
|
+
3. Dynamic Client Registration (DCR) support - Better Auth MCP plugin provides this
|
|
154
|
+
|
|
155
|
+
**Note:** Remote MCP servers with OAuth cannot be configured via \`claude_desktop_config.json\` - they must be added through the Claude Desktop UI.
|
|
156
|
+
|
|
157
|
+
### Option 2: Local Development (No OAuth)
|
|
158
|
+
|
|
159
|
+
For local development without OAuth, you can create a proxy MCP server script:
|
|
160
|
+
|
|
161
|
+
1. Create \`mcp-server.js\` in your project root:
|
|
162
|
+
|
|
163
|
+
\`\`\`javascript
|
|
164
|
+
#!/usr/bin/env node
|
|
165
|
+
import { spawn } from 'child_process';
|
|
166
|
+
|
|
167
|
+
// Start Next.js dev server in background
|
|
168
|
+
const server = spawn('npm', ['run', 'dev'], { stdio: 'inherit' });
|
|
169
|
+
|
|
170
|
+
// Wait for server to be ready
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
console.log('MCP server ready at http://localhost:3000${config.mcp.basePath || '/api/mcp'}');
|
|
173
|
+
}, 3000);
|
|
174
|
+
|
|
175
|
+
process.on('SIGINT', () => {
|
|
176
|
+
server.kill();
|
|
177
|
+
process.exit();
|
|
178
|
+
});
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
2. Add to \`claude_desktop_config.json\`:
|
|
182
|
+
|
|
183
|
+
\`\`\`json
|
|
184
|
+
{
|
|
185
|
+
"mcpServers": {
|
|
186
|
+
"my-app": {
|
|
187
|
+
"command": "node",
|
|
188
|
+
"args": ["mcp-server.js"]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
### Option 3: Using ngrok for Local OAuth Testing
|
|
195
|
+
|
|
196
|
+
1. Start your dev server: \`npm run dev\`
|
|
197
|
+
2. Expose with ngrok: \`ngrok http 3000\`
|
|
198
|
+
3. Add to Claude Desktop via **Settings > Connectors** with your ngrok URL
|
|
199
|
+
`,
|
|
200
|
+
'utf-8',
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Write MCP metadata to disk
|
|
208
|
+
*/
|
|
209
|
+
export function writeMcp(config: OpenSaasConfig, outputPath: string) {
|
|
210
|
+
const generated = generateMcp(config, outputPath)
|
|
211
|
+
|
|
212
|
+
if (!generated) {
|
|
213
|
+
// Clean up MCP directory if MCP is disabled
|
|
214
|
+
const mcpDir = path.join(outputPath, '.opensaas', 'mcp')
|
|
215
|
+
if (fs.existsSync(mcpDir)) {
|
|
216
|
+
fs.rmSync(mcpDir, { recursive: true, force: true })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return generated
|
|
221
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generatePrismaSchema } from './prisma.js'
|
|
3
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
|
+
import { text, integer, relationship, checkbox, timestamp } from '@opensaas/stack-core/fields'
|
|
5
|
+
|
|
6
|
+
describe('Prisma Schema Generator', () => {
|
|
7
|
+
describe('generatePrismaSchema', () => {
|
|
8
|
+
it('should generate basic schema with datasource and generator', () => {
|
|
9
|
+
const config: OpenSaasConfig = {
|
|
10
|
+
db: {
|
|
11
|
+
provider: 'sqlite',
|
|
12
|
+
url: 'file:./dev.db',
|
|
13
|
+
},
|
|
14
|
+
lists: {},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const schema = generatePrismaSchema(config)
|
|
18
|
+
|
|
19
|
+
expect(schema).toMatchSnapshot()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should use custom opensaasPath for generator output', () => {
|
|
23
|
+
const config: OpenSaasConfig = {
|
|
24
|
+
db: {
|
|
25
|
+
provider: 'sqlite',
|
|
26
|
+
url: 'file:./dev.db',
|
|
27
|
+
},
|
|
28
|
+
opensaasPath: '.custom-path',
|
|
29
|
+
lists: {},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const schema = generatePrismaSchema(config)
|
|
33
|
+
|
|
34
|
+
expect(schema).toMatchSnapshot()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should generate model with basic fields', () => {
|
|
38
|
+
const config: OpenSaasConfig = {
|
|
39
|
+
db: {
|
|
40
|
+
provider: 'sqlite',
|
|
41
|
+
url: 'file:./dev.db',
|
|
42
|
+
},
|
|
43
|
+
lists: {
|
|
44
|
+
User: {
|
|
45
|
+
fields: {
|
|
46
|
+
name: text({ validation: { isRequired: true } }),
|
|
47
|
+
email: text({ validation: { isRequired: true } }),
|
|
48
|
+
age: integer(),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const schema = generatePrismaSchema(config)
|
|
55
|
+
|
|
56
|
+
expect(schema).toMatchSnapshot()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should generate model with checkbox field', () => {
|
|
60
|
+
const config: OpenSaasConfig = {
|
|
61
|
+
db: {
|
|
62
|
+
provider: 'sqlite',
|
|
63
|
+
url: 'file:./dev.db',
|
|
64
|
+
},
|
|
65
|
+
lists: {
|
|
66
|
+
Post: {
|
|
67
|
+
fields: {
|
|
68
|
+
title: text(),
|
|
69
|
+
isPublished: checkbox({ defaultValue: false }),
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const schema = generatePrismaSchema(config)
|
|
76
|
+
|
|
77
|
+
expect(schema).toMatchSnapshot()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should generate model with timestamp field', () => {
|
|
81
|
+
const config: OpenSaasConfig = {
|
|
82
|
+
db: {
|
|
83
|
+
provider: 'sqlite',
|
|
84
|
+
url: 'file:./dev.db',
|
|
85
|
+
},
|
|
86
|
+
lists: {
|
|
87
|
+
Post: {
|
|
88
|
+
fields: {
|
|
89
|
+
title: text(),
|
|
90
|
+
publishedAt: timestamp(),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const schema = generatePrismaSchema(config)
|
|
97
|
+
|
|
98
|
+
expect(schema).toMatchSnapshot()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should generate many-to-one relationship', () => {
|
|
102
|
+
const config: OpenSaasConfig = {
|
|
103
|
+
db: {
|
|
104
|
+
provider: 'sqlite',
|
|
105
|
+
url: 'file:./dev.db',
|
|
106
|
+
},
|
|
107
|
+
lists: {
|
|
108
|
+
User: {
|
|
109
|
+
fields: {
|
|
110
|
+
name: text(),
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
Post: {
|
|
114
|
+
fields: {
|
|
115
|
+
title: text(),
|
|
116
|
+
author: relationship({ ref: 'User.posts' }),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const schema = generatePrismaSchema(config)
|
|
123
|
+
|
|
124
|
+
expect(schema).toMatchSnapshot()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should generate one-to-many relationship', () => {
|
|
128
|
+
const config: OpenSaasConfig = {
|
|
129
|
+
db: {
|
|
130
|
+
provider: 'sqlite',
|
|
131
|
+
url: 'file:./dev.db',
|
|
132
|
+
},
|
|
133
|
+
lists: {
|
|
134
|
+
User: {
|
|
135
|
+
fields: {
|
|
136
|
+
name: text(),
|
|
137
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
Post: {
|
|
141
|
+
fields: {
|
|
142
|
+
title: text(),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const schema = generatePrismaSchema(config)
|
|
149
|
+
|
|
150
|
+
expect(schema).toMatchSnapshot()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should generate multiple models', () => {
|
|
154
|
+
const config: OpenSaasConfig = {
|
|
155
|
+
db: {
|
|
156
|
+
provider: 'postgresql',
|
|
157
|
+
url: process.env.DATABASE_URL || 'postgresql://localhost:5432/db',
|
|
158
|
+
},
|
|
159
|
+
lists: {
|
|
160
|
+
User: {
|
|
161
|
+
fields: {
|
|
162
|
+
name: text(),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
Post: {
|
|
166
|
+
fields: {
|
|
167
|
+
title: text(),
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
Comment: {
|
|
171
|
+
fields: {
|
|
172
|
+
content: text(),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const schema = generatePrismaSchema(config)
|
|
179
|
+
|
|
180
|
+
expect(schema).toMatchSnapshot()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should always include system fields', () => {
|
|
184
|
+
const config: OpenSaasConfig = {
|
|
185
|
+
db: {
|
|
186
|
+
provider: 'sqlite',
|
|
187
|
+
url: 'file:./dev.db',
|
|
188
|
+
},
|
|
189
|
+
lists: {
|
|
190
|
+
User: {
|
|
191
|
+
fields: {
|
|
192
|
+
name: text(),
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const schema = generatePrismaSchema(config)
|
|
199
|
+
|
|
200
|
+
expect(schema).toContain('id String @id @default(cuid())')
|
|
201
|
+
expect(schema).toContain('createdAt DateTime @default(now())')
|
|
202
|
+
expect(schema).toContain('updatedAt DateTime @updatedAt')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should handle empty lists config', () => {
|
|
206
|
+
const config: OpenSaasConfig = {
|
|
207
|
+
db: {
|
|
208
|
+
provider: 'sqlite',
|
|
209
|
+
url: 'file:./dev.db',
|
|
210
|
+
},
|
|
211
|
+
lists: {},
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const schema = generatePrismaSchema(config)
|
|
215
|
+
|
|
216
|
+
expect(schema).toContain('generator client {')
|
|
217
|
+
expect(schema).toContain('datasource db {')
|
|
218
|
+
expect(schema).not.toContain('model')
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generateTypes } from './types.js'
|
|
3
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
|
+
import { text, integer, relationship, checkbox } from '@opensaas/stack-core/fields'
|
|
5
|
+
|
|
6
|
+
describe('Types Generator', () => {
|
|
7
|
+
describe('generateTypes', () => {
|
|
8
|
+
it('should generate type definitions for basic model', () => {
|
|
9
|
+
const config: OpenSaasConfig = {
|
|
10
|
+
db: {
|
|
11
|
+
provider: 'sqlite',
|
|
12
|
+
url: 'file:./dev.db',
|
|
13
|
+
},
|
|
14
|
+
lists: {
|
|
15
|
+
User: {
|
|
16
|
+
fields: {
|
|
17
|
+
name: text({ validation: { isRequired: true } }),
|
|
18
|
+
email: text({ validation: { isRequired: true } }),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const types = generateTypes(config)
|
|
25
|
+
|
|
26
|
+
expect(types).toMatchSnapshot()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should generate CreateInput type', () => {
|
|
30
|
+
const config: OpenSaasConfig = {
|
|
31
|
+
db: {
|
|
32
|
+
provider: 'sqlite',
|
|
33
|
+
url: 'file:./dev.db',
|
|
34
|
+
},
|
|
35
|
+
lists: {
|
|
36
|
+
Post: {
|
|
37
|
+
fields: {
|
|
38
|
+
title: text({ validation: { isRequired: true } }),
|
|
39
|
+
content: text(),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const types = generateTypes(config)
|
|
46
|
+
|
|
47
|
+
expect(types).toMatchSnapshot()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should generate UpdateInput type', () => {
|
|
51
|
+
const config: OpenSaasConfig = {
|
|
52
|
+
db: {
|
|
53
|
+
provider: 'sqlite',
|
|
54
|
+
url: 'file:./dev.db',
|
|
55
|
+
},
|
|
56
|
+
lists: {
|
|
57
|
+
Post: {
|
|
58
|
+
fields: {
|
|
59
|
+
title: text({ validation: { isRequired: true } }),
|
|
60
|
+
content: text(),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const types = generateTypes(config)
|
|
67
|
+
|
|
68
|
+
expect(types).toMatchSnapshot()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should generate WhereInput type', () => {
|
|
72
|
+
const config: OpenSaasConfig = {
|
|
73
|
+
db: {
|
|
74
|
+
provider: 'sqlite',
|
|
75
|
+
url: 'file:./dev.db',
|
|
76
|
+
},
|
|
77
|
+
lists: {
|
|
78
|
+
User: {
|
|
79
|
+
fields: {
|
|
80
|
+
name: text(),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const types = generateTypes(config)
|
|
87
|
+
|
|
88
|
+
expect(types).toMatchSnapshot()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should generate Context type with all operations', () => {
|
|
92
|
+
const config: OpenSaasConfig = {
|
|
93
|
+
db: {
|
|
94
|
+
provider: 'sqlite',
|
|
95
|
+
url: 'file:./dev.db',
|
|
96
|
+
},
|
|
97
|
+
lists: {
|
|
98
|
+
User: {
|
|
99
|
+
fields: {
|
|
100
|
+
name: text(),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const types = generateTypes(config)
|
|
107
|
+
|
|
108
|
+
expect(types).toMatchSnapshot()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle relationship fields in types', () => {
|
|
112
|
+
const config: OpenSaasConfig = {
|
|
113
|
+
db: {
|
|
114
|
+
provider: 'sqlite',
|
|
115
|
+
url: 'file:./dev.db',
|
|
116
|
+
},
|
|
117
|
+
lists: {
|
|
118
|
+
User: {
|
|
119
|
+
fields: {
|
|
120
|
+
name: text(),
|
|
121
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
Post: {
|
|
125
|
+
fields: {
|
|
126
|
+
title: text(),
|
|
127
|
+
author: relationship({ ref: 'User.posts' }),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const types = generateTypes(config)
|
|
134
|
+
|
|
135
|
+
expect(types).toMatchSnapshot()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should handle relationship fields in CreateInput', () => {
|
|
139
|
+
const config: OpenSaasConfig = {
|
|
140
|
+
db: {
|
|
141
|
+
provider: 'sqlite',
|
|
142
|
+
url: 'file:./dev.db',
|
|
143
|
+
},
|
|
144
|
+
lists: {
|
|
145
|
+
Post: {
|
|
146
|
+
fields: {
|
|
147
|
+
title: text(),
|
|
148
|
+
author: relationship({ ref: 'User.posts' }),
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
User: {
|
|
152
|
+
fields: {
|
|
153
|
+
name: text(),
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const types = generateTypes(config)
|
|
160
|
+
|
|
161
|
+
expect(types).toMatchSnapshot()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should handle relationship fields in UpdateInput', () => {
|
|
165
|
+
const config: OpenSaasConfig = {
|
|
166
|
+
db: {
|
|
167
|
+
provider: 'sqlite',
|
|
168
|
+
url: 'file:./dev.db',
|
|
169
|
+
},
|
|
170
|
+
lists: {
|
|
171
|
+
Post: {
|
|
172
|
+
fields: {
|
|
173
|
+
title: text(),
|
|
174
|
+
author: relationship({ ref: 'User.posts' }),
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
User: {
|
|
178
|
+
fields: {
|
|
179
|
+
name: text(),
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const types = generateTypes(config)
|
|
186
|
+
|
|
187
|
+
expect(types).toMatchSnapshot()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should generate types for multiple lists', () => {
|
|
191
|
+
const config: OpenSaasConfig = {
|
|
192
|
+
db: {
|
|
193
|
+
provider: 'sqlite',
|
|
194
|
+
url: 'file:./dev.db',
|
|
195
|
+
},
|
|
196
|
+
lists: {
|
|
197
|
+
User: {
|
|
198
|
+
fields: {
|
|
199
|
+
name: text(),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
Post: {
|
|
203
|
+
fields: {
|
|
204
|
+
title: text(),
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
Comment: {
|
|
208
|
+
fields: {
|
|
209
|
+
content: text(),
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const types = generateTypes(config)
|
|
216
|
+
|
|
217
|
+
expect(types).toMatchSnapshot()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should handle integer fields correctly', () => {
|
|
221
|
+
const config: OpenSaasConfig = {
|
|
222
|
+
db: {
|
|
223
|
+
provider: 'sqlite',
|
|
224
|
+
url: 'file:./dev.db',
|
|
225
|
+
},
|
|
226
|
+
lists: {
|
|
227
|
+
Product: {
|
|
228
|
+
fields: {
|
|
229
|
+
name: text(),
|
|
230
|
+
price: integer(),
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const types = generateTypes(config)
|
|
237
|
+
|
|
238
|
+
expect(types).toContain('price:')
|
|
239
|
+
expect(types).toContain('number')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('should handle checkbox fields correctly', () => {
|
|
243
|
+
const config: OpenSaasConfig = {
|
|
244
|
+
db: {
|
|
245
|
+
provider: 'sqlite',
|
|
246
|
+
url: 'file:./dev.db',
|
|
247
|
+
},
|
|
248
|
+
lists: {
|
|
249
|
+
Post: {
|
|
250
|
+
fields: {
|
|
251
|
+
title: text(),
|
|
252
|
+
isPublished: checkbox(),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const types = generateTypes(config)
|
|
259
|
+
|
|
260
|
+
expect(types).toContain('isPublished:')
|
|
261
|
+
expect(types).toContain('boolean')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('should include header comment', () => {
|
|
265
|
+
const config: OpenSaasConfig = {
|
|
266
|
+
db: {
|
|
267
|
+
provider: 'sqlite',
|
|
268
|
+
url: 'file:./dev.db',
|
|
269
|
+
},
|
|
270
|
+
lists: {},
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const types = generateTypes(config)
|
|
274
|
+
|
|
275
|
+
expect(types).toContain('/**')
|
|
276
|
+
expect(types).toContain('Generated types from OpenSaas configuration')
|
|
277
|
+
expect(types).toContain('DO NOT EDIT')
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
})
|