|
| 1 | +// @ts-check |
| 2 | +import pkg from '../../package.json'; |
| 3 | +import renovate from '../../renovate.json'; |
| 4 | + |
| 5 | +const packageJsonDeps = [ |
| 6 | + ...Object.keys(pkg.dependencies ?? {}), |
| 7 | + ...Object.keys(pkg.devDependencies ?? {}), |
| 8 | +]; |
| 9 | + |
| 10 | +/** @type {{ groupName?: string, matchPackageNames?: string[], matchPackagePatterns?: string[] }[]} */ |
| 11 | +const groupRules = renovate.packageRules.filter(r => r.groupName); |
| 12 | +const groupedRenovateDeps = groupRules.flatMap(r => r.matchPackageNames ?? []); |
| 13 | +const groupedRenovatePatterns = groupRules.flatMap(r => (r.matchPackagePatterns ?? []).map(p => new RegExp(p))); |
| 14 | + |
| 15 | +/** Strip the version specifier from an `npm install` argument, handling scoped packages. |
| 16 | + * @param {string} arg e.g. "eslint@7", "@babel/core@latest", "@scope/pkg" |
| 17 | + * @returns {string} e.g. "eslint", "@babel/core", "@scope/pkg" |
| 18 | + */ |
| 19 | +const depNameFromNpmArg = (arg) => { |
| 20 | + const start = arg.startsWith('@') ? 1 : 0; |
| 21 | + const i = arg.indexOf('@', start); |
| 22 | + return i === -1 ? arg : arg.slice(0, i); |
| 23 | +}; |
| 24 | + |
| 25 | +// Collect all packages listed in update:* scripts (excludes update:*:test variants) |
| 26 | +const updateScriptDeps = new Set( |
| 27 | + Object.entries(pkg.scripts) |
| 28 | + .filter(([key]) => /^update:[^:]+$/.test(key)) |
| 29 | + .flatMap(([, script]) => script.split(/\s+/).slice(2).map(depNameFromNpmArg)), |
| 30 | +); |
| 31 | + |
| 32 | +describe('renovate.json', () => { |
| 33 | + test('all package.json dependencies belong to a renovate group', () => { |
| 34 | + const ungrouped = packageJsonDeps.filter(dep => ( |
| 35 | + !groupedRenovateDeps.includes(dep) && !groupedRenovatePatterns.some(re => re.test(dep)) |
| 36 | + )); |
| 37 | + expect(ungrouped).toEqual([]); |
| 38 | + }); |
| 39 | +}); |
| 40 | + |
| 41 | +describe('package.json update:* scripts', () => { |
| 42 | + test('all package.json dependencies appear in an update:* script', () => { |
| 43 | + const missing = packageJsonDeps.filter(dep => !updateScriptDeps.has(dep)); |
| 44 | + expect(missing).toEqual([]); |
| 45 | + }); |
| 46 | + |
| 47 | + test('explicit renovate groups match their corresponding update:* script', () => { |
| 48 | + const scripts = /** @type {Record<string, string>} */ (pkg.scripts); |
| 49 | + for (const rule of groupRules) { |
| 50 | + const { groupName, matchPackageNames } = rule; |
| 51 | + if (!groupName || !matchPackageNames) continue; |
| 52 | + const scriptKey = `update:${groupName.replace(/ /g, '-')}`; |
| 53 | + const script = scripts[scriptKey]; |
| 54 | + if (!script) continue; |
| 55 | + |
| 56 | + const scriptPackages = new Set(script.split(/\s+/).slice(2).map(depNameFromNpmArg)); |
| 57 | + const groupPackages = new Set(matchPackageNames); |
| 58 | + expect({ group: rule.groupName, packages: [...scriptPackages].sort() }) |
| 59 | + .toEqual({ group: rule.groupName, packages: [...groupPackages].sort() }); |
| 60 | + } |
| 61 | + }); |
| 62 | +}); |
0 commit comments