@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,215 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
- import * as fs from 'fs'
3
- import * as path from 'path'
4
- import * as os from 'os'
5
-
6
- // Mock chokidar
7
- const mockWatcherOn = vi.fn()
8
- const mockWatcherClose = vi.fn()
9
- const mockWatch = vi.fn(() => ({
10
- on: mockWatcherOn,
11
- close: mockWatcherClose,
12
- }))
13
-
14
- vi.mock('chokidar', () => ({
15
- default: {
16
- watch: mockWatch,
17
- },
18
- }))
19
-
20
- // Mock the generate command
21
- vi.mock('./generate.js', () => ({
22
- generateCommand: vi.fn().mockResolvedValue(undefined),
23
- }))
24
-
25
- // Mock ora
26
- vi.mock('ora', () => ({
27
- default: vi.fn(() => ({
28
- start: vi.fn().mockReturnThis(),
29
- succeed: vi.fn().mockReturnThis(),
30
- fail: vi.fn().mockReturnThis(),
31
- text: '',
32
- })),
33
- }))
34
-
35
- // Mock chalk
36
- vi.mock('chalk', () => ({
37
- default: {
38
- bold: {
39
- cyan: vi.fn((str) => str),
40
- },
41
- cyan: vi.fn((str) => str),
42
- gray: vi.fn((str) => str),
43
- red: vi.fn((str) => str),
44
- yellow: vi.fn((str) => str),
45
- },
46
- }))
47
-
48
- describe('Dev Command', () => {
49
- let tempDir: string
50
- let originalCwd: string
51
- let originalExit: typeof process.exit
52
- let exitCode: number | undefined
53
-
54
- beforeEach(() => {
55
- vi.clearAllMocks()
56
-
57
- // Create temp directory
58
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dev-test-'))
59
- originalCwd = process.cwd()
60
- process.chdir(tempDir)
61
-
62
- // Mock process.exit
63
- originalExit = process.exit
64
- exitCode = undefined
65
- process.exit = vi.fn((code?: number) => {
66
- exitCode = code
67
- throw new Error(`process.exit(${code})`)
68
- }) as never
69
-
70
- // Create opensaas.config.ts file
71
- fs.writeFileSync(
72
- path.join(tempDir, 'opensaas.config.ts'),
73
- `
74
- import { config } from '@opensaas/stack-core'
75
- export default config({
76
- lists: {}
77
- })
78
- `,
79
- )
80
- })
81
-
82
- afterEach(() => {
83
- // Restore
84
- process.chdir(originalCwd)
85
- process.exit = originalExit
86
-
87
- // Clean up
88
- if (fs.existsSync(tempDir)) {
89
- fs.rmSync(tempDir, { recursive: true, force: true })
90
- }
91
- })
92
-
93
- describe('devCommand', () => {
94
- it('should fail if config file does not exist', async () => {
95
- // Remove config file
96
- fs.unlinkSync(path.join(tempDir, 'opensaas.config.ts'))
97
-
98
- const { devCommand } = await import('./dev.js')
99
-
100
- try {
101
- await devCommand()
102
- } catch {
103
- // Expected to throw
104
- }
105
-
106
- expect(exitCode).toBe(1)
107
- })
108
-
109
- it('should call generateCommand initially', async () => {
110
- const { generateCommand } = await import('./generate.js')
111
- const { devCommand } = await import('./dev.js')
112
-
113
- // Run dev command in background (don't await)
114
- devCommand().catch(() => {
115
- // Ignore errors
116
- })
117
-
118
- // Wait a bit for initial generation
119
- await new Promise((resolve) => setTimeout(resolve, 100))
120
-
121
- expect(generateCommand).toHaveBeenCalled()
122
- })
123
-
124
- it('should set up file watcher for config file', async () => {
125
- const { devCommand } = await import('./dev.js')
126
-
127
- // Run dev command
128
- devCommand().catch(() => {
129
- // Ignore errors
130
- })
131
-
132
- await new Promise((resolve) => setTimeout(resolve, 100))
133
-
134
- // Verify watcher was set up
135
- expect(mockWatch).toHaveBeenCalled()
136
- expect(mockWatch.mock.calls.length).toBeGreaterThan(0)
137
-
138
- const watchPath = mockWatch.mock.calls[0]![0]
139
- expect(watchPath).toContain('opensaas.config.ts')
140
-
141
- const watchOptions = mockWatch.mock.calls[0]![1]
142
- expect(watchOptions).toMatchObject({
143
- persistent: true,
144
- ignoreInitial: true,
145
- })
146
- })
147
-
148
- it('should register change event handler', async () => {
149
- const { devCommand } = await import('./dev.js')
150
-
151
- devCommand().catch(() => {
152
- // Ignore errors
153
- })
154
-
155
- await new Promise((resolve) => setTimeout(resolve, 100))
156
-
157
- expect(mockWatcherOn).toHaveBeenCalledWith('change', expect.any(Function))
158
- })
159
-
160
- it('should register error event handler', async () => {
161
- const { devCommand } = await import('./dev.js')
162
-
163
- devCommand().catch(() => {
164
- // Ignore errors
165
- })
166
-
167
- await new Promise((resolve) => setTimeout(resolve, 100))
168
-
169
- expect(mockWatcherOn).toHaveBeenCalledWith('error', expect.any(Function))
170
- })
171
-
172
- it('should regenerate on config file change', async () => {
173
- const { generateCommand } = await import('./generate.js')
174
- const { devCommand } = await import('./dev.js')
175
-
176
- devCommand().catch(() => {
177
- // Ignore errors
178
- })
179
-
180
- await new Promise((resolve) => setTimeout(resolve, 100))
181
-
182
- // Simulate file change
183
- const changeHandler = mockWatcherOn.mock.calls.find((call) => call[0] === 'change')?.[1]
184
- expect(changeHandler).toBeDefined()
185
-
186
- if (changeHandler) {
187
- await changeHandler()
188
- }
189
-
190
- // generateCommand should be called again
191
- expect(vi.mocked(generateCommand).mock.calls.length).toBeGreaterThan(1)
192
- })
193
-
194
- it('should close watcher on SIGINT', async () => {
195
- const { devCommand } = await import('./dev.js')
196
-
197
- devCommand().catch(() => {
198
- // Ignore errors
199
- })
200
-
201
- await new Promise((resolve) => setTimeout(resolve, 100))
202
-
203
- // Simulate SIGINT - catch the error from process.exit
204
- try {
205
- process.emit('SIGINT', 'SIGINT')
206
- } catch {
207
- // Expected to throw from process.exit mock
208
- }
209
-
210
- await new Promise((resolve) => setTimeout(resolve, 100))
211
-
212
- expect(mockWatcherClose).toHaveBeenCalled()
213
- })
214
- })
215
- })
@@ -1,48 +0,0 @@
1
- import * as path from 'path'
2
- import * as fs from 'fs'
3
- import chalk from 'chalk'
4
- import chokidar from 'chokidar'
5
- import { generateCommand } from './generate.js'
6
-
7
- export async function devCommand() {
8
- const cwd = process.cwd()
9
- const configPath = path.join(cwd, 'opensaas.config.ts')
10
-
11
- // Check if config exists
12
- if (!fs.existsSync(configPath)) {
13
- console.error(chalk.red('Error: opensaas.config.ts not found in current directory'))
14
- console.error(chalk.gray(' Please run this command from your project root'))
15
- process.exit(1)
16
- }
17
-
18
- console.log(chalk.bold.cyan('\nOpenSaas Dev Mode\n'))
19
- console.log(chalk.gray('Watching for changes to opensaas.config.ts...\n'))
20
-
21
- // Run initial generation
22
- await generateCommand()
23
-
24
- // Watch for changes
25
- const watcher = chokidar.watch(configPath, {
26
- persistent: true,
27
- ignoreInitial: true,
28
- })
29
-
30
- watcher.on('change', async () => {
31
- console.log(chalk.yellow('\nConfig changed, regenerating...\n'))
32
- await generateCommand()
33
- })
34
-
35
- watcher.on('error', (error) => {
36
- console.error(chalk.red('\nWatcher error:'), error)
37
- })
38
-
39
- // Keep the process running
40
- console.log(chalk.gray('Press Ctrl+C to stop watching\n'))
41
-
42
- // Handle graceful shutdown
43
- process.on('SIGINT', () => {
44
- console.log(chalk.yellow('\n\nStopping dev mode...'))
45
- watcher.close()
46
- process.exit(0)
47
- })
48
- }
@@ -1,282 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
- import * as fs from 'fs'
3
- import * as path from 'path'
4
- import * as os from 'os'
5
- import type { OpenSaasConfig } from '@opensaas/stack-core'
6
- import { text } from '@opensaas/stack-core/fields'
7
- import { writePrismaSchema, writeTypes, writeContext } from '../generator/index.js'
8
-
9
- // Mock ora module
10
- vi.mock('ora', () => ({
11
- default: vi.fn(() => ({
12
- start: vi.fn().mockReturnThis(),
13
- succeed: vi.fn().mockReturnThis(),
14
- fail: vi.fn().mockReturnThis(),
15
- text: '',
16
- })),
17
- }))
18
-
19
- // Mock chalk module
20
- vi.mock('chalk', () => ({
21
- default: {
22
- bold: vi.fn((str) => str),
23
- cyan: vi.fn((str) => str),
24
- gray: vi.fn((str) => str),
25
- red: vi.fn((str) => str),
26
- yellow: vi.fn((str) => str),
27
- green: vi.fn((str) => str),
28
- },
29
- }))
30
-
31
- describe('Generate Command Integration', () => {
32
- let tempDir: string
33
-
34
- beforeEach(() => {
35
- // Create temp directory for testing
36
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'generate-test-'))
37
- })
38
-
39
- afterEach(() => {
40
- // Clean up
41
- if (fs.existsSync(tempDir)) {
42
- fs.rmSync(tempDir, { recursive: true, force: true })
43
- }
44
- })
45
-
46
- describe('Generator Integration', () => {
47
- it('should generate all files for a basic config', () => {
48
- const config: OpenSaasConfig = {
49
- db: {
50
- provider: 'sqlite',
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- prismaClientConstructor: (() => null) as any,
53
- },
54
- lists: {
55
- User: {
56
- fields: {
57
- name: text({ validation: { isRequired: true } }),
58
- email: text({ validation: { isRequired: true } }),
59
- },
60
- },
61
- },
62
- }
63
-
64
- // Generate files
65
- const prismaPath = path.join(tempDir, 'prisma', 'schema.prisma')
66
- const typesPath = path.join(tempDir, '.opensaas', 'types.ts')
67
- const contextPath = path.join(tempDir, '.opensaas', 'context.ts')
68
-
69
- writePrismaSchema(config, prismaPath)
70
- writeTypes(config, typesPath)
71
- writeContext(config, contextPath)
72
-
73
- // Verify all files exist
74
- expect(fs.existsSync(prismaPath)).toBe(true)
75
- expect(fs.existsSync(typesPath)).toBe(true)
76
- expect(fs.existsSync(contextPath)).toBe(true)
77
-
78
- // Verify file contents with snapshots
79
- const prismaSchema = fs.readFileSync(prismaPath, 'utf-8')
80
- expect(prismaSchema).toMatchSnapshot('prisma-schema')
81
-
82
- const types = fs.readFileSync(typesPath, 'utf-8')
83
- expect(types).toMatchSnapshot('types')
84
-
85
- const context = fs.readFileSync(contextPath, 'utf-8')
86
- expect(context).toMatchSnapshot('context')
87
- })
88
-
89
- it('should create directories if they do not exist', () => {
90
- const config: OpenSaasConfig = {
91
- db: {
92
- provider: 'sqlite',
93
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
- prismaClientConstructor: (() => null) as any,
95
- },
96
- lists: {},
97
- }
98
-
99
- const prismaPath = path.join(tempDir, 'prisma', 'schema.prisma')
100
-
101
- writePrismaSchema(config, prismaPath)
102
-
103
- expect(fs.existsSync(path.join(tempDir, 'prisma'))).toBe(true)
104
- expect(fs.existsSync(prismaPath)).toBe(true)
105
- })
106
-
107
- it('should overwrite existing files', () => {
108
- const config1: OpenSaasConfig = {
109
- db: {
110
- provider: 'sqlite',
111
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
- prismaClientConstructor: (() => null) as any,
113
- },
114
- lists: {
115
- User: {
116
- fields: {
117
- name: text(),
118
- },
119
- },
120
- },
121
- }
122
-
123
- const config2: OpenSaasConfig = {
124
- db: {
125
- provider: 'sqlite',
126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
- prismaClientConstructor: (() => null) as any,
128
- },
129
- lists: {
130
- Post: {
131
- fields: {
132
- title: text(),
133
- },
134
- },
135
- },
136
- }
137
-
138
- const prismaPath = path.join(tempDir, 'prisma', 'schema.prisma')
139
-
140
- // Generate first config
141
- writePrismaSchema(config1, prismaPath)
142
- let schema = fs.readFileSync(prismaPath, 'utf-8')
143
- expect(schema).toMatchSnapshot('overwrite-before')
144
-
145
- // Generate second config (should overwrite)
146
- writePrismaSchema(config2, prismaPath)
147
- schema = fs.readFileSync(prismaPath, 'utf-8')
148
- expect(schema).toMatchSnapshot('overwrite-after')
149
- })
150
-
151
- it('should handle custom opensaasPath', () => {
152
- const config: OpenSaasConfig = {
153
- db: {
154
- provider: 'sqlite',
155
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
- prismaClientConstructor: (() => null) as any,
157
- },
158
- opensaasPath: '.custom',
159
- lists: {},
160
- }
161
-
162
- const typesPath = path.join(tempDir, '.custom', 'types.ts')
163
- const contextPath = path.join(tempDir, '.custom', 'context.ts')
164
-
165
- writeTypes(config, typesPath)
166
- writeContext(config, contextPath)
167
-
168
- expect(fs.existsSync(path.join(tempDir, '.custom'))).toBe(true)
169
- expect(fs.existsSync(typesPath)).toBe(true)
170
- expect(fs.existsSync(contextPath)).toBe(true)
171
- })
172
-
173
- it('should generate consistent output across multiple runs', () => {
174
- const config: OpenSaasConfig = {
175
- db: {
176
- provider: 'sqlite',
177
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
- prismaClientConstructor: (() => null) as any,
179
- },
180
- lists: {
181
- User: {
182
- fields: {
183
- name: text(),
184
- },
185
- },
186
- },
187
- }
188
-
189
- const prismaPath = path.join(tempDir, 'prisma', 'schema.prisma')
190
-
191
- // Generate twice
192
- writePrismaSchema(config, prismaPath)
193
- const schema1 = fs.readFileSync(prismaPath, 'utf-8')
194
-
195
- writePrismaSchema(config, prismaPath)
196
- const schema2 = fs.readFileSync(prismaPath, 'utf-8')
197
-
198
- // Should be identical
199
- expect(schema1).toBe(schema2)
200
- expect(schema1).toMatchSnapshot('consistent-output')
201
- })
202
-
203
- it('should handle empty lists config', () => {
204
- const config: OpenSaasConfig = {
205
- db: {
206
- provider: 'sqlite',
207
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
- prismaClientConstructor: (() => null) as any,
209
- },
210
- lists: {},
211
- }
212
-
213
- const prismaPath = path.join(tempDir, 'prisma', 'schema.prisma')
214
- const typesPath = path.join(tempDir, '.opensaas', 'types.ts')
215
-
216
- writePrismaSchema(config, prismaPath)
217
- writeTypes(config, typesPath)
218
-
219
- expect(fs.existsSync(prismaPath)).toBe(true)
220
- expect(fs.existsSync(typesPath)).toBe(true)
221
-
222
- const schema = fs.readFileSync(prismaPath, 'utf-8')
223
- expect(schema).toMatchSnapshot('empty-lists-schema')
224
-
225
- const types = fs.readFileSync(typesPath, 'utf-8')
226
- expect(types).toMatchSnapshot('empty-lists-types')
227
- })
228
-
229
- it('should handle different database providers', () => {
230
- const providers = ['sqlite', 'postgresql', 'mysql'] as const
231
-
232
- providers.forEach((provider) => {
233
- const config: OpenSaasConfig = {
234
- db: {
235
- provider,
236
- url: provider === 'sqlite' ? 'file:./dev.db' : 'postgresql://localhost:5432/db',
237
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
238
- prismaClientConstructor: (() => null) as any,
239
- },
240
- lists: {},
241
- }
242
-
243
- const prismaPath = path.join(tempDir, `${provider}-schema.prisma`)
244
- writePrismaSchema(config, prismaPath)
245
-
246
- const schema = fs.readFileSync(prismaPath, 'utf-8')
247
- expect(schema).toMatchSnapshot(`${provider}-provider`)
248
- })
249
- })
250
-
251
- it('should generate files in correct locations', () => {
252
- const config: OpenSaasConfig = {
253
- db: {
254
- provider: 'sqlite',
255
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
- prismaClientConstructor: (() => null) as any,
257
- },
258
- lists: {
259
- User: {
260
- fields: {
261
- name: text(),
262
- },
263
- },
264
- },
265
- }
266
-
267
- writePrismaSchema(config, path.join(tempDir, 'prisma', 'schema.prisma'))
268
- writeTypes(config, path.join(tempDir, '.opensaas', 'types.ts'))
269
- writeContext(config, path.join(tempDir, '.opensaas', 'context.ts'))
270
-
271
- // Verify directory structure
272
- const prismaDir = path.join(tempDir, 'prisma')
273
- const opensaasDir = path.join(tempDir, '.opensaas')
274
-
275
- expect(fs.existsSync(prismaDir)).toBe(true)
276
- expect(fs.existsSync(opensaasDir)).toBe(true)
277
- expect(fs.readdirSync(prismaDir)).toContain('schema.prisma')
278
- expect(fs.readdirSync(opensaasDir)).toContain('types.ts')
279
- expect(fs.readdirSync(opensaasDir)).toContain('context.ts')
280
- })
281
- })
282
- })