@opensaas/stack-cli 0.1.1 → 0.1.2

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.
@@ -1,308 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
- import { initCommand } from './init.js'
3
- import * as fs from 'fs'
4
- import * as path from 'path'
5
- import * as os from 'os'
6
-
7
- // Mock prompts module
8
- vi.mock('prompts', () => ({
9
- default: vi.fn(),
10
- }))
11
-
12
- // Mock ora module
13
- vi.mock('ora', () => ({
14
- default: vi.fn(() => ({
15
- start: vi.fn().mockReturnThis(),
16
- succeed: vi.fn().mockReturnThis(),
17
- fail: vi.fn().mockReturnThis(),
18
- text: '',
19
- })),
20
- }))
21
-
22
- // Mock chalk module
23
- vi.mock('chalk', () => ({
24
- default: {
25
- bold: {
26
- cyan: vi.fn((str) => str),
27
- green: vi.fn((str) => str),
28
- },
29
- cyan: vi.fn((str) => str),
30
- gray: vi.fn((str) => str),
31
- red: vi.fn((str) => str),
32
- yellow: vi.fn((str) => str),
33
- green: vi.fn((str) => str),
34
- },
35
- }))
36
-
37
- describe('Init Command', () => {
38
- let tempDir: string
39
- let originalCwd: string
40
- let originalExit: typeof process.exit
41
- let exitCode: number | undefined
42
-
43
- beforeEach(() => {
44
- // Create temp directory for testing
45
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'init-test-'))
46
- originalCwd = process.cwd()
47
- process.chdir(tempDir)
48
-
49
- // Mock process.exit
50
- originalExit = process.exit
51
- exitCode = undefined
52
- process.exit = vi.fn((code?: number) => {
53
- exitCode = code
54
- throw new Error(`process.exit(${code})`)
55
- }) as never
56
- })
57
-
58
- afterEach(() => {
59
- // Restore
60
- process.chdir(originalCwd)
61
- process.exit = originalExit
62
-
63
- // Clean up
64
- if (fs.existsSync(tempDir)) {
65
- fs.rmSync(tempDir, { recursive: true, force: true })
66
- }
67
- })
68
-
69
- describe('initCommand', () => {
70
- it('should create project structure with provided name', async () => {
71
- const projectName = 'test-project'
72
-
73
- try {
74
- await initCommand(projectName)
75
- } catch {
76
- // Expected to throw due to mocked process.exit
77
- }
78
-
79
- const projectPath = path.join(tempDir, projectName)
80
- expect(fs.existsSync(projectPath)).toBe(true)
81
- expect(fs.existsSync(path.join(projectPath, 'app'))).toBe(true)
82
- expect(fs.existsSync(path.join(projectPath, 'lib'))).toBe(true)
83
- expect(fs.existsSync(path.join(projectPath, 'prisma'))).toBe(true)
84
- })
85
-
86
- it('should create package.json with correct content', async () => {
87
- const projectName = 'test-project'
88
-
89
- try {
90
- await initCommand(projectName)
91
- } catch {
92
- // Expected to throw due to mocked process.exit
93
- }
94
-
95
- const projectPath = path.join(tempDir, projectName)
96
- const packageJson = JSON.parse(
97
- fs.readFileSync(path.join(projectPath, 'package.json'), 'utf-8'),
98
- )
99
-
100
- expect(packageJson.name).toBe(projectName)
101
- expect(packageJson.scripts.generate).toBe('opensaas generate')
102
- expect(packageJson.scripts['db:push']).toBe('prisma db push')
103
- expect(packageJson.dependencies['@opensaas/stack-core']).toBeDefined()
104
- })
105
-
106
- it('should create tsconfig.json', async () => {
107
- const projectName = 'test-project'
108
-
109
- try {
110
- await initCommand(projectName)
111
- } catch {
112
- // Expected to throw
113
- }
114
-
115
- const projectPath = path.join(tempDir, projectName)
116
- const tsConfig = JSON.parse(fs.readFileSync(path.join(projectPath, 'tsconfig.json'), 'utf-8'))
117
-
118
- expect(tsConfig.compilerOptions.target).toBe('ES2022')
119
- expect(tsConfig.compilerOptions.moduleResolution).toBe('bundler')
120
- })
121
-
122
- it('should create opensaas.config.ts with User list', async () => {
123
- const projectName = 'test-project'
124
-
125
- try {
126
- await initCommand(projectName)
127
- } catch {
128
- // Expected to throw
129
- }
130
-
131
- const projectPath = path.join(tempDir, projectName)
132
- const config = fs.readFileSync(path.join(projectPath, 'opensaas.config.ts'), 'utf-8')
133
-
134
- expect(config).toContain('config({')
135
- expect(config).toContain('lists: {')
136
- expect(config).toContain('User: list({')
137
- expect(config).toContain('name: text(')
138
- expect(config).toContain('email: text(')
139
- expect(config).toContain('password: password(')
140
- })
141
-
142
- it('should create .env file', async () => {
143
- const projectName = 'test-project'
144
-
145
- try {
146
- await initCommand(projectName)
147
- } catch {
148
- // Expected to throw
149
- }
150
-
151
- const projectPath = path.join(tempDir, projectName)
152
- const env = fs.readFileSync(path.join(projectPath, '.env'), 'utf-8')
153
-
154
- expect(env).toContain('DATABASE_URL')
155
- expect(env).toContain('file:./dev.db')
156
- })
157
-
158
- it('should create .gitignore file', async () => {
159
- const projectName = 'test-project'
160
-
161
- try {
162
- await initCommand(projectName)
163
- } catch {
164
- // Expected to throw
165
- }
166
-
167
- const projectPath = path.join(tempDir, projectName)
168
- const gitignore = fs.readFileSync(path.join(projectPath, '.gitignore'), 'utf-8')
169
-
170
- expect(gitignore).toContain('node_modules')
171
- expect(gitignore).toContain('.opensaas')
172
- expect(gitignore).toContain('prisma/dev.db')
173
- })
174
-
175
- it('should create lib/context.ts', async () => {
176
- const projectName = 'test-project'
177
-
178
- try {
179
- await initCommand(projectName)
180
- } catch {
181
- // Expected to throw
182
- }
183
-
184
- const projectPath = path.join(tempDir, projectName)
185
- const context = fs.readFileSync(path.join(projectPath, 'lib', 'context.ts'), 'utf-8')
186
-
187
- expect(context).toContain('import { PrismaClient }')
188
- expect(context).toContain('export async function getContext(')
189
- })
190
-
191
- it('should create app/page.tsx', async () => {
192
- const projectName = 'test-project'
193
-
194
- try {
195
- await initCommand(projectName)
196
- } catch {
197
- // Expected to throw
198
- }
199
-
200
- const projectPath = path.join(tempDir, projectName)
201
- const page = fs.readFileSync(path.join(projectPath, 'app', 'page.tsx'), 'utf-8')
202
-
203
- expect(page).toContain('export default function Home(')
204
- expect(page).toContain('Welcome to OpenSaas')
205
- })
206
-
207
- it('should create app/layout.tsx', async () => {
208
- const projectName = 'test-project'
209
-
210
- try {
211
- await initCommand(projectName)
212
- } catch {
213
- // Expected to throw
214
- }
215
-
216
- const projectPath = path.join(tempDir, projectName)
217
- const layout = fs.readFileSync(path.join(projectPath, 'app', 'layout.tsx'), 'utf-8')
218
-
219
- expect(layout).toContain('export default function RootLayout(')
220
- expect(layout).toContain('export const metadata')
221
- })
222
-
223
- it('should create README.md', async () => {
224
- const projectName = 'test-project'
225
-
226
- try {
227
- await initCommand(projectName)
228
- } catch {
229
- // Expected to throw
230
- }
231
-
232
- const projectPath = path.join(tempDir, projectName)
233
- const readme = fs.readFileSync(path.join(projectPath, 'README.md'), 'utf-8')
234
-
235
- expect(readme).toContain(`# ${projectName}`)
236
- expect(readme).toContain('Getting Started')
237
- expect(readme).toContain('npm run generate')
238
- })
239
-
240
- it('should create next.config.js', async () => {
241
- const projectName = 'test-project'
242
-
243
- try {
244
- await initCommand(projectName)
245
- } catch {
246
- // Expected to throw
247
- }
248
-
249
- const projectPath = path.join(tempDir, projectName)
250
- const nextConfig = fs.readFileSync(path.join(projectPath, 'next.config.js'), 'utf-8')
251
-
252
- expect(nextConfig).toContain('serverComponentsExternalPackages')
253
- expect(nextConfig).toContain('@opensaas/stack-core')
254
- })
255
-
256
- it('should fail if directory already exists', async () => {
257
- const projectName = 'test-project'
258
-
259
- // Create directory first
260
- fs.mkdirSync(path.join(tempDir, projectName))
261
-
262
- try {
263
- await initCommand(projectName)
264
- } catch {
265
- // Expected to throw
266
- }
267
-
268
- expect(exitCode).toBe(1)
269
- })
270
-
271
- it('should validate project name format', async () => {
272
- const prompts = await import('prompts')
273
-
274
- // Test with a valid name that should succeed
275
- const validName = 'test-project-valid'
276
-
277
- vi.mocked(prompts.default).mockResolvedValue({
278
- name: validName,
279
- })
280
-
281
- try {
282
- await initCommand(undefined)
283
- } catch {
284
- // Expected to throw due to mocked process.exit
285
- }
286
-
287
- // Project should be created with valid name
288
- expect(fs.existsSync(path.join(tempDir, validName))).toBe(true)
289
- })
290
-
291
- it('should verify cleanup behavior exists', () => {
292
- // This test verifies the cleanup logic is present in the code
293
- // Full end-to-end testing of cleanup is difficult in ESM environment
294
- // The actual cleanup logic in init.ts catches errors and removes the directory
295
-
296
- const projectName = 'test-project-cleanup'
297
- const projectPath = path.join(tempDir, projectName)
298
-
299
- // Create a test directory
300
- fs.mkdirSync(projectPath)
301
- expect(fs.existsSync(projectPath)).toBe(true)
302
-
303
- // Verify cleanup can work
304
- fs.rmSync(projectPath, { recursive: true, force: true })
305
- expect(fs.existsSync(projectPath)).toBe(false)
306
- })
307
- })
308
- })