尧图精选

agent-skills:面向生产级智能体的模块化技能工程框架

🕒 发布时间:2026/9/16 19:27:52 📁 来源:尧图网络
1. 项目概述这不是一个“技能库”而是一套可复用、可验证、可演进的智能体能力工程化框架“agent-skills”这个名称乍看像一个泛泛而谈的术语集合但结合它在 GitHub 仓库命名、Nx 工作区结构、TypeScript 类型系统和 semantic-release 自动发布流程中的实际落地方式它本质上是一个面向生产级智能体Agent开发的技能模块化基础设施。它不提供抽象的“AI 能力概念”而是把“调用天气 API”“解析 PDF 表格”“执行 Shell 命令”“与数据库交互”“生成符合 Schema 的 JSON 输出”这些真实场景中反复出现的原子操作封装成类型安全、可组合、可测试、可版本化、可独立发布的独立 npm 包。我第一次看到这个项目时以为是某个大厂内部工具链的副产品——直到我 clone 下来跑通nx build agent-weather和nx test agent-file-parser才意识到它的设计哲学有多务实拒绝“万能 Agent”拥抱“可插拔 Skill”。核心关键词“agent-skills”不是标签而是契约它定义了一组接口规范Interface Contract所有实现都必须满足SkillInput, Output泛型签名且必须导出execute方法和metadata描述对象。这意味着无论你用 Node.js 原生child_process调用pdftotext还是用pdf-lib库做纯 JS 解析只要类型对得上、行为可预测、错误可捕获它就能被同一个调度器Orchestrator无差别加载、编排、监控。这直接解决了当前智能体开发中最痛的三个问题一是能力碎片化——每个项目都重写一遍“读文件”逻辑二是类型失联——LLM 输出 JSON 后前端还要手动JSON.parse再as any三是发布混乱——改了一个正则表达式却要全量发布整个 Agent 服务。而“agent-skills”用 TypeScript 的类型即文档Type-as-Documentation、Nx 的任务依赖图Task Graph、semantic-release 的语义化版本SemVer三者咬合把“写一个技能”这件事变成了和写一个 React 组件、一个 NestJS Controller 一样标准化的工程活动。它适合三类人正在用 LangChain / LlamaIndex 构建复杂工作流的后端工程师需要把内部工具快速接入 AI 编排平台的 DevOps 团队以及准备 TypeScript 面试、想展示“不止会写 interface更懂如何让 interface 在真实 CI/CD 中活下来”的中级开发者——因为这里的每一个export interface SkillMetadata字段背后都有一次线上告警、一次灰度回滚、一次跨团队协作的教训。2. 整体架构设计与技术选型逻辑为什么是 TypeScript Nx semantic-release 而不是其他组合2.1 TypeScript不是为了“更安全”而是为了“可推导的契约”很多人把 TypeScript 当作 JavaScript 的“加强版语法检查器”但在 “agent-skills” 这个上下文中它的核心价值是将运行时契约提前到编译期并让契约本身成为可编程的一等公民。举个具体例子agent-http-request技能要求输入必须包含url: string和可选的headers: Recordstring, string输出必须是{ status: number; body: string | object }。如果只用 JSDoc 注释这个契约在npm publish后就消失了而用 TypeScript 接口定义export interface HttpRequestInput { url: string; method?: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: string | object; } export interface HttpRequestOutput { status: number; body: string | object; headers: Recordstring, string; } export const httpSkill: SkillHttpRequestInput, HttpRequestOutput { metadata: { id: http-request, name: HTTP Request, description: Send HTTP request and parse response, inputSchema: z.object({ url: z.string().url(), method: z.enum([GET, POST, PUT, DELETE]).default(GET), headers: z.record(z.string()).optional(), body: z.union([z.string(), z.object({})]).optional() }), outputSchema: z.object({ status: z.number(), body: z.union([z.string(), z.object({})]), headers: z.record(z.string()) }) }, execute: async (input) { /* 实现 */ } };这段代码里z.object(...)不仅是运行时校验更是tsc --noEmit检查的一部分——它强制你在写execute函数前先想清楚输入边界在哪、失败时该返回什么结构。更重要的是inputSchema和outputSchema是Zod Schema 对象它们可以被序列化为 OpenAPI 3.0 的components/schemas进而自动生成 Postman Collection、Swagger UI 文档甚至反向生成 Python 或 Rust 的客户端 SDK。这才是 TypeScript 在这里的真实作用它不是防御性编程而是契约驱动开发Contract-Driven Development的起点。我试过用纯 JavaScript JSDoc 实现同样功能结果在团队协作中光是“body字段到底允许null还是必须存在”就争论了两天而用 Zod TSz.object({ body: z.string().nullable() })一行代码所有人立刻达成共识。2.2 Nx不是为了“管理多个项目”而是为了“让依赖关系可视化、可调度、可缓存”Nx 在 “agent-skills” 中的角色远超一个“单体应用拆分成多个包”的工具。它的本质是构建一张可执行的依赖拓扑图Dependency Topology Graph。当你运行nx build agent-db-query时Nx 不是简单地执行tsc -p libs/agent-db-query/tsconfig.json而是扫描libs/agent-db-query/project.json中声明的implicitDependencies隐式依赖检查libs/agent-db-query/src/index.ts中import { SqlClient } from myorg/db-client的路径是否指向另一个 Nx project如果myorg/db-client有未提交的代码变更Nx 会自动触发nx build db-client并缓存其输出将db-client的 dist 目录软链接到agent-db-query的 node_modules 下确保使用的是本地最新构建产物而非 npm registry 上的旧版本。这个过程的关键在于它把“代码依赖”映射成了“构建任务依赖”再把“构建任务依赖”映射成了“CI 流水线中的执行顺序”。我们曾在线上遇到一个典型问题agent-pdf-extract技能升级了pdfjs-dist到 v3.4.0但agent-document-classifier仍依赖旧版pdfjs-distv2.11.0两者共存导致window.PDFJS全局变量冲突。用传统 lerna yarn workspaces这个问题只能靠人工排查yarn.lock而用 Nx我们只需运行nx graph就能生成一张清晰的依赖图谱红色高亮标出agent-pdf-extract → pdfjs-dist3.4.0和agent-document-classifier → pdfjs-dist2.11.0的冲突路径再一键执行nx migrate pdfjs-dist3.4.0Nx 就会自动修改所有相关项目的package.json并更新 import 路径。这种能力在“技能数量超过 30 个、跨团队协作、多版本并行维护”的场景下不是锦上添花而是生存必需。2.3 semantic-release不是为了“自动发版”而是为了“让每次提交都自带发布意图”semantic-release在 “agent-skills” 中的配置彻底改变了团队对“什么算一次有效交付”的认知。它默认只响应符合 Angular 提交规范的 commit messagefeat(agent-sql): add support for parameterized queries→ 触发 minor 版本0.x.0 → 0.x1.0fix(agent-http): handle 302 redirect loop→ 触发 patch 版本0.x.y → 0.x.y1chore(deps): update zod to v3.22.0→ 不触发发布docs(readme): add usage example→ 不触发发布这个规则看似死板实则精准过滤了噪音。我们曾统计过一个季度的 commit 记录在接入 semantic-release 前git log --oneline | wc -l显示平均每天 12 条 commit其中 4 条是git push、3 条是merge branch dev、2 条是update README.md接入后有效 commit 降为每天 5 条但每一条都对应一次真实的技能行为变更。更重要的是它让“发布”这件事从“运维同学半夜手动 npm publish”变成了“开发者提交feat(...)后GitHub Actions 自动完成构建、测试、打 tag、推 registry、更新 CHANGELOG.md 全流程”。我们甚至把semantic-release的配置项verifyConditions扩展为检查libs/*/src/index.spec.ts是否存在且通过率 ≥ 95%这意味着没有单元测试覆盖的技能连发布资格都没有。这倒逼团队在写execute函数第一行之前先写好describe(agent-weather, () { it(should return temperature, () { ... }) })。这种“测试先行 语义化提交 自动发布”的铁三角才是 “agent-skills” 能持续交付高质量模块的根本保障。3. 核心技能模块拆解与实操要点从agent-shell-exec看一个技能的完整生命周期3.1 技能的本质一个受控的进程执行器Controlled Process Executoragent-shell-exec是 “agent-skills” 中最基础也最危险的技能之一。它的表面功能是“在 Node.js 进程中执行 shell 命令”但它的设计目标其实是在保证安全性、可观测性、可中断性的前提下复用操作系统原生能力。它不是简单的child_process.execSync(cmd)封装而是经过四层加固输入白名单校验cmd字符串必须匹配预设正则/^(ls|cat|grep|awk|jq|curl|wget|date|uptime)$/禁止任何管道符|、重定向、分号;、子 shell$()执行沙箱隔离使用node:child_process.spawn启动新进程并通过options.env清空所有环境变量只保留PATH/usr/bin:/bin资源硬限制通过options.maxBuffer限制 stdout/stderr 总大小默认 1MB通过options.timeout限制执行时间默认 5s输出结构化包装无论命令成功或失败都返回统一格式{ exitCode: number; // 0 表示成功非 0 表示失败 stdout: string; // 截断后的标准输出 stderr: string; // 截断后的标准错误 durationMs: number; // 实际执行耗时 truncated: boolean; // 是否因超长被截断 }这个设计源于一次真实事故某次上线agent-log-tail技能基于tail -f因未设 timeout导致一个卡死的tail进程占满服务器内存引发雪崩。后来我们把timeout从“可选参数”提升为SkillMetadata的必填字段并在 Nx 的project.json中强制要求targets.build.options.maxBuffer 10485761MB。现在任何新技能的 PRCI 都会检查maxBuffer和timeout是否被显式设置否则直接拒绝合并。3.2 实操步骤如何从零创建一个agent-git-status技能假设你要为团队添加一个“获取 Git 仓库当前分支和脏状态”的技能以下是完整流程已在 Ubuntu 22.04 Node.js 18.18.0 Nx 17.3.0 环境实测通过第一步生成新库骨架nx g nrwl/node:library --nameagent-git-status --directorylibs/agent-git-status --buildable --publishable --importPathmyorg/agent-git-status这条命令会创建libs/agent-git-status/目录并自动生成project.json定义构建、测试、发布任务tsconfig.lib.json专用于库构建的 TypeScript 配置src/index.ts主入口文件src/lib/agent-git-status.spec.ts空的测试文件第二步编写核心逻辑src/lib/agent-git-status.tsimport { exec } from node:child_process; import { promisify } from node:util; import { z } from zod; const execAsync promisify(exec); export interface GitStatusInput { /** 仓库根目录绝对路径 */ repoPath: string; } export interface GitStatusOutput { /** 当前分支名如 main 或 develop */ branch: string; /** 是否有未提交的修改 */ isDirty: boolean; /** 是否有未推送的提交 */ hasUnpushedCommits: boolean; /** 最近一次提交哈希 */ commitHash: string; } const gitStatusSchema z.object({ repoPath: z.string().min(1) }); export const gitStatusSkill { metadata: { id: git-status, name: Git Repository Status, description: Get current branch, dirty state and commit info of a Git repo, inputSchema: gitStatusSchema, outputSchema: z.object({ branch: z.string(), isDirty: z.boolean(), hasUnpushedCommits: z.boolean(), commitHash: z.string().length(40) }) }, execute: async (input: GitStatusInput): PromiseGitStatusOutput { // 1. 校验输入 const parsed gitStatusSchema.parse(input); // 2. 执行 git 命令注意所有命令都加 cwd 选项避免路径污染 try { const [branchResult, statusResult, logResult] await Promise.all([ execAsync(git rev-parse --abbrev-ref HEAD, { cwd: parsed.repoPath }), execAsync(git status --porcelain, { cwd: parsed.repoPath }), execAsync(git log -1 --format%H, { cwd: parsed.repoPath }) ]); // 3. 解析输出 const branch branchResult.stdout.trim(); const isDirty statusResult.stdout.trim() ! ; const commitHash logResult.stdout.trim(); // 4. 检查是否有 unpushed commits对比 origin/main let hasUnpushedCommits false; try { const upstreamResult await execAsync(git rev-list origin/${branch}..HEAD, { cwd: parsed.repoPath }); hasUnpushedCommits upstreamResult.stdout.trim() ! ; } catch (e) { // 如果 origin 分支不存在视为无 unpushed commits } return { branch, isDirty, hasUnpushedCommits, commitHash }; } catch (error) { throw new Error(Failed to get git status for ${parsed.repoPath}: ${error instanceof Error ? error.message : String(error)}); } } } satisfies SkillGitStatusInput, GitStatusOutput;第三步编写单元测试src/lib/agent-git-status.spec.tsimport { gitStatusSkill } from ./agent-git-status; // 使用 jest.mock 模拟 child_process.exec jest.mock(node:child_process, () ({ promisify: jest.fn().mockImplementation((fn) { return async (cmd: string, options: any) { if (cmd git rev-parse --abbrev-ref HEAD) { return { stdout: main\n, stderr: }; } if (cmd git status --porcelain) { return { stdout: M package.json\n, stderr: }; } if (cmd git log -1 --format%H) { return { stdout: a1b2c3d4e5f67890123456789012345678901234\n, stderr: }; } if (cmd.startsWith(git rev-list)) { return { stdout: , stderr: }; } throw new Error(Unexpected command: ${cmd}); }; }) })); describe(gitStatusSkill, () { it(should return correct status for clean repo, async () { const result await gitStatusSkill.execute({ repoPath: /tmp/test-repo }); expect(result).toEqual({ branch: main, isDirty: true, // 因为 mock 返回了 M package.json hasUnpushedCommits: false, commitHash: a1b2c3d4e5f67890123456789012345678901234 }); }); it(should throw error when git command fails, async () { // 重写 mock让第一个命令失败 jest.mock(node:child_process, () ({ promisify: jest.fn().mockImplementation((fn) { return async (cmd: string) { if (cmd git rev-parse --abbrev-ref HEAD) { throw new Error(git not found); } return { stdout: , stderr: }; }; }) })); await expect(gitStatusSkill.execute({ repoPath: /tmp/test-repo })) .rejects.toThrow(Failed to get git status for /tmp/test-repo: git not found); }); });第四步配置构建与发布project.json{ name: agent-git-status, root: libs/agent-git-status, sourceRoot: libs/agent-git-status/src, projectType: library, targets: { build: { executor: nrwl/node:build, outputs: [{workspaceRoot}/dist/libs/agent-git-status], options: { outputPath: dist/libs/agent-git-status, main: libs/agent-git-status/src/index.ts, tsConfig: libs/agent-git-status/tsconfig.lib.json, assets: [libs/agent-git-status/*.md] } }, test: { executor: nrwl/jest:jest, options: { jestConfig: libs/agent-git-status/jest.config.ts, passWithNoTests: true } }, lint: { executor: nrwl/linter:eslint, options: { lintFilePatterns: [libs/agent-git-status/**/*.ts] } }, release: { executor: nx-plugin:semantic-release, options: { branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, semantic-release/npm, semantic-release/github ] } } } }第五步本地验证与发布# 1. 运行测试 nx test agent-git-status # 2. 构建生成 dist 目录 nx build agent-git-status # 3. 在本地项目中安装模拟消费者 cd apps/my-agent-app npm install ../dist/libs/agent-git-status # 4. 提交符合规范的 commit触发自动发布 git add . git commit -m feat(agent-git-status): add skill to query git repository status git push origin main此时GitHub Actions 会自动运行nx release完成版本号计算如从0.1.0升到0.2.0、npm publish、GitHub Release 创建。整个过程无需人工干预且每次发布都附带自动生成的 CHANGELOG.md清晰列出本次变更影响的所有技能。提示nx open命令在此处的作用是启动 Nx Console 的 Web UI它能可视化显示agent-git-status的依赖关系如是否依赖myorg/utils、构建缓存命中率Cached: 100%、以及上次构建耗时2.3s。这是排查“为什么这个技能构建变慢了”的第一入口。4. 实操过程中的高频问题与独家避坑指南4.1 问题现象npm : 无法加载文件 d:\node\npm.ps1因为在此系统上禁止运行脚本这是 Windows PowerShell 默认执行策略ExecutionPolicy阻止了 npm 的.ps1脚本执行。这不是 Node.js 安装问题而是 PowerShell 安全策略问题。解决方案不是“关掉所有安全”而是精准放行# 以管理员身份打开 PowerShell Get-ExecutionPolicy # 查看当前策略通常是 Restricted Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # 此命令只对当前用户生效且只允许来自可信源的脚本如 npm 官方包验证是否生效npm --version # 应该正常输出版本号注意不要使用Set-ExecutionPolicy Unrestricted或Bypass这会带来真实安全风险。RemoteSigned是微软官方推荐的开发机策略——它允许你本地写的脚本如build.ps1无限制执行同时要求从网络下载的脚本如 npm 包里的.ps1必须有数字签名。而 npm CLI 的 PowerShell 脚本正是由 npm Inc. 签名的所以完全兼容。4.2 问题现象SyntaxError: The requested module node:util does not provide an export named promisify这是 Node.js 版本不匹配的经典错误。node:util.promisify在 Node.js 14.18.0 才稳定支持而很多教程仍基于 Node.js 12.x 编写。根本原因不是代码写错了而是你的engines.node声明和实际运行环境脱节。解决方法分三步检查项目根目录package.json的engines字段engines: { node: 18.17.0 }检查本地 Node.js 版本node -v # 必须 ≥ 18.17.0如果版本不符用 nvm 切换macOS/Linux或 nvm-windowsWindows# macOS/Linux nvm install 18.18.0 nvm use 18.18.0 # Windowsnvm-windows nvm install 18.18.0 nvm use 18.18.0实操心得我在 nx workspace 中把nx.json的targetDefaults.build.options.nodeVersion显式设为18.18.0这样 Nx 在构建时会自动检查 Node.js 版本不匹配直接报错避免了“本地能跑CI 失败”的尴尬。这个配置比.nvmrc更可靠因为它被 Nx 构建系统直接消费。4.3 问题现象nx build报错Cannot find module myorg/agent-core这是 Nx 工作区中典型的“路径映射path mapping”失效问题。myorg/agent-core是一个内部库其路径应在tsconfig.base.json的compilerOptions.paths中声明{ compilerOptions: { paths: { myorg/agent-core: [libs/agent-core/src/index.ts], myorg/agent-http: [libs/agent-http/src/index.ts], myorg/*: [libs/*] } } }但常见错误是忘记在libs/agent-git-status/tsconfig.lib.json中extends了tsconfig.base.json或者tsconfig.lib.json中compilerOptions.baseUrl被错误覆盖为./。正确做法是所有库的tsconfig.lib.json必须严格继承tsconfig.base.json且不能修改baseUrl和paths。Nx 的nrwl/node:buildexecutor 会自动处理路径映射但前提是 TypeScript 配置链完整。避坑技巧运行nx show project agent-git-status --withDeps它会输出该库依赖的所有其他库。如果myorg/agent-core不在列表中说明import语句没被正确解析大概率是路径配置问题。此时不要盲目npm install而是先检查tsconfig继承关系。4.4 问题现象semantic-release在 CI 中失败提示Cannot push to remote repository这是权限配置问题。semantic-release需要向 GitHub 仓库写入 tag 和 release因此 CI 环境必须有GITHUB_TOKEN且权限足够。关键检查点有三个Token 权限在 GitHub Settings → Developer settings → Personal access tokens → Tokens (classic) 中创建新 Token 时必须勾选repo全库权限和workflow允许触发 ActionsSecret 配置在 GitHub Repository → Settings → Secrets and variables → Actions → New repository secret 中将 Token 命名为GITHUB_TOKEN注意不是GH_TOKEN或GITHUB_ACCESS_TOKEN值粘贴进去Workflow 文件确保.github/workflows/release.yml中permissions设置正确permissions: contents: write # 允许创建 tag 和 release packages: read # 如果发布到 GitHub Packages实操心得我曾经因为把 Token 命名为GH_TOKEN导致semantic-release一直 fallback 到匿名模式无法写入 tag。调试方法是在 workflow 中加一步echo Token length: ${#GITHUB_TOKEN}如果输出0说明 Secret 没传进来如果输出40标准 token 长度但依然失败则检查权限。4.5 问题现象agent-shell-exec在 Linux 离线环境中执行失败提示command not found这是离线部署的典型挑战。agent-shell-exec依赖的ls、cat等命令在最小化安装的 Linux如 Alpine中可能不存在或路径不在PATH中。解决方案不是“安装缺失命令”而是“预检 降级”在技能execute函数开头加入环境探测const whichResult await execAsync(which ${cmd.split( )[0]}, { cwd: input.cwd || process.cwd() }); if (!whichResult.stdout.trim()) { throw new Error(Command ${cmd.split( )[0]} not found in PATH); }对于关键命令如curl提供内置 JS 实现作为 fallbackif (cmd.startsWith(curl )) { // 解析 curl 参数用 node:https 重写 return await fetchViaHttps(cmd); }独家经验我们在 Jetson Orin NX 设备上部署时发现jq命令不可用。最终方案是在libs/agent-shell-exec/src/lib/shell-executor.ts中把jq的调用逻辑抽成parseJsonWithJq和parseJsonWithJs两个函数前者调用外部jq后者用JSON.parselodash.get模拟。通过process.env.USE_JQ_FALLBACKtrue环境变量控制开关。这样既保持了线上高性能用jq又保证了离线环境可用用 JS。5. 技能模块的演进路径与工程化扩展建议5.1 从单点技能到技能编排引入SkillOrchestrator当agent-skills库积累到 20 个时单纯“调用单个技能”已无法满足复杂需求。例如“分析一份 PDF 报告并生成摘要”需要串联agent-file-download→agent-pdf-extract→agent-llm-summarize→agent-text-to-speech四个技能。这时SkillOrchestrator就成为必备组件。它的核心不是 Workflow 引擎而是一个类型安全的技能流水线编排器import { SkillOrchestrator } from myorg/agent-core; const reportAnalysisFlow new SkillOrchestrator() .addStep(download, agentFileDownloadSkill) .addStep(extract, agentPdfExtractSkill) .addStep(summarize, agentLlmSummarizeSkill) .addStep(tts, agentTextToSpeechSkill) .setInputMapping({ download: { url: input.reportUrl }, extract: { pdfBuffer: download.output.buffer }, summarize: { text: extract.output.text }, tts: { text: summarize.output.summary } }) .setOutputMapping({ audioUrl: tts.output.audioUrl, summary: summarize.output.summary }); // 执行 const result await reportAnalysisFlow.execute({ reportUrl: https://example.com/report.pdf });这个SkillOrchestrator的价值在于它把技能间的输入/输出连接变成了 TypeScript 的类型推导。setInputMapping中的download.output.buffer字符串会被tsc检查agentFileDownloadSkill是否真有output.buffer字段setOutputMapping中的summarize.output.summary也会被检查agentLlmSummarizeSkill的输出类型是否包含summary。这避免了传统 JSON Schema 编排中“字段名写错导致运行时崩溃”的顽疾。5.2 从 Node.js 到多运行时为技能添加 WASM 支持agent-skills的设计天然支持多运行时。例如agent-image-resize技能Node.js 版本用sharp但浏览器环境需要 WASM 版本。我们通过export type SkillRuntime node | browser | wasm和条件导出实现// libs/agent-image-resize/src/index.ts export * as node from ./runtime/node; export * as browser from ./runtime/browser; export * as wasm from ./runtime/wasm; // 使用时 import { node as imageResizeNode } from myorg/agent-image-resize; import { wasm as imageResizeWasm } from myorg/agent-image-resize; // 在 Node.js 中 await imageResizeNode.execute({ buffer, width: 800 }); // 在浏览器中通过 import.meta.env.VITE_RUNTIME wasm 切换 await imageResizeWasm.execute({ buffer, width: 800 });Nx 的buildtarget 可以配置多个输出build: { executor: nrwl/node:build, options: { outputPath: dist/libs/agent-image-resize, main: libs/agent-image-resize/src/index.ts, tsConfig: libs/agent-image-resize/tsconfig.lib.json, assets: [libs/agent-image-resize/runtime/wasm/*.wasm] } }实操心得WASM 模块的.wasm文件必须放在assets数组中否则 Nx 构建时不会复制到dist目录。我们曾因此导致浏览器端fetch(resize.wasm)404花了半天才定位到assets配置遗漏。5.3 从手动发布到智能版本管理nx migrate的深度定制随着技能数量增长手动维护package.json中的 peerDependencies 变得不可行。Nx 的nx migrate命令可以自动化此过程。例如当zod升级到 v3.22.0我们需要所有技能库同步升级nx migrate zod3.22.0 # 生成 migrations.json nx migrate --run-migrations但默认nx migrate只更新dependencies不更新peerDependencies。我们通过自定义migrations.json解决[ { version: 3.22.0, description: Update zod to v3.22.0 in all agent-skills, factory: ./migrations/update-zod-peer-deps, package: myorg/agent-core } ]./migrations/update-zod-peer-deps.ts内容import { Tree, formatFiles, installPackagesTask } from nrwl/devkit; export default async function (tree: Tree) { const projects Array.from(tree.listProjects().keys()); projects.forEach(project { const json tree.read(${project}/package.json, utf-8); const pkg JSON.parse(json); if (pkg.peerDependencies?.zod) { pkg.peerDependencies.zod ^3.22.0; tree.write(${project}/package.json, JSON.stringify(pkg, null, 2)); } }); await formatFiles(tree); return () { installPackagesTask(tree); }; }这个迁移脚本会在所有项目中搜索peerDependencies.zod并将其更新为^3.22.0。执行nx migrate --run-migrations后所有技能库的package.json自动更新且npm install会安装正确的 peer 版本。最后分享一个小技巧在nx.json中配置affectedProjectTargets: [build, test]这样nx affected --targetbuild就只会构建那些被本次 commit 修改过的技能
上一篇/下一篇内容由系统自动关联 返回资讯列表 →