@ranger1/dx 0.1.44 → 0.1.47

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,7 +1,7 @@
1
1
  ---
2
2
  description: review (Codex)
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  write: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: build PR context file
3
3
  mode: subagent
4
- model: openai/gpt-5.1-codex-mini
4
+ model: github-copilot/claude-sonnet-4.5
5
5
  temperature: 0.1
6
6
  tools:
7
7
  bash: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: PR fix review
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  write: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: PR precheck (checkout + lint + build)
3
3
  mode: subagent
4
- model: openai/gpt-5.2-codex
4
+ model: openai/gpt-5.3-codex
5
5
  temperature: 0.1
6
6
  tools:
7
7
  bash: true
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  allowed-tools: [Bash, Read, Glob, TodoWrite, Edit, Grep]
3
3
  description: 'Git 工作流:Issue/Commit/PR 自动化'
4
- agent: middle
4
+ agent: documenter
5
5
  ---
6
6
 
7
7
  ## 用法
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  allowed-tools: [Bash, Read, Glob, TodoWrite, Edit, Grep, AskUserQuestion]
3
3
  description: 'Create release versions with intelligent changelog generation from release branch'
4
- agent: quick
4
+ agent: documenter
5
5
  ---
6
6
 
7
7
  ## Usage
@@ -15,10 +15,10 @@
15
15
  "variant": "high"
16
16
  },
17
17
  "librarian": {
18
- "model": "openai/gpt-5.1-codex-mini"
18
+ "model": "github-copilot/claude-sonnet-4.5"
19
19
  },
20
20
  "explore": {
21
- "model": "openai/gpt-5.1-codex-mini"
21
+ "model": "github-copilot/claude-sonnet-4.5"
22
22
  },
23
23
  "multimodal-looker": {
24
24
  "model": "github-copilot/gemini-3-flash-preview"
@@ -14,6 +14,9 @@
14
14
  },
15
15
  "middle": {
16
16
  "model": "github-copilot/claude-sonnet-4.5"
17
+ },
18
+ "documenter": {
19
+ "model": "github-copilot/claude-sonnet-4.5"
17
20
  }
18
21
  },
19
22
  "permission": {
package/lib/logger.js CHANGED
@@ -5,6 +5,44 @@ function resolveProjectRoot() {
5
5
  return process.env.DX_PROJECT_ROOT || process.cwd()
6
6
  }
7
7
 
8
+ export function sanitizeForLog(input) {
9
+ let text = input == null ? '' : String(input)
10
+
11
+ // CLI token args (vercel)
12
+ text = text.replace(/--token=("[^"]*"|'[^']*'|[^\s]+)/gi, '--token=***')
13
+ text = text.replace(/--token\s+("[^"]*"|'[^']*'|[^\s]+)/gi, '--token ***')
14
+
15
+ // Env style secrets
16
+ text = text.replace(/\bVERCEL_TOKEN=([^\s]+)/g, 'VERCEL_TOKEN=***')
17
+ text = text.replace(/\bTELEGRAM_BOT_TOKEN=([^\s]+)/g, 'TELEGRAM_BOT_TOKEN=***')
18
+ text = text.replace(
19
+ /\bTELEGRAM_BOT_WEBHOOK_SECRET=([^\s]+)/g,
20
+ 'TELEGRAM_BOT_WEBHOOK_SECRET=***',
21
+ )
22
+
23
+ // Authorization bearer
24
+ text = text.replace(
25
+ /Authorization:\s*Bearer\s+([^\s]+)/gi,
26
+ 'Authorization: Bearer ***',
27
+ )
28
+
29
+ // JSON-ish token fields
30
+ text = text.replace(/"token"\s*:\s*"[^"]*"/gi, '"token":"***"')
31
+ text = text.replace(
32
+ /("secret_token"\s*:\s*")([^"]*)(")/gi,
33
+ '$1***$3',
34
+ )
35
+ text = text.replace(/\bsecret_token=([^\s&]+)/gi, 'secret_token=***')
36
+
37
+ // Telegram bot token in URLs
38
+ text = text.replace(
39
+ /api\.telegram\.org\/bot([^/\s]+)(\/|$)/gi,
40
+ 'api.telegram.org/bot***$2',
41
+ )
42
+
43
+ return text
44
+ }
45
+
8
46
  // 处理输出管道被关闭导致的 EPIPE 错误,避免进程在清理阶段崩溃
9
47
  try {
10
48
  const handleBrokenPipe = err => {
@@ -48,34 +86,39 @@ export class Logger {
48
86
 
49
87
  // 基础日志方法
50
88
  info(message, prefix = '🚀') {
51
- const output = `${prefix} ${message}`
89
+ const safeMessage = sanitizeForLog(message)
90
+ const output = `${prefix} ${safeMessage}`
52
91
  console.log(output)
53
- this.writeLog('info', message)
92
+ this.writeLog('info', safeMessage)
54
93
  }
55
94
 
56
95
  success(message) {
57
- const output = `✅ ${message}`
96
+ const safeMessage = sanitizeForLog(message)
97
+ const output = `✅ ${safeMessage}`
58
98
  console.log(output)
59
- this.writeLog('success', message)
99
+ this.writeLog('success', safeMessage)
60
100
  }
61
101
 
62
102
  warn(message) {
63
- const output = `⚠️ ${message}`
103
+ const safeMessage = sanitizeForLog(message)
104
+ const output = `⚠️ ${safeMessage}`
64
105
  console.log(output)
65
- this.writeLog('warn', message)
106
+ this.writeLog('warn', safeMessage)
66
107
  }
67
108
 
68
109
  error(message) {
69
- const output = `❌ ${message}`
110
+ const safeMessage = sanitizeForLog(message)
111
+ const output = `❌ ${safeMessage}`
70
112
  console.log(output)
71
- this.writeLog('error', message)
113
+ this.writeLog('error', safeMessage)
72
114
  }
73
115
 
74
116
  debug(message) {
75
117
  if (this.logLevel === 'debug') {
76
- const output = `🐛 ${message}`
118
+ const safeMessage = sanitizeForLog(message)
119
+ const output = `🐛 ${safeMessage}`
77
120
  console.log(output)
78
- this.writeLog('debug', message)
121
+ this.writeLog('debug', safeMessage)
79
122
  }
80
123
  }
81
124
 
@@ -84,17 +127,20 @@ export class Logger {
84
127
  const prefix = stepNumber ? `步骤 ${stepNumber}:` : '执行:'
85
128
  const separator = '=================================='
86
129
 
130
+ const safeMessage = sanitizeForLog(message)
131
+
87
132
  console.log(`\n${separator}`)
88
- console.log(`🚀 ${prefix} ${message}`)
133
+ console.log(`🚀 ${prefix} ${safeMessage}`)
89
134
  console.log(separator)
90
135
 
91
- this.writeLog('step', `${prefix} ${message}`)
136
+ this.writeLog('step', `${prefix} ${safeMessage}`)
92
137
  }
93
138
 
94
139
  // 进度显示
95
140
  progress(message) {
96
- process.stdout.write(`⌛ ${message}...`)
97
- this.writeLog('progress', `开始: ${message}`)
141
+ const safeMessage = sanitizeForLog(message)
142
+ process.stdout.write(`⌛ ${safeMessage}...`)
143
+ this.writeLog('progress', `开始: ${safeMessage}`)
98
144
  }
99
145
 
100
146
  progressDone() {
@@ -104,8 +150,9 @@ export class Logger {
104
150
 
105
151
  // 命令执行日志
106
152
  command(command) {
107
- console.log(`💻 执行: ${command}`)
108
- this.writeLog('command', command)
153
+ const safeCommand = sanitizeForLog(command)
154
+ console.log(`💻 执行: ${safeCommand}`)
155
+ this.writeLog('command', safeCommand)
109
156
  }
110
157
 
111
158
  // 分隔符
@@ -118,15 +165,16 @@ export class Logger {
118
165
  if (data.length === 0) return
119
166
 
120
167
  if (headers.length > 0) {
121
- console.log(`\n${headers.join('\t')}`)
122
- console.log('-'.repeat(headers.join('\t').length))
168
+ const safeHeaders = headers.map(h => sanitizeForLog(h))
169
+ console.log(`\n${safeHeaders.join('\t')}`)
170
+ console.log('-'.repeat(safeHeaders.join('\t').length))
123
171
  }
124
172
 
125
173
  data.forEach(row => {
126
174
  if (Array.isArray(row)) {
127
- console.log(row.join('\t'))
175
+ console.log(row.map(cell => sanitizeForLog(cell)).join('\t'))
128
176
  } else {
129
- console.log(row)
177
+ console.log(sanitizeForLog(row))
130
178
  }
131
179
  })
132
180
  console.log()
@@ -136,7 +184,9 @@ export class Logger {
136
184
  ports(portInfo) {
137
185
  console.log('\n📡 服务端口信息:')
138
186
  portInfo.forEach(({ service, port, url }) => {
139
- console.log(` ${service}: http://localhost:${port} ${url ? `(${url})` : ''}`)
187
+ const safeService = sanitizeForLog(service)
188
+ const safeUrl = url ? sanitizeForLog(url) : ''
189
+ console.log(` ${safeService}: http://localhost:${port} ${safeUrl ? `(${safeUrl})` : ''}`)
140
190
  })
141
191
  console.log()
142
192
  }
@@ -146,8 +196,9 @@ export class Logger {
146
196
  if (!this.enableFile) return
147
197
 
148
198
  try {
199
+ const safeMessage = sanitizeForLog(message)
149
200
  const timestamp = this.formatTimestamp()
150
- const logLine = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`
201
+ const logLine = `[${timestamp}] [${level.toUpperCase()}] ${safeMessage}\n`
151
202
 
152
203
  const logFile = join(this.logDir, `ai-cli-${new Date().toISOString().split('T')[0]}.log`)
153
204
  writeFileSync(logFile, logLine, { flag: 'a', encoding: 'utf8' })
@@ -22,7 +22,9 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
22
22
 
23
23
  if (missingVars.length > 0) {
24
24
  logger.error('缺少以下 Telegram Bot 环境变量:')
25
- missingVars.forEach(v => logger.error(` - ${v}`))
25
+ missingVars.forEach(v => {
26
+ logger.error(` - ${v}`)
27
+ })
26
28
  logger.warn('跳过 Webhook 配置,请手动设置')
27
29
  return
28
30
  }
@@ -67,8 +69,15 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
67
69
  }
68
70
  else {
69
71
  logger.error(`Telegram Webhook 设置失败: ${result.description}`)
70
- logger.info('请手动执行以下命令:')
71
- logger.info(curlCmd)
72
+ logger.info('请手动执行以下命令(不要把明文 token/secret 写进日志):')
73
+ const manualPayload = JSON.stringify({
74
+ url: webhookUrl,
75
+ secret_token: '<YOUR_WEBHOOK_SECRET>',
76
+ drop_pending_updates: false,
77
+ })
78
+ logger.info(
79
+ `curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" -H "Content-Type: application/json" -d '${manualPayload}' --silent`,
80
+ )
72
81
  }
73
82
  }
74
83
  catch (error) {
@@ -82,15 +91,18 @@ export async function handleTelegramBotDeploy(environment, projectId, orgId, tok
82
91
  */
83
92
  async function getLatestDeploymentUrl(projectId, orgId, token, environment) {
84
93
  try {
85
- const cmd = [
86
- 'vercel',
87
- 'ls',
88
- `--token=${token}`,
89
- orgId ? `--scope=${orgId}` : '',
90
- '--json',
91
- ].filter(Boolean).join(' ')
92
-
93
- const output = execSync(cmd, { encoding: 'utf8' })
94
+ const cmd = ['vercel', 'ls', orgId ? `--scope=${orgId}` : '', '--json']
95
+ .filter(Boolean)
96
+ .join(' ')
97
+
98
+ const output = execSync(cmd, {
99
+ encoding: 'utf8',
100
+ env: {
101
+ ...process.env,
102
+ // 不通过 CLI args 传递 token,避免出现在错误信息/日志中
103
+ VERCEL_TOKEN: token,
104
+ },
105
+ })
94
106
  const deployments = JSON.parse(output)
95
107
 
96
108
  // 根据环境筛选部署
@@ -1,11 +1,105 @@
1
- import { execSync } from 'node:child_process'
1
+ import { execSync, spawn } from 'node:child_process'
2
2
  import { join } from 'node:path'
3
3
  import { envManager } from './env.js'
4
- import { execManager } from './exec.js'
5
4
  import { logger } from './logger.js'
6
5
 
7
6
  const ALLOWED_ENVIRONMENTS = ['development', 'staging', 'production']
8
7
 
8
+ function collectErrorText(err) {
9
+ const parts = []
10
+ if (err?.message) parts.push(String(err.message))
11
+ if (err?.stderr) parts.push(String(err.stderr))
12
+ if (err?.stdout) parts.push(String(err.stdout))
13
+ return parts.join('\n')
14
+ }
15
+
16
+ function isMissingFilesError(err) {
17
+ const text = collectErrorText(err)
18
+ return (
19
+ text.includes('missing_files') ||
20
+ text.includes('Missing files') ||
21
+ text.includes('code":"missing_files"')
22
+ )
23
+ }
24
+
25
+ async function runVercel(args, options = {}) {
26
+ const { env, cwd } = options
27
+ const MAX_CAPTURE = 20000
28
+
29
+ return new Promise((resolve, reject) => {
30
+ const child = spawn('vercel', args, {
31
+ cwd: cwd || process.cwd(),
32
+ env: env || process.env,
33
+ stdio: ['inherit', 'pipe', 'pipe'],
34
+ })
35
+
36
+ let stdout = ''
37
+ let stderr = ''
38
+
39
+ const append = (current, chunk) => {
40
+ const next = current + chunk
41
+ return next.length > MAX_CAPTURE ? next.slice(-MAX_CAPTURE) : next
42
+ }
43
+
44
+ child.stdout.on('data', data => {
45
+ process.stdout.write(data)
46
+ stdout = append(stdout, data.toString('utf8'))
47
+ })
48
+
49
+ child.stderr.on('data', data => {
50
+ process.stderr.write(data)
51
+ stderr = append(stderr, data.toString('utf8'))
52
+ })
53
+
54
+ child.on('error', error => {
55
+ error.stdout = stdout
56
+ error.stderr = stderr
57
+ reject(error)
58
+ })
59
+
60
+ child.on('close', code => {
61
+ if (code === 0) return resolve({ code, stdout, stderr })
62
+ const error = new Error(`vercel ${args[0] || ''} 失败 (exit ${code})`)
63
+ error.code = code
64
+ error.stdout = stdout
65
+ error.stderr = stderr
66
+ reject(error)
67
+ })
68
+ })
69
+ }
70
+
71
+ export async function deployPrebuiltWithFallback(options) {
72
+ const {
73
+ baseArgs,
74
+ env,
75
+ cwd,
76
+ run = runVercel,
77
+ cleanupArchiveParts = () => {
78
+ try {
79
+ execSync('rm -f .vercel/source.tgz.part*', { stdio: 'ignore' })
80
+ } catch {
81
+ // ignore
82
+ }
83
+ },
84
+ onMissingFiles = () => {
85
+ logger.warn('检测到 missing_files,自动使用 --archive=tgz 重试一次')
86
+ },
87
+ } = options || {}
88
+
89
+ try {
90
+ await run(baseArgs, { env, cwd })
91
+ return { usedArchive: false }
92
+ } catch (e) {
93
+ if (!isMissingFilesError(e)) throw e
94
+ onMissingFiles(e)
95
+ cleanupArchiveParts()
96
+ const archiveArgs = baseArgs.slice()
97
+ archiveArgs.splice(2, 0, '--archive=tgz')
98
+ await run(archiveArgs, { env, cwd })
99
+ return { usedArchive: true }
100
+ }
101
+ }
102
+
9
103
  export async function deployToVercel(target, options = {}) {
10
104
  const { environment = 'staging' } = options
11
105
 
@@ -63,7 +157,9 @@ export async function deployToVercel(target, options = {}) {
63
157
  // 如果有缺失变量,统一报错并退出
64
158
  if (missingVars.length > 0) {
65
159
  logger.error('缺少以下 Vercel 环境变量:')
66
- missingVars.forEach(v => logger.error(` - ${v}`))
160
+ missingVars.forEach(v => {
161
+ logger.error(` - ${v}`)
162
+ })
67
163
  logger.info('')
68
164
  logger.info('请在 .env.<env>.local 中配置这些变量,例如:')
69
165
  logger.info(' VERCEL_TOKEN=<your-vercel-token>')
@@ -120,6 +216,9 @@ export async function deployToVercel(target, options = {}) {
120
216
  envVars.VERCEL_ORG_ID = orgId
121
217
  }
122
218
 
219
+ // 不通过 CLI args 传递 token,避免出现在错误信息/日志中
220
+ envVars.VERCEL_TOKEN = token
221
+
123
222
  // 绕过 Vercel Git author 权限检查:临时修改最新 commit 的 author
124
223
  const authorEmail = process.env.VERCEL_GIT_COMMIT_AUTHOR_EMAIL
125
224
  let originalAuthor = null
@@ -138,52 +237,37 @@ export async function deployToVercel(target, options = {}) {
138
237
  try {
139
238
  // 第一步:本地构建
140
239
  logger.step(`本地构建 ${t} (${environment})`)
141
- const buildCmd = [
142
- 'vercel build',
143
- `--local-config="${configPath}"`,
144
- '--yes',
145
- `--token=${token}`,
146
- ]
240
+ const buildArgs = ['build', '--local-config', configPath, '--yes']
147
241
 
148
242
  // staging 和 production 环境需要 --prod 标志,确保构建产物与部署环境匹配
149
243
  if (environment === 'staging' || environment === 'production') {
150
- buildCmd.push('--prod')
244
+ buildArgs.push('--prod')
151
245
  }
152
246
 
153
247
  if (orgId) {
154
- buildCmd.push(`--scope=${orgId}`)
248
+ buildArgs.push('--scope', orgId)
155
249
  }
156
250
 
157
- execSync(buildCmd.join(' '), {
158
- stdio: 'inherit',
159
- cwd: process.cwd(),
160
- env: envVars,
161
- })
251
+ await runVercel(buildArgs, { env: envVars, cwd: process.cwd() })
162
252
  logger.success(`${t} 本地构建成功`)
163
253
 
164
254
  // 第二步:上传预构建产物
165
255
  logger.step(`部署 ${t} 到 Vercel (${environment})`)
166
- const deployCmd = [
167
- 'vercel deploy',
168
- '--prebuilt',
169
- `--local-config="${configPath}"`,
170
- '--yes',
171
- `--token=${token}`,
172
- ]
256
+ const baseDeployArgs = ['deploy', '--prebuilt', '--local-config', configPath, '--yes']
173
257
 
174
258
  // staging 和 production 环境都添加 --prod 标志以绑定固定域名
175
259
  if (environment === 'staging' || environment === 'production') {
176
- deployCmd.push('--prod')
260
+ baseDeployArgs.push('--prod')
177
261
  }
178
262
 
179
263
  if (orgId) {
180
- deployCmd.push(`--scope=${orgId}`)
264
+ baseDeployArgs.push('--scope', orgId)
181
265
  }
182
266
 
183
- execSync(deployCmd.join(' '), {
184
- stdio: 'inherit',
185
- cwd: process.cwd(),
267
+ await deployPrebuiltWithFallback({
268
+ baseArgs: baseDeployArgs,
186
269
  env: envVars,
270
+ cwd: process.cwd(),
187
271
  })
188
272
  logger.success(`${t} 部署成功`)
189
273
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger1/dx",
3
- "version": "0.1.44",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {