描述在 grunt 中实现许可证冲突自动提示
解读
面试官真正想考察的是:
- 你是否理解“许可证冲突”在前端交付中的合规风险(GPL 污染、商业授权冲突等)
- 能否把合规检查无缝嵌入 Grunt 的构建生命周期,做到一次配置、持续拦截
- 对 Grunt 插件机制、任务链、事件钩子、异步流程控制是否熟悉
- 是否具备“把合规左移”的意识:在本地构建阶段就提示,而不是上线前才扫描
国内大厂目前普遍要求“二进制交付物可溯源”,所以这道题既考技术,也考合规敏感度。
知识点
- license-checker、license-webpack-plugin、fossa-cli 等扫描工具的命令行输出格式
- grunt-contrib-copy、grunt-contrib-clean 等基础插件的链式调用
- grunt.registerMultiTask / grunt.task.run 的顺序与异步回调(this.async)
- grunt.event.on('watch') 与 grunt-contrib-watch 的联动,实现增量扫描
- grunt.log.warn / grunt.fail.warn 的差异化:warn 仅提示不中断,fail.warn 可中断构建
- 白名单机制:OSI 列表 + 公司合规清单(如禁止 GPL-3.0、AGPL)
- 国内镜像提速:把 license-checker 的 registry 指向淘宝源,避免 CI 超时
- 输出格式:控制台彩色表格 + 生成 licenses-report.json 供后续审计平台消费
答案
“我会把许可证冲突检查做成一个可插拔的 Grunt 子任务,与现有构建流程零耦合,步骤如下:
-
安装依赖
npm i -D license-checker semver chalk -
在 Gruntfile.js 顶部引入模块
const checker = require('license-checker');
const semver = require('semver');
const chalk = require('chalk'); -
注册自定义多任务
grunt.registerMultiTask('license', '自动提示许可证冲突', function() {
const done = this.async();
const options = this.options({
// 公司合规白名单
whitelist: ['MIT', 'BSD-2-Clause', 'Apache-2.0', 'MPL-2.0'],
// 遇到冲突是否立即失败
failOnViolation: true,
// 输出文件,供审计平台抓取
output: 'dist/licenses-report.json'
});checker.init({
start: '.',
production: true,
json: true,
customPath: require.resolve('license-checker/package.json')
}, (err, filtered) => {
if (err) { grunt.fail.fatal(err); return; }
const violations = [];
Object.entries(filtered).forEach(([pkg, meta]) => {
const licenses = Array.isArray(meta.licenses) ? meta.licenses : [meta.licenses];
const hasUnapproved = licenses.every(l => !options.whitelist.includes(l));
if (hasUnapproved) violations.push({ pkg, licenses });
});if (violations.length) { grunt.log.writeln(chalk.red.bold('发现许可证冲突:')); violations.forEach(v => grunt.log.writeln(` ${v.pkg} -> ${v.licenses.join(', ')}`)); grunt.file.write(options.output, JSON.stringify(violations, null, 2)); if (options.failOnViolation) { grunt.fail.warn('许可证合规检查未通过,构建已终止!'); } else { grunt.log.writeln(chalk.yellow.bold('已生成报告,但未阻塞构建')); } } else { grunt.log.ok('所有依赖许可证均在白名单内'); grunt.file.write(options.output, JSON.stringify({ status: 'clean' }, null, 2)); } done();});
}); -
把任务插入默认构建链
grunt.registerTask('default', ['clean', 'eslint', 'license', 'uglify', 'copy']); -
本地开发阶段增量提示
grunt.initConfig({
watch: {
js: {
files: ['src/**/*.js'],
tasks: ['eslint', 'license']
}
}
});
这样,每次保存文件都会触发增量扫描,一旦有人引入 GPL-3.0 等高风险许可证,终端立即红色高亮提示,CI 阶段也可通过 failOnViolation:true 强制阻断,确保上线包 100% 通过合规审计。”
拓展思考
- 如果项目采用 Monorepo,可在根目录统一扫描,子包通过
--relativeLicensePath生成相对路径报告,避免重复 - 对于 私有仓库无 license 字段 的包,可维护一份 internal-whitelist.json,由法务审批后手动录入
- 结合 husky + lint-staged,在 pre-commit 阶段只扫描
git diff引入的新依赖,把扫描耗时从 30s 降到 3s,实现“秒级”反馈 - 国内合规趋势:2024 年起部分省份 App 上架需提交《开源软件清单》,可将 licenses-report.json 直接对接工信部备案系统模板,减少人工誊写
- 若公司已有二进制成分分析平台(如自研“白泽”),可在 Grunt 任务里通过 curl 把报告 POST 到平台 API,实现集中式风险看板,完成 DevSecOps 闭环