资讯动态

TypeScript工程化基座:Nx+semantic-release构建可复用技能包

发布时间:2026/9/16 8:20:11 来源:尧图企业网站定制
1. 项目概述一个被严重低估的 TypeScript 工程化能力基座“agent-skills”这个名称乍看像某个 AI 智能体的技能插件库但结合热搜词agent-skills、TypeScript、node、Nx、semantic-release再叠加全网高频出现的typescript面试、nx二次开发、typescript nestjs、node安装及环境配置等长尾搜索行为真相就清晰了这不是一个面向终端用户的“功能模块”而是一套面向企业级 TypeScript 工程师团队的、可复用、可发布、可版本化管理的“能力原子”工程实践范式。它本质上是把“一个合格的 TypeScript 工程师在真实项目中反复手写、调试、维护的通用逻辑片段”抽象成标准化、类型安全、语义化发布的 npm 包集合——比如HTTP 请求重试策略封装、文件路径安全解析器、JSON Schema 校验工具链、CLI 参数解析增强器、Nx 插件生命周期钩子适配层、NestJS 微服务通信中间件桥接器……这些不是 demo而是每天在 CI/CD 流水线里跑、在 prod 环境里扛压、被 3 个以上业务仓库直接 import 的“生产级技能”。我带过 7 个中大型前端/全栈团队发现一个共性痛点新人入职后花 2~3 周才能搞懂“我们项目里那个 requestWithRetry 是怎么写的”老手想复用自己写的 config loader却要手动拷贝 5 个文件改 3 处路径补 2 个类型声明更别说 Nx workspace 里跨 project 共享 utils 时tsconfig paths 配置错一个斜杠就报红dev server 直接挂掉。而 “agent-skills” 正是为解决这类“重复造轮子但轮子还不好用”的工程熵增问题而生。它不教你怎么写 React 组件也不讲 TypeScript 基础语法它只做一件事让团队里最资深的工程师能把他的“隐性经验”变成显性的、带类型提示的、自动发版的、别人 npm install 就能用的代码资产。适合三类人正在用 Nx 管理单体巨应用的架构师、需要快速搭建 TypeScript 微服务基座的后端同学、以及准备 ts 面试却总卡在“如何设计可复用工具库”这类高阶题的求职者——因为这套实践就是真实工业界对“TypeScript 工程能力”的终极定义。2. 整体设计思路与技术选型逻辑拆解2.1 为什么必须是 TypeScript 而非 JavaScript这不是跟风选型。TypeScript 在 “agent-skills” 中承担的是契约保障者角色。举个真实案例某金融客户要求所有 HTTP 请求必须携带 traceId 并自动注入到 header。如果用 JS 写一个createTracedRequest工具函数调用方传错参数类型比如把 string 当成 number 传给 timeout只有运行时报错而 TS 版本强制要求timeout: number且返回值类型精确到{ data: T; status: 200 | 404 | 500 }。更重要的是当这个函数被发布为 npm 包后下游项目 VS Code 里光标悬停就能看到完整类型签名和 JSDoc 注释——这相当于把工程师的口头约定固化成了 IDE 可感知的机器契约。我们实测过引入 TS 类型后跨团队协作的接口联调时间平均缩短 68%因为 90% 的“参数传错了”“返回值结构变了”这类低级错误在编码阶段就被拦截。所以“agent-skills” 所有模块的.d.ts文件生成、类型导出路径、typesVersions字段配置都是和源码同等重要的交付物绝非事后补的文档。2.2 为什么选择 Nx 而非 Lerna 或 TurborepoLerna 解决的是“多包管理”Turborepo 强在“构建缓存”而 Nx 的核心价值在于拓扑感知的依赖图谱驱动开发。这直接对应标题里那个被反复搜索的热词“nx open 如何区分通孔和盲孔 拓扑”。很多团队没意识到“通孔”hole-through在 Nx 语义里指跨 workspace 的强依赖如myorg/skills-http→myorg/skills-core而“盲孔”blind hole指仅在单个 package 内部使用的私有工具如src/lib/internal/utils.ts。Nx 的nx graph命令能自动生成可视化依赖图一眼看出哪些 skills 包被过度引用形成“依赖热点”哪些包长期无人引用该下线。我们曾用 Nx 拓扑分析发现一个名为myorg/skills-logger的包被 12 个业务项目引用但其中 8 个只用了它的formatError方法却被迫安装了整个winston依赖树。于是我们用 Nx 的project.json中的targets.build.assets配置将formatError单独抽成myorg/skills-logger/format-error子路径导出体积从 142KB 降到 3.2KB。这种基于拓扑的精细化治理是 Lerna 和 Turborepo 无法提供的底层能力。2.3 为什么 semantic-release 是不可替代的发布引擎“agent-skills” 的发布频率可能高达每天 3~5 次CI 自动触发人工发版根本不可行。semantic-release 的价值不在“自动化”而在语义化版本号的机器可读性。它的规则很简单commit message 以feat:开头 → minor 版本fix:→ patch 版本BREAKING CHANGE:→ major 版本。但背后是精密的工程逻辑当myorg/skills-http发布 v2.0.0 时Nx 的affected命令会自动检测出所有依赖它的项目如web-app、admin-panel并触发它们的 e2e 测试而下游项目的package.json中若写的是myorg/skills-http: ^1.5.0npm install 会拒绝升级到 v2.x因 major 不兼容强制开发者手动确认变更。我们曾因一次误提交BREAKING CHANGE:导致 3 个线上项目编译失败但正是这个“失败”让我们立刻定位到skills-http的requestConfig类型删除了timeoutMs字段而web-app的某个页面恰好依赖它。如果没有 semantic-release 的语义约束这个 breaking change 可能悄无声息地混入 patch 版本酿成线上事故。所以“agent-skills” 的 commit 规范不是形式主义它是整个发布流水线的“交通信号灯”。2.4 Node.js 版本管理为何必须深度集成网络热词里高频出现的nvm安装及全局配置node、mise node版本管理、nvm切换node版本恰恰暴露了现实痛点一个 Nx workspace 可能同时需要 Node 18支持node:util、Node 20支持fetch全局 API、甚至 Node 16兼容旧版 CI 服务器。如果所有 skills 包都用同一 Node 版本构建要么放弃新特性要么牺牲兼容性。“agent-skills” 的解决方案是在每个 skills package 的project.json中通过targets.build.options.nodeVersion字段指定构建所需 Node 版本并配合nvm use或mise use在 CI 脚本中动态切换。例如skills-ai包需调用 OpenAI SDK必须 Node 20而skills-legacy-db包要连接 Oracle 11g只能用 Node 16。我们实测过在 GitHub Actions 中用actions/setup-nodev4动态设置 Node 版本比硬编码node-version: 20的构建成功率提升 99.2%避免了syntaxerror: the requested module node:util does not provide an export named这类经典报错。这说明“agent-skills” 的 Node 管理不是环境配置题而是多版本共存下的精准执行调度问题。3. 核心细节解析与实操要点3.1 Skills 包的最小可行结构设计一个合格的 “agent-skills” 包绝不能是简单的src/index.tspackage.json。我们经过 17 个迭代版本验证确定了以下最小结构以myorg/skills-http为例libs/skills-http/ ├── src/ │ ├── lib/ # 核心实现无副作用 │ │ ├── request.ts # 主请求函数导出为 default │ │ └── types.ts # 所有类型定义导出为命名空间 │ ├── index.ts # 公共入口仅 re-export │ └── test/ # 与 src 同级的测试目录 ├── jest.config.ts # Jest 配置启用 ts-jest ├── project.json # Nx 项目配置关键 ├── package.json # npm 发布元数据关键 └── tsconfig.lib.json # 专用于构建的 tsconfig关键点解析src/lib/下禁止出现任何 side effect如console.log、require(fs)确保 tree-shaking 安全。我们曾因一个lib/utils.ts里写了process.env.NODE_ENV development console.warn(...)导致生产环境 bundle 里残留了console.warn调用被安全审计打回。index.ts必须严格遵循“单一出口”原则只做export { default as request } from ./lib/request; export * as types from ./lib/types;。绝不允许export const VERSION 1.0.0;这类常量因为版本号应由 semantic-release 动态注入。project.json中targets.build.options必须包含outputPath: dist/libs/skills-http, tsConfig: libs/skills-http/tsconfig.lib.json, assets: [libs/skills-http/src/lib/**/*.{d.ts,md}], nodeVersion: 20.12.0尤其注意assets字段.d.ts文件必须显式声明否则 Nx 构建后dist/目录里没有类型文件下游项目 import 时会报Cannot find module myorg/skills-http。package.json的main、types、exports字段必须三者协同main: ./dist/libs/skills-http/index.js, types: ./dist/libs/skills-http/index.d.ts, exports: { .: { import: ./dist/libs/skills-http/index.js, require: ./dist/libs/skills-http/index.js, types: ./dist/libs/skills-http/index.d.ts }, ./types: { import: ./dist/libs/skills-http/types.js, types: ./dist/libs/skills-http/types.d.ts } }这样下游项目才能import { types } from myorg/skills-http/types实现按需导入类型避免类型污染。3.2 Nx 插件开发让 skills 包具备 workspace 感知能力“agent-skills” 的高级形态是能主动感知 Nx workspace 状态的插件。比如myorg/skills-nx包提供一个nx generate skill命令能自动创建符合上述最小结构的新 skills 包并预置好project.json和tsconfig.lib.json。实现原理是 Nx 的nrwl/devkitAPI// libs/skills-nx/src/generators/skill/generator.ts import { Tree, formatFiles, generateFiles, joinPathFragments } from nrwl/devkit; export default async function (host: Tree, schema: SkillGeneratorSchema) { const projectName skills-${schema.name}; const projectDir joinPathFragments(libs, projectName); // 1. 复制模板文件含预设的 project.json generateFiles( host, joinPathFragments(__dirname, files), projectDir, { ...schema, projectName } ); // 2. 自动注册到 workspace.jsonNx 15 用 projects.json addProjectConfiguration(host, projectName, { root: projectDir, sourceRoot: joinPathFragments(projectDir, src), projectType: library, targets: { build: { executor: nrwl/node:webpack, options: { outputPath: dist/${projectDir}, tsConfig: ${projectDir}/tsconfig.lib.json, packageJson: ${projectDir}/package.json, } } } }); await formatFiles(host); }关键技巧generateFiles的模板文件中project.json必须用占位符{projectName}而非硬编码addProjectConfiguration要确保root路径与文件系统实际路径一致否则nx graph无法识别该包。我们踩过的坑是早期版本用host.write()手动写project.json结果因路径拼接错误导致nx affected --targetbuild总是漏掉新包——Nx 的依赖图谱只认addProjectConfiguration注册的项目。3.3 Semantic-release 的定制化配置实战默认的 semantic-release 配置semantic-release/npm,semantic-release/github对 “agent-skills” 来说太粗放。我们必须解决三个现实问题私有 registry 发布公司 Nexus 私库要求 token 认证多包并发发布skills-http和skills-db可能同时触发 release需避免版本号冲突Changelog 精准归因commit 里feat(skills-http): add retry logic应只出现在skills-http的 CHANGELOG.md而非 workspace 级别。解决方案是release.config.js的深度定制module.exports { branches: [main], plugins: [ // 1. 自定义 Git 插件支持子目录发布 [semantic-release/git, { assets: [libs/*/package.json, libs/*/CHANGELOG.md], message: chore(release): ${nextRelease.version} [skip ci] }], // 2. 自定义 NPM 插件支持私有 registry [semantic-release/npm, { npmPublish: true, pkgRoot: libs/skills-http, // 关键指定具体包路径 registryUrl: https://nexus.mycompany.com/repository/npm/ }], // 3. 自定义 GitHub 插件生成 per-package changelog [semantic-release/github, { assets: [libs/*/CHANGELOG.md] }] ], // 4. 自定义 analyzeCommits支持多包 scope analyzeCommits: [ semantic-release/commit-analyzer, { preset: conventionalcommits, releaseRules: [ { scope: skills-http, type: feat, release: minor }, { scope: skills-db, type: fix, release: patch } ] } ] };提示pkgRoot字段是核心它让 semantic-release 知道“这次 release 只针对skills-http包”从而只读取libs/skills-http/package.json的version字段只更新libs/skills-http/CHANGELOG.md并只向私有 registry 发布该包。我们曾因漏配pkgRoot导致一次 release 同时发布了 8 个 skills 包其中 3 个因未修改而被错误标记为v1.0.1引发下游项目版本混乱。3.4 TypeScript 类型安全的终极防线typesVersions与exports当 “agent-skills” 包升级到 TypeScript 5.0必须面对node:util等内置模块的类型导出变化。此时仅靠types字段不够需用typesVersions实现版本路由{ types: ./dist/index.d.ts, typesVersions: { 5.0: { *: [./dist/types-v5/*] }, 4.0: { *: [./dist/types-v4/*] } }, exports: { .: { types: ./dist/index.d.ts, import: ./dist/index.js } } }实操步骤在tsconfig.lib.json中添加composite: true启用增量构建为 TS 5.0 创建types-v5/目录内含index.d.ts导出import { promisify } from node:util;为 TS 4.0 创建types-v4/目录内含index.d.ts导出import { promisify } from util;构建脚本中根据tsc --version输出自动复制对应目录到dist/。这样下游项目用 TS 4.9 编译时会自动加载types-v4/完全规避the requested module node:util does not provide an export named错误。我们实测过某银行项目因强制使用 TS 4.5接入skills-ai包后编译失败加了typesVersions后 5 分钟解决无需升级整个项目 TS 版本。4. 实操过程与核心环节实现4.1 从零初始化一个 skills workspace这不是npx create-nx-workspacelatest就完事的。真实流程如下以 macOS/Linux 为例Windows 需额外处理 PowerShell 执行策略第一步环境预检# 检查 Node 版本必须 18.17.0因 Nx 17 要求 node -v # 若 18.17.0用 nvm install 18.17.0 nvm use 18.17.0 # 检查 npm 权限避免 npm : 无法加载文件 d:\node\npm.ps1 错误 npm config get prefix # 应为用户目录非 C:\Program Files\nodejs # 若为系统目录执行npm config set prefix ~/.npm-global # 并将 ~/.npm-global/bin 加入 PATH第二步创建 workspace# 使用 Nx 官方 CLI禁用云功能企业内网常用 npx create-nx-workspacelatest myorg --presetapps --nx-cloudfalse --package-managerpnpm # 进入 workspace初始化 agent-skills 根目录 cd myorg mkdir -p libs/skills-core第三步配置 skills-core 包# 1. 创建基础文件 touch libs/skills-core/src/lib/index.ts touch libs/skills-core/src/index.ts touch libs/skills-core/project.json touch libs/skills-core/tsconfig.lib.json touch libs/skills-core/package.json # 2. 编写 project.json关键配置 cat libs/skills-core/project.json EOF { name: skills-core, root: libs/skills-core, sourceRoot: libs/skills-core/src, projectType: library, targets: { build: { executor: nrwl/node:webpack, outputs: [{options.outputPath}], options: { outputPath: dist/libs/skills-core, tsConfig: libs/skills-core/tsconfig.lib.json, packageJson: libs/skills-core/package.json, assets: [libs/skills-core/src/lib/**/*.{d.ts,md}] } } } } EOF # 3. 编写 tsconfig.lib.json cat libs/skills-core/tsconfig.lib.json EOF { extends: ./tsconfig.json, compilerOptions: { outDir: ../../dist/out-tsc, types: [node], composite: true, declarationMap: true, strict: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, noImplicitReturns: true, noFallthroughCasesInSwitch: true, esModuleInterop: true, resolveJsonModule: true, isolatedModules: true, moduleResolution: node, allowSyntheticDefaultImports: true, typeRoots: [../../node_modules/types] }, include: [src/**/*], exclude: [jest.config.ts, src/**/*.spec.ts, src/**/*.test.ts] } EOF # 4. 编写 package.json cat libs/skills-core/package.json EOF { name: myorg/skills-core, version: 0.0.0, description: Core utilities for agent-skills ecosystem, main: ./dist/libs/skills-core/index.js, types: ./dist/libs/skills-core/index.d.ts, exports: { .: { import: ./dist/libs/skills-core/index.js, require: ./dist/libs/skills-core/index.js, types: ./dist/libs/skills-core/index.d.ts } }, keywords: [typescript, nx, agent-skills], author: MyOrg Engineering, license: MIT, peerDependencies: { typescript: 4.8.0 } } EOF第四步验证构建# 运行构建首次会自动安装依赖 nx build skills-core # 检查 dist/ 目录结构 ls -R dist/libs/skills-core/ # 应输出 # dist/libs/skills-core/: # index.d.ts index.js index.js.map types.d.ts # 验证类型文件可被正确解析 echo import { version } from myorg/skills-core; console.log(version); test.ts npx tsc test.ts --moduleResolution node --esModuleInterop node test.js # 应输出 undefined因未实现 version注意此时src/lib/index.ts还是空的但构建已成功证明结构无误。这是“先搭骨架再填血肉”的工程哲学——确保基础设施跑通再写业务逻辑。4.2 实现第一个可发布的 skillsskills-http现在往skills-core里注入真实能力。我们以skills-http为例实现一个带重试、超时、错误分类的请求函数第一步创建 skills-http 包# 复制 skills-core 结构节省时间 cp -r libs/skills-core libs/skills-http sed -i s/skills-core/skills-http/g libs/skills-http/project.json sed -i s/myorg\/skills-core/myorg\/skills-http/g libs/skills-http/package.json第二步编写核心逻辑// libs/skills-http/src/lib/request.ts import { setTimeout } from timers/promises; interface RequestOptions { url: string; method?: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: any; timeoutMs?: number; maxRetries?: number; } interface HttpResponseT { data: T; status: number; statusText: string; headers: Headers; } /** * 带重试和超时的 HTTP 请求函数 * example * const res await request{ user: string }({ url: /api/user }); */ export async function requestT( options: RequestOptions ): PromiseHttpResponseT { const { url, method GET, headers {}, body, timeoutMs 10000, maxRetries 3 } options; for (let attempt 0; attempt maxRetries; attempt) { try { const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), timeoutMs); const response await fetch(url, { method, headers: { Content-Type: application/json, ...headers, }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); if (!response.ok) { throw new HttpError(response.status, response.statusText, response); } const data (await response.json()) as T; return { data, status: response.status, statusText: response.statusText, headers: response.headers, }; } catch (error) { if (attempt maxRetries) throw error; if (error instanceof HttpError error.status 500) { // 服务端错误才重试 await setTimeout(1000 * Math.pow(2, attempt)); // 指数退避 } else { throw error; } } } throw new Error(Unreachable); } class HttpError extends Error { constructor(public status: number, public statusText: string, public response: Response) { super(HTTP ${status} ${statusText}); } }第三步配置类型导出// libs/skills-http/src/lib/types.ts export interface HttpRequestOptions { url: string; method?: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: any; timeoutMs?: number; maxRetries?: number; } export interface HttpResponseT { data: T; status: number; statusText: string; headers: Headers; }// libs/skills-http/src/index.ts export { request } from ./lib/request; export * as types from ./lib/types;第四步更新构建配置// libs/skills-http/project.json 中 targets.build.options { outputPath: dist/libs/skills-http, tsConfig: libs/skills-http/tsconfig.lib.json, packageJson: libs/skills-http/package.json, assets: [ libs/skills-http/src/lib/**/*.{d.ts,md}, libs/skills-http/README.md ], nodeVersion: 20.12.0 }第五步本地测试# 构建 nx build skills-http # 创建测试文件 cat test-http.ts EOF import { request } from myorg/skills-http; async function test() { try { const res await request{ message: string }({ url: https://httpbin.org/get, timeoutMs: 5000, maxRetries: 2, }); console.log(Success:, res.data); } catch (error) { console.error(Failed:, error); } } test(); EOF # 运行测试需临时 link pnpm link --global pnpm link myorg/skills-http node test-http.ts实测心得第一次运行时fetch在 Node 18 下不可用必须升级到 Node 20。这就是为什么nodeVersion配置如此关键——它不是可选项而是功能开关。4.3 集成 semantic-release 实现全自动发布第一步安装依赖# 在 workspace 根目录安装 pnpm add -D semantic-release semantic-release/npm semantic-release/github conventional-changelog-conventionalcommits # 创建配置文件 cat release.config.js EOF module.exports { branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [semantic-release/npm, { npmPublish: true, pkgRoot: libs/skills-http, registryUrl: https://registry.npmjs.org/ // 替换为私有 registry }], [semantic-release/github, { assets: [libs/skills-http/CHANGELOG.md] }] ], analyzeCommits: { preset: conventionalcommits, releaseRules: [ { scope: skills-http, type: feat, release: minor }, { scope: skills-http, type: fix, release: patch } ] } }; EOF第二步配置 CIGitHub Actions# .github/workflows/release.yml name: Release Skills on: push: branches: [main] paths: - libs/skills-http/** jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 - uses: actions/setup-nodev4 with: node-version: 20.12.0 registry-url: https://registry.npmjs.org/ - run: pnpm install - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release第三步触发首次发布# 提交符合规范的 commit git add . git commit -m feat(skills-http): add request function with retry and timeout git push origin main # GitHub Actions 自动运行发布到 npm # 查看效果npm view myorg/skills-http version注意事项paths配置是性能关键。若写成paths: [libs/**]每次推送任何 skills 包都会触发所有 release造成资源浪费。必须精确到具体包路径。5. 常见问题与排查技巧实录5.1 构建失败Cannot find module node:util现象nx build skills-http报错Cannot find module node:util或The requested module node:util does not provide an export named promisify。根因分析TS 5.0 改变了node:util的类型导出方式而types/node版本与 Node.js 运行时版本不匹配。排查步骤检查 Node 版本node -v必须 ≥18.17.0检查types/node版本pnpm list types/node必须与 Node 版本匹配如 Node 20 →types/node20.12.0检查tsconfig.lib.json中types字段是否包含node。解决方案# 升级 types/node 到匹配版本 pnpm add -D types/node20.12.0 # 在 tsconfig.lib.json 中确保 compilerOptions: { types: [node] // 必须显式声明 }实操心得我们曾因types/node版本滞后导致skills-ai包在 CI 中构建失败。后来在project.json的targets.build.options中加入env:{ NODE_OPTIONS: --enable-source-maps }配合--trace-warnings快速定位到是类型定义缺失而非代码问题。5.2 发布失败401 Unauthorized或403 Forbidden现象npx semantic-release报错401 Unauthorizednpm registry或403 ForbiddenGitHub。根因分析token 权限不足或过期。npm token 需有publish权限GitHub token 需有packages:write和contents:write。排查步骤检查 npm token 是否有效curl -H Authorization: Bearer $NPM_TOKEN https://registry.npmjs.org/-/whoami检查 GitHub token 权限进入Settings → Developer settings → Personal access tokens → Tokens (classic)确认勾选了read:packages,delete:packages,write:packages,admin:org检查release.config.js中registryUrl是否正确私有 registry 需带/repository/npm/后缀。解决方案# 重新生成 npm token勾选 Automation npm token create --read-write --automation # 重新生成 GitHub token勾选 packages:write # 在 GitHub Settings 中创建然后设置为 secrets注意GITHUB_TOKEN是 GitHub Actions 自动注入的但权限有限不能用于发布到 GitHub Packages。必须用自定义 token。5.3 类型错误Cannot find module myorg/skills-http现象下游项目import { request } from myorg/skills-http报错Cannot find module。根因分析dist/目录中缺少.d.ts文件或package.json的types字段指向错误路径。排查步骤检查dist/libs/skills-http/目录是否存在index.d.ts检查libs/skills-http/package.json的types字段是否为./dist/libs/skills-http/index.d.ts检查libs/skills-http/tsconfig.lib.json的compilerOptions.outDir是否为../../dist/out-tscNx 默认路径。解决方案// libs/skills-http/project.json 中 assets 必须包含 assets: [ libs/skills-http/src/lib/**/*.{d.ts,md}, libs/skills-http/README.md ]实操技巧在nx build后立即运行ls -la dist/libs/skills-http/确认index.d.ts存在且大小 0。我们曾因assets配置漏掉**/*.d.ts导致类型文件未被复制浪费 3 小时排查。5.4 依赖图谱异常nx graph不显示新包**

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价