@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.
@@ -0,0 +1,233 @@
1
+ import { logger } from '../../logger.js'
2
+ import { confirmManager } from '../../confirm.js'
3
+ import { execManager } from '../../exec.js'
4
+ import { showHelp, showCommandHelp } from '../help.js'
5
+
6
+ export function handleHelp(cli, args = []) {
7
+ void cli
8
+ if (args[0]) showCommandHelp(args[0])
9
+ else showHelp()
10
+ }
11
+
12
+ export function handleDev(cli, args = []) {
13
+ return cli.reportDevCommandRemoved(args)
14
+ }
15
+
16
+ export async function handleBuild(cli, args) {
17
+ const target = args[0] || 'all'
18
+ const environment = cli.determineEnvironment()
19
+ const envKey = cli.normalizeEnvKey(environment)
20
+
21
+ const buildConfig = cli.commands.build[target]
22
+ if (!buildConfig) {
23
+ // 兼容用户误输入或快捷命令,把 "all./scripts/dx" 这类粘连错误拆分
24
+ const fixed = String(target).split(/\s|\t|\r|\n|\u00A0|\.|\//)[0]
25
+ if (fixed && cli.commands.build[fixed]) {
26
+ logger.warn(`自动修正构建目标: ${target} -> ${fixed}`)
27
+ return await handleBuild(cli, [fixed])
28
+ }
29
+ logger.error(`未找到构建目标: ${target}`)
30
+ process.exitCode = 1
31
+ return
32
+ }
33
+
34
+ logger.step(`构建 ${target} (${environment})`)
35
+
36
+ // 处理嵌套配置
37
+ let config = buildConfig
38
+ if (typeof config === 'object' && !config.command) {
39
+ // 如果是嵌套配置,尝试获取环境特定的配置(兼容 dev/prod 与 development/production 命名)
40
+ if (config[envKey]) config = config[envKey]
41
+ else if (envKey === 'staging' && config.prod) config = config.prod
42
+ else config = config.dev || config
43
+ }
44
+
45
+ if (config.concurrent) {
46
+ await cli.handleConcurrentCommands(config.commands, 'build', envKey)
47
+ } else if (config.sequential) {
48
+ await cli.handleSequentialCommands(config.commands, envKey)
49
+ } else {
50
+ await cli.executeCommand(config)
51
+ }
52
+ }
53
+
54
+ export async function handleTest(cli, args) {
55
+ const type = args[0] || 'e2e'
56
+ const target = args[1] || 'all'
57
+ const testPath = args[2] // 可选的测试文件路径
58
+
59
+ // 解析 -t 参数用于指定特定测试用例(使用原始参数列表)
60
+ const allArgs = cli.args // 使用原始参数列表包含所有标志
61
+ const testNamePatternIndex = allArgs.indexOf('-t')
62
+ let testNamePattern = null
63
+ if (testNamePatternIndex !== -1 && testNamePatternIndex + 1 < allArgs.length) {
64
+ testNamePattern = allArgs[testNamePatternIndex + 1]
65
+ }
66
+
67
+ // 根据测试类型自动设置环境标志
68
+ if (type === 'e2e' && !cli.flags.e2e) {
69
+ cli.flags.e2e = true
70
+ } else if (type === 'unit' && !cli.flags.test) {
71
+ cli.flags.test = true
72
+ }
73
+
74
+ let testConfig = cli.commands.test[type]?.[target] || cli.commands.test[type]
75
+
76
+ if (!testConfig) {
77
+ logger.error(`未找到测试配置: ${type}.${target}`)
78
+ process.exitCode = 1
79
+ return
80
+ }
81
+
82
+ // 如果提供了测试文件路径,使用专门的单文件测试配置
83
+ if (testPath && type === 'e2e' && target === 'backend') {
84
+ let command = `pnpm --filter ./apps/backend run test:e2e:file ${testPath}`
85
+
86
+ // 如果指定了测试用例名称,添加 -t 参数
87
+ if (testNamePattern) {
88
+ const escapedPattern = String(testNamePattern).replace(/(["`\\$])/g, '\\$1')
89
+ command += ` -t "${escapedPattern}"`
90
+ }
91
+
92
+ testConfig = {
93
+ ...testConfig,
94
+ command: command,
95
+ description: testNamePattern
96
+ ? `运行单个E2E测试文件的特定用例: ${testPath} -> ${testNamePattern}`
97
+ : `运行单个E2E测试文件: ${testPath}`
98
+ }
99
+
100
+ if (testNamePattern) {
101
+ logger.step(`运行 ${type} 测试用例: ${testNamePattern} (文件: ${testPath})`)
102
+ } else {
103
+ logger.step(`运行单个 ${type} 测试: ${testPath}`)
104
+ }
105
+ } else {
106
+ logger.step(`运行 ${type} 测试`)
107
+ }
108
+
109
+ await cli.executeCommand(testConfig)
110
+ }
111
+
112
+ export async function handleLint(cli, args) {
113
+ void args
114
+ const baseConfig = cli.commands.lint
115
+ if (!baseConfig || !baseConfig.command) {
116
+ logger.error('未找到 lint 命令配置')
117
+ process.exitCode = 1
118
+ return
119
+ }
120
+
121
+ const config = { ...baseConfig }
122
+
123
+ if (cli.flags.fix) {
124
+ logger.step('运行代码检查(自动修复模式: --fix)')
125
+ const cmd = String(config.command)
126
+ // 若已包含 ` -- ` 分隔符,直接在末尾追加 --fix;否则通过 `--` 传递给 Nx 下游
127
+ config.command = cmd.includes(' -- ')
128
+ ? `${cmd} --fix`
129
+ : `${cmd} -- --fix`
130
+ } else {
131
+ logger.step('运行代码检查')
132
+ }
133
+
134
+ await cli.executeCommand(config)
135
+ }
136
+
137
+ export async function handleClean(cli, args) {
138
+ const target = args[0] || 'all'
139
+ const cleanConfig = cli.commands.clean[target]
140
+
141
+ if (!cleanConfig) {
142
+ logger.error(`未找到清理目标: ${target}`)
143
+ process.exitCode = 1
144
+ return
145
+ }
146
+
147
+ // 危险操作确认
148
+ if (cleanConfig.dangerous) {
149
+ const confirmed = await confirmManager.confirmDangerous(
150
+ `清理操作: ${target}`,
151
+ '当前环境',
152
+ cli.flags.Y
153
+ )
154
+
155
+ if (!confirmed) {
156
+ logger.info('操作已取消')
157
+ return
158
+ }
159
+ }
160
+
161
+ logger.step(`清理 ${target}`)
162
+ await cli.executeCommand(cleanConfig)
163
+ }
164
+
165
+ export async function handleCache(cli, args) {
166
+ const action = args[0] || 'clear'
167
+ const cacheConfig = cli.commands.cache?.[action]
168
+
169
+ if (!cacheConfig) {
170
+ logger.error(`未找到缓存操作: ${action}`)
171
+ logger.info('用法: dx cache clear')
172
+ process.exitCode = 1
173
+ return
174
+ }
175
+
176
+ // 危险操作确认
177
+ if (cacheConfig.dangerous) {
178
+ const confirmed = await confirmManager.confirmDangerous(
179
+ `缓存清理: ${action}`,
180
+ '当前环境',
181
+ cli.flags.Y
182
+ )
183
+
184
+ if (!confirmed) {
185
+ logger.info('操作已取消')
186
+ return
187
+ }
188
+
189
+ // 二次确认(更醒目):强调将清理全局 pnpm store 与 ~/.pnpm-store
190
+ if (!cli.flags.Y && action === 'clear') {
191
+ const second = await confirmManager.confirm(
192
+ '二次确认:将清理全局 pnpm store 与 ~/.pnpm-store,可能影响其他项目,是否继续?',
193
+ false,
194
+ false
195
+ )
196
+ if (!second) {
197
+ logger.info('操作已取消')
198
+ return
199
+ }
200
+ }
201
+ }
202
+
203
+ logger.step(`执行缓存操作: ${action}`)
204
+ await cli.executeCommand(cacheConfig)
205
+ }
206
+
207
+ export async function handleInstall(cli, args) {
208
+ void args
209
+ const installConfig = cli.commands.install
210
+ if (!installConfig) {
211
+ logger.error('未找到 install 命令配置')
212
+ process.exitCode = 1
213
+ return
214
+ }
215
+
216
+ logger.step('安装依赖')
217
+ await cli.executeCommand(installConfig)
218
+ }
219
+
220
+ export async function handleStatus(cli, args) {
221
+ void args
222
+ logger.step('系统状态')
223
+
224
+ const status = execManager.getStatus()
225
+ console.log(`运行中的进程: ${status.runningProcesses}`)
226
+
227
+ if (status.processes.length > 0) {
228
+ logger.table(
229
+ status.processes.map(p => [p.id, p.command, `${Math.round(p.duration/1000)}s`]),
230
+ ['进程ID', '命令', '运行时长']
231
+ )
232
+ }
233
+ }
@@ -0,0 +1,239 @@
1
+ import { logger } from '../../logger.js'
2
+ import { confirmManager } from '../../confirm.js'
3
+ import { envManager } from '../../env.js'
4
+ import { getPassthroughArgs } from '../args.js'
5
+
6
+ export async function handleDatabase(cli, args) {
7
+ const action = args[0]
8
+ if (!action) {
9
+ logger.error('请指定数据库操作: generate, migrate, deploy, reset, seed, format, script')
10
+ process.exitCode = 1
11
+ return
12
+ }
13
+
14
+ const dbConfig = cli.commands.db[action]
15
+ if (!dbConfig) {
16
+ logger.error(`未找到数据库操作: ${action}`)
17
+ process.exitCode = 1
18
+ return
19
+ }
20
+
21
+ const environment = cli.determineEnvironment()
22
+ const envKey = cli.normalizeEnvKey(environment)
23
+
24
+ // 兼容旧用法:非 dev 环境使用 migrate 时给出明确提示,推荐改用 deploy
25
+ if (action === 'migrate' && envKey && envKey !== 'dev') {
26
+ const envFlag = cli.getEnvironmentFlagExample(envKey) || `--${envKey}`
27
+ logger.warn(`检测到在非开发环境执行 migrate: ${environment || envKey}`)
28
+ logger.info('建议仅在开发环境使用 `dx db migrate --dev --name <migration-name>` 创建迁移。')
29
+ logger.info(`如需在当前环境应用已有迁移,请使用: ${cli.invocation} db deploy ${envFlag}`)
30
+ }
31
+
32
+ // 处理 script 子命令
33
+ if (action === 'script') {
34
+ const scriptName = args[1]
35
+ if (!scriptName) {
36
+ logger.error('请指定要运行的脚本名称')
37
+ logger.info('用法: dx db script <script-name> [环境标志]')
38
+ logger.info('示例: dx db script fix-email-verified-status --dev')
39
+ process.exitCode = 1
40
+ return
41
+ }
42
+ return await handleDatabaseScript(cli, scriptName, envKey, dbConfig)
43
+ }
44
+
45
+ logger.step(`执行数据库操作: ${action} (${environment})`)
46
+
47
+ // 处理嵌套配置
48
+ let config = dbConfig
49
+ if (typeof config === 'object' && !config.command) {
50
+ // 如果是嵌套配置,尝试获取环境特定的配置(兼容 dev/prod 与 development/production 命名)
51
+ if (config[envKey]) config = config[envKey]
52
+ else if (envKey === 'staging' && config.prod) config = config.prod
53
+ else config = config.dev || config
54
+ }
55
+
56
+ // 危险操作确认
57
+ if (config.dangerous) {
58
+ const confirmed = await confirmManager.confirmDatabaseOperation(
59
+ action,
60
+ envManager.getEnvironmentDescription(environment),
61
+ cli.flags.Y,
62
+ )
63
+
64
+ if (!confirmed) {
65
+ logger.info('操作已取消')
66
+ return
67
+ }
68
+ }
69
+
70
+ // 支持为 migrate 传入迁移名:--name/-n
71
+ let command = config.command
72
+ if (action === 'migrate' && envKey === 'dev') {
73
+ const allArgs = cli.args
74
+ let migrationName = null
75
+ // 优先解析显式标志 --name/-n
76
+ const nameIdx = allArgs.indexOf('--name')
77
+ const shortIdx = allArgs.indexOf('-n')
78
+ if (nameIdx !== -1 && nameIdx + 1 < allArgs.length) {
79
+ migrationName = allArgs[nameIdx + 1]
80
+ } else if (shortIdx !== -1 && shortIdx + 1 < allArgs.length) {
81
+ migrationName = allArgs[shortIdx + 1]
82
+ }
83
+
84
+ if (!migrationName) {
85
+ logger.error('开发环境执行 migrate 时必须通过 --name 或 -n 指定迁移名称(禁止位置参数)')
86
+ logger.info('原因: 缺少迁移名会进入 Prisma 的交互式输入流程,脚本将被阻塞。')
87
+ logger.info(`正确示例: ${cli.invocation} db migrate --dev --name init-user-table`)
88
+ logger.info(`如仅需应用已有迁移,请使用 ${cli.invocation} db deploy --dev。`)
89
+ logger.info(`提示: 执行 ${cli.invocation} help db 查看完整用法与更多示例。`)
90
+ process.exitCode = 1
91
+ return
92
+ }
93
+
94
+ const escaped = String(migrationName).replace(/(["`\\$])/g, '\\$1')
95
+ // 将参数传递给 Nx 下游 prisma:需要通过 -- 分割
96
+ command = `${command} -- --name "${escaped}"`
97
+ logger.info(`使用迁移名: ${migrationName}`)
98
+ }
99
+
100
+ // 对以下数据库操作固定禁用 Nx 缓存,避免命中缓存导致未实际执行:generate/migrate/deploy/reset/seed
101
+ const disableCacheActions = new Set(['generate', 'migrate', 'deploy', 'reset', 'seed'])
102
+ const extraEnv = disableCacheActions.has(action) ? { NX_CACHE: 'false' } : {}
103
+ if (disableCacheActions.has(action)) {
104
+ logger.info('为数据库操作禁用 Nx 缓存: NX_CACHE=false')
105
+ // 为每个子命令注入 --skip-nx-cache(支持 && || ; 拼接的多命令)
106
+ command = command
107
+ .split(/(\s*&&\s*|\s*\|\|\s*|\s*;\s*)/)
108
+ .map(part => {
109
+ // 保留分隔符原样
110
+ if (/^(\s*&&\s*|\s*\|\|\s*|\s*;\s*)$/.test(part)) return part
111
+ // 跳过空字符串
112
+ if (!part.trim()) return part
113
+ // 只对 npx nx 命令注入
114
+ if (!part.includes('npx nx')) return part
115
+ // 在 -- 之前插入,或追加到末尾
116
+ if (part.includes(' -- ')) {
117
+ return part.replace(' -- ', ' --skip-nx-cache -- ')
118
+ }
119
+ return `${part} --skip-nx-cache`
120
+ })
121
+ .join('')
122
+ }
123
+
124
+ if (action === 'reset' && envKey !== 'prod') {
125
+ extraEnv.PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION = 'yes'
126
+ logger.info('非生产环境重置数据库,已自动确认危险操作: PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION=yes')
127
+ }
128
+
129
+ const execFlags = { ...cli.flags }
130
+ ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
131
+ key => delete execFlags[key],
132
+ )
133
+ if (envKey === 'prod') execFlags.prod = true
134
+ else if (envKey === 'dev') execFlags.dev = true
135
+ else if (envKey === 'test') execFlags.test = true
136
+ else if (envKey === 'e2e') execFlags.e2e = true
137
+ else if (envKey === 'staging') execFlags.staging = true
138
+
139
+ await cli.executeCommand(
140
+ { ...config, command, env: { ...(config.env || {}), ...extraEnv } },
141
+ execFlags,
142
+ )
143
+ }
144
+
145
+ export async function handleDatabaseScript(cli, scriptName, envKey, dbConfig) {
146
+ // 自动去除 .ts 扩展名(如果用户提供了)
147
+ const cleanScriptName = scriptName.endsWith('.ts') ? scriptName.slice(0, -3) : scriptName
148
+
149
+ // 基础路径校验,避免路径遍历
150
+ if (
151
+ cleanScriptName.includes('/') ||
152
+ cleanScriptName.includes('\\') ||
153
+ cleanScriptName.includes('..')
154
+ ) {
155
+ logger.error(`脚本名称不能包含路径分隔符或父目录引用: ${cleanScriptName}`)
156
+ process.exitCode = 1
157
+ return
158
+ }
159
+
160
+ const { existsSync } = await import('fs')
161
+ const { join, resolve, relative } = await import('path')
162
+ const scriptsRoot = join(process.cwd(), 'apps/backend/prisma/scripts')
163
+ const scriptPath = resolve(scriptsRoot, `${cleanScriptName}.ts`)
164
+
165
+ if (relative(scriptsRoot, scriptPath).startsWith('..')) {
166
+ logger.error(`脚本路径解析结果已超出允许目录: ${scriptPath}`)
167
+ process.exitCode = 1
168
+ return
169
+ }
170
+
171
+ if (!existsSync(scriptPath)) {
172
+ logger.error(`脚本文件不存在: ${scriptPath}`)
173
+ logger.info('可用的脚本文件位于: apps/backend/prisma/scripts/')
174
+ process.exitCode = 1
175
+ return
176
+ }
177
+
178
+ const environment = envKey === 'dev' ? 'development' : envKey === 'prod' ? 'production' : envKey
179
+ logger.step(`执行数据库脚本: ${cleanScriptName} (${environment})`)
180
+ logger.info(`脚本路径: ${scriptPath}`)
181
+
182
+ // 处理嵌套配置
183
+ let config = dbConfig
184
+ if (typeof config === 'object' && !config.command) {
185
+ if (config[envKey]) config = config[envKey]
186
+ else if (envKey === 'staging' && config.prod) config = config.prod
187
+ else config = config.dev || config
188
+ }
189
+
190
+ // 危险操作确认
191
+ if (config.dangerous && envKey === 'prod') {
192
+ const confirmed = await confirmManager.confirmDatabaseOperation(
193
+ `script: ${cleanScriptName}`,
194
+ envManager.getEnvironmentDescription(environment),
195
+ cli.flags.Y,
196
+ )
197
+
198
+ if (!confirmed) {
199
+ logger.info('操作已取消')
200
+ return
201
+ }
202
+ }
203
+
204
+ // 替换命令中的脚本名称占位符
205
+ let command = config.command.replace('{SCRIPT_NAME}', cleanScriptName)
206
+
207
+ // 获取 -- 后面的 passthrough 参数并追加到命令
208
+ const passthroughArgs = getPassthroughArgs(cli.args)
209
+ if (passthroughArgs.length > 0) {
210
+ const escapedArgs = passthroughArgs.map(arg => {
211
+ // 对包含空格或特殊字符的参数加引号
212
+ if (/[\s"'`$\\]/.test(arg)) {
213
+ return `"${arg.replace(/(["`\\$])/g, '\\$1')}"`
214
+ }
215
+ return arg
216
+ })
217
+ command = `${command} ${escapedArgs.join(' ')}`
218
+ logger.info(`传递给脚本的参数: ${passthroughArgs.join(' ')}`)
219
+ }
220
+
221
+ // 为脚本执行禁用 Nx 缓存
222
+ const extraEnv = { NX_CACHE: 'false' }
223
+ logger.info('为数据库脚本禁用 Nx 缓存: NX_CACHE=false')
224
+
225
+ const execFlags = { ...cli.flags }
226
+ ;['dev', 'development', 'prod', 'production', 'test', 'e2e', 'staging', 'stage'].forEach(
227
+ key => delete execFlags[key],
228
+ )
229
+ if (envKey === 'prod') execFlags.prod = true
230
+ else if (envKey === 'dev') execFlags.dev = true
231
+ else if (envKey === 'test') execFlags.test = true
232
+ else if (envKey === 'e2e') execFlags.e2e = true
233
+ else if (envKey === 'staging') execFlags.staging = true
234
+
235
+ await cli.executeCommand(
236
+ { ...config, command, env: { ...(config.env || {}), ...extraEnv } },
237
+ execFlags,
238
+ )
239
+ }
@@ -0,0 +1,76 @@
1
+ import { logger } from '../../logger.js'
2
+ import { envManager } from '../../env.js'
3
+ import { validateEnvironment } from '../../validate-env.js'
4
+
5
+ export async function handleDeploy(cli, args) {
6
+ const target = args[0]
7
+ if (!target) {
8
+ logger.error('请指定部署目标: front, admin, all')
9
+ logger.info(`用法: ${cli.invocation} deploy <target> [环境标志]`)
10
+ logger.info(`示例: ${cli.invocation} deploy front --staging`)
11
+ process.exitCode = 1
12
+ return
13
+ }
14
+
15
+ const normalizedTarget = String(target).toLowerCase()
16
+ const deployTargets = Object.keys(cli.commands.deploy || {})
17
+ if (!cli.commands.deploy?.[normalizedTarget]) {
18
+ logger.error(`未找到部署目标: ${target}`)
19
+ if (deployTargets.length > 0) {
20
+ logger.info(`可用目标: ${deployTargets.join(', ')}`)
21
+ }
22
+ logger.info(`用法: ${cli.invocation} deploy <target> [环境标志]`)
23
+ process.exitCode = 1
24
+ return
25
+ }
26
+
27
+ if (cli.flags.test || cli.flags.e2e) {
28
+ logger.error('deploy 命令仅支持 --dev/--staging/--prod 环境标志')
29
+ process.exitCode = 1
30
+ return
31
+ }
32
+
33
+ const selectedEnvs = []
34
+ if (cli.flags.dev) selectedEnvs.push('development')
35
+ if (cli.flags.staging) selectedEnvs.push('staging')
36
+ if (cli.flags.prod) selectedEnvs.push('production')
37
+
38
+ if (selectedEnvs.length > 1) {
39
+ logger.error('deploy 命令不支持同时传入多个环境标志')
40
+ logger.info('请使用 --dev、--staging 或 --prod 中的一个')
41
+ process.exitCode = 1
42
+ return
43
+ }
44
+
45
+ const environment = selectedEnvs[0] || 'staging'
46
+
47
+ cli.ensureRepoRoot()
48
+
49
+ // 只执行基础环境校验(检查 .env 文件结构),跳过后端环境变量校验
50
+ // Vercel 部署所需的环境变量由 vercel-deploy.js 自行校验
51
+ try {
52
+ validateEnvironment()
53
+ } catch (error) {
54
+ logger.error(error.message)
55
+ process.exitCode = 1
56
+ return
57
+ }
58
+
59
+ // 加载环境变量层,但不校验后端必需变量
60
+ const layeredEnv = envManager.collectEnvFromLayers(null, environment)
61
+ if (envManager.latestEnvWarnings && envManager.latestEnvWarnings.length > 0) {
62
+ envManager.latestEnvWarnings.forEach(message => logger.warn(message))
63
+ }
64
+
65
+ // 仅在目标变量不存在或是占位符时才使用 .env 文件的值
66
+ for (const [key, value] of Object.entries(layeredEnv)) {
67
+ const currentValue = process.env[key]
68
+ if (!currentValue || envManager.isPlaceholderEnvValue(currentValue)) {
69
+ process.env[key] = value
70
+ }
71
+ }
72
+ envManager.syncEnvironments(environment)
73
+
74
+ const { deployToVercel } = await import('../../vercel-deploy.js')
75
+ await deployToVercel(normalizedTarget, { environment })
76
+ }
@@ -0,0 +1,34 @@
1
+ import { logger } from '../../logger.js'
2
+
3
+ export async function handleExport(cli, args) {
4
+ const target = args[0]
5
+ if (!target) {
6
+ logger.error('请指定导出目标')
7
+ logger.info(`用法: ${cli.invocation} export <target> [环境标志]`)
8
+ process.exitCode = 1
9
+ return
10
+ }
11
+
12
+ const exportConfig = cli.commands.export?.[target]
13
+ if (!exportConfig) {
14
+ const targets = Object.keys(cli.commands.export || {})
15
+ logger.error(`未找到导出目标: ${target}`)
16
+ if (targets.length > 0) {
17
+ logger.info(`可用目标: ${targets.join(', ')}`)
18
+ }
19
+ process.exitCode = 1
20
+ return
21
+ }
22
+
23
+ const environment = cli.determineEnvironment()
24
+ const envKey = cli.normalizeEnvKey(environment)
25
+ let config = exportConfig
26
+ if (typeof config === 'object' && !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
+ logger.step(`导出 ${target} (${environment})`)
33
+ await cli.executeCommand(config)
34
+ }
@@ -0,0 +1,22 @@
1
+ import { logger } from '../../logger.js'
2
+
3
+ export async function handlePackage(cli, args) {
4
+ const target = args[0] || 'backend'
5
+ if (target !== 'backend') {
6
+ logger.error(`暂不支持打包目标: ${target}`)
7
+ logger.info(`当前仅支持 ${cli.invocation} package backend`)
8
+ process.exitCode = 1
9
+ return
10
+ }
11
+
12
+ cli.ensureRepoRoot()
13
+
14
+ const environment = cli.determineEnvironment()
15
+ const passthroughFlags = cli.args.filter(token =>
16
+ ['--skip-build', '--keep-workdir'].includes(token),
17
+ )
18
+
19
+ logger.step(`打包 ${target} (${environment})`)
20
+ const { runBackendPackage } = await import('../../backend-package.js')
21
+ await runBackendPackage([`--env=${environment}`, ...passthroughFlags])
22
+ }