@ranger1/dx 0.1.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.
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/bin/dx-with-version-env.js +8 -0
- package/bin/dx.js +86 -0
- package/lib/backend-package.js +664 -0
- package/lib/cli/args.js +19 -0
- package/lib/cli/commands/core.js +233 -0
- package/lib/cli/commands/db.js +239 -0
- package/lib/cli/commands/deploy.js +76 -0
- package/lib/cli/commands/export.js +34 -0
- package/lib/cli/commands/package.js +22 -0
- package/lib/cli/commands/stack.js +451 -0
- package/lib/cli/commands/start.js +83 -0
- package/lib/cli/commands/worktree.js +149 -0
- package/lib/cli/dx-cli.js +864 -0
- package/lib/cli/flags.js +96 -0
- package/lib/cli/help.js +209 -0
- package/lib/cli/index.js +4 -0
- package/lib/confirm.js +213 -0
- package/lib/env.js +296 -0
- package/lib/exec.js +643 -0
- package/lib/logger.js +188 -0
- package/lib/run-with-version-env.js +173 -0
- package/lib/sdk-build.js +424 -0
- package/lib/start-dev.js +401 -0
- package/lib/telegram-webhook.js +134 -0
- package/lib/validate-env.js +284 -0
- package/lib/vercel-deploy.js +237 -0
- package/lib/worktree.js +1032 -0
- package/package.json +34 -0
package/lib/sdk-build.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SDK 构建模块
|
|
5
|
+
* 集成历史 bash 构建流程到 Node.js
|
|
6
|
+
* 支持开发版本和生产版本的构建发布
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync, rmSync } from 'node:fs'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
import { logger } from './logger.js'
|
|
12
|
+
import { execManager } from './exec.js'
|
|
13
|
+
import { confirmManager } from './confirm.js'
|
|
14
|
+
import { envManager } from './env.js'
|
|
15
|
+
|
|
16
|
+
class SDKBuilder {
|
|
17
|
+
constructor(argv = []) {
|
|
18
|
+
this.projectRoot = process.env.DX_PROJECT_ROOT || process.cwd()
|
|
19
|
+
this.sdkRoot = join(this.projectRoot, 'apps/sdk')
|
|
20
|
+
this.backendPid = null
|
|
21
|
+
this.backendStartedByBuilder = false
|
|
22
|
+
this.args = Array.isArray(argv) ? argv : []
|
|
23
|
+
|
|
24
|
+
// 版本参数:第一个非标志位参数,默认为 dev
|
|
25
|
+
this.versionArg = this.args.find(arg => !arg.startsWith('-')) || 'dev'
|
|
26
|
+
|
|
27
|
+
// 简化模式:SDK 构建统一走“在线生成 OpenAPI”流程,不再区分 online/offline 模式
|
|
28
|
+
this.offline = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async build(version = 'dev') {
|
|
32
|
+
try {
|
|
33
|
+
logger.step('SDK 构建开始')
|
|
34
|
+
|
|
35
|
+
// 判断是否为开发版本
|
|
36
|
+
const isDevVersion = version === 'dev' || version.includes('dev')
|
|
37
|
+
const isDefaultDevKeyword = version === 'dev'
|
|
38
|
+
|
|
39
|
+
// 确定最终版本号
|
|
40
|
+
let finalVersion
|
|
41
|
+
let shouldUpdateVersion = false
|
|
42
|
+
|
|
43
|
+
if (isDefaultDevKeyword) {
|
|
44
|
+
// 用户传入默认关键字 'dev':从 package.json 读取现有版本,不修改
|
|
45
|
+
const packageJson = JSON.parse(readFileSync(join(this.sdkRoot, 'package.json'), 'utf8'))
|
|
46
|
+
finalVersion = packageJson.version
|
|
47
|
+
shouldUpdateVersion = false
|
|
48
|
+
logger.info(`使用 package.json 现有版本: ${finalVersion}`)
|
|
49
|
+
} else {
|
|
50
|
+
// 用户显式指定版本号(包括自定义 dev 版本如 1.2.3-dev):使用该版本并更新
|
|
51
|
+
finalVersion = version
|
|
52
|
+
shouldUpdateVersion = true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 开发版本警告
|
|
56
|
+
if (isDevVersion) {
|
|
57
|
+
logger.warn('⚠️ 开发版本构建:此版本仅用于本地/CI 验证,禁止发布到 npm!')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!isDevVersion) {
|
|
61
|
+
const confirmed = await confirmManager.confirmRelease(finalVersion, true)
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
logger.info('SDK 发布已取消')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 步骤 1: OpenAPI 准备(统一走在线链路)
|
|
69
|
+
logger.info(
|
|
70
|
+
'SDK 构建将通过 backend export:openapi 自动生成 OpenAPI 规范,无需手动启动后端服务',
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// 步骤 4: 构建 SDK
|
|
74
|
+
await this.buildSDK(finalVersion, shouldUpdateVersion)
|
|
75
|
+
|
|
76
|
+
// 步骤 5: 运行测试(如果需要)
|
|
77
|
+
if (!isDevVersion) {
|
|
78
|
+
await this.runTests()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 步骤 6: 打包
|
|
82
|
+
await this.packageSDK()
|
|
83
|
+
|
|
84
|
+
logger.success(`🎉 SDK 构建完成! 版本: ${finalVersion}`)
|
|
85
|
+
|
|
86
|
+
if (!isDevVersion) {
|
|
87
|
+
this.showReleaseInstructions(finalVersion)
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.error('SDK 构建失败')
|
|
91
|
+
logger.error(error.message)
|
|
92
|
+
throw error
|
|
93
|
+
} finally {
|
|
94
|
+
await this.cleanup()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 检查后端健康
|
|
99
|
+
async isBackendHealthy() {
|
|
100
|
+
try {
|
|
101
|
+
await execManager.executeCommand('curl -sf http://localhost:3000/api/v1/health')
|
|
102
|
+
return true
|
|
103
|
+
} catch {
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 检查和清理端口
|
|
109
|
+
async checkAndCleanPort(port) {
|
|
110
|
+
logger.step(`检查并清理端口 ${port}`)
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const processes = await execManager.getPortProcesses(port)
|
|
114
|
+
if (processes.length > 0) {
|
|
115
|
+
logger.warn(`端口 ${port} 被占用,将自动清理: ${processes.join(', ')}`)
|
|
116
|
+
await execManager.killPortProcesses(port)
|
|
117
|
+
logger.success(`端口 ${port} 已清理`)
|
|
118
|
+
|
|
119
|
+
// 等待端口释放
|
|
120
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
121
|
+
} else {
|
|
122
|
+
logger.success(`端口 ${port} 未被占用`)
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error.message.includes('lsof')) {
|
|
126
|
+
logger.warn('无法检查端口占用情况,将尝试直接启动服务')
|
|
127
|
+
} else {
|
|
128
|
+
throw error
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 启动后端服务
|
|
134
|
+
async startBackendService() {
|
|
135
|
+
logger.step('启动后端服务')
|
|
136
|
+
|
|
137
|
+
// 以 development 层启动后端(用于 SDK OpenAPI 导出)
|
|
138
|
+
const environment = 'development'
|
|
139
|
+
envManager.syncEnvironments(environment)
|
|
140
|
+
|
|
141
|
+
const envFlags = envManager.buildEnvFlags('backend', environment)
|
|
142
|
+
const command = envFlags
|
|
143
|
+
? `pnpm exec dotenv --override ${envFlags} -- npx nx dev backend`
|
|
144
|
+
: 'npx nx dev backend'
|
|
145
|
+
|
|
146
|
+
logger.info(`启动命令: ${command}`)
|
|
147
|
+
logger.info('后端服务将在后台运行...')
|
|
148
|
+
|
|
149
|
+
// 异步启动后端服务(不阻塞)
|
|
150
|
+
execManager
|
|
151
|
+
.spawnCommand(command, {
|
|
152
|
+
cwd: this.projectRoot,
|
|
153
|
+
stdio: 'ignore', // 忽略子进程输出,避免管道阻塞导致卡住
|
|
154
|
+
detached: false,
|
|
155
|
+
env: {
|
|
156
|
+
...process.env,
|
|
157
|
+
APP_ENV: 'development',
|
|
158
|
+
NODE_ENV: 'development',
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
.catch(() => {
|
|
162
|
+
// 静默处理后端进程错误,因为我们会在后面检查端口是否就绪
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
logger.info('后端进程已启动(后台)')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 等待后端就绪
|
|
169
|
+
async waitForBackend() {
|
|
170
|
+
logger.step('等待后端服务启动')
|
|
171
|
+
|
|
172
|
+
const maxWait = 60000 // 60秒
|
|
173
|
+
const interval = 2000 // 2秒
|
|
174
|
+
const maxAttempts = Math.floor(maxWait / interval)
|
|
175
|
+
let attempt = 0
|
|
176
|
+
|
|
177
|
+
logger.progress('检查后端服务状态')
|
|
178
|
+
|
|
179
|
+
while (attempt < maxAttempts) {
|
|
180
|
+
try {
|
|
181
|
+
// 检查带有全局前缀的健康检查端点
|
|
182
|
+
await execManager.executeCommand('curl -sf http://localhost:3000/api/v1/health')
|
|
183
|
+
logger.progressDone()
|
|
184
|
+
logger.success('后端服务已启动成功!')
|
|
185
|
+
return
|
|
186
|
+
} catch (error) {
|
|
187
|
+
attempt++
|
|
188
|
+
if (attempt < maxAttempts) {
|
|
189
|
+
await new Promise(resolve => setTimeout(resolve, interval))
|
|
190
|
+
process.stdout.write('.')
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
logger.progressDone()
|
|
196
|
+
throw new Error(`后端服务启动超时 (>${maxWait / 1000}s)`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 构建 SDK
|
|
200
|
+
async buildSDK(version, shouldUpdateVersion = false) {
|
|
201
|
+
logger.step(`构建 SDK 版本 ${version}`)
|
|
202
|
+
|
|
203
|
+
// 切换到 SDK 目录
|
|
204
|
+
process.chdir(this.sdkRoot)
|
|
205
|
+
logger.info(`工作目录: ${this.sdkRoot}`)
|
|
206
|
+
|
|
207
|
+
// 清理旧的构建文件
|
|
208
|
+
await this.cleanOldFiles()
|
|
209
|
+
|
|
210
|
+
// 根据 shouldUpdateVersion 决定是否更新版本号
|
|
211
|
+
if (shouldUpdateVersion) {
|
|
212
|
+
await this.updateVersion(version)
|
|
213
|
+
} else {
|
|
214
|
+
logger.info('使用现有版本号,跳过 package.json 修改')
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 生成 OpenAPI SDK
|
|
218
|
+
await this.generateOpenAPISDK()
|
|
219
|
+
|
|
220
|
+
// 安装依赖
|
|
221
|
+
await this.installDependencies()
|
|
222
|
+
|
|
223
|
+
// 检查 webpack-cli
|
|
224
|
+
await this.checkWebpackCli()
|
|
225
|
+
|
|
226
|
+
// 构建项目
|
|
227
|
+
await this.buildProject()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 清理旧文件
|
|
231
|
+
async cleanOldFiles() {
|
|
232
|
+
logger.info('清理旧的构建文件和SDK包')
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
// 运行 pnpm clean
|
|
236
|
+
await execManager.executeCommand('pnpm clean', { cwd: this.sdkRoot })
|
|
237
|
+
|
|
238
|
+
// 清理包文件
|
|
239
|
+
await execManager.executeCommand('rm -f *.tgz', { cwd: this.sdkRoot })
|
|
240
|
+
|
|
241
|
+
// 清理生成文件
|
|
242
|
+
const cleanPaths = ['src/generated', 'openapi/generated', 'dist']
|
|
243
|
+
for (const path of cleanPaths) {
|
|
244
|
+
try {
|
|
245
|
+
rmSync(join(this.sdkRoot, path), { recursive: true, force: true })
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// 忽略不存在的路径
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logger.success('清理完成')
|
|
252
|
+
} catch (error) {
|
|
253
|
+
logger.warn(`清理时出现警告: ${error.message}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 更新版本号
|
|
258
|
+
async updateVersion(version) {
|
|
259
|
+
logger.info(`更新版本号到 ${version}`)
|
|
260
|
+
|
|
261
|
+
await execManager.executeCommand(`pnpm pkg set version=${version}`, {
|
|
262
|
+
cwd: this.sdkRoot,
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
logger.success('版本号更新成功')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 生成 OpenAPI SDK
|
|
269
|
+
async generateOpenAPISDK() {
|
|
270
|
+
logger.info('生成 OpenAPI SDK')
|
|
271
|
+
|
|
272
|
+
// 统一使用在线生成流程;如需自定义/离线行为,可直接调用 apps/sdk/scripts/regen_openapi.sh
|
|
273
|
+
const cmd = 'pnpm generate'
|
|
274
|
+
const extraEnv = { SDK_USE_BACKEND_EXPORT: '1' }
|
|
275
|
+
await execManager.executeCommand(cmd, {
|
|
276
|
+
cwd: this.sdkRoot,
|
|
277
|
+
env: extraEnv,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
logger.success(`SDK 生成成功(${this.offline ? '离线' : '在线'})`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 安装依赖
|
|
284
|
+
async installDependencies() {
|
|
285
|
+
logger.info('安装所有依赖')
|
|
286
|
+
|
|
287
|
+
await execManager.executeCommand('pnpm install', {
|
|
288
|
+
cwd: this.sdkRoot,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
logger.success('依赖安装成功')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 检查 webpack-cli
|
|
295
|
+
async checkWebpackCli() {
|
|
296
|
+
logger.info('检查 webpack-cli')
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
await execManager.executeCommand('pnpm ls webpack-cli', {
|
|
300
|
+
cwd: this.sdkRoot,
|
|
301
|
+
})
|
|
302
|
+
logger.success('webpack-cli 已安装')
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw new Error('未找到 webpack-cli,请先安装: pnpm add -D webpack-cli')
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 构建项目
|
|
309
|
+
async buildProject() {
|
|
310
|
+
logger.info('构建项目')
|
|
311
|
+
|
|
312
|
+
await execManager.executeCommand('pnpm build', {
|
|
313
|
+
cwd: this.sdkRoot,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
logger.success('构建成功')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 运行测试
|
|
320
|
+
async runTests() {
|
|
321
|
+
logger.step('运行 Demo 测试')
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
await execManager.executeCommand('pnpm demo', {
|
|
325
|
+
cwd: this.sdkRoot,
|
|
326
|
+
})
|
|
327
|
+
logger.success('Demo 测试通过')
|
|
328
|
+
} catch (error) {
|
|
329
|
+
logger.warn('Demo 测试失败,但将继续构建流程')
|
|
330
|
+
logger.warn('您可以在构建完成后手动运行测试')
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 打包 SDK
|
|
335
|
+
async packageSDK() {
|
|
336
|
+
logger.step('打包项目')
|
|
337
|
+
|
|
338
|
+
await execManager.executeCommand('pnpm pack', {
|
|
339
|
+
cwd: this.sdkRoot,
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// 获取包信息
|
|
343
|
+
const packageJson = JSON.parse(readFileSync(join(this.sdkRoot, 'package.json'), 'utf8'))
|
|
344
|
+
const packageFile = `${packageJson.name}-${packageJson.version}.tgz`
|
|
345
|
+
|
|
346
|
+
logger.success(`打包完成: ${packageFile}`)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 显示发布说明
|
|
350
|
+
showReleaseInstructions(version) {
|
|
351
|
+
logger.separator()
|
|
352
|
+
logger.success('🎉 SDK 构建和打包完成!')
|
|
353
|
+
logger.info(`📦 包文件: ${this.getExpectedPackageFile(version)}`)
|
|
354
|
+
|
|
355
|
+
if (!version.includes('dev')) {
|
|
356
|
+
logger.info('\n下一步操作:')
|
|
357
|
+
logger.info(`1. 检查包文件是否正确`)
|
|
358
|
+
logger.info(`2. 发布到 npm: npm publish ${this.getExpectedPackageFile(version)}`)
|
|
359
|
+
logger.info(`3. 创建 Git 标签: git tag -a 'v${version}' -m 'version ${version}'`)
|
|
360
|
+
logger.info(`4. 推送标签: git push origin 'v${version}'`)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 获取预期的包文件名
|
|
365
|
+
getExpectedPackageFile(version) {
|
|
366
|
+
try {
|
|
367
|
+
const packageJson = JSON.parse(readFileSync(join(this.sdkRoot, 'package.json'), 'utf8'))
|
|
368
|
+
return `${packageJson.name}-${version}.tgz`
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return `ai-sdk-${version}.tgz`
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 清理资源
|
|
375
|
+
async cleanup() {
|
|
376
|
+
logger.info('清理资源...')
|
|
377
|
+
|
|
378
|
+
// 只清理由构建器启动的后端服务
|
|
379
|
+
if (this.backendStartedByBuilder && !this.offline) {
|
|
380
|
+
try {
|
|
381
|
+
// 直接杀掉端口进程,不等待优雅退出
|
|
382
|
+
await execManager.killPortProcesses(3000)
|
|
383
|
+
logger.success('后端服务已清理')
|
|
384
|
+
} catch (error) {
|
|
385
|
+
logger.debug(`清理后端服务时出错: ${error.message}`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 快速清理所有运行中的进程,不等待
|
|
390
|
+
try {
|
|
391
|
+
const processCount = execManager.runningProcesses.size
|
|
392
|
+
if (processCount > 0) {
|
|
393
|
+
logger.debug(`快速清理 ${processCount} 个进程...`)
|
|
394
|
+
for (const [, { process }] of execManager.runningProcesses) {
|
|
395
|
+
try {
|
|
396
|
+
process.kill('SIGKILL') // 直接强制杀掉,不等待
|
|
397
|
+
} catch {}
|
|
398
|
+
}
|
|
399
|
+
execManager.runningProcesses.clear()
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
logger.debug(`快速清理进程时出错: ${error.message}`)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 切换回项目根目录
|
|
406
|
+
process.chdir(this.projectRoot)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 如果直接执行此脚本
|
|
411
|
+
export async function runSdkBuild(argv = []) {
|
|
412
|
+
const builder = new SDKBuilder(argv)
|
|
413
|
+
await builder.build(builder.versionArg)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
417
|
+
runSdkBuild(process.argv.slice(2)).catch(error => {
|
|
418
|
+
logger.error('SDK 构建失败')
|
|
419
|
+
console.error(error)
|
|
420
|
+
process.exit(1)
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export { SDKBuilder }
|