描述在 grunt 中实现许可证冲突自动提示

解读

面试官真正想考察的是:

  1. 你是否理解“许可证冲突”在前端交付中的合规风险(GPL 污染、商业授权冲突等)
  2. 能否把合规检查无缝嵌入 Grunt 的构建生命周期,做到一次配置、持续拦截
  3. 对 Grunt 插件机制、任务链、事件钩子、异步流程控制是否熟悉
  4. 是否具备“把合规左移”的意识:在本地构建阶段就提示,而不是上线前才扫描

国内大厂目前普遍要求“二进制交付物可溯源”,所以这道题既考技术,也考合规敏感度。

知识点

  • 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 子任务,与现有构建流程零耦合,步骤如下:

  1. 安装依赖
    npm i -D license-checker semver chalk

  2. 在 Gruntfile.js 顶部引入模块
    const checker = require('license-checker');
    const semver = require('semver');
    const chalk = require('chalk');

  3. 注册自定义多任务
    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();  
    

    });
    });

  4. 把任务插入默认构建链
    grunt.registerTask('default', ['clean', 'eslint', 'license', 'uglify', 'copy']);

  5. 本地开发阶段增量提示
    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 闭环