@ranger1/dx 0.1.3 → 0.1.4
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/README.md +92 -1
- package/lib/cli/commands/contracts.js +60 -0
- package/lib/cli/commands/release.js +56 -0
- package/lib/cli/dx-cli.js +16 -1
- package/lib/cli/help.js +41 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
一个可安装的 Node.js CLI,用于管理符合约定的 pnpm + nx monorepo 项目的构建/启动/数据库/部署等流程。
|
|
4
4
|
|
|
5
|
+
本工具通过项目内的 `dx/config/*` 配置文件来驱动命令执行:你可以把它理解成「带环境变量分层 + 校验 + 命令编排能力的脚本系统」。
|
|
6
|
+
|
|
5
7
|
## 安装
|
|
6
8
|
|
|
7
9
|
全局安装(推荐):
|
|
@@ -24,6 +26,16 @@ pnpm add -D @ranger1/dx
|
|
|
24
26
|
pnpm exec dx --help
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
## 使用条件(必须满足)
|
|
30
|
+
|
|
31
|
+
- Node.js:>= 20
|
|
32
|
+
- 包管理器:pnpm(dx 内部会调用 `pnpm`)
|
|
33
|
+
- 构建系统:Nx(dx 默认命令配置里大量使用 `npx nx ...`)
|
|
34
|
+
- 环境加载:建议项目依赖 `dotenv-cli`(dx 会用 `pnpm exec dotenv ...` 包裹命令来注入 `.env.*`)
|
|
35
|
+
- 项目结构:默认按 `apps/backend` / `apps/front` / `apps/admin-front` / `apps/sdk` 这类布局编写命令配置
|
|
36
|
+
|
|
37
|
+
如果你的 monorepo 不完全一致,也能用:关键是你在 `dx/config/commands.json` 里把命令写成适配你项目的形式。
|
|
38
|
+
|
|
27
39
|
## 项目配置(必须)
|
|
28
40
|
|
|
29
41
|
dx 会从当前目录向上查找 `dx/config/commands.json` 来定位项目根目录。
|
|
@@ -47,6 +59,86 @@ dx/
|
|
|
47
59
|
|
|
48
60
|
全局安装场景下,如果你不在项目目录内执行,也可以通过 `DX_CONFIG_DIR` / `--config-dir` 显式指定配置目录(目录下需要存在 `commands.json`)。
|
|
49
61
|
|
|
62
|
+
示例:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 在任意目录执行
|
|
66
|
+
dx --config-dir /path/to/your-repo/dx/config status
|
|
67
|
+
|
|
68
|
+
# 或
|
|
69
|
+
DX_CONFIG_DIR=/path/to/your-repo/dx/config dx status
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 配置文件写法
|
|
73
|
+
|
|
74
|
+
### 1) dx/config/commands.json
|
|
75
|
+
|
|
76
|
+
这是核心文件,定义了 dx 各命令要执行的 shell 命令,支持:
|
|
77
|
+
|
|
78
|
+
- 单命令:`{ "command": "..." }`
|
|
79
|
+
- 并发:`{ "concurrent": true, "commands": ["build.front.dev", "build.admin.dev"] }`
|
|
80
|
+
- 串行:`{ "sequential": true, "commands": ["build.backend.prod", "build.sdk"] }`
|
|
81
|
+
- 环境分支:如 `build.backend.dev` / `build.backend.prod`(dx 会根据 `--dev/--prod/--staging/...` 选择)
|
|
82
|
+
- dotenv 包裹:配置里带 `"app": "backend"` 时,dx 会按 `env-layers.json` 拼出 dotenv 层并用 `pnpm exec dotenv ... -- <command>` 执行
|
|
83
|
+
|
|
84
|
+
常见字段(单命令配置):
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
{
|
|
88
|
+
"command": "npx nx build backend --configuration=production",
|
|
89
|
+
"app": "backend", // 可选:用于选择 dotenv 层,并决定需要校验的 env 变量组
|
|
90
|
+
"ports": [3000], // 可选:用于 start 类命令,冲突时自动清理
|
|
91
|
+
"description": "构建后端(生产环境)",
|
|
92
|
+
"dangerous": true, // 可选:危险操作需要确认
|
|
93
|
+
"skipEnvValidation": true, // 可选:跳过 env 校验(仍可加载 dotenv 层)
|
|
94
|
+
"env": { "NX_CACHE": "false" } // 可选:注入额外环境变量
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
命令路径引用(并发/串行的 commands 数组)使用点号字符串,例如:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "concurrent": true, "commands": ["build.shared", "build.front.dev"] }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2) dx/config/env-layers.json
|
|
105
|
+
|
|
106
|
+
用于定义不同环境下加载哪些 `.env.*` 文件(顺序 = 覆盖优先级)。格式:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"development": [".env.development", ".env.development.local"],
|
|
111
|
+
"staging": [".env.staging", ".env.staging.local"],
|
|
112
|
+
"production": [".env.production", ".env.production.local"],
|
|
113
|
+
"test": [".env.test", ".env.test.local"],
|
|
114
|
+
"e2e": [".env.e2e", ".env.e2e.local"]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3) dx/config/required-env.jsonc
|
|
119
|
+
|
|
120
|
+
用于定义哪些环境变量是「必须存在」的(dx 会在执行命令前校验)。它是 jsonc(允许 // 注释)。
|
|
121
|
+
|
|
122
|
+
dx 的校验分组逻辑:
|
|
123
|
+
|
|
124
|
+
- `_common`: 所有命令都会校验
|
|
125
|
+
- `backend`: 当命令配置里 `app` 是 `backend` 时会校验
|
|
126
|
+
- `frontend`: 当命令配置里 `app` 是 `front`/`admin-front` 等前端应用时会校验
|
|
127
|
+
- `development`/`production`/`staging`/`test`/`e2e`: 按当前环境额外补充
|
|
128
|
+
|
|
129
|
+
### 4) dx/config/local-env-allowlist.jsonc + exempted-keys.jsonc
|
|
130
|
+
|
|
131
|
+
这是为了防止误提交机密:
|
|
132
|
+
|
|
133
|
+
- `local-env-allowlist.jsonc`:允许出现在 `.env.*.local` 里的键(这些被认为是“机密”)
|
|
134
|
+
- `exempted-keys.jsonc`:豁免键(允许在非 local 文件中出现真实值)
|
|
135
|
+
|
|
136
|
+
非 local 的 `.env.*` 文件里,机密键必须使用占位符:`__SET_IN_env.local__`。
|
|
137
|
+
|
|
138
|
+
## 示例工程
|
|
139
|
+
|
|
140
|
+
查看 `example/`:包含一个最小可读的 `dx/config` 配置示例,以及如何在一个 pnpm+nx monorepo 中接入 dx。
|
|
141
|
+
|
|
50
142
|
## 命令
|
|
51
143
|
|
|
52
144
|
dx 的命令由 `dx/config/commands.json` 驱动,并且内置了一些 internal runner(避免项目侧依赖任何 `scripts/lib/*.js`):
|
|
@@ -101,4 +193,3 @@ dx test e2e backend
|
|
|
101
193
|
```bash
|
|
102
194
|
npm publish --access public --registry=https://registry.npmjs.org
|
|
103
195
|
```
|
|
104
|
-
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { logger } from '../../logger.js'
|
|
4
|
+
import { execManager } from '../../exec.js'
|
|
5
|
+
|
|
6
|
+
export async function handleContracts(cli, args = []) {
|
|
7
|
+
const action = args[0] || 'generate'
|
|
8
|
+
if (!['generate', 'pull'].includes(action)) {
|
|
9
|
+
logger.error(`不支持的 contracts 子命令: ${action}`)
|
|
10
|
+
logger.info(`用法: ${cli.invocation} contracts [generate|pull]`)
|
|
11
|
+
process.exitCode = 1
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
cli.ensureRepoRoot()
|
|
16
|
+
|
|
17
|
+
// starmomo/ai-monorepo compatibility: packages/api-contracts is expected
|
|
18
|
+
const contractsRoot = join(process.cwd(), 'packages', 'api-contracts')
|
|
19
|
+
if (!existsSync(contractsRoot)) {
|
|
20
|
+
logger.error(`未找到 contracts 目录: ${contractsRoot}`)
|
|
21
|
+
logger.info('期望存在 packages/api-contracts,用于输出生成的 Zod 合约。')
|
|
22
|
+
process.exitCode = 1
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
logger.step('导出 OpenAPI 并生成 Zod 合约')
|
|
27
|
+
|
|
28
|
+
// 1) Export OpenAPI spec to dist/openapi/backend.json
|
|
29
|
+
await execManager.executeCommand('npx nx run backend:swagger', {
|
|
30
|
+
app: 'backend',
|
|
31
|
+
flags: cli.flags,
|
|
32
|
+
env: { NX_CACHE: 'false', SKIP_PRISMA_CONNECT: 'true' },
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// 2) Generate zod client
|
|
36
|
+
const outputDir = join(contractsRoot, 'src', 'generated')
|
|
37
|
+
mkdirSync(outputDir, { recursive: true })
|
|
38
|
+
|
|
39
|
+
const baseUrl = process.env.OPENAPI_BASE_URL || 'http://localhost:3000/api/v1'
|
|
40
|
+
logger.info(`使用 API 基地址: ${baseUrl}`)
|
|
41
|
+
|
|
42
|
+
const generatorCommand = [
|
|
43
|
+
'pnpm exec openapi-zod-client',
|
|
44
|
+
'dist/openapi/backend.json',
|
|
45
|
+
'--output packages/api-contracts/src/generated/backend.ts',
|
|
46
|
+
'--api-client-name aiBackendClient',
|
|
47
|
+
`--base-url "${baseUrl}"`,
|
|
48
|
+
'--with-alias',
|
|
49
|
+
'--with-docs',
|
|
50
|
+
'--with-deprecated',
|
|
51
|
+
'--export-schemas',
|
|
52
|
+
'--prettier prettier.config.js',
|
|
53
|
+
].join(' ')
|
|
54
|
+
|
|
55
|
+
await execManager.executeCommand(generatorCommand, {
|
|
56
|
+
flags: cli.flags,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
logger.success('Zod 合约已更新(packages/api-contracts)')
|
|
60
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { logger } from '../../logger.js'
|
|
4
|
+
|
|
5
|
+
export async function handleRelease(cli, args = []) {
|
|
6
|
+
const action = args[0]
|
|
7
|
+
const version = args[1]
|
|
8
|
+
|
|
9
|
+
if (action !== 'version') {
|
|
10
|
+
logger.error(`用法: ${cli.invocation} release version <版本号>`)
|
|
11
|
+
process.exitCode = 1
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!version) {
|
|
16
|
+
logger.error(`请提供要发布的版本号,例如: ${cli.invocation} release version 1.2.3`)
|
|
17
|
+
process.exitCode = 1
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
cli.ensureRepoRoot()
|
|
22
|
+
|
|
23
|
+
const semverPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/
|
|
24
|
+
if (!semverPattern.test(version)) {
|
|
25
|
+
logger.warn(`版本号 ${version} 不符合常见语义化版本格式,仍将继续更新`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const packageFiles = [
|
|
29
|
+
'apps/backend/package.json',
|
|
30
|
+
'apps/front/package.json',
|
|
31
|
+
'apps/admin-front/package.json',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
logger.step(`统一更新版本号 -> ${version}`)
|
|
35
|
+
|
|
36
|
+
for (const relativePath of packageFiles) {
|
|
37
|
+
const fullPath = join(process.cwd(), relativePath)
|
|
38
|
+
if (!existsSync(fullPath)) {
|
|
39
|
+
logger.warn(`跳过,未找到文件: ${relativePath}`)
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const raw = readFileSync(fullPath, 'utf8')
|
|
45
|
+
const pkg = JSON.parse(raw)
|
|
46
|
+
const previous = pkg.version || '0.0.0'
|
|
47
|
+
pkg.version = version
|
|
48
|
+
writeFileSync(fullPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
|
|
49
|
+
logger.info(`更新 ${relativePath}: ${previous} -> ${version}`)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.error(`更新 ${relativePath} 失败: ${error?.message || String(error)}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
logger.success(`版本号已同步为 ${version}`)
|
|
56
|
+
}
|
package/lib/cli/dx-cli.js
CHANGED
|
@@ -26,6 +26,8 @@ import { handleDatabase } from './commands/db.js'
|
|
|
26
26
|
import { handleWorktree } from './commands/worktree.js'
|
|
27
27
|
import { handlePackage } from './commands/package.js'
|
|
28
28
|
import { handleExport } from './commands/export.js'
|
|
29
|
+
import { handleContracts } from './commands/contracts.js'
|
|
30
|
+
import { handleRelease } from './commands/release.js'
|
|
29
31
|
|
|
30
32
|
class DxCli {
|
|
31
33
|
constructor(options = {}) {
|
|
@@ -58,6 +60,8 @@ class DxCli {
|
|
|
58
60
|
worktree: args => handleWorktree(this, args),
|
|
59
61
|
package: args => handlePackage(this, args),
|
|
60
62
|
export: args => handleExport(this, args),
|
|
63
|
+
contracts: args => handleContracts(this, args),
|
|
64
|
+
release: args => handleRelease(this, args),
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
this.flagDefinitions = FLAG_DEFINITIONS
|
|
@@ -277,10 +281,21 @@ class DxCli {
|
|
|
277
281
|
return
|
|
278
282
|
}
|
|
279
283
|
|
|
284
|
+
this.validateInputs()
|
|
285
|
+
|
|
286
|
+
// Commands that should work without env/dependency checks.
|
|
287
|
+
// - help: printing help should never require env vars.
|
|
288
|
+
// - status: should be available even when env is incomplete.
|
|
289
|
+
// - release: only edits package.json versions.
|
|
290
|
+
const skipStartupChecks = new Set(['help', 'status', 'release'])
|
|
291
|
+
if (skipStartupChecks.has(this.command)) {
|
|
292
|
+
await this.routeCommand()
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
280
296
|
// 在执行命令前先校验参数与选项
|
|
281
297
|
await this.ensureDependencies()
|
|
282
298
|
await this.runStartupChecks()
|
|
283
|
-
this.validateInputs()
|
|
284
299
|
|
|
285
300
|
// 设置详细模式
|
|
286
301
|
if (this.flags.verbose) {
|
package/lib/cli/help.js
CHANGED
|
@@ -55,6 +55,10 @@ DX CLI v${version} - 统一开发环境管理工具
|
|
|
55
55
|
|
|
56
56
|
lint 运行代码检查
|
|
57
57
|
|
|
58
|
+
contracts [generate] 导出后端 OpenAPI 并生成 Zod 合约(packages/api-contracts)
|
|
59
|
+
|
|
60
|
+
release version <semver> 统一同步 backend/front/admin-front 的版本号
|
|
61
|
+
|
|
58
62
|
clean [target] 清理操作
|
|
59
63
|
target: all, deps (默认: all)
|
|
60
64
|
|
|
@@ -93,6 +97,8 @@ DX CLI v${version} - 统一开发环境管理工具
|
|
|
93
97
|
dx worktree list # 列出所有worktree
|
|
94
98
|
dx clean deps # 清理并重新安装依赖
|
|
95
99
|
dx cache clear # 清除 Nx 与依赖缓存
|
|
100
|
+
dx contracts # 导出 OpenAPI 并生成 Zod 合约
|
|
101
|
+
dx release version 1.2.3 # 同步 backend/front/admin-front 版本号
|
|
96
102
|
|
|
97
103
|
# Stagewise 桥接(固定端口,自动清理占用)
|
|
98
104
|
dx start stagewise-front # 桥接 front: 3001 -> 3002(工作目录 apps/front)
|
|
@@ -204,6 +210,41 @@ start 命令用法:
|
|
|
204
210
|
dx start stagewise-front # Stagewise 桥接用户前端,端口 3001 -> 3002
|
|
205
211
|
|
|
206
212
|
提示: service 省略时默认启动 dev 套件,可结合 --dev/--staging/--prod 标志使用。
|
|
213
|
+
`)
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
case 'contracts':
|
|
217
|
+
console.log(`
|
|
218
|
+
contracts 命令用法:
|
|
219
|
+
dx contracts [generate|pull]
|
|
220
|
+
|
|
221
|
+
说明:
|
|
222
|
+
1) 先运行: npx nx run backend:swagger
|
|
223
|
+
2) 再运行: openapi-zod-client 生成 packages/api-contracts/src/generated/backend.ts
|
|
224
|
+
|
|
225
|
+
环境变量:
|
|
226
|
+
OPENAPI_BASE_URL 默认: http://localhost:3000/api/v1
|
|
227
|
+
|
|
228
|
+
示例:
|
|
229
|
+
dx contracts
|
|
230
|
+
OPENAPI_BASE_URL="https://api.example.com/api/v1" dx contracts
|
|
231
|
+
`)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
case 'release':
|
|
235
|
+
console.log(`
|
|
236
|
+
release 命令用法:
|
|
237
|
+
dx release version <semver>
|
|
238
|
+
|
|
239
|
+
说明:
|
|
240
|
+
同步更新以下 package.json 的 version 字段(存在则更新,不存在则跳过):
|
|
241
|
+
- apps/backend/package.json
|
|
242
|
+
- apps/front/package.json
|
|
243
|
+
- apps/admin-front/package.json
|
|
244
|
+
|
|
245
|
+
示例:
|
|
246
|
+
dx release version 1.2.3
|
|
247
|
+
dx release version 1.2.3-beta.1
|
|
207
248
|
`)
|
|
208
249
|
return
|
|
209
250
|
|