Angular Schematics Tree修改未持久化致npm命令执行失败问题排查
问题分析
你遇到的核心问题是Angular Schematics的虚拟文件系统与磁盘文件不同步:
- 调用
tree.overwrite()只是修改了内存中的package.json,并没有同步到磁盘 execSync()执行npm命令时,读取的是磁盘上的原始文件,此时你添加的prepare脚本还不存在,所以报错
修复方案
1. 先将修改后的package.json写入磁盘
在调用execSync之前,需要把内存中修改好的package.json同步到磁盘。可以直接用Node.js的fs模块完成。
2. 完善代码细节
- 检查
devDependencies字段是否存在,避免直接赋值报错 - 处理
prepare脚本已存在的情况(可选) - 添加错误捕获,方便调试
- 确保husky命令执行环境正确
修改后的完整代码:
import { type Rule, type SchematicContext, type Tree, } from '@angular-devkit/schematics'; import {normalize} from '@angular-devkit/core'; import {execSync} from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; export function main(options: {directory: string}): Rule { return (tree: Tree, context: SchematicContext) => { context.logger.info('Adding husky ...'); const dirPath = normalize(options.directory); return updatePackageJson(dirPath)(tree, context); }; } function updatePackageJson(directory: string): Rule { return (tree: Tree, context: SchematicContext) => { const pkgPath = path.join(directory, 'package.json'); const buffer = tree.read(pkgPath); if (!buffer) { throw new Error(`Package.json not found at ${pkgPath}`); } // 解析并修改package.json const pkgContent = buffer.toString(); const parsedPackage = JSON.parse(pkgContent); // 确保devDependencies存在 if (!parsedPackage.devDependencies) { parsedPackage.devDependencies = {}; } parsedPackage.devDependencies.husky = '^8.0.1'; // 处理prepare脚本:如果已存在可以选择提示或覆盖 if (parsedPackage.scripts?.prepare) { context.logger.warn('Existing prepare script found, will be overwritten'); } parsedPackage.scripts = {...parsedPackage.scripts, prepare: 'husky install'}; // 1. 先写入内存树 const updatedPkgContent = JSON.stringify(parsedPackage, null, 2); tree.overwrite(pkgPath, updatedPkgContent); // 2. 同步到磁盘(关键:让npm能读取到修改后的文件) fs.writeFileSync(pkgPath, updatedPkgContent); try { // 执行npm安装和husky初始化 context.logger.info('Installing husky...'); execSync(`npm install --prefix=${directory}`, {stdio: 'inherit'}); // husky 8+安装后prepare脚本会自动执行,这里可以省略手动调用,不过如果需要确保执行也可以保留 context.logger.info('Running prepare script...'); execSync(`npm run prepare --prefix=${directory}`, {stdio: 'inherit'}); // 添加commit-msg钩子 const commitMsgHookPath = path.join(directory, '.husky', 'commit-msg'); context.logger.info('Adding commit-msg hook...'); execSync(`npx husky add ${commitMsgHookPath} 'npx --no -- commitlint --edit "$1"'`, { cwd: directory, stdio: 'inherit' }); // 添加pre-push钩子 const prePushHookPath = path.join(directory, '.husky', 'pre-push'); context.logger.info('Adding pre-push hook...'); execSync(`npx husky add ${prePushHookPath} 'npm run test'`, { cwd: directory, stdio: 'inherit' }); context.logger.info('Husky setup completed successfully!'); } catch (error) { context.logger.error(`Husky setup failed: ${(error as Error).message}`); throw error; } return tree; }; }
关键说明
- 同步磁盘文件:通过
fs.writeFileSync将修改后的package.json写入磁盘,确保npm命令能读取到最新内容 stdio: 'inherit':让execSync执行的命令输出直接显示在终端,方便调试cwd: directory:执行husky命令时设置工作目录,避免路径问题- husky自动执行prepare:husky 8.x版本在
npm install后会自动触发prepare脚本,所以手动调用npm run prepare不是必须的,但保留也没问题
额外提示
- 如果你的原理图需要发布给其他开发者使用,建议添加对
commitlint的依赖检查(比如提示用户安装@commitlint/cli和@commitlint/config-conventional) - 可以通过
Schematics的packageJson工具函数来更安全地修改package.json,比如@schematics/angular中的addPackageJsonDependency方法
内容的提问来源于stack exchange,提问作者David Miguel Yusta
相关产品推荐
相关产品推荐

