@zhin.js/cli 1.0.13 → 1.0.15
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/CHANGELOG.md +16 -0
- package/TEST_GENERATION.md +398 -0
- package/lib/commands/new.d.ts.map +1 -1
- package/lib/commands/new.js +534 -4
- package/lib/commands/new.js.map +1 -1
- package/package.json +22 -14
- package/src/commands/new.ts +552 -6
- package/tests/new-integration.test.ts +232 -0
- package/tests/utils.test.ts +77 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
import * as fs from 'fs-extra'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
import { execSync } from 'child_process'
|
|
6
|
+
|
|
7
|
+
describe('CLI new command integration', () => {
|
|
8
|
+
let testDir: string
|
|
9
|
+
const cliPath = path.resolve(__dirname, '../lib/cli.js')
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
testDir = path.join(tmpdir(), `zhin-cli-integration-${Date.now()}`)
|
|
13
|
+
await fs.ensureDir(testDir)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
try {
|
|
18
|
+
await fs.remove(testDir)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// Ignore cleanup errors
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should create normal plugin with all files', async () => {
|
|
25
|
+
const pluginName = 'test-normal-plugin'
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
execSync(
|
|
29
|
+
`node "${cliPath}" new ${pluginName} --type normal --skip-install`,
|
|
30
|
+
{
|
|
31
|
+
cwd: testDir,
|
|
32
|
+
stdio: 'ignore',
|
|
33
|
+
timeout: 10000
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
} catch (error: any) {
|
|
37
|
+
// Command might exit with code 1 due to missing package.json, but files should be created
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const pluginDir = path.join(testDir, 'plugins', pluginName)
|
|
41
|
+
|
|
42
|
+
// Check directory structure
|
|
43
|
+
expect(await fs.pathExists(pluginDir)).toBe(true)
|
|
44
|
+
expect(await fs.pathExists(path.join(pluginDir, 'src'))).toBe(true)
|
|
45
|
+
expect(await fs.pathExists(path.join(pluginDir, 'tests'))).toBe(true)
|
|
46
|
+
expect(await fs.pathExists(path.join(pluginDir, 'client'))).toBe(true)
|
|
47
|
+
expect(await fs.pathExists(path.join(pluginDir, 'lib'))).toBe(true)
|
|
48
|
+
expect(await fs.pathExists(path.join(pluginDir, 'dist'))).toBe(true)
|
|
49
|
+
|
|
50
|
+
// Check package.json
|
|
51
|
+
const packageJsonPath = path.join(pluginDir, 'package.json')
|
|
52
|
+
expect(await fs.pathExists(packageJsonPath)).toBe(true)
|
|
53
|
+
const packageJson = await fs.readJson(packageJsonPath)
|
|
54
|
+
expect(packageJson.name).toBe(`zhin.js-${pluginName}`)
|
|
55
|
+
expect(packageJson.scripts.test).toBe('vitest run')
|
|
56
|
+
expect(packageJson.scripts['test:watch']).toBe('vitest')
|
|
57
|
+
expect(packageJson.scripts['test:coverage']).toBe('vitest run --coverage')
|
|
58
|
+
expect(packageJson.devDependencies.vitest).toBe('latest')
|
|
59
|
+
expect(packageJson.devDependencies['@vitest/coverage-v8']).toBe('latest')
|
|
60
|
+
|
|
61
|
+
// Check tsconfig.json
|
|
62
|
+
const tsconfigPath = path.join(pluginDir, 'tsconfig.json')
|
|
63
|
+
expect(await fs.pathExists(tsconfigPath)).toBe(true)
|
|
64
|
+
const tsconfig = await fs.readJson(tsconfigPath)
|
|
65
|
+
expect(tsconfig.compilerOptions.target).toBe('ES2022')
|
|
66
|
+
expect(tsconfig.compilerOptions.module).toBe('ESNext')
|
|
67
|
+
|
|
68
|
+
// Check src/index.ts
|
|
69
|
+
const srcIndexPath = path.join(pluginDir, 'src', 'index.ts')
|
|
70
|
+
expect(await fs.pathExists(srcIndexPath)).toBe(true)
|
|
71
|
+
const srcIndex = await fs.readFile(srcIndexPath, 'utf-8')
|
|
72
|
+
expect(srcIndex).toContain('useLogger')
|
|
73
|
+
expect(srcIndex).toContain('useContext')
|
|
74
|
+
|
|
75
|
+
// Check tests/index.test.ts
|
|
76
|
+
const testFilePath = path.join(pluginDir, 'tests', 'index.test.ts')
|
|
77
|
+
expect(await fs.pathExists(testFilePath)).toBe(true)
|
|
78
|
+
const testFile = await fs.readFile(testFilePath, 'utf-8')
|
|
79
|
+
expect(testFile).toContain('describe')
|
|
80
|
+
expect(testFile).toContain('Plugin Instance')
|
|
81
|
+
expect(testFile).toContain('Plugin Lifecycle')
|
|
82
|
+
expect(testFile).toContain('Plugin Features')
|
|
83
|
+
expect(testFile).toContain('Custom Tests')
|
|
84
|
+
expect(testFile).toContain('@zhin.js/core')
|
|
85
|
+
|
|
86
|
+
// Check README.md
|
|
87
|
+
const readmePath = path.join(pluginDir, 'README.md')
|
|
88
|
+
expect(await fs.pathExists(readmePath)).toBe(true)
|
|
89
|
+
const readme = await fs.readFile(readmePath, 'utf-8')
|
|
90
|
+
expect(readme).toContain(pluginName)
|
|
91
|
+
|
|
92
|
+
// Check .gitignore
|
|
93
|
+
const gitignorePath = path.join(pluginDir, '.gitignore')
|
|
94
|
+
expect(await fs.pathExists(gitignorePath)).toBe(true)
|
|
95
|
+
const gitignore = await fs.readFile(gitignorePath, 'utf-8')
|
|
96
|
+
expect(gitignore).toContain('node_modules/')
|
|
97
|
+
expect(gitignore).toContain('lib/')
|
|
98
|
+
expect(gitignore).toContain('dist/')
|
|
99
|
+
|
|
100
|
+
// Check CHANGELOG.md
|
|
101
|
+
const changelogPath = path.join(pluginDir, 'CHANGELOG.md')
|
|
102
|
+
expect(await fs.pathExists(changelogPath)).toBe(true)
|
|
103
|
+
|
|
104
|
+
// Check client files
|
|
105
|
+
const clientIndexPath = path.join(pluginDir, 'client', 'index.tsx')
|
|
106
|
+
expect(await fs.pathExists(clientIndexPath)).toBe(true)
|
|
107
|
+
const clientIndex = await fs.readFile(clientIndexPath, 'utf-8')
|
|
108
|
+
expect(clientIndex).toContain('addPage')
|
|
109
|
+
expect(clientIndex).toContain('@zhin.js/client')
|
|
110
|
+
|
|
111
|
+
const clientTsconfigPath = path.join(pluginDir, 'client', 'tsconfig.json')
|
|
112
|
+
expect(await fs.pathExists(clientTsconfigPath)).toBe(true)
|
|
113
|
+
}, 30000)
|
|
114
|
+
|
|
115
|
+
it('should create service plugin with service test template', async () => {
|
|
116
|
+
const serviceName = 'test-service'
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
execSync(
|
|
120
|
+
`node "${cliPath}" new ${serviceName} --type service --skip-install`,
|
|
121
|
+
{
|
|
122
|
+
cwd: testDir,
|
|
123
|
+
stdio: 'ignore',
|
|
124
|
+
timeout: 10000
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
} catch (error: any) {
|
|
128
|
+
// Ignore exit code
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const pluginDir = path.join(testDir, 'plugins', serviceName)
|
|
132
|
+
const testFilePath = path.join(pluginDir, 'tests', 'index.test.ts')
|
|
133
|
+
|
|
134
|
+
expect(await fs.pathExists(testFilePath)).toBe(true)
|
|
135
|
+
const testFile = await fs.readFile(testFilePath, 'utf-8')
|
|
136
|
+
|
|
137
|
+
// Service test should have TODO comments
|
|
138
|
+
expect(testFile).toContain('TODO')
|
|
139
|
+
expect(testFile).toContain('Service Instance')
|
|
140
|
+
expect(testFile).toContain('Service Methods')
|
|
141
|
+
expect(testFile).toContain('Service Lifecycle')
|
|
142
|
+
expect(testFile).toContain('Service Dependencies')
|
|
143
|
+
expect(testFile).toContain('Custom Tests')
|
|
144
|
+
}, 30000)
|
|
145
|
+
|
|
146
|
+
it('should create adapter plugin with adapter test template', async () => {
|
|
147
|
+
const adapterName = 'test-adapter'
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
execSync(
|
|
151
|
+
`node "${cliPath}" new ${adapterName} --type adapter --skip-install`,
|
|
152
|
+
{
|
|
153
|
+
cwd: testDir,
|
|
154
|
+
stdio: 'ignore',
|
|
155
|
+
timeout: 10000
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
} catch (error: any) {
|
|
159
|
+
// Ignore exit code
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const pluginDir = path.join(testDir, 'plugins', adapterName)
|
|
163
|
+
const testFilePath = path.join(pluginDir, 'tests', 'index.test.ts')
|
|
164
|
+
|
|
165
|
+
expect(await fs.pathExists(testFilePath)).toBe(true)
|
|
166
|
+
const testFile = await fs.readFile(testFilePath, 'utf-8')
|
|
167
|
+
|
|
168
|
+
// Adapter test should have Mock classes
|
|
169
|
+
expect(testFile).toContain('MockTestAdapterBot')
|
|
170
|
+
expect(testFile).toContain('MockTestAdapterAdapter')
|
|
171
|
+
expect(testFile).toContain('extends EventEmitter')
|
|
172
|
+
expect(testFile).toContain('extends Adapter')
|
|
173
|
+
expect(testFile).toContain('Adapter Instance')
|
|
174
|
+
expect(testFile).toContain('Bot Management')
|
|
175
|
+
expect(testFile).toContain('Adapter Lifecycle')
|
|
176
|
+
expect(testFile).toContain('Event Handling')
|
|
177
|
+
expect(testFile).toContain('Message Sending')
|
|
178
|
+
expect(testFile).toContain('Message Receiving')
|
|
179
|
+
expect(testFile).toContain('Bot Methods')
|
|
180
|
+
expect(testFile).toContain('Custom Tests')
|
|
181
|
+
}, 30000)
|
|
182
|
+
|
|
183
|
+
it('should create official plugin with @zhin.js scope', async () => {
|
|
184
|
+
const pluginName = 'official-plugin'
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
execSync(
|
|
188
|
+
`node "${cliPath}" new ${pluginName} --type normal --is-official --skip-install`,
|
|
189
|
+
{
|
|
190
|
+
cwd: testDir,
|
|
191
|
+
stdio: 'ignore',
|
|
192
|
+
timeout: 10000
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
} catch (error: any) {
|
|
196
|
+
// Ignore exit code
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const pluginDir = path.join(testDir, 'plugins', pluginName)
|
|
200
|
+
const packageJsonPath = path.join(pluginDir, 'package.json')
|
|
201
|
+
|
|
202
|
+
expect(await fs.pathExists(packageJsonPath)).toBe(true)
|
|
203
|
+
const packageJson = await fs.readJson(packageJsonPath)
|
|
204
|
+
expect(packageJson.name).toBe(`@zhin.js/${pluginName}`)
|
|
205
|
+
}, 30000)
|
|
206
|
+
|
|
207
|
+
it('should handle plugin name with hyphens', async () => {
|
|
208
|
+
const pluginName = 'my-awesome-plugin'
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
execSync(
|
|
212
|
+
`node "${cliPath}" new ${pluginName} --type normal --skip-install`,
|
|
213
|
+
{
|
|
214
|
+
cwd: testDir,
|
|
215
|
+
stdio: 'ignore',
|
|
216
|
+
timeout: 10000
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
} catch (error: any) {
|
|
220
|
+
// Ignore exit code
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const pluginDir = path.join(testDir, 'plugins', pluginName)
|
|
224
|
+
const srcIndexPath = path.join(pluginDir, 'src', 'index.ts')
|
|
225
|
+
|
|
226
|
+
expect(await fs.pathExists(srcIndexPath)).toBe(true)
|
|
227
|
+
const srcIndex = await fs.readFile(srcIndexPath, 'utf-8')
|
|
228
|
+
|
|
229
|
+
// Should convert to PascalCase: MyAwesomePlugin
|
|
230
|
+
expect(srcIndex).toContain('MyAwesomePlugin')
|
|
231
|
+
}, 30000)
|
|
232
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import * as fs from 'fs-extra'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
|
|
6
|
+
describe('CLI Utils', () => {
|
|
7
|
+
describe('env utilities', () => {
|
|
8
|
+
let testDir: string
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
testDir = path.join(tmpdir(), `zhin-test-${Date.now()}`)
|
|
12
|
+
await fs.ensureDir(testDir)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await fs.remove(testDir)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should export loadEnvFiles function', async () => {
|
|
20
|
+
const { loadEnvFiles } = await import('../src/utils/env')
|
|
21
|
+
expect(typeof loadEnvFiles).toBe('function')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should export getEnvLoadOrder function', async () => {
|
|
25
|
+
const { getEnvLoadOrder } = await import('../src/utils/env')
|
|
26
|
+
expect(typeof getEnvLoadOrder).toBe('function')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('getEnvLoadOrder should return correct order', async () => {
|
|
30
|
+
const { getEnvLoadOrder } = await import('../src/utils/env')
|
|
31
|
+
const order = getEnvLoadOrder('development')
|
|
32
|
+
expect(order).toBeInstanceOf(Array)
|
|
33
|
+
expect(order.length).toBe(2)
|
|
34
|
+
expect(order[0]).toContain('.env')
|
|
35
|
+
expect(order[1]).toContain('development')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('loadEnvFiles should not throw when no env files exist', async () => {
|
|
39
|
+
const { loadEnvFiles } = await import('../src/utils/env')
|
|
40
|
+
expect(() => loadEnvFiles(testDir, 'test')).not.toThrow()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('loadEnvFiles should load env file if exists', async () => {
|
|
44
|
+
const { loadEnvFiles } = await import('../src/utils/env')
|
|
45
|
+
const envPath = path.join(testDir, '.env')
|
|
46
|
+
await fs.writeFile(envPath, 'TEST_VAR=test_value')
|
|
47
|
+
|
|
48
|
+
loadEnvFiles(testDir, 'test')
|
|
49
|
+
expect(process.env.TEST_VAR).toBe('test_value')
|
|
50
|
+
|
|
51
|
+
// 清理
|
|
52
|
+
delete process.env.TEST_VAR
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('logger utilities', () => {
|
|
57
|
+
it('should export logger', async () => {
|
|
58
|
+
const { logger } = await import('../src/utils/logger')
|
|
59
|
+
expect(logger).toBeDefined()
|
|
60
|
+
expect(typeof logger).toBe('object')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('logger should have log methods', async () => {
|
|
64
|
+
const { logger } = await import('../src/utils/logger')
|
|
65
|
+
expect(typeof logger.info).toBe('function')
|
|
66
|
+
expect(typeof logger.warn).toBe('function')
|
|
67
|
+
expect(typeof logger.error).toBe('function')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('process utilities', () => {
|
|
72
|
+
it('should export process utilities', async () => {
|
|
73
|
+
const processUtils = await import('../src/utils/process')
|
|
74
|
+
expect(processUtils).toBeDefined()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
})
|