@opensaas/stack-cli 0.4.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 (90) hide show
  1. package/README.md +76 -0
  2. package/dist/commands/migrate.d.ts +9 -0
  3. package/dist/commands/migrate.d.ts.map +1 -0
  4. package/dist/commands/migrate.js +299 -0
  5. package/dist/commands/migrate.js.map +1 -0
  6. package/dist/index.js +3 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/mcp/lib/documentation-provider.d.ts +23 -0
  9. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -1
  10. package/dist/mcp/lib/documentation-provider.js +471 -0
  11. package/dist/mcp/lib/documentation-provider.js.map +1 -1
  12. package/dist/mcp/lib/wizards/migration-wizard.d.ts +80 -0
  13. package/dist/mcp/lib/wizards/migration-wizard.d.ts.map +1 -0
  14. package/dist/mcp/lib/wizards/migration-wizard.js +499 -0
  15. package/dist/mcp/lib/wizards/migration-wizard.js.map +1 -0
  16. package/dist/mcp/server/index.d.ts.map +1 -1
  17. package/dist/mcp/server/index.js +103 -0
  18. package/dist/mcp/server/index.js.map +1 -1
  19. package/dist/mcp/server/stack-mcp-server.d.ts +85 -0
  20. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -1
  21. package/dist/mcp/server/stack-mcp-server.js +219 -0
  22. package/dist/mcp/server/stack-mcp-server.js.map +1 -1
  23. package/dist/migration/generators/migration-generator.d.ts +60 -0
  24. package/dist/migration/generators/migration-generator.d.ts.map +1 -0
  25. package/dist/migration/generators/migration-generator.js +510 -0
  26. package/dist/migration/generators/migration-generator.js.map +1 -0
  27. package/dist/migration/introspectors/index.d.ts +12 -0
  28. package/dist/migration/introspectors/index.d.ts.map +1 -0
  29. package/dist/migration/introspectors/index.js +10 -0
  30. package/dist/migration/introspectors/index.js.map +1 -0
  31. package/dist/migration/introspectors/keystone-introspector.d.ts +59 -0
  32. package/dist/migration/introspectors/keystone-introspector.d.ts.map +1 -0
  33. package/dist/migration/introspectors/keystone-introspector.js +229 -0
  34. package/dist/migration/introspectors/keystone-introspector.js.map +1 -0
  35. package/dist/migration/introspectors/nextjs-introspector.d.ts +59 -0
  36. package/dist/migration/introspectors/nextjs-introspector.d.ts.map +1 -0
  37. package/dist/migration/introspectors/nextjs-introspector.js +159 -0
  38. package/dist/migration/introspectors/nextjs-introspector.js.map +1 -0
  39. package/dist/migration/introspectors/prisma-introspector.d.ts +45 -0
  40. package/dist/migration/introspectors/prisma-introspector.d.ts.map +1 -0
  41. package/dist/migration/introspectors/prisma-introspector.js +190 -0
  42. package/dist/migration/introspectors/prisma-introspector.js.map +1 -0
  43. package/dist/migration/types.d.ts +86 -0
  44. package/dist/migration/types.d.ts.map +1 -0
  45. package/dist/migration/types.js +5 -0
  46. package/dist/migration/types.js.map +1 -0
  47. package/package.json +10 -2
  48. package/plugin/.claude-plugin/plugin.json +15 -0
  49. package/plugin/README.md +112 -0
  50. package/plugin/agents/migration-assistant.md +150 -0
  51. package/plugin/commands/analyze-schema.md +34 -0
  52. package/plugin/commands/generate-config.md +33 -0
  53. package/plugin/commands/validate-migration.md +34 -0
  54. package/plugin/skills/opensaas-migration/SKILL.md +192 -0
  55. package/.turbo/turbo-build.log +0 -4
  56. package/CHANGELOG.md +0 -410
  57. package/CLAUDE.md +0 -298
  58. package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
  59. package/src/commands/dev.test.ts +0 -215
  60. package/src/commands/dev.ts +0 -48
  61. package/src/commands/generate.test.ts +0 -282
  62. package/src/commands/generate.ts +0 -182
  63. package/src/commands/init.ts +0 -34
  64. package/src/commands/mcp.ts +0 -135
  65. package/src/generator/__snapshots__/context.test.ts.snap +0 -361
  66. package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
  67. package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
  68. package/src/generator/context.test.ts +0 -139
  69. package/src/generator/context.ts +0 -227
  70. package/src/generator/index.ts +0 -7
  71. package/src/generator/lists.test.ts +0 -335
  72. package/src/generator/lists.ts +0 -140
  73. package/src/generator/plugin-types.ts +0 -147
  74. package/src/generator/prisma-config.ts +0 -46
  75. package/src/generator/prisma-extensions.ts +0 -159
  76. package/src/generator/prisma.test.ts +0 -211
  77. package/src/generator/prisma.ts +0 -161
  78. package/src/generator/types.test.ts +0 -268
  79. package/src/generator/types.ts +0 -537
  80. package/src/index.ts +0 -42
  81. package/src/mcp/lib/documentation-provider.ts +0 -203
  82. package/src/mcp/lib/features/catalog.ts +0 -301
  83. package/src/mcp/lib/generators/feature-generator.ts +0 -598
  84. package/src/mcp/lib/types.ts +0 -89
  85. package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
  86. package/src/mcp/server/index.ts +0 -240
  87. package/src/mcp/server/stack-mcp-server.ts +0 -301
  88. package/tsconfig.json +0 -13
  89. package/tsconfig.tsbuildinfo +0 -1
  90. package/vitest.config.ts +0 -26
@@ -1,140 +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
- * Map field type string to TypeScript field type name
7
- */
8
- function getFieldTypeName(fieldType: string): string {
9
- const typeMap: Record<string, string> = {
10
- text: 'TextField',
11
- integer: 'IntegerField',
12
- checkbox: 'CheckboxField',
13
- timestamp: 'TimestampField',
14
- password: 'PasswordField',
15
- select: 'SelectField',
16
- relationship: 'RelationshipField',
17
- json: 'JsonField',
18
- virtual: 'VirtualField',
19
- }
20
-
21
- return typeMap[fieldType] || 'BaseFieldConfig'
22
- }
23
-
24
- /**
25
- * Generate Lists namespace with TypeInfo for each list
26
- * This provides strongly-typed hooks with Prisma input types
27
- *
28
- * @example
29
- * ```typescript
30
- * // Generated output:
31
- * export declare namespace Lists {
32
- * export type Post = import('@opensaas/stack-core').ListConfig<Lists.Post.TypeInfo>
33
- *
34
- * namespace Post {
35
- * export type Item = import('./types').Post
36
- * export type TypeInfo = {
37
- * key: 'Post'
38
- * item: Item
39
- * inputs: {
40
- * create: import('./prisma-client/client').Prisma.PostCreateInput
41
- * update: import('./prisma-client/client').Prisma.PostUpdateInput
42
- * }
43
- * }
44
- * }
45
- * }
46
- * ```
47
- */
48
- export function generateListsNamespace(config: OpenSaasConfig): string {
49
- const lines: string[] = []
50
-
51
- // Add header comment
52
- lines.push('/**')
53
- lines.push(' * Generated Lists namespace from OpenSaas configuration')
54
- lines.push(' * DO NOT EDIT - This file is automatically generated')
55
- lines.push(' *')
56
- lines.push(' * This file provides TypeInfo for each list, enabling strong typing')
57
- lines.push(' * for hooks with Prisma input types.')
58
- lines.push(' *')
59
- lines.push(' * @example')
60
- lines.push(' * ```typescript')
61
- lines.push(" * import type { Lists } from './.opensaas/lists'")
62
- lines.push(' *')
63
- lines.push(' * // Use TypeInfo as generic parameter')
64
- lines.push(' * Post: list<Lists.Post.TypeInfo>({')
65
- lines.push(' * hooks: {')
66
- lines.push(' * resolveInput: async ({ operation, resolvedData }) => {')
67
- lines.push(' * // resolvedData is Prisma.PostCreateInput or Prisma.PostUpdateInput')
68
- lines.push(' * return resolvedData')
69
- lines.push(' * }')
70
- lines.push(' * }')
71
- lines.push(' * })')
72
- lines.push(' *')
73
- lines.push(' * // Or use as typed constant')
74
- lines.push(' * const Post: Lists.Post = list({ ... })')
75
- lines.push(' * ```')
76
- lines.push(' */')
77
- lines.push('')
78
-
79
- // Start Lists namespace
80
- lines.push('export declare namespace Lists {')
81
-
82
- // Generate type for each list
83
- for (const [listName, listConfig] of Object.entries(config.lists)) {
84
- lines.push(
85
- ` export type ${listName} = import('@opensaas/stack-core').ListConfig<Lists.${listName}.TypeInfo>`,
86
- )
87
- lines.push('')
88
- lines.push(` namespace ${listName} {`)
89
- lines.push(` export type Item = import('./types').${listName}`)
90
- lines.push('')
91
-
92
- // Generate Fields type
93
- lines.push(` /**`)
94
- lines.push(` * Field configurations for ${listName}`)
95
- lines.push(` * Maps field names to their field config types`)
96
- lines.push(` */`)
97
- lines.push(` export type Fields = {`)
98
- for (const [fieldName, fieldConfig] of Object.entries(listConfig.fields)) {
99
- const fieldTypeName = getFieldTypeName(fieldConfig.type)
100
- lines.push(
101
- ` ${fieldName}: import('@opensaas/stack-core').${fieldTypeName}<Lists.${listName}.TypeInfo>`,
102
- )
103
- }
104
- lines.push(` }`)
105
- lines.push('')
106
-
107
- // Generate TypeInfo with fields property
108
- lines.push(` export type TypeInfo = {`)
109
- lines.push(` key: '${listName}'`)
110
- lines.push(` fields: Fields`)
111
- lines.push(` item: Item`)
112
- lines.push(` inputs: {`)
113
- lines.push(` create: import('./prisma-client/client').Prisma.${listName}CreateInput`)
114
- lines.push(` update: import('./prisma-client/client').Prisma.${listName}UpdateInput`)
115
- lines.push(` }`)
116
- lines.push(` }`)
117
- lines.push(` }`)
118
- lines.push('')
119
- }
120
-
121
- // Close Lists namespace
122
- lines.push('}')
123
-
124
- return lines.join('\n')
125
- }
126
-
127
- /**
128
- * Write Lists namespace to file
129
- */
130
- export function writeLists(config: OpenSaasConfig, outputPath: string): void {
131
- const lists = generateListsNamespace(config)
132
-
133
- // Ensure directory exists
134
- const dir = path.dirname(outputPath)
135
- if (!fs.existsSync(dir)) {
136
- fs.mkdirSync(dir, { recursive: true })
137
- }
138
-
139
- fs.writeFileSync(outputPath, lists, 'utf-8')
140
- }
@@ -1,147 +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 TypeScript declaration for plugin data types
7
- * Creates type-safe access to config._pluginData
8
- */
9
- function generatePluginDataInterface(config: OpenSaasConfig): string {
10
- const lines: string[] = []
11
-
12
- lines.push('/**')
13
- lines.push(' * Plugin data storage types')
14
- lines.push(' * Provides type-safe access to config._pluginData')
15
- lines.push(' */')
16
- lines.push('export interface PluginData {')
17
-
18
- // Check if we have plugin data types to generate
19
- if (config._pluginData && Object.keys(config._pluginData).length > 0) {
20
- for (const pluginName of Object.keys(config._pluginData)) {
21
- // Skip internal keys
22
- if (pluginName.startsWith('__')) continue
23
-
24
- // For each plugin, we'll add a Record<string, unknown> entry
25
- // TODO: In the future, plugins could export their data types for proper typing
26
- lines.push(` ${pluginName}?: Record<string, unknown>`)
27
- }
28
- }
29
-
30
- lines.push('}')
31
-
32
- return lines.join('\n')
33
- }
34
-
35
- /**
36
- * Generate TypeScript declaration for plugin runtime services
37
- * Creates type-safe access to context.plugins
38
- */
39
- function generatePluginServicesInterface(config: OpenSaasConfig): string {
40
- const lines: string[] = []
41
- const imports: string[] = []
42
-
43
- // Check if we have plugins with runtime functions
44
- const pluginsWithRuntime = (config._plugins || config.plugins || []).filter((p) => p.runtime)
45
-
46
- // Collect imports from plugins that provide runtime service types
47
- if (pluginsWithRuntime.length > 0) {
48
- for (const plugin of pluginsWithRuntime) {
49
- if (plugin.runtimeServiceTypes) {
50
- imports.push(plugin.runtimeServiceTypes.import)
51
- }
52
- }
53
- }
54
-
55
- // Add imports at the top if any exist
56
- if (imports.length > 0) {
57
- lines.push(...imports)
58
- lines.push('')
59
- }
60
-
61
- lines.push('/**')
62
- lines.push(' * Plugin runtime services')
63
- lines.push(' * Provides type-safe access to context.plugins')
64
- lines.push(' * Extends Record to allow compatibility with base AccessContext type')
65
- lines.push(' */')
66
- lines.push(
67
- 'export interface PluginServices extends Record<string, Record<string, any> | undefined> {',
68
- )
69
-
70
- if (pluginsWithRuntime.length > 0) {
71
- for (const plugin of pluginsWithRuntime) {
72
- if (plugin.runtimeServiceTypes) {
73
- // Use typed runtime service from plugin
74
- lines.push(` ${plugin.name}?: ${plugin.runtimeServiceTypes.typeName}`)
75
- } else {
76
- // Fallback to Record<string, any> for plugins without type metadata
77
- lines.push(` ${plugin.name}?: Record<string, any>`)
78
- }
79
- }
80
- }
81
-
82
- lines.push('}')
83
-
84
- return lines.join('\n')
85
- }
86
-
87
- /**
88
- * Generate module augmentation for OpenSaaS core types
89
- * Note: We cannot augment _pluginData or plugins properties directly due to type constraints
90
- * Instead, users should cast to PluginServices when accessing context.plugins
91
- */
92
- function generateModuleAugmentation(): string {
93
- const lines: string[] = []
94
-
95
- lines.push('')
96
- lines.push('/**')
97
- lines.push(' * Declare this module to make interfaces available globally')
98
- lines.push(' * Import this file to get typed plugin access')
99
- lines.push(' */')
100
- lines.push('declare global {')
101
- lines.push(' // Plugin types are available as PluginServices interface')
102
- lines.push('}')
103
-
104
- return lines.join('\n')
105
- }
106
-
107
- /**
108
- * Generate complete plugin types file
109
- */
110
- export function generatePluginTypes(config: OpenSaasConfig): string {
111
- const lines: string[] = []
112
-
113
- // Add header comment
114
- lines.push('/**')
115
- lines.push(' * Generated plugin types from OpenSaas configuration')
116
- lines.push(' * DO NOT EDIT - This file is automatically generated')
117
- lines.push(' *')
118
- lines.push(' * This file provides type-safe access to:')
119
- lines.push(' * - config._pluginData - Plugin configuration data')
120
- lines.push(' * - context.plugins - Plugin runtime services')
121
- lines.push(' */')
122
- lines.push('')
123
-
124
- // Generate interfaces
125
- lines.push(generatePluginDataInterface(config))
126
- lines.push(generatePluginServicesInterface(config))
127
-
128
- // Generate module augmentation
129
- lines.push(generateModuleAugmentation())
130
-
131
- return lines.join('\n')
132
- }
133
-
134
- /**
135
- * Write plugin types to file
136
- */
137
- export function writePluginTypes(config: OpenSaasConfig, outputPath: string): void {
138
- const pluginTypes = generatePluginTypes(config)
139
-
140
- // Ensure directory exists
141
- const dir = path.dirname(outputPath)
142
- if (!fs.existsSync(dir)) {
143
- fs.mkdirSync(dir, { recursive: true })
144
- }
145
-
146
- fs.writeFileSync(outputPath, pluginTypes, 'utf-8')
147
- }
@@ -1,46 +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 Prisma config file for CLI commands
7
- *
8
- * Prisma 7 requires a prisma.config.ts file at the project root for CLI commands
9
- * like `prisma db push` and `prisma migrate dev`. This is separate from the
10
- * runtime configuration (which uses adapters in opensaas.config.ts).
11
- *
12
- * The CLI config provides the database URL for schema operations, while the
13
- * runtime config provides adapters for actual query execution.
14
- */
15
- export function generatePrismaConfig(_config: OpenSaasConfig): string {
16
- const lines: string[] = []
17
-
18
- // Import dotenv for environment variable loading
19
- lines.push("import 'dotenv/config'")
20
- lines.push("import { defineConfig, env } from 'prisma/config'")
21
- lines.push('')
22
- lines.push('export default defineConfig({')
23
- lines.push(" schema: 'prisma/schema.prisma',")
24
- lines.push(' datasource: {')
25
- lines.push(" url: env('DATABASE_URL'),")
26
- lines.push(' },')
27
- lines.push('})')
28
- lines.push('')
29
-
30
- return lines.join('\n')
31
- }
32
-
33
- /**
34
- * Write Prisma config to file
35
- */
36
- export function writePrismaConfig(config: OpenSaasConfig, outputPath: string): void {
37
- const prismaConfig = generatePrismaConfig(config)
38
-
39
- // Ensure directory exists
40
- const dir = path.dirname(outputPath)
41
- if (!fs.existsSync(dir)) {
42
- fs.mkdirSync(dir, { recursive: true })
43
- }
44
-
45
- fs.writeFileSync(outputPath, prismaConfig, 'utf-8')
46
- }
@@ -1,159 +0,0 @@
1
- import type { OpenSaasConfig, FieldConfig } from '@opensaas/stack-core'
2
- import * as fs from 'fs'
3
- import * as path from 'path'
4
-
5
- /**
6
- * Generate Prisma result extensions configuration
7
- * This creates a Prisma client extension that calls field resolveOutput hooks
8
- */
9
- export function generatePrismaExtensions(config: OpenSaasConfig): string {
10
- const lines: string[] = []
11
-
12
- // Add header comment
13
- lines.push('/**')
14
- lines.push(' * Generated Prisma result extensions from OpenSaas configuration')
15
- lines.push(' * DO NOT EDIT - This file is automatically generated')
16
- lines.push(' */')
17
- lines.push('')
18
-
19
- // Add imports
20
- lines.push("import { Prisma } from './prisma-client/client'")
21
- lines.push("import configOrPromise from '../opensaas.config'")
22
- lines.push('')
23
-
24
- // Resolve config synchronously if possible (will be resolved by context.ts anyway)
25
- lines.push('// Resolve config - may be a promise if plugins are present')
26
- lines.push('let resolvedConfig: any = null')
27
- lines.push('const configPromise = Promise.resolve(configOrPromise)')
28
- lines.push('configPromise.then(cfg => { resolvedConfig = cfg })')
29
- lines.push('')
30
-
31
- // Check if any fields have result extensions or are virtual
32
- let hasExtensions = false
33
- for (const listConfig of Object.values(config.lists)) {
34
- for (const fieldConfig of Object.values(listConfig.fields)) {
35
- if (fieldConfig.resultExtension || fieldConfig.virtual) {
36
- hasExtensions = true
37
- break
38
- }
39
- }
40
- if (hasExtensions) break
41
- }
42
-
43
- if (!hasExtensions) {
44
- // No extensions needed - export a no-op
45
- lines.push('/**')
46
- lines.push(' * No result extensions configured')
47
- lines.push(' */')
48
- lines.push('export const prismaExtensions = {}')
49
- lines.push('')
50
- return lines.join('\n')
51
- }
52
-
53
- // Generate result extensions
54
- lines.push('/**')
55
- lines.push(' * Prisma result extensions for field transformations and virtual fields')
56
- lines.push(' * Delegates to field resolveOutput hooks from config for runtime transformations')
57
- lines.push(' */')
58
- lines.push('export const prismaExtensions = Prisma.defineExtension({')
59
- lines.push(' result: {')
60
-
61
- // Generate extensions for each list
62
- for (const [listName, listConfig] of Object.entries(config.lists)) {
63
- // Include both fields with resultExtension AND virtual fields
64
- const fieldsWithExtensions: Array<[string, FieldConfig]> = Object.entries(
65
- listConfig.fields,
66
- ).filter(([_, config]) => config.resultExtension || config.virtual)
67
-
68
- if (fieldsWithExtensions.length === 0) continue
69
-
70
- const modelKey = listName.charAt(0).toLowerCase() + listName.slice(1) // camelCase
71
-
72
- lines.push(` ${modelKey}: {`)
73
-
74
- for (const [fieldName, fieldConfig] of fieldsWithExtensions) {
75
- const isVirtual = fieldConfig.virtual
76
-
77
- lines.push(` ${fieldName}: {`)
78
-
79
- if (isVirtual) {
80
- // Virtual fields don't need database fields - they compute from the full item
81
- lines.push(` needs: {},`)
82
- } else {
83
- // Non-virtual fields need their database value
84
- lines.push(` needs: { ${fieldName}: true },`)
85
- }
86
-
87
- lines.push(` compute: (${modelKey}) => {`)
88
-
89
- if (!isVirtual) {
90
- // For non-virtual fields, get the database value and check nullability
91
- lines.push(` const value = ${modelKey}.${fieldName}`)
92
- lines.push(` if (value === null || value === undefined) {`)
93
- lines.push(` return undefined`)
94
- lines.push(` }`)
95
- }
96
-
97
- lines.push(` // Call field's resolveOutput hook if available (synchronously)`)
98
- lines.push(` if (!resolvedConfig) {`)
99
- lines.push(
100
- ` // Config not yet resolved - return undefined for virtual, value for regular`,
101
- )
102
- if (isVirtual) {
103
- lines.push(` return undefined`)
104
- } else {
105
- lines.push(` return value`)
106
- }
107
- lines.push(` }`)
108
- lines.push(
109
- ` const fieldConfig = resolvedConfig.lists['${listName}'].fields['${fieldName}']`,
110
- )
111
- lines.push(` if (fieldConfig.hooks?.resolveOutput) {`)
112
- lines.push(` return fieldConfig.hooks.resolveOutput({`)
113
- lines.push(` operation: 'query',`)
114
- if (isVirtual) {
115
- lines.push(` value: undefined, // Virtual fields have no stored value`)
116
- } else {
117
- lines.push(` value,`)
118
- }
119
- lines.push(` item: ${modelKey},`)
120
- lines.push(` listKey: '${listName}',`)
121
- lines.push(` fieldName: '${fieldName}',`)
122
- lines.push(
123
- ` context: null as any, // Extension context doesn't have full context`,
124
- )
125
- lines.push(` })`)
126
- lines.push(` }`)
127
- if (isVirtual) {
128
- lines.push(` return undefined`)
129
- } else {
130
- lines.push(` return value`)
131
- }
132
- lines.push(` },`)
133
- lines.push(` },`)
134
- }
135
-
136
- lines.push(` },`)
137
- }
138
-
139
- lines.push(' },')
140
- lines.push('})')
141
- lines.push('')
142
-
143
- return lines.join('\n')
144
- }
145
-
146
- /**
147
- * Write Prisma extensions configuration to file
148
- */
149
- export function writePrismaExtensions(config: OpenSaasConfig, outputPath: string): void {
150
- const extensions = generatePrismaExtensions(config)
151
-
152
- // Ensure directory exists
153
- const dir = path.dirname(outputPath)
154
- if (!fs.existsSync(dir)) {
155
- fs.mkdirSync(dir, { recursive: true })
156
- }
157
-
158
- fs.writeFileSync(outputPath, extensions, 'utf-8')
159
- }
@@ -1,211 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { generatePrismaSchema } from './prisma.js'
3
- import type { OpenSaasConfig } from '@opensaas/stack-core'
4
- import { text, integer, relationship, checkbox, timestamp } from '@opensaas/stack-core/fields'
5
-
6
- describe('Prisma Schema Generator', () => {
7
- describe('generatePrismaSchema', () => {
8
- it('should generate basic schema with datasource and generator', () => {
9
- const config: OpenSaasConfig = {
10
- db: {
11
- provider: 'sqlite',
12
- },
13
- lists: {},
14
- }
15
-
16
- const schema = generatePrismaSchema(config)
17
-
18
- expect(schema).toMatchSnapshot()
19
- })
20
-
21
- it('should use custom opensaasPath for generator output', () => {
22
- const config: OpenSaasConfig = {
23
- db: {
24
- provider: 'sqlite',
25
- },
26
- opensaasPath: '.custom-path',
27
- lists: {},
28
- }
29
-
30
- const schema = generatePrismaSchema(config)
31
-
32
- expect(schema).toMatchSnapshot()
33
- })
34
-
35
- it('should generate model with basic fields', () => {
36
- const config: OpenSaasConfig = {
37
- db: {
38
- provider: 'sqlite',
39
- },
40
- lists: {
41
- User: {
42
- fields: {
43
- name: text({ validation: { isRequired: true } }),
44
- email: text({ validation: { isRequired: true } }),
45
- age: integer(),
46
- },
47
- },
48
- },
49
- }
50
-
51
- const schema = generatePrismaSchema(config)
52
-
53
- expect(schema).toMatchSnapshot()
54
- })
55
-
56
- it('should generate model with checkbox field', () => {
57
- const config: OpenSaasConfig = {
58
- db: {
59
- provider: 'sqlite',
60
- },
61
- lists: {
62
- Post: {
63
- fields: {
64
- title: text(),
65
- isPublished: checkbox({ defaultValue: false }),
66
- },
67
- },
68
- },
69
- }
70
-
71
- const schema = generatePrismaSchema(config)
72
-
73
- expect(schema).toMatchSnapshot()
74
- })
75
-
76
- it('should generate model with timestamp field', () => {
77
- const config: OpenSaasConfig = {
78
- db: {
79
- provider: 'sqlite',
80
- },
81
- lists: {
82
- Post: {
83
- fields: {
84
- title: text(),
85
- publishedAt: timestamp(),
86
- },
87
- },
88
- },
89
- }
90
-
91
- const schema = generatePrismaSchema(config)
92
-
93
- expect(schema).toMatchSnapshot()
94
- })
95
-
96
- it('should generate many-to-one relationship', () => {
97
- const config: OpenSaasConfig = {
98
- db: {
99
- provider: 'sqlite',
100
- },
101
- lists: {
102
- User: {
103
- fields: {
104
- name: text(),
105
- },
106
- },
107
- Post: {
108
- fields: {
109
- title: text(),
110
- author: relationship({ ref: 'User.posts' }),
111
- },
112
- },
113
- },
114
- }
115
-
116
- const schema = generatePrismaSchema(config)
117
-
118
- expect(schema).toMatchSnapshot()
119
- })
120
-
121
- it('should generate one-to-many relationship', () => {
122
- const config: OpenSaasConfig = {
123
- db: {
124
- provider: 'sqlite',
125
- },
126
- lists: {
127
- User: {
128
- fields: {
129
- name: text(),
130
- posts: relationship({ ref: 'Post.author', many: true }),
131
- },
132
- },
133
- Post: {
134
- fields: {
135
- title: text(),
136
- },
137
- },
138
- },
139
- }
140
-
141
- const schema = generatePrismaSchema(config)
142
-
143
- expect(schema).toMatchSnapshot()
144
- })
145
-
146
- it('should generate multiple models', () => {
147
- const config: OpenSaasConfig = {
148
- db: {
149
- provider: 'postgresql',
150
- },
151
- lists: {
152
- User: {
153
- fields: {
154
- name: text(),
155
- },
156
- },
157
- Post: {
158
- fields: {
159
- title: text(),
160
- },
161
- },
162
- Comment: {
163
- fields: {
164
- content: text(),
165
- },
166
- },
167
- },
168
- }
169
-
170
- const schema = generatePrismaSchema(config)
171
-
172
- expect(schema).toMatchSnapshot()
173
- })
174
-
175
- it('should always include system fields', () => {
176
- const config: OpenSaasConfig = {
177
- db: {
178
- provider: 'sqlite',
179
- },
180
- lists: {
181
- User: {
182
- fields: {
183
- name: text(),
184
- },
185
- },
186
- },
187
- }
188
-
189
- const schema = generatePrismaSchema(config)
190
-
191
- expect(schema).toContain('id String @id @default(cuid())')
192
- expect(schema).toContain('createdAt DateTime @default(now())')
193
- expect(schema).toContain('updatedAt DateTime @updatedAt')
194
- })
195
-
196
- it('should handle empty lists config', () => {
197
- const config: OpenSaasConfig = {
198
- db: {
199
- provider: 'sqlite',
200
- },
201
- lists: {},
202
- }
203
-
204
- const schema = generatePrismaSchema(config)
205
-
206
- expect(schema).toContain('generator client {')
207
- expect(schema).toContain('datasource db {')
208
- expect(schema).not.toContain('model')
209
- })
210
- })
211
- })