@weotro/dx 0.1.21 → 0.2.1
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 +78 -766
- package/bin/dx.js +100 -182
- package/example/.env.development +1 -0
- package/example/README.md +24 -0
- package/example/dx/scripts/build +2 -0
- package/example/dx/scripts/db +2 -0
- package/example/dx/scripts/deploy +2 -0
- package/example/dx/scripts/setup +2 -0
- package/example/dx/scripts/start +12 -0
- package/example/dx/scripts/test +2 -0
- package/lib/env.js +40 -257
- package/lib/logger.js +5 -238
- package/lib/setup.js +72 -0
- package/package.json +27 -13
- package/skills/ask_3th/SKILL.md +2 -0
- package/skills/delegate_3th/SKILL.md +4 -2
- package/skills/delegate_3th/scripts/delegate_3th.py +37 -41
- package/skills/delegate_3th/scripts/test_delegate_3th.py +43 -17
- package/skills/dx-adopt/SKILL.md +88 -0
- package/skills/dx-adopt/agents/openai.yaml +2 -0
- package/skills/dx-adopt/references/dx-0.2-contract.md +95 -0
- package/skills/dx-adopt/references/migrate-0.1.md +48 -0
- package/bin/dx-with-version-env.js +0 -8
- package/lib/artifact-deploy/artifact-builder.js +0 -144
- package/lib/artifact-deploy/config.js +0 -180
- package/lib/artifact-deploy/remote-script.js +0 -301
- package/lib/artifact-deploy/remote-transport.js +0 -86
- package/lib/artifact-deploy.js +0 -70
- package/lib/backend-artifact-deploy/artifact-builder.js +0 -267
- package/lib/backend-artifact-deploy/config.js +0 -218
- package/lib/backend-artifact-deploy/path-utils.js +0 -18
- package/lib/backend-artifact-deploy/remote-phases.js +0 -14
- package/lib/backend-artifact-deploy/remote-result.js +0 -44
- package/lib/backend-artifact-deploy/remote-script.js +0 -507
- package/lib/backend-artifact-deploy/remote-transport.js +0 -123
- package/lib/backend-artifact-deploy/rollback.js +0 -5
- package/lib/backend-artifact-deploy/runtime-package.js +0 -46
- package/lib/backend-artifact-deploy.js +0 -91
- package/lib/backend-package.js +0 -674
- package/lib/cli/args.js +0 -38
- package/lib/cli/command-result.js +0 -1
- package/lib/cli/commands/contracts.js +0 -60
- package/lib/cli/commands/core.js +0 -533
- package/lib/cli/commands/db.js +0 -231
- package/lib/cli/commands/deploy.js +0 -175
- package/lib/cli/commands/env.js +0 -120
- package/lib/cli/commands/export.js +0 -39
- package/lib/cli/commands/package.js +0 -22
- package/lib/cli/commands/release.js +0 -55
- package/lib/cli/commands/stack.js +0 -427
- package/lib/cli/commands/start.js +0 -58
- package/lib/cli/commands/worktree.js +0 -145
- package/lib/cli/dx-cli.js +0 -1098
- package/lib/cli/flags.js +0 -123
- package/lib/cli/help-model.js +0 -234
- package/lib/cli/help-renderer.js +0 -137
- package/lib/cli/help-schema.js +0 -552
- package/lib/cli/help.js +0 -141
- package/lib/cli/index.js +0 -4
- package/lib/cli/nx-command.js +0 -13
- package/lib/confirm.js +0 -213
- package/lib/env-policy.js +0 -134
- package/lib/env-profile.js +0 -531
- package/lib/exec.js +0 -698
- package/lib/nx-ignore.js +0 -45
- package/lib/run-with-version-env.js +0 -163
- package/lib/sdk-build.js +0 -424
- package/lib/start-dev.js +0 -401
- package/lib/telegram-webhook.js +0 -431
- package/lib/validate-env.js +0 -317
- package/lib/vercel-deploy.js +0 -549
- package/lib/worktree.js +0 -1052
- package/skills/delegate_3th/scripts/__pycache__/delegate_3th.cpython-314.pyc +0 -0
- package/skills/delegate_3th/scripts/__pycache__/delegate_cc.cpython-314.pyc +0 -0
- package/skills/delegate_3th/scripts/__pycache__/test_delegate_cc.cpython-314.pyc +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dx-adopt
|
|
3
|
+
description: 将使用 @weotro/dx 0.1.x 的项目迁移到 0.2.1,或为新项目、未接入项目直接接入 0.2.1;实现应用脚本、适配环境与 worktree setup,并验证实际开发和 CI 入口。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DX 0.2.1 项目接入与升级
|
|
7
|
+
|
|
8
|
+
在用户指定的应用仓库完成接入;未指定时使用当前仓库。适用于已有 monorepo 和新项目,不要求使用 Nx、Prisma 或 PM2。产出能运行的项目脚本和接入说明,不仅修改版本号。只读咨询则给出基于该项目的差异和迁移方案。
|
|
9
|
+
|
|
10
|
+
本技能随 dx 分发,引用文件均位于本技能目录,安装后无需访问 dx 源码仓库。适用的是 0.2.1 调用契约;项目实际安装包与本文不同时,先核对包版本、CLI 帮助和实现证据再修改。
|
|
11
|
+
|
|
12
|
+
## 1. 确定项目状态
|
|
13
|
+
|
|
14
|
+
读取适用的 AGENTS.md、工作区差异、package.json、锁文件、CI 和实际任务入口。确认目标是使用 dx 的应用,而非误把 dx 工具仓库当成应用。保留现有包管理器和工具链。
|
|
15
|
+
|
|
16
|
+
核对 Node 版本、dx 的实际来源和版本:区分全局 `dx`、项目依赖与 CI 安装步骤。可用 `command -v dx`、`dx --version` 和项目包管理器的依赖查询核对;CLI 不存在时从依赖与调用点继续盘点,不阻塞脚本编写。不要用会自动下载其他版本的命令代替版本核验。
|
|
17
|
+
|
|
18
|
+
按证据选择路径:
|
|
19
|
+
|
|
20
|
+
| 项目状态 | 执行路径 |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| 依赖 0.1.x,或仍使用 dx/config、旧 internal runner、旧 CLI 参数 | 阅读 [0.1.x 迁移](references/migrate-0.1.md),建立旧能力到新脚本的对应关系后实施 |
|
|
23
|
+
| 从未接入 dx,或新建项目直接采用 0.2.1 | 跳过旧配置迁移,从现有工具命令生成下文应用入口 |
|
|
24
|
+
| 已是 0.2.x,但部分入口缺失或行为错误 | 对齐目标版本并对照契约只补缺口,保留已工作的脚本,避免重复生成 |
|
|
25
|
+
|
|
26
|
+
三条路径都先阅读 [0.2.1 能力与调用契约](references/dx-0.2-contract.md)。文件中说明哪些能力由 dx 提供,哪些必须由应用实现。
|
|
27
|
+
|
|
28
|
+
## 2. 落实版本与入口
|
|
29
|
+
|
|
30
|
+
沿用项目的安装方式,将应用依赖、锁文件、开发说明与 CI 对齐到同一个已核验的 0.2.1 包。项目已有 dx 依赖就更新原有声明;全局安装的项目明确记录全局命令和 CI 的安装步骤,不顺手引入第二套入口。检查所用 registry 是否提供目标版本;尚未发布时可使用用户提供的构建包验证,如实记录阻塞,不用 latest 冒充 0.2.1。
|
|
31
|
+
|
|
32
|
+
全局安装采用明确版本的示例为 `npm install -g @weotro/dx@0.2.1`。全局 dx 可在应用尚未安装依赖时运行 setup;只放在应用 devDependencies 中的 dx 不能假定新 clone 后已可执行,必须保留安装 dx 的引导步骤。
|
|
33
|
+
|
|
34
|
+
在约定的项目根创建 `dx/scripts/`,只实现需要的命令:
|
|
35
|
+
|
|
36
|
+
| 入口 | 应用需要实现的内容 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| setup | 基础复制完成后的依赖安装、必要文件生成等准备;可重复执行并保留用户本地配置 |
|
|
39
|
+
| start | 已存在的启动目标;monorepo 的 stack 必须映射到项目定义的全部服务,单应用只启动指定服务 |
|
|
40
|
+
| build | 根据实际目标和 APP_ENV 调用构建工具,明确需要的 NODE_ENV 与工具配置 |
|
|
41
|
+
| test | 单元、集成、E2E 的分流、路径参数和所需服务,不能靠命令名猜测 dx 的环境选择 |
|
|
42
|
+
| db | 实际数据库子命令、前置检查及确认规则 |
|
|
43
|
+
| deploy | 项目部署过程、环境检查、确认、失败处理和恢复 |
|
|
44
|
+
|
|
45
|
+
脚本名无扩展名,带有效 shebang 并设置可执行权限。内部逻辑可放入项目自己的 `.mjs` / `.cjs` 或 Shell 文件;无扩展名 Node 脚本的模块模式应与目标项目及最低 Node 版本匹配,不能假定每个项目都是 ESM。
|
|
46
|
+
|
|
47
|
+
若已有项目脚本可复用,使用薄包装。例如确认 `scripts/dx/build.mjs` 已实现后:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
#!/bin/sh
|
|
51
|
+
exec node scripts/dx/build.mjs "$@"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
包装调用实际工具或实现文件,避免 `dx build` → package script → `dx build` 的循环。不要导入 dx 私有 lib、恢复旧配置解释器,或为每个项目重建通用任务框架。原有 lint 等任务可通过同名脚本继续暴露;`initial` 是 dx 保留命令。
|
|
55
|
+
|
|
56
|
+
实现项目帮助和未知目标的明确失败。需要的入口必须调用真实实现;不需要的入口可以不创建。没有完成的构建、测试、数据库或部署步骤不能用 `echo` / `exit 0` 占位后宣称接入完成。
|
|
57
|
+
|
|
58
|
+
## 3. 接好参数、环境与准备顺序
|
|
59
|
+
|
|
60
|
+
按契约使用 dx 注入的环境,不再自行运行相同两层 dotenv 加载。项目若有额外服务专属文件,明确由哪个脚本加载及覆盖关系。
|
|
61
|
+
|
|
62
|
+
实现和说明 `start` 时,将环境选择与运行位置分开:`dx start admin --prod` 使用当前机器项目根的 `.env.production`、`.env.production.local` 在当前机器启动 admin。`--prod` / `--staging` 等只选择配置及 APP_ENV / NODE_ENV;应用 start 应保持本机启动语义,不将这些标志解释成 SSH、远程启动或部署。具体示例与资源连接边界见契约的“环境”部分。
|
|
63
|
+
|
|
64
|
+
dx 识别环境标志但不会从 argv 删除它们。应用消费首个 `--` 前属于环境选择的标志后,再把工具参数逐项传给工具;`--` 后的数据不得再次当作 dx 标志解析。明确工具是否需要保留或消费这个分隔符。禁止把 argv 重新拼成 shell 命令或用 eval。
|
|
65
|
+
|
|
66
|
+
setup 的顺序是 **dx 复制缺失的 .local 文件 → dx 加载环境 → 应用 setup**。先创建有效的应用 setup 入口,基础复制才会执行。应用 setup 不必再次调用 dx 的基类,也不能递归执行 `dx setup`。新 clone 没有本地凭据来源时,给出准备方法,不能凭空生成有效秘密。
|
|
67
|
+
|
|
68
|
+
对构建、E2E、预发和生产选择分别核对 APP_ENV / NODE_ENV。需要测试层的调用明确使用 `--test` 或 `--e2e`;需要生产构建的应用在自己的构建实现中控制工具行为。不要恢复旧 dx 对 Next/Nx 命令文本的隐式识别。
|
|
69
|
+
|
|
70
|
+
确认 `start stack` 的服务成员、依赖顺序、失败策略和停止方式。多进程由应用收尾,Ctrl-C 与 SIGTERM 处理要幂等;仅创建多个后台进程后返回成功不算启动适配完成。
|
|
71
|
+
|
|
72
|
+
## 4. 更新使用方并验收
|
|
73
|
+
|
|
74
|
+
更新 package scripts、CI、容器或部署调用、开发说明与相关 agent 指令,让它们使用已实现的新入口。旧项目的删除时机与覆盖表按迁移参考执行;新项目无需创建兼容文件或迁移旧功能。
|
|
75
|
+
|
|
76
|
+
使用目标项目自身的验证方式,按实际副作用选择测试环境。项目适配不自动包含生产部署、正式数据库迁移或远端发布;已有明确授权的操作继续执行,否则先用本地、隔离资源或项目已有的模拟入口验证对应链路。不要把真实部署当作通用冒烟。
|
|
77
|
+
|
|
78
|
+
验收应得到以下证据,而非仅列命令:
|
|
79
|
+
|
|
80
|
+
- `dx --version` 对应所选安装源,`dx --help` 在根和子目录发现正确入口;用 Git 文件模式确认脚本执行权限会随 clone 保留。
|
|
81
|
+
- 在隔离工程用非敏感标记值验证两层覆盖、显式环境选择及 argv 边界;不输出真实环境变量集合。
|
|
82
|
+
- 新 clone 的依赖引导能成立;linked worktree 中缺失的 .local 在应用 setup 前可用,已有本地值不覆盖。没有主 checkout 的环境单独记录,不能假定 CI 自动拥有本地文件。
|
|
83
|
+
- 应用 setup、实际构建与相关测试通过。验证 start 的单应用和 stack 成员及退出收尾;无相应服务时注明不适用。
|
|
84
|
+
- db/deploy 若属需求,其参数路由、前置校验与失败行为有测试;未获授权的生产执行记录为未执行,不写成部署验证通过。
|
|
85
|
+
|
|
86
|
+
`dx --help` 只做发现;`dx <command> --help` 会运行应用脚本,尤其 `dx setup --help` 仍会先复制文件。只有确认项目帮助处理无业务副作用后,才把它用作冒烟。不要假定 dx 提供通用 dry-run。
|
|
87
|
+
|
|
88
|
+
交付说明写清项目走哪条路径、安装源与版本、已实现命令及用法、环境文件与 setup 行为、实际验证、未完成项。升级任务同时给出旧能力的去向;首次接入只给出新增入口。未指定提交或发布时,完成本地改动与验证即可。
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# DX 0.2.1 能力与调用契约
|
|
2
|
+
|
|
3
|
+
本文随技能发布,供应用接入时独立使用。实际安装包不同于 0.2.1 时核对行为,不将此表外推为其他版本的保证。
|
|
4
|
+
|
|
5
|
+
## 能力分界
|
|
6
|
+
|
|
7
|
+
| dx 提供 | 应用提供 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| 最近工程发现、脚本检查、进程调用、退出状态 | 构建、测试、数据库和部署的具体实现 |
|
|
10
|
+
| dotenv 两层加载、dotenv-expand 变量展开、环境选择 | 必需变量与占位符校验、额外服务配置 |
|
|
11
|
+
| setup 前的 worktree 本地文件准备 | 安装依赖、生成文件、准备服务 |
|
|
12
|
+
| initial 的包内技能分发 | 目标项目的代码与脚本;initial 不执行接入迁移 |
|
|
13
|
+
| 帮助与版本查询 | 应用子命令帮助、目标选择、编排和恢复 |
|
|
14
|
+
|
|
15
|
+
不内置 Nx、Prisma、PM2、Vercel、SDK 构建、制品部署、回滚、端口清理、自动重试或环境策略配置。这些工具可以继续在应用中使用。
|
|
16
|
+
|
|
17
|
+
## 安装与技能
|
|
18
|
+
|
|
19
|
+
- 包名 `@weotro/dx`,Node.js 至少 20.11.0;支持 macOS/Linux,不支持 Windows。setup 的 Git 定位需要已安装 Git。
|
|
20
|
+
- 全局安装可独立于应用 node_modules 工作;本地依赖需先有安装它的引导途径。运行时依赖由 dx 包自己携带,应用不必为了 dx 的环境注入安装 dotenv CLI。
|
|
21
|
+
- `dx initial` 使用安装包中的 skills/,覆盖更新 `~/.agents/skills` 的托管技能,为 `~/.claude/skills` 建立链接,并按原规则清理托管旧副本和历史已删除技能。其他技能保留。
|
|
22
|
+
- initial 无需应用工程,不加载项目环境,不运行 setup;setup 也不会自动执行 initial。新技能随包安装后,通过 initial 同步;仅把 dx 版本写入 package.json 不等于技能已更新。
|
|
23
|
+
|
|
24
|
+
## 脚本路由
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
项目根/
|
|
28
|
+
dx/scripts/setup
|
|
29
|
+
dx/scripts/start
|
|
30
|
+
dx/scripts/build
|
|
31
|
+
dx/scripts/test
|
|
32
|
+
dx/scripts/db
|
|
33
|
+
dx/scripts/deploy
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
按需提供入口,不要求六个全部存在。自定义名称匹配 `[a-z][a-z0-9-]*`;initial 为保留命令。每个入口为可读、可执行、无扩展名的文件,shebang 解释器使用绝对路径,首行最多 255 字节,不含换行。
|
|
37
|
+
|
|
38
|
+
从调用目录向上找到最近的 `dx/scripts`,对应项目根作为应用脚本 cwd。最近标记损坏、不可读或缺少所请求入口时直接失败;不会执行外层同名命令。未发现内层标记才继续向上查找。
|
|
39
|
+
|
|
40
|
+
`dx build admin --prod` 只运行 `dx/scripts/build`,脚本仍收到 `admin`、`--prod`。没有自动的二级命令查找、app 目录推断、默认 all 或构建编排。`start stack` 与 `start admin` 也调用同一个 start 文件,由应用选择服务。
|
|
41
|
+
|
|
42
|
+
无参数和顶层 `--help` 显示帮助,`--version` 无需工程。`dx initial --help` 是内置帮助;其他命令后的 `--help` 交给应用,不由 dx 截获。
|
|
43
|
+
|
|
44
|
+
## 环境
|
|
45
|
+
|
|
46
|
+
**环境名称选择配置,执行位置是运行 dx 的当前机器。** 例如在开发电脑执行 `dx start admin --prod`,dx 读取该电脑项目根的 `.env.production`,再加载 `.env.production.local` 覆盖,设置 APP_ENV / NODE_ENV 为 production,并在该电脑调用 `dx/scripts/start` 启动 admin。它不表示到生产服务器运行,也不触发远程连接、发布或部署;`--staging` 等标志同理。在服务器执行时,当前机器就是那台服务器。
|
|
47
|
+
|
|
48
|
+
应用 start 保持本机启动语义;需要远程部署时,由应用 deploy 等明确入口实现。进程访问哪些数据库或 API 取决于配置中的地址:本机运行仍可能连接远端资源,运行位置与资源位置应分别说明。
|
|
49
|
+
|
|
50
|
+
从找到的项目根加载,优先级由低到高:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
继承环境变量 < .env.<environment> < .env.<environment>.local
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
这意味着 CI 注入的普通变量也可能被文件中的同名值覆盖。公开文件只放适合入库的默认值或明确占位符,真实值放入忽略的 local 文件或按项目方式注入;项目脚本自己校验必要值。
|
|
57
|
+
|
|
58
|
+
环境选择按下列顺序:
|
|
59
|
+
|
|
60
|
+
1. 命令后、第一个 `--` 前的环境标志;优先级依次为 `--prod`、`--staging`、`--dev`、`--test`、`--e2e`。建议调用方只使用一个。
|
|
61
|
+
2. 调用者 APP_ENV,其次 NODE_ENV;都没有时为 development。选择名称转小写,dev/local 映射为 development;不支持的名称报错。
|
|
62
|
+
|
|
63
|
+
| 选定环境 / APP_ENV | 注入的 NODE_ENV | 两层文件 |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| development | development | .env.development、.env.development.local |
|
|
66
|
+
| staging | production | .env.staging、.env.staging.local |
|
|
67
|
+
| production | production | .env.production、.env.production.local |
|
|
68
|
+
| test | test | .env.test、.env.test.local |
|
|
69
|
+
| e2e | test | .env.e2e、.env.e2e.local |
|
|
70
|
+
|
|
71
|
+
APP_ENV 和 NODE_ENV 在展开变量前确定,不允许文件重新设置。不从环境文件中读取 APP_ENV 来决定加载哪套文件。命令名本身不会改环境:未指定环境的 `dx test` 或 `dx build` 不会自动选择 test 或 production。
|
|
72
|
+
|
|
73
|
+
文件缺失跳过,读取失败则停止;只修改传给应用的子进程环境。dotenv 负责引号、注释和多行值,dotenv-expand 按其语法展开同层已定义变量、前层和调用者变量。迁移旧手写解析或 shell source 时,应使用非敏感样例复核 `$`、引号、转义及依赖顺序,不能当作 shell 脚本执行。
|
|
74
|
+
|
|
75
|
+
不加载通用 `.env` / `.env.local`、应用子目录文件或 `dx/config/env-layers.json`。`DX_CONFIG_DIR` 和顶层 `--config-dir` 不再选择工程或配置。选择环境的标志保留在 argv;`--` 后的标志只属于应用参数。
|
|
76
|
+
|
|
77
|
+
## setup 的公共步骤
|
|
78
|
+
|
|
79
|
+
必须先有合法 `dx/scripts/setup`。入口检查通过后,顺序为复制 → 环境加载 → 应用脚本。
|
|
80
|
+
|
|
81
|
+
- 当前是 linked worktree 时,从 Git 登记的主工作目录递归复制所有以 `.local` 结尾的普通文件,含隐藏与被 Git 忽略的文件,按相对路径写入当前 Git worktree 根并保留文件权限。
|
|
82
|
+
- 跳过已经存在的文件、目录和链接;不覆盖、不更新已有副本,不会在每次 start/build 时同步。
|
|
83
|
+
- 跳过源符号链接、`.git` 元数据和嵌套 worktree;目标父路径必须是实际目录。失败停止应用 setup;已成功复制的文件保留,修复后可再次执行。
|
|
84
|
+
- 主工作目录(含从其子目录调用)、非 Git 工程、bare 仓库下没有主 checkout 的 worktree 不复制。Git 不可用会报错。
|
|
85
|
+
- 项目根若是 Git 根的子目录,复制目标仍是 Git worktree 根,环境加载位置仍是项目根。
|
|
86
|
+
|
|
87
|
+
复制 `.env.local` 不代表会加载它;加载始终只认选定环境的两层文件。新 clone 不共享另一个 clone 的本地文件,也不会自动向远端下载凭据。
|
|
88
|
+
|
|
89
|
+
## 进程与操作责任
|
|
90
|
+
|
|
91
|
+
argv 逐项传递,空字符串、引号、空格、`--` 保留;dx 不拼接 shell 字符串。stdin/stdout/stderr 直接继承,应用退出码保留。
|
|
92
|
+
|
|
93
|
+
脚本保持前台终端;dx 转发 SIGINT/SIGTERM,并保留应用最终信号或退出码。终端 Ctrl-C 可能与转发同时到达,应用应幂等清理自己启动的进程。dx 不监督任意后代或后台服务。
|
|
94
|
+
|
|
95
|
+
单进程 Shell 包装优先使用 `exec`。多服务启动、端口冲突、工具版本、秘密校验、数据库确认和部署恢复均由应用完成;没有通用 `--yes`、`--dry-run` 或自动修复能力。
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# 从 0.1.x 迁移
|
|
2
|
+
|
|
3
|
+
仅用于已有旧版调用的项目。0.2.0 起采用硬切换,本次接入目标为 0.2.1:旧命令表和内部 runner 不再执行,迁移的目标是把项目实际需要的行为放入应用脚本,而非重建旧 dx。
|
|
4
|
+
|
|
5
|
+
## 先盘点,再替换
|
|
6
|
+
|
|
7
|
+
检查项目真实使用的 `dx/config/commands.json`、环境层/策略配置、包脚本、CI、容器入口、Nx 等工具目标和项目已有实现。只记录环境文件路径与变量名,避免将秘密值复制进报告。旧版本实现只有在行为无法由项目源码解释时才按实际版本查证。
|
|
8
|
+
|
|
9
|
+
建立一张迁移表,每项包含:旧入口与调用者、实际执行内容、环境、顺序/并发关系、安全条件、新入口/实现文件、验证方式与结果。不能仅把旧命令名改成新命令名;同一个 `dx build admin` 在新版本下也需要实际的 build 文件。
|
|
10
|
+
|
|
11
|
+
## 旧能力的去向
|
|
12
|
+
|
|
13
|
+
| 旧依赖或配置 | 0.2.1 项目应做什么 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| commands.json 的 command、嵌套子命令和环境分支 | 对应顶层 dx/scripts 文件,内部自行解析目标与子命令,根据 APP_ENV 调用真实实现 |
|
|
16
|
+
| concurrent、commands 引用、all/development 聚合 | 在项目脚本中明确任务成员、顺序/并发、失败传播与退出收尾 |
|
|
17
|
+
| internal: pm2-stack、backend-artifact-deploy 等 | 直接使用项目工具完成原先需要的流程;不能保留 internal 字段期待 dx 执行 |
|
|
18
|
+
| app、目标别名、自动推断目录、requiresPath/fileCommand | 在项目脚本验证目标和路径并按 argv 调用;旧占位符替换不再发生 |
|
|
19
|
+
| env-layers.json、自定义环境路径、{app} 层 | 迁到标准两层,或由应用明确加载额外文件;通用 .env 和 .env.local 不参与 dx 加载 |
|
|
20
|
+
| env-policy、requiredVars、占位符检查、dangerous/确认 | 由实际 db/deploy 等入口执行必要检查与确认,不能因删配置而丢掉原保护 |
|
|
21
|
+
| ports、自动终止服务、重试、Prisma 自动生成 | 将必要准备放入 setup 或具体应用流程;启动失败不再由 dx 自动修复 |
|
|
22
|
+
| Next/Nx 构建识别、强制 NODE_ENV、轮询变量 | 核对项目工具要求,在项目实现中显式设置;不要假定 dx 仍设置 NX_DAEMON 等变量 |
|
|
23
|
+
| SDK 构建、制品上传、Vercel、回滚、版本注入 | 项目维护自己的构建/发布脚本和故障处理;dx 不再提供这些实现 |
|
|
24
|
+
| dx-with-version-env、私有 lib import、旧 bin 路径 | 改成项目自己的实现及公开 dx 脚本入口,检查 package scripts、CI 和容器调用点 |
|
|
25
|
+
| dx install、lint、clean、cache 等旧命令 | 按项目需要保留为自定义脚本,或把其调用者改成实际工具;标准安装准备可归入 setup |
|
|
26
|
+
| --config-dir、DX_CONFIG_DIR、自定义根定位 | 在目标工程建立 dx/scripts 标记,调整调用 cwd;旧配置目录不再定义工程 |
|
|
27
|
+
| 技能分发 | skills 与 dx initial 仍保留。不要把 initial 删除或误改为项目 setup |
|
|
28
|
+
|
|
29
|
+
## 实施顺序
|
|
30
|
+
|
|
31
|
+
1. 保留旧实现证据,完成迁移表。只清理已确认没有消费者的旧功能;需求不明确但仍被 CI 使用的行为先保留在项目侧。
|
|
32
|
+
2. 创建必需的应用脚本及内部实现。使用实际工具命令,避免复制整套旧 dx 私有运行时。每条分支有成功、失败及参数处理行为;项目帮助应在业务操作前处理。
|
|
33
|
+
3. 整理环境。原先的普通环境变量优先级、APP_ENV/NODE_ENV 关系、构建和测试环境逐项复核;以随技能的 [当前契约](dx-0.2-contract.md) 为准。已有 dotenv 包若仍被应用其他模块使用,应保留。
|
|
34
|
+
4. 更新安装版本与调用者,进行本地/隔离验收。CI 显式安装同版本 dx,保证创建脚本时的可执行权限入库。仅设置 CI secret 不足以证明未被文件同名值覆盖。
|
|
35
|
+
5. 消费者和验证均切换后,删除无人使用的旧 dx 配置、包装和依赖,更新文档。保留 Prisma schema、PM2 ecosystem、Nx 配置等仍被项目脚本使用的工具文件。
|
|
36
|
+
|
|
37
|
+
## 容易漏掉的行为
|
|
38
|
+
|
|
39
|
+
- `dx build admin --prod` 中的 --prod 仍进入 build 的 argv。直接 `exec nx build "$@"` 可能把 dx 标志误传给 Nx;在项目解析层消费环境标志,正确处理 `--` 边界。
|
|
40
|
+
- `.env.<环境>.local` 中已有值不会被基础 setup 更新;旧 worktree 需要同步新增配置时明确处理缺项,不能通过再次 setup 假定全部刷新。
|
|
41
|
+
- 缺少环境文件不会让 dx 自行阻止数据库或部署。需要的变量、生产目标、不可逆操作和失败恢复须在应用中验证;不能删除原确认条件后裸调用工具。
|
|
42
|
+
- `start stack` 不再隐含 PM2 重置或端口清理。启动哪些服务、是否清理冲突端口、如何退出由项目确定。
|
|
43
|
+
- 测试文件参数不再由 dx 的 fileCommand 模板转换;验证文件和目录范围都传给了正确测试目标。
|
|
44
|
+
- 新的 NODE_ENV 映射与命令名无关。旧项目若依赖“build 一定 production”或“test 一定 test”,调整调用参数或项目工具逻辑并测试。
|
|
45
|
+
|
|
46
|
+
## 完成条件
|
|
47
|
+
|
|
48
|
+
迁移表中每个实际使用的旧能力都已实现、替换或在用户授权下明确取消;无未解释的旧私有接口与配置消费者;根目录、子目录、fresh clone/worktree 和 CI 的安装与调用方式明确。报告实际验证结果,不能把假脚本返回成功或未执行的部署写成迁移完成。
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { cp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { basename, dirname, join } from 'node:path'
|
|
5
|
-
import { promisify } from 'node:util'
|
|
6
|
-
import { execManager } from '../exec.js'
|
|
7
|
-
import { resolveWithinBase } from '../backend-artifact-deploy/path-utils.js'
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile)
|
|
10
|
-
const tarEnv = {
|
|
11
|
-
...process.env,
|
|
12
|
-
COPYFILE_DISABLE: '1',
|
|
13
|
-
COPY_EXTENDED_ATTRIBUTES_DISABLE: '1',
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function assertSafeNamePart(value, label) {
|
|
17
|
-
const text = String(value || '').trim()
|
|
18
|
-
if (!text || text.includes('/') || text.includes('\\') || text.includes('..')) {
|
|
19
|
-
throw new Error(`${label} 越界,已拒绝: ${text}`)
|
|
20
|
-
}
|
|
21
|
-
return text
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function defaultNowTag() {
|
|
25
|
-
const now = new Date()
|
|
26
|
-
const pad = value => String(value).padStart(2, '0')
|
|
27
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function defaultRunBuild(build, environment) {
|
|
31
|
-
await execManager.executeCommand(build.command, {
|
|
32
|
-
app: build.app || undefined,
|
|
33
|
-
skipEnvValidation: !build.app,
|
|
34
|
-
flags: environment === 'production'
|
|
35
|
-
? { prod: true }
|
|
36
|
-
: environment === 'staging'
|
|
37
|
-
? { staging: true }
|
|
38
|
-
: { dev: true },
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function defaultReadVersion(config) {
|
|
43
|
-
if (config.artifact.version) return config.artifact.version
|
|
44
|
-
if (config.build.versionCommand) {
|
|
45
|
-
const { stdout } = await execFileAsync('bash', ['-lc', config.build.versionCommand], {
|
|
46
|
-
cwd: config.projectRoot,
|
|
47
|
-
env: process.env,
|
|
48
|
-
})
|
|
49
|
-
return String(stdout).trim()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const parsed = JSON.parse(await readFile(config.build.versionFile, 'utf8'))
|
|
53
|
-
return String(parsed.version || '').trim()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function defaultStageFiles(sourceDir, stageDir) {
|
|
57
|
-
if (!existsSync(sourceDir)) throw new Error(`缺少待打包目录: ${sourceDir}`)
|
|
58
|
-
await rm(stageDir, { recursive: true, force: true })
|
|
59
|
-
await mkdir(stageDir, { recursive: true })
|
|
60
|
-
for (const entry of await readdir(sourceDir)) {
|
|
61
|
-
await cp(join(sourceDir, entry), join(stageDir, entry), { recursive: true })
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function defaultAssertNoEnvFiles(stageDir) {
|
|
66
|
-
const queue = ['.']
|
|
67
|
-
const envFiles = []
|
|
68
|
-
while (queue.length > 0) {
|
|
69
|
-
const relativeDir = queue.shift()
|
|
70
|
-
const currentDir = relativeDir === '.' ? stageDir : join(stageDir, relativeDir)
|
|
71
|
-
for (const entry of await readdir(currentDir, { withFileTypes: true })) {
|
|
72
|
-
const relativePath = relativeDir === '.' ? entry.name : join(relativeDir, entry.name)
|
|
73
|
-
if (entry.name.startsWith('.env')) envFiles.push(relativePath.replace(/\\/g, '/'))
|
|
74
|
-
if (entry.isDirectory()) queue.push(relativePath)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (envFiles.length > 0) throw new Error(`制品目录包含 .env* 文件: ${envFiles.join(', ')}`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function createArtifactNames({ version, timeTag, bundleName, releaseName }) {
|
|
81
|
-
const safeVersion = assertSafeNamePart(version, 'version')
|
|
82
|
-
const safeTimeTag = assertSafeNamePart(timeTag, 'timeTag')
|
|
83
|
-
const safeBundleName = assertSafeNamePart(bundleName, 'bundleName')
|
|
84
|
-
const safeReleaseName = assertSafeNamePart(releaseName, 'releaseName')
|
|
85
|
-
const versionName = `${safeReleaseName}-v${safeVersion}-${safeTimeTag}`
|
|
86
|
-
const innerArchiveName = `${versionName}.tgz`
|
|
87
|
-
return {
|
|
88
|
-
versionName,
|
|
89
|
-
innerArchiveName,
|
|
90
|
-
checksumName: `${innerArchiveName}.sha256`,
|
|
91
|
-
bundleName: `${safeBundleName}-v${safeVersion}-${safeTimeTag}.tgz`,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function buildArtifact(config, deps = {}) {
|
|
96
|
-
const runBuild = deps.runBuild || defaultRunBuild
|
|
97
|
-
const readVersion = deps.readVersion || defaultReadVersion
|
|
98
|
-
const nowTag = deps.nowTag || defaultNowTag
|
|
99
|
-
const stageFiles = deps.stageFiles || defaultStageFiles
|
|
100
|
-
const assertNoEnvFiles = deps.assertNoEnvFiles || defaultAssertNoEnvFiles
|
|
101
|
-
const version = await readVersion(config)
|
|
102
|
-
if (!version) throw new Error('无法解析制品版本')
|
|
103
|
-
|
|
104
|
-
const names = createArtifactNames({
|
|
105
|
-
version,
|
|
106
|
-
timeTag: nowTag(),
|
|
107
|
-
bundleName: config.artifact.bundleName,
|
|
108
|
-
releaseName: config.artifact.releaseName,
|
|
109
|
-
})
|
|
110
|
-
const outputDir = resolveWithinBase(config.artifact.outputDir, '.', 'artifact.outputDir')
|
|
111
|
-
const stageDir = resolveWithinBase(outputDir, names.versionName, 'stageDir')
|
|
112
|
-
const innerArchivePath = resolveWithinBase(outputDir, names.innerArchiveName, 'innerArchivePath')
|
|
113
|
-
const checksumPath = resolveWithinBase(outputDir, names.checksumName, 'checksumPath')
|
|
114
|
-
const bundlePath = resolveWithinBase(outputDir, names.bundleName, 'bundlePath')
|
|
115
|
-
|
|
116
|
-
await runBuild(config.build, config.environment)
|
|
117
|
-
await mkdir(outputDir, { recursive: true })
|
|
118
|
-
await stageFiles(config.build.sourceDir, stageDir)
|
|
119
|
-
await assertNoEnvFiles(stageDir)
|
|
120
|
-
await execFileAsync('tar', ['-czf', innerArchivePath, '.'], { cwd: stageDir, env: tarEnv })
|
|
121
|
-
|
|
122
|
-
const archiveName = basename(innerArchivePath)
|
|
123
|
-
let checksum
|
|
124
|
-
try {
|
|
125
|
-
checksum = await execFileAsync('sha256sum', [archiveName], { cwd: dirname(innerArchivePath) })
|
|
126
|
-
} catch {
|
|
127
|
-
checksum = await execFileAsync('shasum', ['-a', '256', archiveName], { cwd: dirname(innerArchivePath) })
|
|
128
|
-
}
|
|
129
|
-
await writeFile(checksumPath, checksum.stdout)
|
|
130
|
-
await execFileAsync('tar', ['-czf', bundlePath, archiveName, basename(checksumPath)], {
|
|
131
|
-
cwd: outputDir,
|
|
132
|
-
env: tarEnv,
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
version,
|
|
137
|
-
versionName: names.versionName,
|
|
138
|
-
innerArchiveName: names.innerArchiveName,
|
|
139
|
-
checksumName: names.checksumName,
|
|
140
|
-
bundlePath,
|
|
141
|
-
innerArchivePath,
|
|
142
|
-
checksumPath,
|
|
143
|
-
}
|
|
144
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { isAbsolute } from 'node:path'
|
|
2
|
-
import { resolveWithinBase } from '../backend-artifact-deploy/path-utils.js'
|
|
3
|
-
|
|
4
|
-
function requireString(value, fieldPath) {
|
|
5
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
6
|
-
throw new Error(`缺少必填配置: ${fieldPath}`)
|
|
7
|
-
}
|
|
8
|
-
return value.trim()
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function optionalString(value) {
|
|
12
|
-
return typeof value === 'string' && value.trim() ? value.trim() : null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function requireSafeName(value, fieldPath) {
|
|
16
|
-
const name = requireString(value, fieldPath)
|
|
17
|
-
if (!/^[A-Za-z0-9._-]+$/.test(name) || name.includes('..')) {
|
|
18
|
-
throw new Error(`${fieldPath} 包含非法字符: ${name}`)
|
|
19
|
-
}
|
|
20
|
-
return name
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function requirePositiveInteger(value, fieldPath) {
|
|
24
|
-
const parsed = Number(value)
|
|
25
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
26
|
-
throw new Error(`缺少必填配置: ${fieldPath}`)
|
|
27
|
-
}
|
|
28
|
-
return parsed
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function resolveProjectPath(projectRoot, value, fieldPath) {
|
|
32
|
-
return resolveWithinBase(projectRoot, requireString(value, fieldPath), fieldPath)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function requireRemoteBaseDir(value, fieldPath) {
|
|
36
|
-
const baseDir = requireString(value, fieldPath)
|
|
37
|
-
if (!isAbsolute(baseDir)) throw new Error(`${fieldPath} 必须是绝对路径: ${baseDir}`)
|
|
38
|
-
if (!/^\/[A-Za-z0-9._/-]*$/.test(baseDir)) {
|
|
39
|
-
throw new Error(`${fieldPath} 包含非法字符: ${baseDir}`)
|
|
40
|
-
}
|
|
41
|
-
return baseDir.replace(/\/+$/, '') || '/'
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function resolveRemoteConfig(remoteConfig, environment) {
|
|
45
|
-
if (!remoteConfig || typeof remoteConfig !== 'object') return null
|
|
46
|
-
if (typeof remoteConfig.host === 'string') return remoteConfig
|
|
47
|
-
|
|
48
|
-
const selected = remoteConfig[environment]
|
|
49
|
-
if (!selected || typeof selected !== 'object') {
|
|
50
|
-
throw new Error(`缺少必填配置: remote.${environment}`)
|
|
51
|
-
}
|
|
52
|
-
return selected
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function resolveBuildCommand(buildConfig, environment) {
|
|
56
|
-
if (buildConfig?.commands && typeof buildConfig.commands === 'object') {
|
|
57
|
-
return requireString(buildConfig.commands[environment], `build.commands.${environment}`)
|
|
58
|
-
}
|
|
59
|
-
return requireString(buildConfig?.command, 'build.command')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function resolveHealthCheck(healthCheckConfig) {
|
|
63
|
-
if (healthCheckConfig == null) return null
|
|
64
|
-
const url = requireString(healthCheckConfig.url, 'verify.healthCheck.url')
|
|
65
|
-
try {
|
|
66
|
-
new URL(url)
|
|
67
|
-
} catch {
|
|
68
|
-
throw new Error('缺少必填配置: verify.healthCheck.url')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
url,
|
|
73
|
-
timeoutSeconds: healthCheckConfig.timeoutSeconds == null
|
|
74
|
-
? 10
|
|
75
|
-
: requirePositiveInteger(healthCheckConfig.timeoutSeconds, 'verify.healthCheck.timeoutSeconds'),
|
|
76
|
-
maxWaitSeconds: healthCheckConfig.maxWaitSeconds == null
|
|
77
|
-
? 24
|
|
78
|
-
: requirePositiveInteger(healthCheckConfig.maxWaitSeconds, 'verify.healthCheck.maxWaitSeconds'),
|
|
79
|
-
retryIntervalSeconds: healthCheckConfig.retryIntervalSeconds == null
|
|
80
|
-
? 2
|
|
81
|
-
: requirePositiveInteger(
|
|
82
|
-
healthCheckConfig.retryIntervalSeconds,
|
|
83
|
-
'verify.healthCheck.retryIntervalSeconds',
|
|
84
|
-
),
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function resolveArtifactDeployConfig({ cli, target, targetConfig, environment, flags = {} }) {
|
|
89
|
-
const deployConfig = targetConfig?.artifactDeploy
|
|
90
|
-
if (!deployConfig || typeof deployConfig !== 'object') {
|
|
91
|
-
throw new Error('缺少必填配置: artifactDeploy')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const buildConfig = deployConfig.build || {}
|
|
95
|
-
const artifactConfig = deployConfig.artifact || {}
|
|
96
|
-
const startupConfig = deployConfig.startup || {}
|
|
97
|
-
const runConfig = deployConfig.deploy || {}
|
|
98
|
-
const verifyConfig = deployConfig.verify || {}
|
|
99
|
-
const buildOnly = Boolean(flags.buildOnly)
|
|
100
|
-
const remoteConfig = resolveRemoteConfig(deployConfig.remote, environment)
|
|
101
|
-
const startupMode = optionalString(startupConfig.mode) || 'command'
|
|
102
|
-
|
|
103
|
-
if (!['systemd', 'command'].includes(startupMode)) {
|
|
104
|
-
throw new Error('startup.mode 仅支持 systemd 或 command')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const versionFile = optionalString(buildConfig.versionFile)
|
|
108
|
-
const versionCommand = optionalString(buildConfig.versionCommand)
|
|
109
|
-
const version = optionalString(artifactConfig.version)
|
|
110
|
-
if (!versionFile && !versionCommand && !version) {
|
|
111
|
-
throw new Error('缺少必填配置: build.versionFile、build.versionCommand 或 artifact.version')
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const serviceName = optionalString(startupConfig.serviceName)
|
|
115
|
-
const startupCommand = optionalString(startupConfig.command)
|
|
116
|
-
if (serviceName && !/^[A-Za-z0-9_.@-]+$/.test(serviceName)) {
|
|
117
|
-
throw new Error(`startup.serviceName 包含非法字符: ${serviceName}`)
|
|
118
|
-
}
|
|
119
|
-
if (startupMode === 'systemd' && !serviceName) {
|
|
120
|
-
throw new Error('缺少必填配置: startup.serviceName')
|
|
121
|
-
}
|
|
122
|
-
if (startupMode === 'command' && !startupCommand) {
|
|
123
|
-
throw new Error('缺少必填配置: startup.command')
|
|
124
|
-
}
|
|
125
|
-
const healthCheck = resolveHealthCheck(verifyConfig.healthCheck)
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
projectRoot: cli.projectRoot,
|
|
129
|
-
target,
|
|
130
|
-
environment,
|
|
131
|
-
build: {
|
|
132
|
-
app: optionalString(buildConfig.app),
|
|
133
|
-
command: resolveBuildCommand(buildConfig, environment),
|
|
134
|
-
sourceDir: resolveProjectPath(cli.projectRoot, buildConfig.sourceDir, 'build.sourceDir'),
|
|
135
|
-
versionFile: versionFile
|
|
136
|
-
? resolveProjectPath(cli.projectRoot, versionFile, 'build.versionFile')
|
|
137
|
-
: null,
|
|
138
|
-
versionCommand,
|
|
139
|
-
},
|
|
140
|
-
artifact: {
|
|
141
|
-
outputDir: resolveProjectPath(cli.projectRoot, artifactConfig.outputDir, 'artifact.outputDir'),
|
|
142
|
-
bundleName: requireSafeName(artifactConfig.bundleName, 'artifact.bundleName'),
|
|
143
|
-
releaseName: requireSafeName(
|
|
144
|
-
optionalString(artifactConfig.releaseName) || String(target),
|
|
145
|
-
'artifact.releaseName',
|
|
146
|
-
),
|
|
147
|
-
version,
|
|
148
|
-
},
|
|
149
|
-
remote: buildOnly
|
|
150
|
-
? null
|
|
151
|
-
: {
|
|
152
|
-
host: requireString(remoteConfig?.host, 'remote.host'),
|
|
153
|
-
port: remoteConfig?.port == null ? 22 : requirePositiveInteger(remoteConfig.port, 'remote.port'),
|
|
154
|
-
user: requireString(remoteConfig?.user, 'remote.user'),
|
|
155
|
-
baseDir: requireRemoteBaseDir(remoteConfig?.baseDir, 'remote.baseDir'),
|
|
156
|
-
},
|
|
157
|
-
startup: {
|
|
158
|
-
mode: startupMode,
|
|
159
|
-
serviceName,
|
|
160
|
-
command: startupCommand,
|
|
161
|
-
rollbackCommand: optionalString(startupConfig.rollbackCommand),
|
|
162
|
-
},
|
|
163
|
-
deploy: {
|
|
164
|
-
keepReleases: runConfig.keepReleases == null
|
|
165
|
-
? 5
|
|
166
|
-
: requirePositiveInteger(runConfig.keepReleases, 'deploy.keepReleases'),
|
|
167
|
-
installCommand: optionalString(runConfig.installCommand),
|
|
168
|
-
},
|
|
169
|
-
verify: {
|
|
170
|
-
command: optionalString(verifyConfig.command),
|
|
171
|
-
maxWaitSeconds: verifyConfig.maxWaitSeconds == null
|
|
172
|
-
? healthCheck?.maxWaitSeconds || 24
|
|
173
|
-
: requirePositiveInteger(verifyConfig.maxWaitSeconds, 'verify.maxWaitSeconds'),
|
|
174
|
-
retryIntervalSeconds: verifyConfig.retryIntervalSeconds == null
|
|
175
|
-
? healthCheck?.retryIntervalSeconds || 2
|
|
176
|
-
: requirePositiveInteger(verifyConfig.retryIntervalSeconds, 'verify.retryIntervalSeconds'),
|
|
177
|
-
healthCheck,
|
|
178
|
-
},
|
|
179
|
-
}
|
|
180
|
-
}
|