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