@ranger1/dx 0.1.10 → 0.1.12

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.
@@ -0,0 +1,178 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import { homedir } from 'node:os'
4
+ import { spawn, spawnSync } from 'node:child_process'
5
+ import { logger } from '../../logger.js'
6
+ import { confirmManager } from '../../confirm.js'
7
+ import { getPassthroughArgs } from '../args.js'
8
+
9
+ function ensureOpencodeAvailable() {
10
+ const result = spawnSync('opencode', ['--version'], { stdio: 'ignore' })
11
+ if (result?.error?.code === 'ENOENT') {
12
+ return { ok: false, reason: 'missing' }
13
+ }
14
+ return { ok: true }
15
+ }
16
+
17
+ function resolveAiConfig(cli, name) {
18
+ const raw = cli.commands?.ai?.[name]
19
+ if (!raw) return null
20
+
21
+ const environment = cli.determineEnvironment()
22
+ const envKey = cli.normalizeEnvKey(environment)
23
+ let config = raw
24
+
25
+ // 允许按环境分支(保持与 build/start/export 一致)
26
+ if (typeof config === 'object' && config && !config.promptFile && !config.command) {
27
+ if (config[envKey]) config = config[envKey]
28
+ else if (envKey === 'staging' && config.prod) config = config.prod
29
+ else config = config.dev || config
30
+ }
31
+
32
+ return config
33
+ }
34
+
35
+ function expandHomePath(input) {
36
+ const raw = String(input ?? '')
37
+ if (raw === '~') return homedir()
38
+ if (raw.startsWith('~/') || raw.startsWith('~\\')) {
39
+ return resolve(homedir(), raw.slice(2))
40
+ }
41
+ return raw
42
+ }
43
+
44
+ export async function handleAi(cli, args = []) {
45
+ const name = args[0]
46
+ if (!name) {
47
+ const names = Object.keys(cli.commands?.ai || {})
48
+ logger.error('请指定 ai 任务名称')
49
+ logger.info(`用法: ${cli.invocation} ai <name> [-- <opencode flags>...]`)
50
+ if (names.length > 0) {
51
+ logger.info(`可用任务: ${names.join(', ')}`)
52
+ }
53
+ process.exitCode = 1
54
+ return
55
+ }
56
+
57
+ const config = resolveAiConfig(cli, name)
58
+ if (!config) {
59
+ const names = Object.keys(cli.commands?.ai || {})
60
+ logger.error(`未找到 ai 任务配置: ${name}`)
61
+ if (names.length > 0) {
62
+ logger.info(`可用任务: ${names.join(', ')}`)
63
+ }
64
+ process.exitCode = 1
65
+ return
66
+ }
67
+
68
+ const availability = ensureOpencodeAvailable()
69
+ if (!availability.ok) {
70
+ logger.error('未找到 opencode 可执行文件(PATH 中不存在 opencode)')
71
+ logger.info('请先安装并确保 `opencode --version` 可用')
72
+ process.exitCode = 1
73
+ return
74
+ }
75
+
76
+ const promptFile = config?.promptFile
77
+ if (!promptFile) {
78
+ logger.error(`ai.${name} 缺少 promptFile 配置(指向一个 .md 文件)`)
79
+ process.exitCode = 1
80
+ return
81
+ }
82
+
83
+ const promptPath = resolve(process.cwd(), expandHomePath(promptFile))
84
+ if (!existsSync(promptPath)) {
85
+ logger.error(`未找到提示词文件: ${promptFile}`)
86
+ logger.info(`解析后的路径: ${promptPath}`)
87
+ process.exitCode = 1
88
+ return
89
+ }
90
+
91
+ let promptText = ''
92
+ try {
93
+ promptText = readFileSync(promptPath, 'utf8')
94
+ } catch (error) {
95
+ logger.error(`读取提示词文件失败: ${promptFile}`)
96
+ logger.error(error?.message || String(error))
97
+ process.exitCode = 1
98
+ return
99
+ }
100
+
101
+ // 固定全权限:这会让 opencode 在当前目录拥有 bash/edit 等工具的自动执行权
102
+ if (!cli.flags.Y) {
103
+ const confirmed = await confirmManager.confirmDangerous(
104
+ `ai.${name}(将以 OPENCODE_PERMISSION="allow" 运行,全权限)`,
105
+ '当前目录',
106
+ false,
107
+ )
108
+ if (!confirmed) {
109
+ logger.info('操作已取消')
110
+ return
111
+ }
112
+ }
113
+
114
+ const model = config?.model ? String(config.model) : null
115
+ const agent = config?.agent ? String(config.agent) : null
116
+ const format = config?.format ? String(config.format) : null
117
+ const attach = config?.attach ? String(config.attach) : null
118
+
119
+ const passthrough = getPassthroughArgs(cli.args)
120
+ const configPassthrough = Array.isArray(config?.passthrough)
121
+ ? config.passthrough.map(v => String(v))
122
+ : []
123
+
124
+ const opencodeArgs = ['run']
125
+ if (model) opencodeArgs.push('--model', model)
126
+ if (agent) opencodeArgs.push('--agent', agent)
127
+ if (format) opencodeArgs.push('--format', format)
128
+ if (attach) opencodeArgs.push('--attach', attach)
129
+ if (configPassthrough.length > 0) opencodeArgs.push(...configPassthrough)
130
+ if (Array.isArray(passthrough) && passthrough.length > 0) opencodeArgs.push(...passthrough)
131
+ opencodeArgs.push(promptText)
132
+
133
+ logger.step(`ai ${name}`)
134
+ logger.command(`opencode ${opencodeArgs.filter(a => a !== promptText).join(' ')} <prompt-from-file>`)
135
+
136
+ await new Promise(resolvePromise => {
137
+ const child = spawn('opencode', opencodeArgs, {
138
+ cwd: process.cwd(),
139
+ stdio: 'inherit',
140
+ env: {
141
+ ...process.env,
142
+ // OpenCode expects OPENCODE_PERMISSION to be JSON (it JSON.parse's the value).
143
+ OPENCODE_PERMISSION: '"allow"',
144
+ },
145
+ })
146
+
147
+ const forwardSignal = signal => {
148
+ try {
149
+ child.kill(signal)
150
+ } catch {}
151
+ }
152
+
153
+ const onSigint = () => forwardSignal('SIGINT')
154
+ const onSigterm = () => forwardSignal('SIGTERM')
155
+ process.on('SIGINT', onSigint)
156
+ process.on('SIGTERM', onSigterm)
157
+
158
+ const cleanup = () => {
159
+ process.off('SIGINT', onSigint)
160
+ process.off('SIGTERM', onSigterm)
161
+ }
162
+
163
+ child.on('error', error => {
164
+ cleanup()
165
+ logger.error(error?.message || String(error))
166
+ process.exitCode = 1
167
+ resolvePromise()
168
+ })
169
+
170
+ child.on('exit', code => {
171
+ cleanup()
172
+ if (typeof code === 'number' && code !== 0) {
173
+ process.exitCode = code
174
+ }
175
+ resolvePromise()
176
+ })
177
+ })
178
+ }
package/lib/cli/dx-cli.js CHANGED
@@ -33,6 +33,7 @@ import { handlePackage } from './commands/package.js'
33
33
  import { handleExport } from './commands/export.js'
34
34
  import { handleContracts } from './commands/contracts.js'
35
35
  import { handleRelease } from './commands/release.js'
36
+ import { handleAi } from './commands/ai.js'
36
37
 
37
38
  class DxCli {
38
39
  constructor(options = {}) {
@@ -67,6 +68,7 @@ class DxCli {
67
68
  export: args => handleExport(this, args),
68
69
  contracts: args => handleContracts(this, args),
69
70
  release: args => handleRelease(this, args),
71
+ ai: args => handleAi(this, args),
70
72
  }
71
73
 
72
74
  this.flagDefinitions = FLAG_DEFINITIONS
@@ -297,7 +299,8 @@ class DxCli {
297
299
  // - help: printing help should never require env vars.
298
300
  // - status: should be available even when env is incomplete.
299
301
  // - release: only edits package.json versions.
300
- const skipStartupChecks = new Set(['help', 'status', 'release'])
302
+ // - ai: wraps external opencode CLI, should not require project deps/prisma/env.
303
+ const skipStartupChecks = new Set(['help', 'status', 'release', 'ai'])
301
304
  if (skipStartupChecks.has(this.command)) {
302
305
  await this.routeCommand()
303
306
  return
package/lib/cli/help.js CHANGED
@@ -2,115 +2,124 @@ import { getPackageVersion } from '../version.js'
2
2
 
3
3
  export function showHelp() {
4
4
  const version = getPackageVersion()
5
- console.log(`
6
- DX CLI v${version} - 统一开发环境管理工具
7
-
8
- 用法:
9
- dx <命令> [选项] [参数...]
10
-
11
- 命令:
12
- start [service] [环境标志] 启动/桥接服务
13
- service: backend, front, admin, all, dev, stack, stagewise-front, stagewise-admin (默认: dev)
14
- stack: PM2 交互式服务栈管理(推荐)- 同时启动三个服务并提供交互式命令
15
- 环境标志: --dev, --staging, --prod, --test, --e2e(支持别名 --development、--production 等)
16
- 说明: 传入 --staging 时会加载 '.env.staging(.local)' 层,同时复用生产构建/启动流程
17
-
18
- build [target] [环境标志] 构建应用
19
- target: backend, shared, front, admin, mobile, all, sdk, affected (默认: all)
20
- 环境标志: --dev, --staging, --prod, --test, --e2e(未指定时默认 --dev)
21
-
22
- deploy <target> [环境标志] 部署前端到 Vercel
23
- target: front, admin, telegram-bot, all
24
- 环境标志: --dev, --staging, --prod(默认 --staging)
25
-
26
- install 安装依赖(使用 frozen-lockfile 确保版本一致)
27
-
28
- package backend [环境标志] 构建后端部署包(生成 backend-<version>-<sha>.tar.gz)
29
- 环境标志: --dev, --staging, --prod, --test, --e2e(默认 --dev)
30
- 产物位置: dist/backend/backend-*.tar.gz
31
- 内含: dist/、node_modules(生产依赖)、prisma/、config/.env.runtime、bin/start.sh
32
-
33
- db [action] [环境标志] 数据库操作
34
- action: generate, migrate, deploy, reset, seed, format, script
35
- 用法示例:
36
- dx db migrate --dev --name add_user_table # 创建新的迁移(开发环境需指定名称)
37
- dx db deploy --dev # 应用开发环境已有迁移
38
- dx db deploy --prod # 生产环境迁移(复用 deploy 流程,需确认)
39
- dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)
40
- dx db script fix-pending-transfer-status --prod # 运行数据库脚本(生产环境,需确认)
41
- dx db script my-script --dev -- --arg1 --arg2 # 向脚本传递额外参数(-- 后面的部分)
42
-
43
- test [type] [target] [path] [-t pattern] 运行测试
44
- type: e2e, unit (默认: e2e)
45
- target: backend, all (默认: all)
46
- path: 测试文件路径 (可选,仅支持e2e backend)
47
- -t pattern: 指定测试用例名称模式 (可选,需要和path一起使用)
48
-
49
- worktree [action] [num...] Git Worktree管理
50
- action: make, del, list, clean
51
- num: issue编号 (make时需要1个,del时支持多个)
52
- 支持批量删除: dx worktree del 123 456 789
53
- 支持非交互式: dx worktree del 123 -Y
54
- 注意:该封装与原生 git worktree 行为不同,勿混用
55
-
56
- lint 运行代码检查
57
-
58
- contracts [generate] 导出后端 OpenAPI 并生成 Zod 合约(packages/api-contracts)
59
-
60
- release version <semver> 统一同步 backend/front/admin-front 的版本号
61
-
62
- clean [target] 清理操作
63
- target: all, deps (默认: all)
64
-
65
- cache [action] 缓存清理
66
- action: clear (默认: clear)
67
-
68
- status 查看系统状态
69
-
70
- 选项:
71
- --dev, --development 使用开发环境
72
- --prod, --production 使用生产环境
73
- --staging, --stage 使用预发环境(加载 .env.staging*.,复用生产流程)
74
- --test 使用测试环境
75
- --e2e 使用E2E测试环境
76
- -Y, --yes 跳过所有确认提示
77
- -v, --verbose 详细输出
78
- -h, --help 显示此帮助信息
79
- -V, --version 显示版本号
80
-
81
- 示例:
82
- dx start stack # PM2 交互式服务栈(推荐)- 同时管理三个服务
83
- dx start backend --dev # 启动后端开发服务
84
- dx start front --dev # 启动用户前端开发服务
85
- dx start admin --dev # 启动管理后台开发服务
86
- dx start all # 同时启动所有开发服务(默认 --dev)
87
- dx build all --prod # 构建所有应用(生产环境)
88
- dx db deploy --dev # 应用开发环境数据库迁移
89
- dx db reset --prod -Y # 重置生产数据库(跳过确认)
90
- dx test e2e backend # 运行后端E2E测试
91
- dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts # 运行单个E2E测试文件
92
- dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts -t "should list all activity definitions" # 运行特定测试用例
93
- dx deploy front --staging # 部署前端到 Vercel(staging)
94
- dx worktree make 88 # 为issue #88创建worktree
95
- dx worktree del 88 # 删除issue #88的worktree
96
- dx worktree del 88 89 90 -Y # 批量删除多个worktree(非交互式)
97
- dx worktree list # 列出所有worktree
98
- dx clean deps # 清理并重新安装依赖
99
- dx cache clear # 清除 Nx 与依赖缓存
100
- dx contracts # 导出 OpenAPI 并生成 Zod 合约
101
- dx release version 1.2.3 # 同步 backend/front/admin-front 版本号
102
-
103
- # Stagewise 桥接(固定端口,自动清理占用)
104
- dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)
105
- dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front
106
-
107
- # Start 用法示例
108
- dx start backend --prod # 以生产环境变量启动后端
109
- dx start backend --dev # 以开发环境变量启动后端
110
- dx start backend --e2e # E2E 环境变量启动后端
111
-
112
-
113
- `)
5
+ const lines = [
6
+ '',
7
+ `DX CLI v${version} - 统一开发环境管理工具`,
8
+ '',
9
+ '用法:',
10
+ ' dx <命令> [选项] [参数...]',
11
+ '',
12
+ '命令:',
13
+ ' start [service] [环境标志] 启动/桥接服务',
14
+ ' service: backend, front, admin, all, dev, stack, stagewise-front, stagewise-admin (默认: dev)',
15
+ ' stack: PM2 交互式服务栈管理(推荐)- 同时启动三个服务并提供交互式命令',
16
+ ' 环境标志: --dev, --staging, --prod, --test, --e2e(支持别名 --development、--production 等)',
17
+ " 说明: 传入 --staging 时会加载 '.env.staging(.local)' 层,同时复用生产构建/启动流程",
18
+ '',
19
+ ' build [target] [环境标志] 构建应用',
20
+ ' target: backend, shared, front, admin, mobile, all, sdk, affected (默认: all)',
21
+ ' 环境标志: --dev, --staging, --prod, --test, --e2e(未指定时默认 --dev)',
22
+ '',
23
+ ' deploy <target> [环境标志] 部署前端到 Vercel',
24
+ ' target: front, admin, telegram-bot, all',
25
+ ' 环境标志: --dev, --staging, --prod(默认 --staging)',
26
+ '',
27
+ ' install 安装依赖(使用 frozen-lockfile 确保版本一致)',
28
+ '',
29
+ ' package backend [环境标志] 构建后端部署包(生成 backend-<version>-<sha>.tar.gz)',
30
+ ' 环境标志: --dev, --staging, --prod, --test, --e2e(默认 --dev)',
31
+ ' 产物位置: dist/backend/backend-*.tar.gz',
32
+ ' 内含: dist/、node_modules(生产依赖)、prisma/、config/.env.runtime、bin/start.sh',
33
+ '',
34
+ ' db [action] [环境标志] 数据库操作',
35
+ ' action: generate, migrate, deploy, reset, seed, format, script',
36
+ ' 用法示例:',
37
+ ' dx db migrate --dev --name add_user_table # 创建新的迁移(开发环境需指定名称)',
38
+ ' dx db deploy --dev # 应用开发环境已有迁移',
39
+ ' dx db deploy --prod # 生产环境迁移(复用 deploy 流程,需确认)',
40
+ ' dx db script fix-email-verified-status --dev # 运行数据库脚本(开发环境)',
41
+ ' dx db script fix-pending-transfer-status --prod # 运行数据库脚本(生产环境,需确认)',
42
+ ' dx db script my-script --dev -- --arg1 --arg2 # 向脚本传递额外参数(-- 后面的部分)',
43
+ '',
44
+ ' test [type] [target] [path] [-t pattern] 运行测试',
45
+ ' type: e2e, unit (默认: e2e)',
46
+ ' target: backend, all (默认: all)',
47
+ ' path: 测试文件路径 (可选,仅支持e2e backend)',
48
+ ' -t pattern: 指定测试用例名称模式 (可选,需要和path一起使用)',
49
+ '',
50
+ ' worktree [action] [num...] Git Worktree管理',
51
+ ' action: make, del, list, clean',
52
+ ' num: issue编号 (make时需要1个,del时支持多个)',
53
+ ' 支持批量删除: dx worktree del 123 456 789',
54
+ ' 支持非交互式: dx worktree del 123 -Y',
55
+ ' 注意:该封装与原生 git worktree 行为不同,勿混用',
56
+ '',
57
+ ' lint 运行代码检查',
58
+ '',
59
+ ' contracts [generate] 导出后端 OpenAPI 并生成 Zod 合约(packages/api-contracts)',
60
+ '',
61
+ ' release version <semver> 统一同步 backend/front/admin-front 的版本号',
62
+ '',
63
+ ' clean [target] 清理操作',
64
+ ' target: all, deps (默认: all)',
65
+ '',
66
+ ' cache [action] 缓存清理',
67
+ ' action: clear (默认: clear)',
68
+ '',
69
+ ' status 查看系统状态',
70
+ '',
71
+ ' ai <name> 运行一个预配置的 opencode 任务(从 commands.json 读取)',
72
+ ' 透传: -- 后的参数将原样传给 opencode run',
73
+ '',
74
+ '选项:',
75
+ ' --dev, --development 使用开发环境',
76
+ ' --prod, --production 使用生产环境',
77
+ ' --staging, --stage 使用预发环境(加载 .env.staging*.,复用生产流程)',
78
+ ' --test 使用测试环境',
79
+ ' --e2e 使用E2E测试环境',
80
+ ' -Y, --yes 跳过所有确认提示',
81
+ ' -v, --verbose 详细输出',
82
+ ' -h, --help 显示此帮助信息',
83
+ ' -V, --version 显示版本号',
84
+ '',
85
+ '示例:',
86
+ ' dx start stack # PM2 交互式服务栈(推荐)- 同时管理三个服务',
87
+ ' dx start backend --dev # 启动后端开发服务',
88
+ ' dx start front --dev # 启动用户前端开发服务',
89
+ ' dx start admin --dev # 启动管理后台开发服务',
90
+ ' dx start all # 同时启动所有开发服务(默认 --dev)',
91
+ ' dx build all --prod # 构建所有应用(生产环境)',
92
+ ' dx db deploy --dev # 应用开发环境数据库迁移',
93
+ ' dx db reset --prod -Y # 重置生产数据库(跳过确认)',
94
+ ' dx test e2e backend # 运行后端E2E测试',
95
+ ' dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts # 运行单个E2E测试文件',
96
+ ' dx test e2e backend e2e/activity/activity.admin.e2e-spec.ts -t "should list all activity definitions" # 运行特定测试用例',
97
+ ' dx deploy front --staging # 部署前端到 Vercel(staging)',
98
+ ' dx worktree make 88 # 为issue #88创建worktree',
99
+ ' dx worktree del 88 # 删除issue #88的worktree',
100
+ ' dx worktree del 88 89 90 -Y # 批量删除多个worktree(非交互式)',
101
+ ' dx worktree list # 列出所有worktree',
102
+ ' dx clean deps # 清理并重新安装依赖',
103
+ ' dx cache clear # 清除 Nx 与依赖缓存',
104
+ ' dx contracts # 导出 OpenAPI 并生成 Zod 合约',
105
+ ' dx release version 1.2.3 # 同步 backend/front/admin-front 版本号',
106
+ '',
107
+ ' dx ai review',
108
+ ' dx ai review -- --share --title "my run"',
109
+ '',
110
+ ' # Stagewise 桥接(固定端口,自动清理占用)',
111
+ ' dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)',
112
+ ' dx start stagewise-admin # 桥接 admin-front: 3500 -> 3501(工作目录 apps/admin-front)',
113
+ '',
114
+ ' # Start 用法示例',
115
+ ' dx start backend --prod # 以生产环境变量启动后端',
116
+ ' dx start backend --dev # 以开发环境变量启动后端',
117
+ ' dx start backend --e2e # 以 E2E 环境变量启动后端',
118
+ '',
119
+ '',
120
+ ]
121
+
122
+ console.log(lines.join('\n'))
114
123
  }
115
124
 
116
125
  export function showCommandHelp(command) {
@@ -248,6 +257,38 @@ release 命令用法:
248
257
  `)
249
258
  return
250
259
 
260
+ case 'ai':
261
+ console.log(
262
+ [
263
+ '',
264
+ 'ai 命令用法:',
265
+ ' dx ai <name> [-- <opencode flags>...]',
266
+ '',
267
+ '说明:',
268
+ ' - 从 dx/config/commands.json 的 ai.<name> 读取一个固定配置并执行 opencode run',
269
+ ' - 本次运行固定注入 OPENCODE_PERMISSION="allow"(全权限)',
270
+ '',
271
+ 'commands.json 示例:',
272
+ ' {',
273
+ ' "ai": {',
274
+ ' "review": {',
275
+ ' "promptFile": "./prompts/review.md",',
276
+ ' "model": "openai/gpt-4.1",',
277
+ ' "agent": "general",',
278
+ ' "format": "default",',
279
+ ' "passthrough": ["--share"]',
280
+ ' }',
281
+ ' }',
282
+ ' }',
283
+ '',
284
+ '示例:',
285
+ ' dx ai review',
286
+ ' dx ai review -- --title "my run"',
287
+ '',
288
+ ].join('\n'),
289
+ )
290
+ return
291
+
251
292
  default:
252
293
  showHelp()
253
294
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {