文件匹配与 Hooks
filePatterns 的三种匹配模式与 minimatch 规则、after_write Hook 的配置与执行模型、--allow-hooks 授权机制、errorHandling 策略,以及背后的安全设计。
本文讲模板生成流程中两个“精确控制”能力:用 filePatterns 控制哪些文件参与渲染,用 Hooks 在文件写入后执行脚本。两者都在 .agilebuilder.config.yaml 中配置。
filePatterns:文件匹配规则
variables.filePatterns 决定哪些文件的内容会被 EJS 渲染:
variables:
enabled: true
filePatterns:
mode: include
patterns:
- "**/*.ts"
- "**/*.json"
- "README.md"
三种 mode
| mode | 行为 | 典型场景 |
|---|---|---|
all | 渲染所有非二进制文件。 | 小模板,到处都有变量。 |
include | 只渲染匹配任一 pattern 的文件。 | 模板含大量不需要渲染的文档、示例。 |
exclude | 渲染未匹配任何 pattern 的文件。 | 大部分文件要渲染,只排除少数目录。 |
要点:
- 匹配对象是相对模板根目录的路径,统一使用
/分隔符(Windows 下也会转换后匹配)。 - pattern 使用 minimatch 语法,且
dot: true——.github/workflows/ci.yml这类以点开头的文件路径可以被**匹配到。 patterns为空数组或缺失时按["**/*"]处理;mode非法时按all处理。- 不匹配的文件(
include未命中 /exclude命中)按原样复制,内容不变。 - 匹配只在
variables.enabled: true时发生;为false时所有文件原样复制。 - 二进制文件永远不参与渲染,与 mode 无关。
常用 pattern 示例:
| pattern | 匹配 |
|---|---|
**/*.ts | 任意深度的 TypeScript 文件 |
package.json | 仅根目录的 package.json |
src/** | src/ 下所有文件 |
**/*.{ts,tsx} | TypeScript 与 TSX 文件 |
路径渲染不受 filePatterns 影响
filePatterns 只控制文件内容是否渲染。只要 variables.enabled: true,文件路径中的 {{var}} 与 EJS 表达式总会被渲染。
after_write Hook
Hook 在全部文件写入目标目录之后执行。当前只处理 after_write 阶段,且只支持 scriptType: shell:
hooks:
after_write:
scriptType: shell
script: npm install
errorHandling: warn
env:
NPM_CONFIG_REGISTRY: https://registry.npmmirror.com
| 字段 | 默认 | 说明 |
|---|---|---|
scriptType | shell | 配置里可以写 nodejs / custom,但授权后执行会报 HOOK_TYPE_UNSUPPORTED——目前只执行 shell。 |
script | —(必填) | 在目标目录中执行的 shell 命令。 |
errorHandling | stop | 失败处理策略,见下文。 |
env | — | 附加环境变量,与当前进程环境合并后传入。 |
执行模型:
- 工作目录是
ag create --target指定的目标目录,因此npm install、git init这类命令直接作用于生成结果。 - Windows 下优先用
bash -c执行;找不到 bash(例如未安装 Git Bash)时自动回退到系统默认 shell。其他平台直接用系统 shell。 - 超时 5 分钟:超时后先发送
SIGTERM,5 秒宽限后SIGKILL,并报HOOK_TIMEOUT。 - Hook 的 stdout/stderr 直接透传到终端,便于排查。
授权机制:为什么 Hook 默认不执行
模板是从 Git 仓库拉取的第三方代码,而 Hook 是以你的用户权限执行的任意 shell 命令。如果 Hook 默认执行,任何人只要把恶意脚本写进模板的配置文件,就能在生成项目时在你的机器上运行它。因此 AgileBuilder 把 Hook 设计为显式授权:
- 默认不执行:未授权时,
after_write被记录为跳过(hooksSkipped),生成正常完成。 - 单次授权:
ag create ... --allow-hooks,优先级最高。 - 默认授权:
ag config set template.allowHooksDefault true,之后未传--allow-hooks时也执行 Hook。只建议在你信任所有模板来源的环境里开启。
未授权时 CLI 不会静默忽略——生成结果的 hooksSkipped 字段会列出被跳过的 Hook,人类输出和 --json 输出中都能看到。
errorHandling:失败处理策略
| 值 | 行为 |
|---|---|
stop(默认) | Hook 失败即中止生成,报 HOOK_FAILED,进程非零退出。适合“Hook 失败等于项目不可用”的场景(如必须完成的依赖安装)。 |
warn | 失败记录为跳过(hooksSkipped),生成照常完成。适合锦上添花型脚本(如格式化、初始化提交)。 |
continue | 与 warn 行为相同。 |
其他安全设计
生成流程还有几道与 Hook 无关、但同样保护你机器的防线:
- 目标目录黑名单:拒绝写入盘符根目录(如
C:\)、Unix 根目录、C:\Windows、C:\Program Files、C:\Program Files (x86)、C:\Program Files\Git及其子路径,违规则报UNSAFE_TARGET_DIR。 - 非空目录保护:目标目录已存在且非空时必须显式传
--overwrite,否则报TARGET_NOT_EMPTY。 - 子目录逃逸防护:
--subdir与source.subdir不允许绝对路径和..段,解析结果必须留在克隆出的模板目录内,否则报TEMPLATE_SUBDIR_UNSAFE。 - 配置文件不外泄:
.agilebuilder.config.yaml/.agilebuilder.config.json不会复制到目标目录。 .git默认跳过:除非传--keep-git,生成结果不带模板仓库的 Git 历史。
排查建议
- Hook 没执行?先确认传了
--allow-hooks或配置了template.allowHooksDefault: true,再检查生成输出里的hooksSkipped。 - 变量没渲染?按顺序检查:
variables.enabled是否为true→ 文件是否命中filePatterns→ 文件是否为二进制。 - 模板仓库里有不想渲染的示例文件(比如文档里的 EJS 片段)?把它们用
exclude排除,或改用include精确圈定渲染范围。