@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.
Files changed (94) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +207 -0
  3. package/CLAUDE.md +60 -12
  4. package/dist/commands/generate.d.ts.map +1 -1
  5. package/dist/commands/generate.js +10 -1
  6. package/dist/commands/generate.js.map +1 -1
  7. package/dist/commands/mcp.d.ts +6 -0
  8. package/dist/commands/mcp.d.ts.map +1 -0
  9. package/dist/commands/mcp.js +116 -0
  10. package/dist/commands/mcp.js.map +1 -0
  11. package/dist/generator/context.d.ts.map +1 -1
  12. package/dist/generator/context.js +21 -3
  13. package/dist/generator/context.js.map +1 -1
  14. package/dist/generator/index.d.ts +3 -0
  15. package/dist/generator/index.d.ts.map +1 -1
  16. package/dist/generator/index.js +3 -0
  17. package/dist/generator/index.js.map +1 -1
  18. package/dist/generator/lists.d.ts +31 -0
  19. package/dist/generator/lists.d.ts.map +1 -0
  20. package/dist/generator/lists.js +91 -0
  21. package/dist/generator/lists.js.map +1 -0
  22. package/dist/generator/plugin-types.d.ts +10 -0
  23. package/dist/generator/plugin-types.d.ts.map +1 -0
  24. package/dist/generator/plugin-types.js +122 -0
  25. package/dist/generator/plugin-types.js.map +1 -0
  26. package/dist/generator/prisma-config.d.ts +17 -0
  27. package/dist/generator/prisma-config.d.ts.map +1 -0
  28. package/dist/generator/prisma-config.js +40 -0
  29. package/dist/generator/prisma-config.js.map +1 -0
  30. package/dist/generator/prisma.d.ts.map +1 -1
  31. package/dist/generator/prisma.js +1 -2
  32. package/dist/generator/prisma.js.map +1 -1
  33. package/dist/generator/types.d.ts.map +1 -1
  34. package/dist/generator/types.js +51 -1
  35. package/dist/generator/types.js.map +1 -1
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp/lib/documentation-provider.d.ts +43 -0
  39. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
  40. package/dist/mcp/lib/documentation-provider.js +163 -0
  41. package/dist/mcp/lib/documentation-provider.js.map +1 -0
  42. package/dist/mcp/lib/features/catalog.d.ts +26 -0
  43. package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
  44. package/dist/mcp/lib/features/catalog.js +291 -0
  45. package/dist/mcp/lib/features/catalog.js.map +1 -0
  46. package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
  47. package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
  48. package/dist/mcp/lib/generators/feature-generator.js +546 -0
  49. package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
  50. package/dist/mcp/lib/types.d.ts +80 -0
  51. package/dist/mcp/lib/types.d.ts.map +1 -0
  52. package/dist/mcp/lib/types.js +5 -0
  53. package/dist/mcp/lib/types.js.map +1 -0
  54. package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
  55. package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
  56. package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
  57. package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
  58. package/dist/mcp/server/index.d.ts +8 -0
  59. package/dist/mcp/server/index.d.ts.map +1 -0
  60. package/dist/mcp/server/index.js +202 -0
  61. package/dist/mcp/server/index.js.map +1 -0
  62. package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
  63. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
  64. package/dist/mcp/server/stack-mcp-server.js +265 -0
  65. package/dist/mcp/server/stack-mcp-server.js.map +1 -0
  66. package/package.json +9 -7
  67. package/src/commands/__snapshots__/generate.test.ts.snap +57 -21
  68. package/src/commands/dev.test.ts +0 -1
  69. package/src/commands/generate.test.ts +18 -8
  70. package/src/commands/generate.ts +12 -0
  71. package/src/commands/mcp.ts +135 -0
  72. package/src/generator/__snapshots__/context.test.ts.snap +8 -8
  73. package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
  74. package/src/generator/__snapshots__/types.test.ts.snap +587 -9
  75. package/src/generator/context.test.ts +13 -8
  76. package/src/generator/context.ts +21 -3
  77. package/src/generator/index.ts +3 -0
  78. package/src/generator/lists.test.ts +335 -0
  79. package/src/generator/lists.ts +102 -0
  80. package/src/generator/plugin-types.ts +147 -0
  81. package/src/generator/prisma-config.ts +46 -0
  82. package/src/generator/prisma.test.ts +0 -10
  83. package/src/generator/prisma.ts +1 -2
  84. package/src/generator/types.test.ts +0 -12
  85. package/src/generator/types.ts +54 -1
  86. package/src/index.ts +4 -0
  87. package/src/mcp/lib/documentation-provider.ts +203 -0
  88. package/src/mcp/lib/features/catalog.ts +301 -0
  89. package/src/mcp/lib/generators/feature-generator.ts +598 -0
  90. package/src/mcp/lib/types.ts +89 -0
  91. package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
  92. package/src/mcp/server/index.ts +240 -0
  93. package/src/mcp/server/stack-mcp-server.ts +301 -0
  94. 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
- url: 'file:./dev.db',
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
- url: 'file:./dev.db',
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 | undefined }')
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
- url: 'file:./dev.db',
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
- url: 'file:./dev.db',
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
- url: 'file:./dev.db',
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
- url: 'file:./dev.db',
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ prismaClientConstructor: (() => null) as any,
122
127
  },
123
128
  lists: {},
124
129
  }
@@ -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
- : `new PrismaClient()`
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 | undefined }
133
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | null }
116
134
  let prisma: PrismaClient | null = null
117
135
 
118
136
  async function getPrisma() {
@@ -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
+ }