diff --git a/jsconfig.json b/jsconfig.json index 8ce75dea3..6080f7637 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -9,7 +9,7 @@ }, "include": [ "src/**/*.js", - "tests/**/*.js", + "tests/jest/**/*.js", ], "exclude": [ "node_modules", diff --git a/package.json b/package.json index 657b2675e..b021d0347 100644 --- a/package.json +++ b/package.json @@ -132,14 +132,17 @@ "test:e2e": "npm run build && npx testcafe", "test:e2e:dev": "npx testcafe --live --dev", "DOCS:update:test-deps": "If CI succeeds, these should be good to update", - "update:test-deps": "npm i @babel/eslint-parser@latest @open-wc/testing-helpers@latest @types/jest@latest eslint@7 eslint-plugin-testcafe@latest jest@latest jest-environment-jsdom@latest sinon@latest testcafe@latest", + "update:test-deps": "npm i @babel/eslint-parser@latest @open-wc/testing-helpers@latest @types/jest@latest eslint@7 eslint-plugin-no-jquery@latest eslint-plugin-testcafe@latest jest@latest jest-environment-jsdom@latest sinon@latest testcafe@latest", "update:test-deps:test": "npm run lint && npm run test && npm run test:e2e", "DOCS:update:build-deps": "These can cause strange changes, so do an npm run build + check file size (git diff --stat), and check the site is as expected", "update:build-deps": "npm i @babel/core@latest @babel/preset-env@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest babel-loader@latest core-js@latest regenerator-runtime@latest sass@latest svgo@latest webpack@latest webpack-cli@latest", "update:build-deps:test": "npm run build", "DOCS:update:dev-deps": "Packages used for development", - "update:dev-deps": "npm i @iiif/presentation-2@latest @iiif/presentation-3@latest concurrently@latest cpx2@latest http-server@latest live-server@latest", + "update:dev-deps": "npm i @iiif/presentation-2@latest @iiif/presentation-3@latest concurrently@latest cpx2@latest http-server@latest hypothesis@latest live-server@latest testcafe-browser-provider-browserstack@latest", + "DOCS:update:runtime-deps": "Core runtime dependencies; do an npm run build + visual check after updating", + "update:runtime-deps": "npm i @webcomponents/webcomponentsjs@latest interactjs@latest iso-language-codes@latest jquery@latest jquery-colorbox@latest jquery-ui@latest jquery-ui-touch-punch@latest jquery.browser@latest lit@latest soundmanager2@latest", + "update:runtime-deps:test": "npm run build", "DOCS:update:ia-deps": "Packages by @internetarchive", - "update:ia-deps": "npm i @internetarchive/ia-activity-indicator@latest @internetarchive/ia-item-navigator@latest @internetarchive/icon-bookmark@latest @internetarchive/icon-close@latest @internetarchive/icon-dl@latest @internetarchive/icon-edit-pencil@latest @internetarchive/icon-ia-logo@latest @internetarchive/icon-magnify-minus@latest @internetarchive/icon-magnify-plus@latest @internetarchive/icon-search@latest @internetarchive/icon-share@latest @internetarchive/icon-toc@latest @internetarchive/icon-visual-adjustment@latest @internetarchive/modal-manager@latest @internetarchive/shared-resize-observer@latest" + "update:ia-deps": "npm i @internetarchive/bergamot-translator@latest @internetarchive/ia-activity-indicator@latest @internetarchive/ia-item-navigator@latest @internetarchive/icon-bookmark@latest @internetarchive/icon-close@latest @internetarchive/icon-dl@latest @internetarchive/icon-edit-pencil@latest @internetarchive/icon-ia-logo@latest @internetarchive/icon-magnify-minus@latest @internetarchive/icon-magnify-plus@latest @internetarchive/icon-search@latest @internetarchive/icon-share@latest @internetarchive/icon-toc@latest @internetarchive/icon-visual-adjustment@latest @internetarchive/modal-manager@latest @internetarchive/shared-resize-observer@latest" } } diff --git a/renovate.json b/renovate.json index 3eec0e199..063197f6c 100644 --- a/renovate.json +++ b/renovate.json @@ -3,13 +3,32 @@ "config:base", "schedule:monthly" ], + "minimumReleaseAge": "14 days", "packageRules": [ { + "matchPackagePatterns": ["*"], + "rangeStrategy": "bump" + }, + { + "matchManagers": ["github-actions"], + "groupName": "GitHub Actions", + "automerge": false + }, + { + "groupName": "ia deps", + "matchPackagePatterns": ["^@internetarchive/"], + "automerge": false + }, + { + "matchPackageNames": ["eslint"], + "allowedVersions": "^7.0.0" + }, + { + "groupName": "test deps", "matchPackageNames": [ "@babel/eslint-parser", "@open-wc/testing-helpers", "@types/jest", - "codecov/codecov-action", "eslint", "eslint-plugin-no-jquery", "eslint-plugin-testcafe", @@ -21,32 +40,48 @@ "automerge": false }, { + "groupName": "build deps", + "matchPackageNames": [ + "@babel/core", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-decorators", + "@babel/preset-env", + "babel-loader", + "core-js", + "regenerator-runtime", + "sass", + "svgo", + "webpack", + "webpack-cli" + ] + }, + { + "groupName": "dev deps", "matchPackageNames": [ + "@iiif/presentation-2", + "@iiif/presentation-3", "concurrently", + "cpx2", "http-server", + "hypothesis", "live-server", - "node-fetch" - ], - "matchUpdateTypes": ["minor", "patch"], - "automerge": false - }, - { - "matchPackagePatterns": ["^@internetarchive/icon-"], - "groupName": "@internetarchive icons", - "rangeStrategy": "bump" + "testcafe-browser-provider-browserstack" + ] }, { - "matchPackagePatterns": ["*"], - "rangeStrategy": "bump" - }, - { - "matchPackagePatterns": ["^actions/"], - "groupName": "GitHub Actions", - "automerge": false - }, - { - "matchPackagePatterns": ["^@internetarchive"], - "schedule": ["at any time"] + "groupName": "runtime deps", + "matchPackageNames": [ + "@webcomponents/webcomponentsjs", + "interactjs", + "iso-language-codes", + "jquery", + "jquery-colorbox", + "jquery-ui", + "jquery-ui-touch-punch", + "jquery.browser", + "lit", + "soundmanager2" + ] } ] } diff --git a/tests/jest/dependencies.test.js b/tests/jest/dependencies.test.js new file mode 100644 index 000000000..3d5ddeb8a --- /dev/null +++ b/tests/jest/dependencies.test.js @@ -0,0 +1,62 @@ +// @ts-check +import pkg from '../../package.json'; +import renovate from '../../renovate.json'; + +const packageJsonDeps = [ + ...Object.keys(pkg.dependencies ?? {}), + ...Object.keys(pkg.devDependencies ?? {}), +]; + +/** @type {{ groupName?: string, matchPackageNames?: string[], matchPackagePatterns?: string[] }[]} */ +const groupRules = renovate.packageRules.filter(r => r.groupName); +const groupedRenovateDeps = groupRules.flatMap(r => r.matchPackageNames ?? []); +const groupedRenovatePatterns = groupRules.flatMap(r => (r.matchPackagePatterns ?? []).map(p => new RegExp(p))); + +/** Strip the version specifier from an `npm install` argument, handling scoped packages. + * @param {string} arg e.g. "eslint@7", "@babel/core@latest", "@scope/pkg" + * @returns {string} e.g. "eslint", "@babel/core", "@scope/pkg" + */ +const depNameFromNpmArg = (arg) => { + const start = arg.startsWith('@') ? 1 : 0; + const i = arg.indexOf('@', start); + return i === -1 ? arg : arg.slice(0, i); +}; + +// Collect all packages listed in update:* scripts (excludes update:*:test variants) +const updateScriptDeps = new Set( + Object.entries(pkg.scripts) + .filter(([key]) => /^update:[^:]+$/.test(key)) + .flatMap(([, script]) => script.split(/\s+/).slice(2).map(depNameFromNpmArg)), +); + +describe('renovate.json', () => { + test('all package.json dependencies belong to a renovate group', () => { + const ungrouped = packageJsonDeps.filter(dep => ( + !groupedRenovateDeps.includes(dep) && !groupedRenovatePatterns.some(re => re.test(dep)) + )); + expect(ungrouped).toEqual([]); + }); +}); + +describe('package.json update:* scripts', () => { + test('all package.json dependencies appear in an update:* script', () => { + const missing = packageJsonDeps.filter(dep => !updateScriptDeps.has(dep)); + expect(missing).toEqual([]); + }); + + test('explicit renovate groups match their corresponding update:* script', () => { + const scripts = /** @type {Record} */ (pkg.scripts); + for (const rule of groupRules) { + const { groupName, matchPackageNames } = rule; + if (!groupName || !matchPackageNames) continue; + const scriptKey = `update:${groupName.replace(/ /g, '-')}`; + const script = scripts[scriptKey]; + if (!script) continue; + + const scriptPackages = new Set(script.split(/\s+/).slice(2).map(depNameFromNpmArg)); + const groupPackages = new Set(matchPackageNames); + expect({ group: rule.groupName, packages: [...scriptPackages].sort() }) + .toEqual({ group: rule.groupName, packages: [...groupPackages].sort() }); + } + }); +});