@opensaas/stack-cli 0.1.7 → 0.3.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 +207 -0
- package/CLAUDE.md +60 -12
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +10 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +116 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/generator/context.d.ts.map +1 -1
- package/dist/generator/context.js +21 -3
- package/dist/generator/context.js.map +1 -1
- package/dist/generator/index.d.ts +3 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +3 -0
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/lists.d.ts +31 -0
- package/dist/generator/lists.d.ts.map +1 -0
- package/dist/generator/lists.js +91 -0
- package/dist/generator/lists.js.map +1 -0
- package/dist/generator/plugin-types.d.ts +10 -0
- package/dist/generator/plugin-types.d.ts.map +1 -0
- package/dist/generator/plugin-types.js +122 -0
- package/dist/generator/plugin-types.js.map +1 -0
- package/dist/generator/prisma-config.d.ts +17 -0
- package/dist/generator/prisma-config.d.ts.map +1 -0
- package/dist/generator/prisma-config.js +40 -0
- package/dist/generator/prisma-config.js.map +1 -0
- package/dist/generator/prisma.d.ts.map +1 -1
- package/dist/generator/prisma.js +1 -2
- package/dist/generator/prisma.js.map +1 -1
- package/dist/generator/types.d.ts.map +1 -1
- package/dist/generator/types.js +51 -1
- 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 +43 -0
- package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
- package/dist/mcp/lib/documentation-provider.js +163 -0
- package/dist/mcp/lib/documentation-provider.js.map +1 -0
- package/dist/mcp/lib/features/catalog.d.ts +26 -0
- package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
- package/dist/mcp/lib/features/catalog.js +291 -0
- package/dist/mcp/lib/features/catalog.js.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
- package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
- package/dist/mcp/lib/generators/feature-generator.js +546 -0
- package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
- package/dist/mcp/lib/types.d.ts +80 -0
- package/dist/mcp/lib/types.d.ts.map +1 -0
- package/dist/mcp/lib/types.js +5 -0
- package/dist/mcp/lib/types.js.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
- package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
- package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
- package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
- package/dist/mcp/server/index.d.ts +8 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js +202 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
- package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
- package/dist/mcp/server/stack-mcp-server.js +265 -0
- package/dist/mcp/server/stack-mcp-server.js.map +1 -0
- package/package.json +9 -7
- package/src/commands/__snapshots__/generate.test.ts.snap +57 -21
- package/src/commands/dev.test.ts +0 -1
- package/src/commands/generate.test.ts +18 -8
- package/src/commands/generate.ts +12 -0
- package/src/commands/mcp.ts +135 -0
- package/src/generator/__snapshots__/context.test.ts.snap +8 -8
- package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
- package/src/generator/__snapshots__/types.test.ts.snap +587 -9
- package/src/generator/context.test.ts +13 -8
- package/src/generator/context.ts +21 -3
- package/src/generator/index.ts +3 -0
- package/src/generator/lists.test.ts +335 -0
- package/src/generator/lists.ts +102 -0
- package/src/generator/plugin-types.ts +147 -0
- package/src/generator/prisma-config.ts +46 -0
- package/src/generator/prisma.test.ts +0 -10
- package/src/generator/prisma.ts +1 -2
- package/src/generator/types.test.ts +0 -12
- package/src/generator/types.ts +54 -1
- package/src/index.ts +4 -0
- package/src/mcp/lib/documentation-provider.ts +203 -0
- package/src/mcp/lib/features/catalog.ts +301 -0
- package/src/mcp/lib/generators/feature-generator.ts +598 -0
- package/src/mcp/lib/types.ts +89 -0
- package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
- package/src/mcp/server/index.ts +240 -0
- package/src/mcp/server/stack-mcp-server.ts +301 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -9,7 +9,8 @@ describe('Context Generator', () => {
|
|
|
9
9
|
const config: OpenSaasConfig = {
|
|
10
10
|
db: {
|
|
11
11
|
provider: 'sqlite',
|
|
12
|
-
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
prismaClientConstructor: (() => null) as any,
|
|
13
14
|
},
|
|
14
15
|
lists: {
|
|
15
16
|
User: {
|
|
@@ -29,7 +30,6 @@ describe('Context Generator', () => {
|
|
|
29
30
|
const config: OpenSaasConfig = {
|
|
30
31
|
db: {
|
|
31
32
|
provider: 'postgresql',
|
|
32
|
-
url: process.env.DATABASE_URL || 'postgresql://localhost:5432/db',
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
34
|
prismaClientConstructor: ((PrismaClient: any) => new PrismaClient()) as any,
|
|
35
35
|
},
|
|
@@ -51,7 +51,8 @@ describe('Context Generator', () => {
|
|
|
51
51
|
const config: OpenSaasConfig = {
|
|
52
52
|
db: {
|
|
53
53
|
provider: 'sqlite',
|
|
54
|
-
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
prismaClientConstructor: (() => null) as any,
|
|
55
56
|
},
|
|
56
57
|
lists: {},
|
|
57
58
|
}
|
|
@@ -59,7 +60,7 @@ describe('Context Generator', () => {
|
|
|
59
60
|
const context = generateContext(config)
|
|
60
61
|
|
|
61
62
|
expect(context).toContain('const globalForPrisma')
|
|
62
|
-
expect(context).toContain('globalThis as unknown as { prisma: PrismaClient |
|
|
63
|
+
expect(context).toContain('globalThis as unknown as { prisma: PrismaClient | null }')
|
|
63
64
|
expect(context).toContain('globalForPrisma.prisma')
|
|
64
65
|
expect(context).toContain("if (process.env.NODE_ENV !== 'production')")
|
|
65
66
|
})
|
|
@@ -68,7 +69,8 @@ describe('Context Generator', () => {
|
|
|
68
69
|
const config: OpenSaasConfig = {
|
|
69
70
|
db: {
|
|
70
71
|
provider: 'sqlite',
|
|
71
|
-
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
prismaClientConstructor: (() => null) as any,
|
|
72
74
|
},
|
|
73
75
|
lists: {},
|
|
74
76
|
}
|
|
@@ -87,7 +89,8 @@ describe('Context Generator', () => {
|
|
|
87
89
|
const config: OpenSaasConfig = {
|
|
88
90
|
db: {
|
|
89
91
|
provider: 'sqlite',
|
|
90
|
-
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
prismaClientConstructor: (() => null) as any,
|
|
91
94
|
},
|
|
92
95
|
lists: {},
|
|
93
96
|
}
|
|
@@ -104,7 +107,8 @@ describe('Context Generator', () => {
|
|
|
104
107
|
const config: OpenSaasConfig = {
|
|
105
108
|
db: {
|
|
106
109
|
provider: 'sqlite',
|
|
107
|
-
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
prismaClientConstructor: (() => null) as any,
|
|
108
112
|
},
|
|
109
113
|
lists: {},
|
|
110
114
|
}
|
|
@@ -118,7 +122,8 @@ describe('Context Generator', () => {
|
|
|
118
122
|
const config: OpenSaasConfig = {
|
|
119
123
|
db: {
|
|
120
124
|
provider: 'sqlite',
|
|
121
|
-
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
|
+
prismaClientConstructor: (() => null) as any,
|
|
122
127
|
},
|
|
123
128
|
lists: {},
|
|
124
129
|
}
|
package/src/generator/context.ts
CHANGED
|
@@ -16,9 +16,27 @@ export function generateContext(config: OpenSaasConfig): string {
|
|
|
16
16
|
const hasStorage = !!config.storage && Object.keys(config.storage).length > 0
|
|
17
17
|
|
|
18
18
|
// Generate the Prisma client instantiation code
|
|
19
|
+
// Prisma 7 requires adapters, so prismaClientConstructor must be provided
|
|
19
20
|
const prismaInstantiation = hasCustomConstructor
|
|
20
21
|
? `resolvedConfig.db.prismaClientConstructor!(PrismaClient)`
|
|
21
|
-
: `
|
|
22
|
+
: `(() => {
|
|
23
|
+
throw new Error(
|
|
24
|
+
'Prisma 7 requires a database adapter. Please add prismaClientConstructor to your opensaas.config.ts db configuration.\\n\\n' +
|
|
25
|
+
'Example for SQLite:\\n' +
|
|
26
|
+
'import { PrismaBetterSQLite3 } from \\'@prisma/adapter-better-sqlite3\\'\\n' +
|
|
27
|
+
'import Database from \\'better-sqlite3\\'\\n\\n' +
|
|
28
|
+
'db: {\\n' +
|
|
29
|
+
' provider: \\'sqlite\\',\\n' +
|
|
30
|
+
' url: process.env.DATABASE_URL || \\'file:./dev.db\\',\\n' +
|
|
31
|
+
' prismaClientConstructor: (PrismaClient) => {\\n' +
|
|
32
|
+
' const db = new Database(process.env.DATABASE_URL || \\'./dev.db\\')\\n' +
|
|
33
|
+
' const adapter = new PrismaBetterSQLite3(db)\\n' +
|
|
34
|
+
' return new PrismaClient({ adapter })\\n' +
|
|
35
|
+
' }\\n' +
|
|
36
|
+
'}\\n\\n' +
|
|
37
|
+
'See https://www.prisma.io/docs/orm/overview/databases/database-drivers for more information.'
|
|
38
|
+
)
|
|
39
|
+
})()`
|
|
22
40
|
|
|
23
41
|
// Generate storage utilities if storage is configured
|
|
24
42
|
const storageUtilities = hasStorage
|
|
@@ -103,7 +121,7 @@ const storage = {
|
|
|
103
121
|
|
|
104
122
|
import { getContext as getOpensaasContext } from '@opensaas/stack-core'
|
|
105
123
|
import type { Session as OpensaasSession, OpenSaasConfig } from '@opensaas/stack-core'
|
|
106
|
-
import { PrismaClient } from './prisma-client'
|
|
124
|
+
import { PrismaClient } from './prisma-client/client'
|
|
107
125
|
import type { Context } from './types'
|
|
108
126
|
import configOrPromise from '../opensaas.config'
|
|
109
127
|
|
|
@@ -112,7 +130,7 @@ const configPromise = Promise.resolve(configOrPromise)
|
|
|
112
130
|
let resolvedConfig: OpenSaasConfig | null = null
|
|
113
131
|
|
|
114
132
|
// Internal Prisma singleton - managed automatically
|
|
115
|
-
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient |
|
|
133
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | null }
|
|
116
134
|
let prisma: PrismaClient | null = null
|
|
117
135
|
|
|
118
136
|
async function getPrisma() {
|
package/src/generator/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { generatePrismaSchema, writePrismaSchema } from './prisma.js'
|
|
2
|
+
export { generatePrismaConfig, writePrismaConfig } from './prisma-config.js'
|
|
2
3
|
export { generateTypes, writeTypes } from './types.js'
|
|
4
|
+
export { generateListsNamespace, writeLists } from './lists.js'
|
|
3
5
|
export { patchPrismaTypes } from './type-patcher.js'
|
|
4
6
|
export { generateContext, writeContext } from './context.js'
|
|
7
|
+
export { generatePluginTypes, writePluginTypes } from './plugin-types.js'
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generateListsNamespace } from './lists.js'
|
|
3
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
4
|
+
import { text, integer, relationship, checkbox } from '@opensaas/stack-core/fields'
|
|
5
|
+
|
|
6
|
+
describe('Lists Namespace Generator', () => {
|
|
7
|
+
describe('generateListsNamespace', () => {
|
|
8
|
+
it('should generate Lists namespace for single list', () => {
|
|
9
|
+
const config: OpenSaasConfig = {
|
|
10
|
+
db: {
|
|
11
|
+
provider: 'sqlite',
|
|
12
|
+
},
|
|
13
|
+
lists: {
|
|
14
|
+
Post: {
|
|
15
|
+
fields: {
|
|
16
|
+
title: text({ validation: { isRequired: true } }),
|
|
17
|
+
content: text(),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const lists = generateListsNamespace(config)
|
|
24
|
+
|
|
25
|
+
expect(lists).toContain('export declare namespace Lists {')
|
|
26
|
+
expect(lists).toContain('export type Post')
|
|
27
|
+
expect(lists).toContain('namespace Post {')
|
|
28
|
+
expect(lists).toContain("export type Item = import('./types').Post")
|
|
29
|
+
expect(lists).toContain('export type TypeInfo')
|
|
30
|
+
expect(lists).toContain("key: 'Post'")
|
|
31
|
+
expect(lists).toContain('item: Item')
|
|
32
|
+
expect(lists).toContain('inputs: {')
|
|
33
|
+
expect(lists).toContain("create: import('./prisma-client/client').Prisma.PostCreateInput")
|
|
34
|
+
expect(lists).toContain("update: import('./prisma-client/client').Prisma.PostUpdateInput")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should generate Lists namespace for multiple lists', () => {
|
|
38
|
+
const config: OpenSaasConfig = {
|
|
39
|
+
db: {
|
|
40
|
+
provider: 'sqlite',
|
|
41
|
+
},
|
|
42
|
+
lists: {
|
|
43
|
+
User: {
|
|
44
|
+
fields: {
|
|
45
|
+
name: text(),
|
|
46
|
+
email: text(),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
Post: {
|
|
50
|
+
fields: {
|
|
51
|
+
title: text(),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
Comment: {
|
|
55
|
+
fields: {
|
|
56
|
+
content: text(),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lists = generateListsNamespace(config)
|
|
63
|
+
|
|
64
|
+
// Check all three lists are present
|
|
65
|
+
expect(lists).toContain('export type User')
|
|
66
|
+
expect(lists).toContain('export type Post')
|
|
67
|
+
expect(lists).toContain('export type Comment')
|
|
68
|
+
|
|
69
|
+
// Check all three namespaces
|
|
70
|
+
expect(lists).toContain('namespace User {')
|
|
71
|
+
expect(lists).toContain('namespace Post {')
|
|
72
|
+
expect(lists).toContain('namespace Comment {')
|
|
73
|
+
|
|
74
|
+
// Check TypeInfo for each
|
|
75
|
+
expect(lists).toContain("key: 'User'")
|
|
76
|
+
expect(lists).toContain("key: 'Post'")
|
|
77
|
+
expect(lists).toContain("key: 'Comment'")
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should include header comment with usage examples', () => {
|
|
81
|
+
const config: OpenSaasConfig = {
|
|
82
|
+
db: {
|
|
83
|
+
provider: 'sqlite',
|
|
84
|
+
},
|
|
85
|
+
lists: {
|
|
86
|
+
Post: {
|
|
87
|
+
fields: {
|
|
88
|
+
title: text(),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const lists = generateListsNamespace(config)
|
|
95
|
+
|
|
96
|
+
expect(lists).toContain('/**')
|
|
97
|
+
expect(lists).toContain('Generated Lists namespace from OpenSaas configuration')
|
|
98
|
+
expect(lists).toContain('DO NOT EDIT')
|
|
99
|
+
expect(lists).toContain('@example')
|
|
100
|
+
expect(lists).toContain('import type { Lists }')
|
|
101
|
+
expect(lists).toContain('list<Lists.Post.TypeInfo>')
|
|
102
|
+
expect(lists).toContain('const Post: Lists.Post = list')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should reference correct import paths', () => {
|
|
106
|
+
const config: OpenSaasConfig = {
|
|
107
|
+
db: {
|
|
108
|
+
provider: 'sqlite',
|
|
109
|
+
},
|
|
110
|
+
lists: {
|
|
111
|
+
User: {
|
|
112
|
+
fields: {
|
|
113
|
+
name: text(),
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const lists = generateListsNamespace(config)
|
|
120
|
+
|
|
121
|
+
// Check ListConfig import
|
|
122
|
+
expect(lists).toContain("import('@opensaas/stack-core').ListConfig")
|
|
123
|
+
|
|
124
|
+
// Check Item import
|
|
125
|
+
expect(lists).toContain("import('./types').User")
|
|
126
|
+
|
|
127
|
+
// Check Prisma imports
|
|
128
|
+
expect(lists).toContain("import('./prisma-client/client').Prisma.UserCreateInput")
|
|
129
|
+
expect(lists).toContain("import('./prisma-client/client').Prisma.UserUpdateInput")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should generate TypeInfo structure correctly', () => {
|
|
133
|
+
const config: OpenSaasConfig = {
|
|
134
|
+
db: {
|
|
135
|
+
provider: 'sqlite',
|
|
136
|
+
},
|
|
137
|
+
lists: {
|
|
138
|
+
Post: {
|
|
139
|
+
fields: {
|
|
140
|
+
title: text(),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const lists = generateListsNamespace(config)
|
|
147
|
+
|
|
148
|
+
// Verify TypeInfo structure
|
|
149
|
+
expect(lists).toContain('export type TypeInfo = {')
|
|
150
|
+
expect(lists).toContain("key: 'Post'")
|
|
151
|
+
expect(lists).toContain('item: Item')
|
|
152
|
+
expect(lists).toContain('inputs: {')
|
|
153
|
+
expect(lists).toContain('create:')
|
|
154
|
+
expect(lists).toContain('update:')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should handle lists with relationships', () => {
|
|
158
|
+
const config: OpenSaasConfig = {
|
|
159
|
+
db: {
|
|
160
|
+
provider: 'sqlite',
|
|
161
|
+
},
|
|
162
|
+
lists: {
|
|
163
|
+
User: {
|
|
164
|
+
fields: {
|
|
165
|
+
name: text(),
|
|
166
|
+
posts: relationship({ ref: 'Post.author', many: true }),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
Post: {
|
|
170
|
+
fields: {
|
|
171
|
+
title: text(),
|
|
172
|
+
author: relationship({ ref: 'User.posts' }),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const lists = generateListsNamespace(config)
|
|
179
|
+
|
|
180
|
+
// Both lists should be generated
|
|
181
|
+
expect(lists).toContain('export type User')
|
|
182
|
+
expect(lists).toContain('export type Post')
|
|
183
|
+
|
|
184
|
+
// Prisma input types should still reference correct types
|
|
185
|
+
expect(lists).toContain('Prisma.UserCreateInput')
|
|
186
|
+
expect(lists).toContain('Prisma.PostCreateInput')
|
|
187
|
+
expect(lists).toContain('Prisma.UserUpdateInput')
|
|
188
|
+
expect(lists).toContain('Prisma.PostUpdateInput')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should handle lists with various field types', () => {
|
|
192
|
+
const config: OpenSaasConfig = {
|
|
193
|
+
db: {
|
|
194
|
+
provider: 'sqlite',
|
|
195
|
+
},
|
|
196
|
+
lists: {
|
|
197
|
+
Product: {
|
|
198
|
+
fields: {
|
|
199
|
+
name: text(),
|
|
200
|
+
price: integer(),
|
|
201
|
+
isAvailable: checkbox(),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const lists = generateListsNamespace(config)
|
|
208
|
+
|
|
209
|
+
// TypeInfo should be generated regardless of field types
|
|
210
|
+
expect(lists).toContain('export type Product')
|
|
211
|
+
expect(lists).toContain('namespace Product {')
|
|
212
|
+
expect(lists).toContain('export type TypeInfo')
|
|
213
|
+
expect(lists).toContain('Prisma.ProductCreateInput')
|
|
214
|
+
expect(lists).toContain('Prisma.ProductUpdateInput')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should close namespace properly', () => {
|
|
218
|
+
const config: OpenSaasConfig = {
|
|
219
|
+
db: {
|
|
220
|
+
provider: 'sqlite',
|
|
221
|
+
},
|
|
222
|
+
lists: {
|
|
223
|
+
User: {
|
|
224
|
+
fields: {
|
|
225
|
+
name: text(),
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const lists = generateListsNamespace(config)
|
|
232
|
+
|
|
233
|
+
// Should have closing brace for namespace
|
|
234
|
+
expect(lists).toMatch(/}\s*$/)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('should generate for empty lists object', () => {
|
|
238
|
+
const config: OpenSaasConfig = {
|
|
239
|
+
db: {
|
|
240
|
+
provider: 'sqlite',
|
|
241
|
+
},
|
|
242
|
+
lists: {},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const lists = generateListsNamespace(config)
|
|
246
|
+
|
|
247
|
+
// Should still have namespace declaration
|
|
248
|
+
expect(lists).toContain('export declare namespace Lists {')
|
|
249
|
+
expect(lists).toContain('}')
|
|
250
|
+
expect(lists).toContain('/**')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should maintain consistent formatting', () => {
|
|
254
|
+
const config: OpenSaasConfig = {
|
|
255
|
+
db: {
|
|
256
|
+
provider: 'sqlite',
|
|
257
|
+
},
|
|
258
|
+
lists: {
|
|
259
|
+
Post: {
|
|
260
|
+
fields: {
|
|
261
|
+
title: text(),
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const lists = generateListsNamespace(config)
|
|
268
|
+
|
|
269
|
+
// Check indentation consistency
|
|
270
|
+
expect(lists).toContain(' export type Post')
|
|
271
|
+
expect(lists).toContain(' namespace Post {')
|
|
272
|
+
expect(lists).toContain(' export type Item')
|
|
273
|
+
expect(lists).toContain(' export type TypeInfo')
|
|
274
|
+
expect(lists).toContain(' key:')
|
|
275
|
+
expect(lists).toContain(' item:')
|
|
276
|
+
expect(lists).toContain(' inputs: {')
|
|
277
|
+
expect(lists).toContain(' create:')
|
|
278
|
+
expect(lists).toContain(' update:')
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('should handle list names with special casing', () => {
|
|
282
|
+
const config: OpenSaasConfig = {
|
|
283
|
+
db: {
|
|
284
|
+
provider: 'sqlite',
|
|
285
|
+
},
|
|
286
|
+
lists: {
|
|
287
|
+
BlogPost: {
|
|
288
|
+
fields: {
|
|
289
|
+
title: text(),
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
APIKey: {
|
|
293
|
+
fields: {
|
|
294
|
+
key: text(),
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const lists = generateListsNamespace(config)
|
|
301
|
+
|
|
302
|
+
// Should preserve exact casing from config
|
|
303
|
+
expect(lists).toContain('export type BlogPost')
|
|
304
|
+
expect(lists).toContain('export type APIKey')
|
|
305
|
+
expect(lists).toContain('namespace BlogPost {')
|
|
306
|
+
expect(lists).toContain('namespace APIKey {')
|
|
307
|
+
expect(lists).toContain("key: 'BlogPost'")
|
|
308
|
+
expect(lists).toContain("key: 'APIKey'")
|
|
309
|
+
expect(lists).toContain('Prisma.BlogPostCreateInput')
|
|
310
|
+
expect(lists).toContain('Prisma.APIKeyCreateInput')
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('should connect List type to TypeInfo via ListConfig generic', () => {
|
|
314
|
+
const config: OpenSaasConfig = {
|
|
315
|
+
db: {
|
|
316
|
+
provider: 'sqlite',
|
|
317
|
+
},
|
|
318
|
+
lists: {
|
|
319
|
+
Post: {
|
|
320
|
+
fields: {
|
|
321
|
+
title: text(),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const lists = generateListsNamespace(config)
|
|
328
|
+
|
|
329
|
+
// Verify the List type uses ListConfig with TypeInfo
|
|
330
|
+
expect(lists).toContain(
|
|
331
|
+
"export type Post = import('@opensaas/stack-core').ListConfig<Lists.Post.TypeInfo>",
|
|
332
|
+
)
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { OpenSaasConfig } from '@opensaas/stack-core'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate Lists namespace with TypeInfo for each list
|
|
7
|
+
* This provides strongly-typed hooks with Prisma input types
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Generated output:
|
|
12
|
+
* export declare namespace Lists {
|
|
13
|
+
* export type Post = import('@opensaas/stack-core').ListConfig<Lists.Post.TypeInfo>
|
|
14
|
+
*
|
|
15
|
+
* namespace Post {
|
|
16
|
+
* export type Item = import('./types').Post
|
|
17
|
+
* export type TypeInfo = {
|
|
18
|
+
* key: 'Post'
|
|
19
|
+
* item: Item
|
|
20
|
+
* inputs: {
|
|
21
|
+
* create: import('./prisma-client/client').Prisma.PostCreateInput
|
|
22
|
+
* update: import('./prisma-client/client').Prisma.PostUpdateInput
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function generateListsNamespace(config: OpenSaasConfig): string {
|
|
30
|
+
const lines: string[] = []
|
|
31
|
+
|
|
32
|
+
// Add header comment
|
|
33
|
+
lines.push('/**')
|
|
34
|
+
lines.push(' * Generated Lists namespace from OpenSaas configuration')
|
|
35
|
+
lines.push(' * DO NOT EDIT - This file is automatically generated')
|
|
36
|
+
lines.push(' *')
|
|
37
|
+
lines.push(' * This file provides TypeInfo for each list, enabling strong typing')
|
|
38
|
+
lines.push(' * for hooks with Prisma input types.')
|
|
39
|
+
lines.push(' *')
|
|
40
|
+
lines.push(' * @example')
|
|
41
|
+
lines.push(' * ```typescript')
|
|
42
|
+
lines.push(" * import type { Lists } from './.opensaas/lists'")
|
|
43
|
+
lines.push(' *')
|
|
44
|
+
lines.push(' * // Use TypeInfo as generic parameter')
|
|
45
|
+
lines.push(' * Post: list<Lists.Post.TypeInfo>({')
|
|
46
|
+
lines.push(' * hooks: {')
|
|
47
|
+
lines.push(' * resolveInput: async ({ operation, resolvedData }) => {')
|
|
48
|
+
lines.push(' * // resolvedData is Prisma.PostCreateInput or Prisma.PostUpdateInput')
|
|
49
|
+
lines.push(' * return resolvedData')
|
|
50
|
+
lines.push(' * }')
|
|
51
|
+
lines.push(' * }')
|
|
52
|
+
lines.push(' * })')
|
|
53
|
+
lines.push(' *')
|
|
54
|
+
lines.push(' * // Or use as typed constant')
|
|
55
|
+
lines.push(' * const Post: Lists.Post = list({ ... })')
|
|
56
|
+
lines.push(' * ```')
|
|
57
|
+
lines.push(' */')
|
|
58
|
+
lines.push('')
|
|
59
|
+
|
|
60
|
+
// Start Lists namespace
|
|
61
|
+
lines.push('export declare namespace Lists {')
|
|
62
|
+
|
|
63
|
+
// Generate type for each list
|
|
64
|
+
for (const listName of Object.keys(config.lists)) {
|
|
65
|
+
lines.push(
|
|
66
|
+
` export type ${listName} = import('@opensaas/stack-core').ListConfig<Lists.${listName}.TypeInfo>`,
|
|
67
|
+
)
|
|
68
|
+
lines.push('')
|
|
69
|
+
lines.push(` namespace ${listName} {`)
|
|
70
|
+
lines.push(` export type Item = import('./types').${listName}`)
|
|
71
|
+
lines.push(` export type TypeInfo = {`)
|
|
72
|
+
lines.push(` key: '${listName}'`)
|
|
73
|
+
lines.push(` item: Item`)
|
|
74
|
+
lines.push(` inputs: {`)
|
|
75
|
+
lines.push(` create: import('./prisma-client/client').Prisma.${listName}CreateInput`)
|
|
76
|
+
lines.push(` update: import('./prisma-client/client').Prisma.${listName}UpdateInput`)
|
|
77
|
+
lines.push(` }`)
|
|
78
|
+
lines.push(` }`)
|
|
79
|
+
lines.push(` }`)
|
|
80
|
+
lines.push('')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Close Lists namespace
|
|
84
|
+
lines.push('}')
|
|
85
|
+
|
|
86
|
+
return lines.join('\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write Lists namespace to file
|
|
91
|
+
*/
|
|
92
|
+
export function writeLists(config: OpenSaasConfig, outputPath: string): void {
|
|
93
|
+
const lists = generateListsNamespace(config)
|
|
94
|
+
|
|
95
|
+
// Ensure directory exists
|
|
96
|
+
const dir = path.dirname(outputPath)
|
|
97
|
+
if (!fs.existsSync(dir)) {
|
|
98
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fs.writeFileSync(outputPath, lists, 'utf-8')
|
|
102
|
+
}
|