@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.
Files changed (61) hide show
  1. package/README.md +76 -0
  2. package/dist/commands/migrate.d.ts.map +1 -1
  3. package/dist/commands/migrate.js +91 -265
  4. package/dist/commands/migrate.js.map +1 -1
  5. package/package.json +7 -2
  6. package/plugin/.claude-plugin/plugin.json +15 -0
  7. package/plugin/README.md +112 -0
  8. package/plugin/agents/migration-assistant.md +150 -0
  9. package/plugin/commands/analyze-schema.md +34 -0
  10. package/plugin/commands/generate-config.md +33 -0
  11. package/plugin/commands/validate-migration.md +34 -0
  12. package/plugin/skills/opensaas-migration/SKILL.md +192 -0
  13. package/.turbo/turbo-build.log +0 -4
  14. package/CHANGELOG.md +0 -462
  15. package/CLAUDE.md +0 -298
  16. package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
  17. package/src/commands/dev.test.ts +0 -215
  18. package/src/commands/dev.ts +0 -48
  19. package/src/commands/generate.test.ts +0 -282
  20. package/src/commands/generate.ts +0 -182
  21. package/src/commands/init.ts +0 -34
  22. package/src/commands/mcp.ts +0 -135
  23. package/src/commands/migrate.ts +0 -534
  24. package/src/generator/__snapshots__/context.test.ts.snap +0 -361
  25. package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
  26. package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
  27. package/src/generator/context.test.ts +0 -139
  28. package/src/generator/context.ts +0 -227
  29. package/src/generator/index.ts +0 -7
  30. package/src/generator/lists.test.ts +0 -335
  31. package/src/generator/lists.ts +0 -140
  32. package/src/generator/plugin-types.ts +0 -147
  33. package/src/generator/prisma-config.ts +0 -46
  34. package/src/generator/prisma-extensions.ts +0 -159
  35. package/src/generator/prisma.test.ts +0 -211
  36. package/src/generator/prisma.ts +0 -161
  37. package/src/generator/types.test.ts +0 -268
  38. package/src/generator/types.ts +0 -537
  39. package/src/index.ts +0 -46
  40. package/src/mcp/lib/documentation-provider.ts +0 -710
  41. package/src/mcp/lib/features/catalog.ts +0 -301
  42. package/src/mcp/lib/generators/feature-generator.ts +0 -598
  43. package/src/mcp/lib/types.ts +0 -89
  44. package/src/mcp/lib/wizards/migration-wizard.ts +0 -584
  45. package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
  46. package/src/mcp/server/index.ts +0 -361
  47. package/src/mcp/server/stack-mcp-server.ts +0 -544
  48. package/src/migration/generators/migration-generator.ts +0 -675
  49. package/src/migration/introspectors/index.ts +0 -12
  50. package/src/migration/introspectors/keystone-introspector.ts +0 -296
  51. package/src/migration/introspectors/nextjs-introspector.ts +0 -209
  52. package/src/migration/introspectors/prisma-introspector.ts +0 -233
  53. package/src/migration/types.ts +0 -92
  54. package/tests/introspectors/keystone-introspector.test.ts +0 -255
  55. package/tests/introspectors/nextjs-introspector.test.ts +0 -302
  56. package/tests/introspectors/prisma-introspector.test.ts +0 -268
  57. package/tests/migration-generator.test.ts +0 -592
  58. package/tests/migration-wizard.test.ts +0 -442
  59. package/tsconfig.json +0 -13
  60. package/tsconfig.tsbuildinfo +0 -1
  61. package/vitest.config.ts +0 -26
@@ -1,139 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { generateContext } from './context.js'
3
- import type { OpenSaasConfig } from '@opensaas/stack-core'
4
- import { text } from '@opensaas/stack-core/fields'
5
-
6
- describe('Context Generator', () => {
7
- describe('generateContext', () => {
8
- it('should generate context factory with default Prisma client', () => {
9
- const config: OpenSaasConfig = {
10
- db: {
11
- provider: 'sqlite',
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- prismaClientConstructor: (() => null) as any,
14
- },
15
- lists: {
16
- User: {
17
- fields: {
18
- name: text(),
19
- },
20
- },
21
- },
22
- }
23
-
24
- const context = generateContext(config)
25
-
26
- expect(context).toMatchSnapshot()
27
- })
28
-
29
- it('should generate context factory with custom Prisma client constructor', () => {
30
- const config: OpenSaasConfig = {
31
- db: {
32
- provider: 'postgresql',
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- prismaClientConstructor: ((PrismaClient: any) => new PrismaClient()) as any,
35
- },
36
- lists: {
37
- User: {
38
- fields: {
39
- name: text(),
40
- },
41
- },
42
- },
43
- }
44
-
45
- const context = generateContext(config)
46
-
47
- expect(context).toMatchSnapshot()
48
- })
49
-
50
- it('should include singleton pattern for Prisma client', () => {
51
- const config: OpenSaasConfig = {
52
- db: {
53
- provider: 'sqlite',
54
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
- prismaClientConstructor: (() => null) as any,
56
- },
57
- lists: {},
58
- }
59
-
60
- const context = generateContext(config)
61
-
62
- expect(context).toContain('const globalForPrisma')
63
- expect(context).toContain(
64
- 'globalThis as unknown as { prisma: ReturnType<typeof createExtendedPrisma> | null }',
65
- )
66
- expect(context).toContain('globalForPrisma.prisma')
67
- expect(context).toContain("if (process.env.NODE_ENV !== 'production')")
68
- })
69
-
70
- it('should include JSDoc comments', () => {
71
- const config: OpenSaasConfig = {
72
- db: {
73
- provider: 'sqlite',
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- prismaClientConstructor: (() => null) as any,
76
- },
77
- lists: {},
78
- }
79
-
80
- const context = generateContext(config)
81
-
82
- expect(context).toContain('/**')
83
- expect(context).toContain('Auto-generated context factory')
84
- expect(context).toContain('DO NOT EDIT')
85
- expect(context).toContain('Get OpenSaas context with optional session')
86
- expect(context).toContain('@param session')
87
- expect(context).toContain('@example')
88
- })
89
-
90
- it('should include usage examples in comments', () => {
91
- const config: OpenSaasConfig = {
92
- db: {
93
- provider: 'sqlite',
94
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
- prismaClientConstructor: (() => null) as any,
96
- },
97
- lists: {},
98
- }
99
-
100
- const context = generateContext(config)
101
-
102
- expect(context).toContain('// Anonymous access')
103
- expect(context).toContain('const context = await getContext()')
104
- expect(context).toContain('// Authenticated access')
105
- expect(context).toContain("const context = await getContext({ userId: 'user-123' })")
106
- })
107
-
108
- it('should export rawOpensaasContext', () => {
109
- const config: OpenSaasConfig = {
110
- db: {
111
- provider: 'sqlite',
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
- prismaClientConstructor: (() => null) as any,
114
- },
115
- lists: {},
116
- }
117
-
118
- const context = generateContext(config)
119
-
120
- expect(context).toMatchSnapshot()
121
- })
122
-
123
- it('should type session parameter correctly', () => {
124
- const config: OpenSaasConfig = {
125
- db: {
126
- provider: 'sqlite',
127
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
- prismaClientConstructor: (() => null) as any,
129
- },
130
- lists: {},
131
- }
132
-
133
- const context = generateContext(config)
134
-
135
- expect(context).toContain('session?: TSession')
136
- expect(context).toContain('<TSession extends OpensaasSession = OpensaasSession>')
137
- })
138
- })
139
- })
@@ -1,227 +0,0 @@
1
- import type { OpenSaasConfig } from '@opensaas/stack-core'
2
- import * as fs from 'fs'
3
- import * as path from 'path'
4
-
5
- /**
6
- * Generate context factory that abstracts Prisma client from developers
7
- *
8
- * Creates a simple API with getContext() and getContextWithSession(session)
9
- * that internally handles Prisma singleton and config imports.
10
- */
11
- export function generateContext(config: OpenSaasConfig): string {
12
- // Check if custom Prisma client constructor is provided
13
- const hasCustomConstructor = !!config.db.prismaClientConstructor
14
-
15
- // Check if storage is configured
16
- const hasStorage = !!config.storage && Object.keys(config.storage).length > 0
17
-
18
- // Generate the Prisma client instantiation code
19
- // Prisma 7 requires adapters, so prismaClientConstructor must be provided
20
- const prismaInstantiation = hasCustomConstructor
21
- ? `resolvedConfig.db.prismaClientConstructor!(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
- })()`
40
-
41
- // Generate storage utilities if storage is configured
42
- const storageUtilities = hasStorage
43
- ? `
44
- /**
45
- * Lazy-loaded storage runtime functions
46
- * Prevents sharp and other storage dependencies from being bundled in client code
47
- */
48
- let storageRuntime: typeof import('@opensaas/stack-storage/runtime') | null = null
49
-
50
- async function getStorageRuntime() {
51
- if (!storageRuntime) {
52
- try {
53
- storageRuntime = await import('@opensaas/stack-storage/runtime')
54
- } catch (error) {
55
- throw new Error(
56
- 'Failed to load @opensaas/stack-storage/runtime. Make sure @opensaas/stack-storage is installed.'
57
- )
58
- }
59
- }
60
- return storageRuntime
61
- }
62
-
63
- /**
64
- * Storage utilities for file/image uploads
65
- */
66
- const storage = {
67
- uploadFile: async (providerName: string, file: File, buffer: Buffer, options?: unknown) => {
68
- const config = await getConfig()
69
- const runtime = await getStorageRuntime()
70
- return runtime.uploadFile(config, providerName, { file, buffer }, options as any)
71
- },
72
-
73
- uploadImage: async (providerName: string, file: File, buffer: Buffer, options?: unknown) => {
74
- const config = await getConfig()
75
- const runtime = await getStorageRuntime()
76
- return runtime.uploadImage(config, providerName, { file, buffer }, options as any)
77
- },
78
-
79
- deleteFile: async (providerName: string, filename: string) => {
80
- const config = await getConfig()
81
- const runtime = await getStorageRuntime()
82
- return runtime.deleteFile(config, providerName, filename)
83
- },
84
-
85
- deleteImage: async (metadata: unknown) => {
86
- const config = await getConfig()
87
- const runtime = await getStorageRuntime()
88
- return runtime.deleteImage(config, metadata as any)
89
- },
90
- }
91
- `
92
- : `
93
- /**
94
- * Storage utilities (not configured)
95
- */
96
- const storage = {
97
- uploadFile: async () => {
98
- throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
99
- },
100
- uploadImage: async () => {
101
- throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
102
- },
103
- deleteFile: async () => {
104
- throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
105
- },
106
- deleteImage: async () => {
107
- throw new Error('Storage is not configured. Add storage providers to your opensaas.config.ts')
108
- },
109
- }
110
- `
111
-
112
- // Always use async version for consistency
113
- return `/**
114
- * Auto-generated context factory
115
- *
116
- * This module provides a simple API for creating OpenSaas contexts.
117
- * It abstracts away Prisma client management and configuration.
118
- *
119
- * DO NOT EDIT - This file is automatically generated by 'pnpm generate'
120
- */
121
-
122
- import { getContext as getOpensaasContext } from '@opensaas/stack-core'
123
- import type { Session as OpensaasSession, OpenSaasConfig } from '@opensaas/stack-core'
124
- import { PrismaClient } from './prisma-client/client'
125
- import type { Context } from './types'
126
- import { prismaExtensions } from './prisma-extensions'
127
- import configOrPromise from '../opensaas.config'
128
-
129
- // Resolve config if it's a Promise (when plugins are present)
130
- const configPromise = Promise.resolve(configOrPromise)
131
- let resolvedConfig: OpenSaasConfig | null = null
132
-
133
- // Internal Prisma singleton - managed automatically
134
- const globalForPrisma = globalThis as unknown as { prisma: ReturnType<typeof createExtendedPrisma> | null }
135
- let prisma: ReturnType<typeof createExtendedPrisma> | null = null
136
-
137
- /**
138
- * Create Prisma client with result extensions
139
- */
140
- function createExtendedPrisma(basePrisma: PrismaClient) {
141
- // Check if there are any extensions to apply
142
- if (Object.keys(prismaExtensions).length === 0) {
143
- return basePrisma
144
- }
145
- // Apply result extensions
146
- return basePrisma.$extends(prismaExtensions)
147
- }
148
-
149
- async function getPrisma() {
150
- if (!prisma) {
151
- if (!resolvedConfig) {
152
- resolvedConfig = await configPromise
153
- }
154
- const basePrisma = ${prismaInstantiation}
155
- const extendedPrisma = createExtendedPrisma(basePrisma)
156
- prisma = globalForPrisma.prisma ?? extendedPrisma
157
- if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
158
- }
159
- return prisma
160
- }
161
-
162
- async function getConfig() {
163
- if (!resolvedConfig) {
164
- resolvedConfig = await configPromise
165
- }
166
- return resolvedConfig
167
- }
168
- ${storageUtilities}
169
- /**
170
- * Get OpenSaas context with optional session
171
- *
172
- * @param session - Optional session object (structure defined by your application)
173
- *
174
- * @example
175
- * \`\`\`typescript
176
- * // Anonymous access
177
- * const context = await getContext()
178
- * const posts = await context.db.post.findMany()
179
- *
180
- * // Authenticated access
181
- * const context = await getContext({ userId: 'user-123' })
182
- * const myPosts = await context.db.post.findMany()
183
- *
184
- * // With custom session type
185
- * type CustomSession = { userId: string; email: string; role: string } | null
186
- * const context = await getContext<CustomSession>({ userId: '123', email: 'user@example.com', role: 'admin' })
187
- * // context.session is now typed as CustomSession
188
- * \`\`\`
189
- */
190
- export async function getContext<TSession extends OpensaasSession = OpensaasSession>(session?: TSession): Promise<Context<TSession>> {
191
- const config = await getConfig()
192
- const prismaClient = await getPrisma()
193
- return getOpensaasContext(config, prismaClient, session ?? null, storage) as unknown as Context<TSession>
194
- }
195
-
196
- /**
197
- * Raw context for synchronous initialization (e.g., Better-auth setup)
198
- * This is only available after config is resolved, use with caution
199
- */
200
- export const rawOpensaasContext = (async () => {
201
- const config = await getConfig()
202
- const prismaClient = await getPrisma()
203
- return getOpensaasContext(config, prismaClient, null, storage) as unknown as Context
204
- })()
205
-
206
- /**
207
- * Re-export resolved config for use in admin pages and server actions
208
- * This is a promise that resolves to the config
209
- */
210
- export const config = getConfig()
211
- `
212
- }
213
-
214
- /**
215
- * Write context factory to file
216
- */
217
- export function writeContext(config: OpenSaasConfig, outputPath: string): void {
218
- const content = generateContext(config)
219
-
220
- // Ensure directory exists
221
- const dir = path.dirname(outputPath)
222
- if (!fs.existsSync(dir)) {
223
- fs.mkdirSync(dir, { recursive: true })
224
- }
225
-
226
- fs.writeFileSync(outputPath, content, 'utf-8')
227
- }
@@ -1,7 +0,0 @@
1
- export { generatePrismaSchema, writePrismaSchema } from './prisma.js'
2
- export { generatePrismaConfig, writePrismaConfig } from './prisma-config.js'
3
- export { generateTypes, writeTypes } from './types.js'
4
- export { generateListsNamespace, writeLists } from './lists.js'
5
- export { generateContext, writeContext } from './context.js'
6
- export { generatePluginTypes, writePluginTypes } from './plugin-types.js'
7
- export { generatePrismaExtensions, writePrismaExtensions } from './prisma-extensions.js'
@@ -1,335 +0,0 @@
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
- })