mediawiki/services/recommendation-api: main (log #1416771)

sourcepatches

This run took 292 seconds.

From 8a6c300b1a0049eb72c2456a5fb55e52693675f3 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Thu, 4 Jul 2024 16:37:53 +0000
Subject: [PATCH] build: Updating eslint-config-wikimedia to 0.28.2

Change-Id: I18fc743c58c0a953f2dcac89ed146f3362eb9252
---
 app.js                                        |  10 +-
 lib/article.creation.morelike.js              |   4 +-
 lib/article.creation.translation.js           |   6 +-
 lib/description.js                            |  15 +-
 package-lock.json                             | 608 +++++++++++-------
 package.json                                  |   2 +-
 routes/article.creation.morelike.js           |   6 +-
 test/features/app/app.js                      |  18 +-
 test/features/app/spec.js                     |  16 +-
 test/features/v1/article.creation.morelike.js |   6 +-
 test/lib/caption.js                           |   4 +-
 test/lib/suggested-edits-common.js            |  18 +-
 test/utils/server.js                          |  12 +-
 13 files changed, 408 insertions(+), 317 deletions(-)

diff --git a/app.js b/app.js
index 6eac1c1..2e61fe4 100644
--- a/app.js
+++ b/app.js
@@ -68,9 +68,7 @@ function initApp(options) {
             'user-agent', 'x-request-id'
         ];
     }
-    app.conf.log_header_whitelist = new RegExp(`^(?:${ app.conf.log_header_whitelist.map((item) => {
-        return item.trim();
-    }).join('|') })$`, 'i');
+    app.conf.log_header_whitelist = new RegExp(`^(?:${ app.conf.log_header_whitelist.map((item) => item.trim()).join('|') })$`, 'i');
 
     // set up the request templates for the APIs
     apiUtil.setupApiTemplates(app);
@@ -151,8 +149,7 @@ function initApp(options) {
 function loadRoutes(app, dir) {
 
     // recursively load routes from .js files under routes/
-    return fs.readdirAsync(dir).map((fname) => {
-        return BBPromise.try(() => {
+    return fs.readdirAsync(dir).map((fname) => BBPromise.try(() => {
             const resolvedPath = path.resolve(dir, fname);
             const isDirectory = fs.statSync(resolvedPath).isDirectory();
             if (isDirectory) {
@@ -185,8 +182,7 @@ function loadRoutes(app, dir) {
             sUtil.wrapRouteHandlers(route, app);
             // all good, use that route
             app.use(route.path, route.router);
-        });
-    }).then(() => {
+        })).then(() => {
         // catch errors
         sUtil.setErrorHandler(app);
         // route loading is now complete, return the app object
diff --git a/lib/article.creation.morelike.js b/lib/article.creation.morelike.js
index 87c5d9c..d0d0a79 100644
--- a/lib/article.creation.morelike.js
+++ b/lib/article.creation.morelike.js
@@ -340,9 +340,7 @@ function getArticleNormalizedRanksFromDb(app, wikidataIds, targetLanguage) {
     const retry = app.conf.mysql_conn.retry || 2;
     const retryDelay = app.conf.mysql_conn.retry_delay || 1000;
 
-    wikidataIds = wikidataIds.map((x) => {
-        return parseInt(x.replace('Q', ''), 10);
-    });
+    wikidataIds = wikidataIds.map((x) => parseInt(x.replace('Q', ''), 10));
 
     return new BBPromise((resolve, reject) => {
         _getArticleNormalizedRanksFromMySQL(
diff --git a/lib/article.creation.translation.js b/lib/article.creation.translation.js
index ae9e21c..9e53b81 100644
--- a/lib/article.creation.translation.js
+++ b/lib/article.creation.translation.js
@@ -126,11 +126,7 @@ function recommend(app, source, target, projectDomain, seed) {
         candidates = getArticlesByPageviews(app, source, target, projectDomain);
     }
     return candidates
-    .then((candidates) => {
-        return candidates.sort((a, b) => {
-            return b.sitelink_count - a.sitelink_count;
-        });
-    });
+    .then((candidates) => candidates.sort((a, b) => b.sitelink_count - a.sitelink_count));
 }
 
 module.exports = {
diff --git a/lib/description.js b/lib/description.js
index 2ac31e4..c03b184 100644
--- a/lib/description.js
+++ b/lib/description.js
@@ -74,13 +74,11 @@ function wikibaseItemHasSiteLink(page, dbName) {
  * @return {!Object} filtered pages
  */
 function filterPages(pages) {
-    return pages.filter(page => {
-        return hasPageProps(page) &&
+    return pages.filter(page => hasPageProps(page) &&
             !isDisambiguationPage(page) &&
             hasWikibaseItem(page) &&
             !hasDescription(page) &&
-            !isPageProtected(page);
-        }
+            !isPageProtected(page)
     );
 }
 
@@ -178,16 +176,13 @@ function buildResponse(
         return [];
     }
     wikiPages.forEach((page) => {
-        const wikidataPage = wikidataPages.find(p =>
-            p.title === page.pageprops.wikibase_item);
+        const wikidataPage = wikidataPages.find(p => p.title === page.pageprops.wikibase_item);
         page.wikibase_item = entities[page.pageprops.wikibase_item];
         page.wikibase_item.protection = wikidataPage.protection;
         delete page.pageprops;
     });
-    return wikiPages.filter((page) => {
-        return !isWikibaseItemPageProtected(page) &&
-            isValidResult(page, targetLang, targetWikiLang, sourceLang, sourceWikiLang);
-    });
+    return wikiPages.filter((page) => !isWikibaseItemPageProtected(page) &&
+            isValidResult(page, targetLang, targetWikiLang, sourceLang, sourceWikiLang));
 }
 
 /**
diff --git a/package-lock.json b/package-lock.json
index 16fc0a6..578e8f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,7 @@
 			},
 			"devDependencies": {
 				"ajv": "^6.9.1",
-				"eslint-config-wikimedia": "0.27.0",
+				"eslint-config-wikimedia": "0.28.2",
 				"extend": "^3.0.2",
 				"mocha": "^6.0.1",
 				"mocha-lcov-reporter": "^1.3.0",
@@ -200,9 +200,9 @@
 			}
 		},
 		"node_modules/@babel/helper-validator-identifier": {
-			"version": "7.22.20",
-			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-			"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+			"version": "7.24.7",
+			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+			"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
 			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
@@ -387,11 +387,14 @@
 			}
 		},
 		"node_modules/@es-joy/jsdoccomment": {
-			"version": "0.42.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz",
-			"integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==",
+			"version": "0.43.1",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz",
+			"integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==",
 			"dev": true,
 			"dependencies": {
+				"@types/eslint": "^8.56.5",
+				"@types/estree": "^1.0.5",
+				"@typescript-eslint/types": "^7.2.0",
 				"comment-parser": "1.4.1",
 				"esquery": "^1.5.0",
 				"jsdoc-type-pratt-parser": "~4.0.0"
@@ -416,9 +419,9 @@
 			}
 		},
 		"node_modules/@eslint-community/regexpp": {
-			"version": "4.10.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
-			"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+			"version": "4.11.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
+			"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
 			"dev": true,
 			"engines": {
 				"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -678,6 +681,22 @@
 			"integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
 			"dev": true
 		},
+		"node_modules/@types/eslint": {
+			"version": "8.56.10",
+			"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
+			"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
+			"dev": true,
+			"dependencies": {
+				"@types/estree": "*",
+				"@types/json-schema": "*"
+			}
+		},
+		"node_modules/@types/estree": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+			"dev": true
+		},
 		"node_modules/@types/json-schema": {
 			"version": "7.0.15",
 			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -690,23 +709,17 @@
 			"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
 			"dev": true
 		},
-		"node_modules/@types/semver": {
-			"version": "7.5.8",
-			"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
-			"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
-			"dev": true
-		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
-			"integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz",
+			"integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/visitor-keys": "5.62.0"
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/visitor-keys": "7.15.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -714,12 +727,12 @@
 			}
 		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
-			"integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz",
+			"integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==",
 			"dev": true,
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -727,21 +740,22 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
-			"integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz",
+			"integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/visitor-keys": "5.62.0",
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/visitor-keys": "7.15.0",
 				"debug": "^4.3.4",
 				"globby": "^11.1.0",
 				"is-glob": "^4.0.3",
-				"semver": "^7.3.7",
-				"tsutils": "^3.21.0"
+				"minimatch": "^9.0.4",
+				"semver": "^7.6.0",
+				"ts-api-utils": "^1.3.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -753,10 +767,19 @@
 				}
 			}
 		},
+		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0"
+			}
+		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -770,6 +793,21 @@
 				}
 			}
 		},
+		"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+			"version": "9.0.5",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+			"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
 			"version": "2.1.2",
 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -777,64 +815,38 @@
 			"dev": true
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
-			"integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz",
+			"integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.2.0",
-				"@types/json-schema": "^7.0.9",
-				"@types/semver": "^7.3.12",
-				"@typescript-eslint/scope-manager": "5.62.0",
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/typescript-estree": "5.62.0",
-				"eslint-scope": "^5.1.1",
-				"semver": "^7.3.7"
+				"@eslint-community/eslint-utils": "^4.4.0",
+				"@typescript-eslint/scope-manager": "7.15.0",
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/typescript-estree": "7.15.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-			}
-		},
-		"node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
-			"version": "5.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
-			"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-			"dev": true,
-			"dependencies": {
-				"esrecurse": "^4.3.0",
-				"estraverse": "^4.1.1"
-			},
-			"engines": {
-				"node": ">=8.0.0"
-			}
-		},
-		"node_modules/@typescript-eslint/utils/node_modules/estraverse": {
-			"version": "4.3.0",
-			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-			"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-			"dev": true,
-			"engines": {
-				"node": ">=4.0"
+				"eslint": "^8.56.0"
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
-			"integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz",
+			"integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"eslint-visitor-keys": "^3.3.0"
+				"@typescript-eslint/types": "7.15.0",
+				"eslint-visitor-keys": "^3.4.3"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -860,9 +872,9 @@
 			}
 		},
 		"node_modules/acorn": {
-			"version": "8.11.2",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
-			"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
+			"version": "8.12.1",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+			"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -1270,12 +1282,12 @@
 			}
 		},
 		"node_modules/braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
 			"dev": true,
 			"dependencies": {
-				"fill-range": "^7.0.1"
+				"fill-range": "^7.1.1"
 			},
 			"engines": {
 				"node": ">=8"
@@ -1320,9 +1332,9 @@
 			}
 		},
 		"node_modules/browserslist-config-wikimedia": {
-			"version": "0.6.1",
-			"resolved": "https://registry.npmjs.org/browserslist-config-wikimedia/-/browserslist-config-wikimedia-0.6.1.tgz",
-			"integrity": "sha512-F3O+12ud7ZwBaiB/RZIMGDgz3nEuXz8RhtdPB4Lkd/WVP5Vy77EqBWRMz4vJ64x8LTTH3BOaHCD2ZuUcgShqyQ==",
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/browserslist-config-wikimedia/-/browserslist-config-wikimedia-0.7.0.tgz",
+			"integrity": "sha512-CTa0lv78dXKEgrYsOLCkqO+9UUS3CV9MWEOYHcymgEvx4mYxB80sCoKRCR7wW2SOMNxjaP9hohrZripjnKuRTA==",
 			"dev": true
 		},
 		"node_modules/buffer-from": {
@@ -1343,15 +1355,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/builtins": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
-			"integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
-			"dev": true,
-			"dependencies": {
-				"semver": "^7.0.0"
-			}
-		},
 		"node_modules/bunyan": {
 			"version": "1.8.15",
 			"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz",
@@ -1801,9 +1804,9 @@
 			"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
 		},
 		"node_modules/core-js-compat": {
-			"version": "3.37.0",
-			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz",
-			"integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==",
+			"version": "3.37.1",
+			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz",
+			"integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==",
 			"dev": true,
 			"dependencies": {
 				"browserslist": "^4.23.0"
@@ -2072,6 +2075,19 @@
 				"node": ">= 0.8"
 			}
 		},
+		"node_modules/enhanced-resolve": {
+			"version": "5.17.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
+			"integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.4",
+				"tapable": "^2.2.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
 		"node_modules/error-ex": {
 			"version": "1.3.2",
 			"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2276,9 +2292,9 @@
 			}
 		},
 		"node_modules/eslint-compat-utils": {
-			"version": "0.5.0",
-			"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz",
-			"integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==",
+			"version": "0.5.1",
+			"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
+			"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
 			"dev": true,
 			"dependencies": {
 				"semver": "^7.5.4"
@@ -2291,28 +2307,28 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.27.0",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.27.0.tgz",
-			"integrity": "sha512-KkZ54+MUnggz17C/RCEMXQSpiiqZRF7p9fjrz4phaaeKlTrjg0B+QbM5zcDWcjGiAWaJUptHaH17+RZldadkUw==",
+			"version": "0.28.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
+			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
 			"dev": true,
 			"dependencies": {
-				"browserslist-config-wikimedia": "^0.6.1",
+				"browserslist-config-wikimedia": "^0.7.0",
 				"eslint": "^8.57.0",
 				"eslint-plugin-compat": "^4.2.0",
 				"eslint-plugin-es-x": "^7.6.0",
-				"eslint-plugin-jest": "^27.9.0",
-				"eslint-plugin-jsdoc": "48.2.1",
-				"eslint-plugin-json-es": "^1.5.7",
-				"eslint-plugin-mediawiki": "^0.6.0",
-				"eslint-plugin-mocha": "^10.4.1",
-				"eslint-plugin-n": "^16.6.2",
-				"eslint-plugin-no-jquery": "^2.7.0",
+				"eslint-plugin-jest": "^28.5.0",
+				"eslint-plugin-jsdoc": "48.2.5",
+				"eslint-plugin-json-es": "^1.6.0",
+				"eslint-plugin-mediawiki": "^0.7.0",
+				"eslint-plugin-mocha": "^10.4.3",
+				"eslint-plugin-n": "^17.7.0",
+				"eslint-plugin-no-jquery": "^3.0.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
-				"eslint-plugin-unicorn": "^51.0.1",
-				"eslint-plugin-vue": "^9.23.0",
+				"eslint-plugin-unicorn": "^53.0.0",
+				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
-				"eslint-plugin-yml": "^1.13.2"
+				"eslint-plugin-yml": "^1.14.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -2337,39 +2353,40 @@
 			}
 		},
 		"node_modules/eslint-plugin-es-x": {
-			"version": "7.6.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz",
-			"integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==",
+			"version": "7.8.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
+			"integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
 			"dev": true,
+			"funding": [
+				"https://github.com/sponsors/ota-meshi",
+				"https://opencollective.com/eslint"
+			],
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.1.2",
-				"@eslint-community/regexpp": "^4.6.0",
-				"eslint-compat-utils": "^0.5.0"
+				"@eslint-community/regexpp": "^4.11.0",
+				"eslint-compat-utils": "^0.5.1"
 			},
 			"engines": {
 				"node": "^14.18.0 || >=16.0.0"
 			},
-			"funding": {
-				"url": "https://github.com/sponsors/ota-meshi"
-			},
 			"peerDependencies": {
 				"eslint": ">=8"
 			}
 		},
 		"node_modules/eslint-plugin-jest": {
-			"version": "27.9.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
-			"integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==",
+			"version": "28.6.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz",
+			"integrity": "sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/utils": "^5.10.0"
+				"@typescript-eslint/utils": "^6.0.0 || ^7.0.0"
 			},
 			"engines": {
-				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+				"node": "^16.10.0 || ^18.12.0 || >=20.0.0"
 			},
 			"peerDependencies": {
-				"@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0",
-				"eslint": "^7.0.0 || ^8.0.0",
+				"@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0",
+				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0",
 				"jest": "*"
 			},
 			"peerDependenciesMeta": {
@@ -2382,19 +2399,19 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc": {
-			"version": "48.2.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.1.tgz",
-			"integrity": "sha512-iUvbcyDZSO/9xSuRv2HQBw++8VkV/pt3UWtX9cpPH0l7GKPq78QC/6+PmyQHHvNZaTjAce6QVciEbnc6J/zH5g==",
+			"version": "48.2.5",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.5.tgz",
+			"integrity": "sha512-ZeTfKV474W1N9niWfawpwsXGu+ZoMXu4417eBROX31d7ZuOk8zyG66SO77DpJ2+A9Wa2scw/jRqBPnnQo7VbcQ==",
 			"dev": true,
 			"dependencies": {
-				"@es-joy/jsdoccomment": "~0.42.0",
+				"@es-joy/jsdoccomment": "~0.43.0",
 				"are-docs-informative": "^0.0.2",
 				"comment-parser": "1.4.1",
 				"debug": "^4.3.4",
 				"escape-string-regexp": "^4.0.0",
 				"esquery": "^1.5.0",
 				"is-builtin-module": "^3.2.1",
-				"semver": "^7.6.0",
+				"semver": "^7.6.1",
 				"spdx-expression-parse": "^4.0.0"
 			},
 			"engines": {
@@ -2405,9 +2422,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -2438,9 +2455,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-json-es": {
-			"version": "1.5.7",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.5.7.tgz",
-			"integrity": "sha512-ehBHcCcJo4iViYx6vp3T+SmwzLIlVDzZNoVxN/txZIiPwDQ26mnYaN5iJ3imqN4l1b8z6rbxEH2kB9XDGxeU/w==",
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.6.0.tgz",
+			"integrity": "sha512-xVn6hufGQH1Aa+yqOhQ43Cq28GuitTcMpQh+uaUh27U2qnVLBrvkN+2xQSnv6zpdLEPS35JCNhq4kvhR+PQCgw==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.3.0",
@@ -2451,9 +2468,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mediawiki": {
-			"version": "0.6.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.6.0.tgz",
-			"integrity": "sha512-a2Zm18N5nPyflBajM2ZWATxucIpYPEmOSjFzUR1OBH3hAL0GY9fx1mpezEwzqAQ862d+kPkolgQOzktnZe8nKA==",
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.7.0.tgz",
+			"integrity": "sha512-1Y2nsFDPp96xOZCB5ivZAgqYe9i6w2u64VoCIaAzPyZnd/2h8VQR3CtD+u4Yk/KrpbKq9AAJjrs5LS8VAz6KOA==",
 			"dev": true,
 			"dependencies": {
 				"eslint-plugin-vue": "^9.23.0",
@@ -2464,9 +2481,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mocha": {
-			"version": "10.4.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.2.tgz",
-			"integrity": "sha512-cur4dVYnSEWTBwdqIBQFxa/9siAhesu0TX+lbJ4ClE9j0eNMNe6BSx3vkFFNz6tGoveyMyELFXa30f3fvuAVDg==",
+			"version": "10.4.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.3.tgz",
+			"integrity": "sha512-emc4TVjq5Ht0/upR+psftuz6IBG5q279p+1dSRDeHf+NS9aaerBi3lXKo1SEzwC29hFIW21gO89CEWSvRsi8IQ==",
 			"dev": true,
 			"dependencies": {
 				"eslint-utils": "^3.0.0",
@@ -2481,40 +2498,73 @@
 			}
 		},
 		"node_modules/eslint-plugin-n": {
-			"version": "16.6.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
-			"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
+			"version": "17.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.9.0.tgz",
+			"integrity": "sha512-CPSaXDXdrT4nsrOrO4mT4VB6FMUkoySRkHWuuJJHVqsIEjIeZgMY1H7AzSwPbDScikBmLN82KeM1u7ixV7PzGg==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.4.0",
-				"builtins": "^5.0.1",
+				"enhanced-resolve": "^5.17.0",
 				"eslint-plugin-es-x": "^7.5.0",
 				"get-tsconfig": "^4.7.0",
-				"globals": "^13.24.0",
+				"globals": "^15.0.0",
 				"ignore": "^5.2.4",
-				"is-builtin-module": "^3.2.1",
-				"is-core-module": "^2.12.1",
-				"minimatch": "^3.1.2",
-				"resolve": "^1.22.2",
+				"minimatch": "^9.0.0",
 				"semver": "^7.5.3"
 			},
 			"engines": {
-				"node": ">=16.0.0"
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			},
 			"funding": {
-				"url": "https://github.com/sponsors/mysticatea"
+				"url": "https://opencollective.com/eslint"
 			},
 			"peerDependencies": {
-				"eslint": ">=7.0.0"
+				"eslint": ">=8.23.0"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/globals": {
+			"version": "15.8.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz",
+			"integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==",
+			"dev": true,
+			"engines": {
+				"node": ">=18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/minimatch": {
+			"version": "9.0.5",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+			"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "2.7.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz",
-			"integrity": "sha512-Aeg7dA6GTH1AcWLlBtWNzOU9efK5KpNi7b0EhBO0o0M+awyzguUUo8gF6hXGjQ9n5h8/uRtYv9zOqQkeC5CG0w==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.2.tgz",
+			"integrity": "sha512-n/+6p6PFhWDNPVLJj1463hw4OTIRBbROGcbhmtOHTgw7yihSKzkwZiQ00EJTneyeR3jRiw5lpWSMCCBhtb8t2g==",
 			"dev": true,
 			"peerDependencies": {
-				"eslint": ">=2.3.0"
+				"eslint": ">=8.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-qunit": {
@@ -2540,17 +2590,17 @@
 			}
 		},
 		"node_modules/eslint-plugin-unicorn": {
-			"version": "51.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz",
-			"integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==",
+			"version": "53.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
+			"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
 			"dev": true,
 			"dependencies": {
-				"@babel/helper-validator-identifier": "^7.22.20",
+				"@babel/helper-validator-identifier": "^7.24.5",
 				"@eslint-community/eslint-utils": "^4.4.0",
-				"@eslint/eslintrc": "^2.1.4",
+				"@eslint/eslintrc": "^3.0.2",
 				"ci-info": "^4.0.0",
 				"clean-regexp": "^1.0.0",
-				"core-js-compat": "^3.34.0",
+				"core-js-compat": "^3.37.0",
 				"esquery": "^1.5.0",
 				"indent-string": "^4.0.0",
 				"is-builtin-module": "^3.2.1",
@@ -2559,11 +2609,11 @@
 				"read-pkg-up": "^7.0.1",
 				"regexp-tree": "^0.1.27",
 				"regjsparser": "^0.10.0",
-				"semver": "^7.5.4",
+				"semver": "^7.6.1",
 				"strip-indent": "^3.0.0"
 			},
 			"engines": {
-				"node": ">=16"
+				"node": ">=18.18"
 			},
 			"funding": {
 				"url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
@@ -2572,6 +2622,105 @@
 				"eslint": ">=8.56.0"
 			}
 		},
+		"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+			"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+			"dev": true,
+			"dependencies": {
+				"ajv": "^6.12.4",
+				"debug": "^4.3.2",
+				"espree": "^10.0.1",
+				"globals": "^14.0.0",
+				"ignore": "^5.2.0",
+				"import-fresh": "^3.2.1",
+				"js-yaml": "^4.1.0",
+				"minimatch": "^3.1.2",
+				"strip-json-comments": "^3.1.1"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/argparse": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+			"dev": true
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/debug": {
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+			"dev": true,
+			"dependencies": {
+				"ms": "2.1.2"
+			},
+			"engines": {
+				"node": ">=6.0"
+			},
+			"peerDependenciesMeta": {
+				"supports-color": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
+			"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/espree": {
+			"version": "10.1.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
+			"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.12.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^4.0.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/globals": {
+			"version": "14.0.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+			"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/js-yaml": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+			"dev": true,
+			"dependencies": {
+				"argparse": "^2.0.1"
+			},
+			"bin": {
+				"js-yaml": "bin/js-yaml.js"
+			}
+		},
 		"node_modules/eslint-plugin-unicorn/node_modules/jsesc": {
 			"version": "3.0.2",
 			"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@@ -2584,10 +2733,16 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/eslint-plugin-unicorn/node_modules/ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+			"dev": true
+		},
 		"node_modules/eslint-plugin-vue": {
-			"version": "9.25.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz",
-			"integrity": "sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==",
+			"version": "9.27.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz",
+			"integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.4.0",
@@ -2596,7 +2751,7 @@
 				"nth-check": "^2.1.1",
 				"postcss-selector-parser": "^6.0.15",
 				"semver": "^7.6.0",
-				"vue-eslint-parser": "^9.4.2",
+				"vue-eslint-parser": "^9.4.3",
 				"xml-name-validator": "^4.0.0"
 			},
 			"engines": {
@@ -3048,9 +3203,9 @@
 			}
 		},
 		"node_modules/fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
 			"dev": true,
 			"dependencies": {
 				"to-regex-range": "^5.0.1"
@@ -3374,9 +3529,9 @@
 			}
 		},
 		"node_modules/get-tsconfig": {
-			"version": "4.7.2",
-			"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz",
-			"integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==",
+			"version": "4.7.5",
+			"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+			"integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
 			"dev": true,
 			"dependencies": {
 				"resolve-pkg-maps": "^1.0.0"
@@ -4768,17 +4923,6 @@
 			"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
 			"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
 		},
-		"node_modules/lru-cache": {
-			"version": "6.0.0",
-			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-			"dependencies": {
-				"yallist": "^4.0.0"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/make-dir": {
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -4841,12 +4985,12 @@
 			}
 		},
 		"node_modules/micromatch": {
-			"version": "4.0.5",
-			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-			"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+			"version": "4.0.7",
+			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
+			"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
 			"dev": true,
 			"dependencies": {
-				"braces": "^3.0.2",
+				"braces": "^3.0.3",
 				"picomatch": "^2.3.1"
 			},
 			"engines": {
@@ -6046,9 +6190,9 @@
 			}
 		},
 		"node_modules/postcss-selector-parser": {
-			"version": "6.0.16",
-			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
-			"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
+			"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
 			"dev": true,
 			"dependencies": {
 				"cssesc": "^3.0.0",
@@ -7154,12 +7298,9 @@
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
 		},
 		"node_modules/semver": {
-			"version": "7.6.0",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
-			"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-			"dependencies": {
-				"lru-cache": "^6.0.0"
-			},
+			"version": "7.6.2",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+			"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
 			"bin": {
 				"semver": "bin/semver.js"
 			},
@@ -7810,6 +7951,15 @@
 				"node": ">=4"
 			}
 		},
+		"node_modules/tapable": {
+			"version": "2.2.1",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+			"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
 		"node_modules/tar": {
 			"version": "4.4.19",
 			"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
@@ -8057,25 +8207,16 @@
 				"node": ">=0.8"
 			}
 		},
-		"node_modules/tslib": {
-			"version": "1.14.1",
-			"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-			"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-			"dev": true
-		},
-		"node_modules/tsutils": {
-			"version": "3.21.0",
-			"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-			"integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+		"node_modules/ts-api-utils": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+			"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
 			"dev": true,
-			"dependencies": {
-				"tslib": "^1.8.1"
-			},
 			"engines": {
-				"node": ">= 6"
+				"node": ">=16"
 			},
 			"peerDependencies": {
-				"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+				"typescript": ">=4.2.0"
 			}
 		},
 		"node_modules/tunnel-agent": {
@@ -8211,9 +8352,9 @@
 			"dev": true
 		},
 		"node_modules/typescript": {
-			"version": "5.4.5",
-			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
-			"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+			"version": "5.5.3",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+			"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
 			"dev": true,
 			"peer": true,
 			"bin": {
@@ -8349,9 +8490,9 @@
 			}
 		},
 		"node_modules/vue-eslint-parser": {
-			"version": "9.4.2",
-			"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
-			"integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
+			"version": "9.4.3",
+			"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+			"integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
 			"dev": true,
 			"dependencies": {
 				"debug": "^4.3.4",
@@ -8373,9 +8514,9 @@
 			}
 		},
 		"node_modules/vue-eslint-parser/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -8616,11 +8757,6 @@
 			"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
 			"dev": true
 		},
-		"node_modules/yallist": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-		},
 		"node_modules/yaml": {
 			"version": "2.4.1",
 			"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
diff --git a/package.json b/package.json
index a940615..533669c 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
 	},
 	"devDependencies": {
 		"ajv": "^6.9.1",
-		"eslint-config-wikimedia": "0.27.0",
+		"eslint-config-wikimedia": "0.28.2",
 		"extend": "^3.0.2",
 		"mocha": "^6.0.1",
 		"mocha-lcov-reporter": "^1.3.0",
diff --git a/routes/article.creation.morelike.js b/routes/article.creation.morelike.js
index aea403f..1abc1d8 100644
--- a/routes/article.creation.morelike.js
+++ b/routes/article.creation.morelike.js
@@ -85,13 +85,11 @@ router.get('/:seed', (req, res) => {
                                     app, ids, language
                                 ).then((results) => {
                                     res.json(results);
-                                }).catch((error) => {
-                                    return BBPromise.reject(new util.HTTPError({
+                                }).catch((error) => BBPromise.reject(new util.HTTPError({
                                         status: 503,
                                         message: 'Cannot retrieve normalized ranks from' +
                                             ' the database. Please try again later.'
-                                    }));
-                                });
+                                    })));
                             }
                         });
                 });
diff --git a/test/features/app/app.js b/test/features/app/app.js
index 0ac8f00..7e3d3e5 100644
--- a/test/features/app/app.js
+++ b/test/features/app/app.js
@@ -17,14 +17,12 @@ describe('express app', function () {
 
     before(() => server.start());
 
-    it('should get robots.txt', () => {
-        return preq.get({
+    it('should get robots.txt', () => preq.get({
             uri: `${ server.config.uri }robots.txt`
         }).then((res) => {
             assert.deepEqual(res.status, 200);
             assert.deepEqual(res.headers.disallow, '/');
-        });
-    });
+        }));
 
     it('should set CORS headers', () => {
         if (server.config.service.conf.cors === false) {
@@ -59,8 +57,7 @@ describe('express app', function () {
         });
     });
 
-    it('should get static content gzipped', () => {
-        return preq.get({
+    it('should get static content gzipped', () => preq.get({
             uri: `${ server.config.uri }static/index.html`,
             headers: {
                 'accept-encoding': 'gzip, deflate'
@@ -70,11 +67,9 @@ describe('express app', function () {
             // if there is no content-length, the reponse was gzipped
             assert.deepEqual(res.headers['content-length'], undefined,
                 'Did not expect the content-length header!');
-        });
-    });
+        }));
 
-    it('should get static content uncompressed', () => {
-        return preq.get({
+    it('should get static content uncompressed', () => preq.get({
             uri: `${ server.config.uri }static/index.html`,
             headers: {
                 'accept-encoding': ''
@@ -83,6 +78,5 @@ describe('express app', function () {
             const contentEncoding = res.headers['content-encoding'];
             assert.deepEqual(res.status, 200);
             assert.deepEqual(contentEncoding, undefined, 'Did not expect gzipped contents!');
-        });
-    });
+        }));
 });
diff --git a/test/features/app/spec.js b/test/features/app/spec.js
index 60b500d..84ca636 100644
--- a/test/features/app/spec.js
+++ b/test/features/app/spec.js
@@ -240,19 +240,15 @@ describe('Swagger spec', function () {
 
     this.timeout(20000);
 
-    before(() => {
-        return server.start();
-    });
+    before(() => server.start());
 
-    it('get the spec', () => {
-        return preq.get(`${ server.config.uri }?spec`)
+    it('get the spec', () => preq.get(`${ server.config.uri }?spec`)
         .then((res) => {
             assert.status(200);
             assert.contentType(res, 'application/json');
             assert.notDeepEqual(res.body, undefined, 'No body received!');
             spec = res.body;
-        });
-    });
+        }));
 
     it('spec validation', () => {
         if (spec['x-default-params']) {
@@ -282,14 +278,12 @@ describe('Swagger spec', function () {
     describe('routes', () => {
 
         constructTests(spec.paths, defParams).forEach((testCase) => {
-            it(testCase.title, () => {
-                return preq(testCase.request)
+            it(testCase.title, () => preq(testCase.request)
                 .then((res) => {
                     validateTestResponse(testCase, res);
                 }, (err) => {
                     validateTestResponse(testCase, err);
-                });
-            });
+                }));
         });
 
     });
diff --git a/test/features/v1/article.creation.morelike.js b/test/features/v1/article.creation.morelike.js
index c280826..dab1388 100644
--- a/test/features/v1/article.creation.morelike.js
+++ b/test/features/v1/article.creation.morelike.js
@@ -29,8 +29,7 @@ before(() => server.start());
 describe('article.creation.morelike', function () {
     this.timeout(20000);
 
-    it('should return recommendations for good article title', () => {
-        return preq.get(
+    it('should return recommendations for good article title', () => preq.get(
             `${ server.config.uri }uz.wikipedia.org/v1/article/creation/morelike/Palov`
         ).then((res) => {
             assert.status(res, 200);
@@ -43,6 +42,5 @@ describe('article.creation.morelike', function () {
                     { wikidata_id: 'Q127418', normalized_rank: 0.891431 }
                 ]
             );
-        });
-    });
+        }));
 });
diff --git a/test/lib/caption.js b/test/lib/caption.js
index 39392d1..1378664 100644
--- a/test/lib/caption.js
+++ b/test/lib/caption.js
@@ -190,9 +190,7 @@ describe('lib:caption', () => {
             }
         };
 
-        const cond = (image, targetLang, sourceLang) => {
-            return image.structured.captions[sourceLang] && !image.structured.captions[targetLang];
-        };
+        const cond = (image, targetLang, sourceLang) => image.structured.captions[sourceLang] && !image.structured.captions[targetLang];
 
         const makeResults = lib.__get__('makeResults');
 
diff --git a/test/lib/suggested-edits-common.js b/test/lib/suggested-edits-common.js
index e8b36f1..e92c212 100644
--- a/test/lib/suggested-edits-common.js
+++ b/test/lib/suggested-edits-common.js
@@ -57,23 +57,17 @@ describe('lib:suggested-edits-common', () => {
 
     describe('getWikiLangForLangCode', () => {
 
-        it('translates language variants to base wiki language codes', () => {
-            return lib.getWikiLangForLangCode(app, req, 'zh-hans').then((res) => {
+        it('translates language variants to base wiki language codes', () => lib.getWikiLangForLangCode(app, req, 'zh-hans').then((res) => {
                 assert.deepEqual(res, 'zh');
-            });
-        });
+            }));
 
-        it('passed through other inputs', () => {
-            return lib.getWikiLangForLangCode(app, req, 'foo').then((res) => {
+        it('passed through other inputs', () => lib.getWikiLangForLangCode(app, req, 'foo').then((res) => {
                 assert.deepEqual(res, 'foo');
-            });
-        });
+            }));
 
-        it('handles undefined', () => {
-            return lib.getWikiLangForLangCode(app, req, undefined).then((res) => {
+        it('handles undefined', () => lib.getWikiLangForLangCode(app, req, undefined).then((res) => {
                 assert.deepEqual(res, undefined);
-            });
-        });
+            }));
 
     });
 
diff --git a/test/utils/server.js b/test/utils/server.js
index 70099e1..44cb649 100644
--- a/test/utils/server.js
+++ b/test/utils/server.js
@@ -29,9 +29,7 @@ config.conf.logging = {
 // make a deep copy of it for later reference
 const origConfig = extend(true, {}, config);
 
-module.exports.stop = () => {
-    return BBPromise.resolve();
-};
+module.exports.stop = () => BBPromise.resolve();
 let options = null;
 const runner = new ServiceRunner();
 
@@ -50,13 +48,9 @@ function start(_options) {
             .then((serviceReturns) => {
                 module.exports.stop = () => {
                     console.log('stopping test server');
-                    serviceReturns.forEach(servers =>
-                        servers.forEach(server =>
-                            server.shutdown()));
+                    serviceReturns.forEach(servers => servers.forEach(server => server.shutdown()));
                     return runner.stop().then(() => {
-                        module.exports.stop = () => {
-                            return BBPromise.resolve();
-                        };
+                        module.exports.stop = () => BBPromise.resolve();
                     });
                 };
                 return true;
-- 
2.39.2

$ date
--- stdout ---
Thu Jul  4 16:33:05 UTC 2024

--- end ---
$ git clone file:///srv/git/mediawiki-services-recommendation-api.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
--- stdout ---

--- end ---
$ git config user.name libraryupgrader
--- stdout ---

--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---

--- end ---
$ git submodule update --init
--- stdout ---

--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.

--- end ---
$ git show-ref refs/heads/master
--- stdout ---
89967d28df149b294c4024ac790c5890156b3b55 refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "ajv": {
      "name": "ajv",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097685,
          "name": "ajv",
          "dependency": "ajv",
          "title": "Prototype Pollution in Ajv",
          "url": "https://github.com/advisories/GHSA-v88g-cgmw-v5xw",
          "severity": "moderate",
          "cwe": [
            "CWE-915",
            "CWE-1321"
          ],
          "cvss": {
            "score": 5.6,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"
          },
          "range": "<6.12.3"
        }
      ],
      "effects": [
        "ajv-keywords",
        "eslint",
        "table"
      ],
      "range": "<6.12.3",
      "nodes": [
        "node_modules/rewire/node_modules/ajv",
        "node_modules/table/node_modules/ajv"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "ajv-keywords": {
      "name": "ajv-keywords",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv"
      ],
      "effects": [],
      "range": "2.1.1",
      "nodes": [
        "node_modules/table/node_modules/ajv-keywords"
      ],
      "fixAvailable": true
    },
    "braces": {
      "name": "braces",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1097496,
          "name": "braces",
          "dependency": "braces",
          "title": "Uncontrolled resource consumption in braces",
          "url": "https://github.com/advisories/GHSA-grv7-fg5c-xmjg",
          "severity": "high",
          "cwe": [
            "CWE-1050"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<3.0.3"
        }
      ],
      "effects": [],
      "range": "<3.0.3",
      "nodes": [
        "node_modules/braces"
      ],
      "fixAvailable": true
    },
    "debug": {
      "name": "debug",
      "severity": "low",
      "isDirect": false,
      "via": [
        {
          "source": 1096793,
          "name": "debug",
          "dependency": "debug",
          "title": "Regular Expression Denial of Service in debug",
          "url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.7,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=3.2.0 <3.2.7"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "3.2.0 - 3.2.6",
      "nodes": [
        "node_modules/mocha/node_modules/debug"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "eslint": {
      "name": "eslint",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv",
        "table"
      ],
      "effects": [
        "rewire"
      ],
      "range": "2.5.0 - 2.5.2 || 4.2.0 - 5.0.0-rc.0",
      "nodes": [
        "node_modules/rewire/node_modules/eslint"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "flat": {
      "name": "flat",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1089152,
          "name": "flat",
          "dependency": "flat",
          "title": "flat vulnerable to Prototype Pollution",
          "url": "https://github.com/advisories/GHSA-2j2x-2gpw-g8fm",
          "severity": "critical",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 9.8,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<5.0.1"
        }
      ],
      "effects": [
        "yargs-unparser"
      ],
      "range": "<5.0.1",
      "nodes": [
        "node_modules/flat"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "limitation": {
      "name": "limitation",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "wikimedia-kad-fork"
      ],
      "effects": [
        "service-runner"
      ],
      "range": ">=0.2.3",
      "nodes": [
        "node_modules/limitation"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "minimatch": {
      "name": "minimatch",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1096485,
          "name": "minimatch",
          "dependency": "minimatch",
          "title": "minimatch ReDoS vulnerability",
          "url": "https://github.com/advisories/GHSA-f8q6-p94x-37v3",
          "severity": "high",
          "cwe": [
            "CWE-400",
            "CWE-1333"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<3.0.5"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "<3.0.5",
      "nodes": [
        "node_modules/mocha/node_modules/minimatch"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "mocha": {
      "name": "mocha",
      "severity": "critical",
      "isDirect": true,
      "via": [
        "debug",
        "minimatch",
        "yargs-unparser"
      ],
      "effects": [],
      "range": "5.1.0 - 9.2.1",
      "nodes": [
        "node_modules/mocha"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "ms": {
      "name": "ms",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1094419,
          "name": "ms",
          "dependency": "ms",
          "title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
          "url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
          "severity": "moderate",
          "cwe": [
            "CWE-1333"
          ],
          "cvss": {
            "score": 5.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": "<2.0.0"
        }
      ],
      "effects": [
        "wikimedia-kad-fork"
      ],
      "range": "<2.0.0",
      "nodes": [
        "node_modules/wikimedia-kad-fork/node_modules/ms"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "preq": {
      "name": "preq",
      "severity": "high",
      "isDirect": true,
      "via": [
        "request",
        "requestretry"
      ],
      "effects": [],
      "range": "*",
      "nodes": [
        "node_modules/preq"
      ],
      "fixAvailable": false
    },
    "request": {
      "name": "request",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1096727,
          "name": "request",
          "dependency": "request",
          "title": "Server-Side Request Forgery in Request",
          "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 6.1,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
          },
          "range": "<=2.88.2"
        },
        "tough-cookie"
      ],
      "effects": [
        "preq",
        "requestretry"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": false
    },
    "requestretry": {
      "name": "requestretry",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1090420,
          "name": "requestretry",
          "dependency": "requestretry",
          "title": "Cookie exposure in requestretry",
          "url": "https://github.com/advisories/GHSA-hjp8-2cm3-cc45",
          "severity": "high",
          "cwe": [
            "CWE-200"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
          },
          "range": "<7.0.0"
        },
        "request"
      ],
      "effects": [
        "preq"
      ],
      "range": "*",
      "nodes": [
        "node_modules/requestretry"
      ],
      "fixAvailable": false
    },
    "rewire": {
      "name": "rewire",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "eslint"
      ],
      "effects": [],
      "range": "4.0.0 - 4.0.1",
      "nodes": [
        "node_modules/rewire"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "service-runner": {
      "name": "service-runner",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "limitation",
        "tar"
      ],
      "effects": [],
      "range": ">=3.0.0",
      "nodes": [
        "node_modules/service-runner"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "swagger-ui": {
      "name": "swagger-ui",
      "severity": "critical",
      "isDirect": true,
      "via": [
        {
          "source": 1085691,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Reverse Tabnapping in swagger-ui",
          "url": "https://github.com/advisories/GHSA-x9p2-fxq6-2m5f",
          "severity": "moderate",
          "cwe": [
            "CWE-1022"
          ],
          "cvss": {
            "score": 4.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
          },
          "range": "<3.18.0"
        },
        {
          "source": 1086900,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-Site Scripting in swagger-ui",
          "url": "https://github.com/advisories/GHSA-388g-jwpg-x6j4",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N"
          },
          "range": "<3.0.13"
        },
        {
          "source": 1088760,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Spoofing attack in swagger-ui",
          "url": "https://github.com/advisories/GHSA-cr3q-pqgq-m8c2",
          "severity": "moderate",
          "cwe": [
            "CWE-20"
          ],
          "cvss": {
            "score": 4.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
          },
          "range": "<4.1.3"
        },
        {
          "source": 1088813,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-site scripting in Swagger-UI",
          "url": "https://github.com/advisories/GHSA-c427-hjc3-wrfw",
          "severity": "critical",
          "cwe": [
            "CWE-79",
            "CWE-352"
          ],
          "cvss": {
            "score": 9.8,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<3.23.11"
        },
        {
          "source": 1092161,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Server side request forgery in SwaggerUI",
          "url": "https://github.com/advisories/GHSA-qrmm-w75w-3wpx",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<4.1.3"
        },
        {
          "source": 1094217,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-Site Scripting in swagger-ui",
          "url": "https://github.com/advisories/GHSA-4f9m-pxwh-68hg",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<3.20.9"
        }
      ],
      "effects": [],
      "range": "<=4.1.2",
      "nodes": [
        "node_modules/swagger-ui"
      ],
      "fixAvailable": false
    },
    "table": {
      "name": "table",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv"
      ],
      "effects": [
        "eslint"
      ],
      "range": "3.7.10 - 4.0.2",
      "nodes": [
        "node_modules/table"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "tar": {
      "name": "tar",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097493,
          "name": "tar",
          "dependency": "tar",
          "title": "Denial of service while parsing a tar file due to lack of folders count validation",
          "url": "https://github.com/advisories/GHSA-f5x3-32g6-xq36",
          "severity": "moderate",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
          },
          "range": "<6.2.1"
        }
      ],
      "effects": [
        "service-runner"
      ],
      "range": "<6.2.1",
      "nodes": [
        "node_modules/tar"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "tough-cookie": {
      "name": "tough-cookie",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097682,
          "name": "tough-cookie",
          "dependency": "tough-cookie",
          "title": "tough-cookie Prototype Pollution vulnerability",
          "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
          "severity": "moderate",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<4.1.3"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<4.1.3",
      "nodes": [
        "node_modules/tough-cookie"
      ],
      "fixAvailable": false
    },
    "wikimedia-kad-fork": {
      "name": "wikimedia-kad-fork",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ms"
      ],
      "effects": [
        "limitation"
      ],
      "range": "*",
      "nodes": [
        "node_modules/wikimedia-kad-fork"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "yargs-unparser": {
      "name": "yargs-unparser",
      "severity": "critical",
      "isDirect": false,
      "via": [
        "flat"
      ],
      "effects": [
        "mocha"
      ],
      "range": "<=1.6.3",
      "nodes": [
        "node_modules/yargs-unparser"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 1,
      "moderate": 12,
      "high": 4,
      "critical": 4,
      "total": 21
    },
    "dependencies": {
      "prod": 205,
      "dev": 578,
      "optional": 9,
      "peer": 1,
      "peerOptional": 0,
      "total": 791
    }
  }
}

--- end ---
Upgrading n:eslint-config-wikimedia from 0.27.0 -> 0.28.2
$ /usr/bin/npm install
--- stderr ---
npm WARN skipping integrity check for git dependency ssh://git@github.com/wikimedia/swagger-ui.git 
npm WARN deprecated kad-fs@0.0.4: This package is no longer maintained.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated kad-memstore@0.0.1: This package is no longer maintained.
npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated sinon@7.5.0: 16.1.1
--- stdout ---

added 801 packages, and audited 802 packages in 1m

113 packages are looking for funding
  run `npm fund` for details

20 vulnerabilities (1 low, 12 moderate, 3 high, 4 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---

/src/repo/app.js
   20:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
   71:37  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  145:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  147:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  159:31  warning  Found non-literal argument in require             security/detect-non-literal-require
  196:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  197:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  235:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types

/src/repo/config.dev.yaml
  22:1  warning  Empty mapping values are forbidden  yml/no-empty-mapping-value
  93:9  warning  Empty mapping values are forbidden  yml/no-empty-mapping-value

/src/repo/config.yaml
  22:1  warning  Empty mapping values are forbidden  yml/no-empty-mapping-value
  93:9  warning  Empty mapping values are forbidden  yml/no-empty-mapping-value

/src/repo/lib/api-util.js
   81:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  128:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/lib/article.creation.morelike.js
  1:11  warning  'setTimeout' is already defined as a built-in global variable  no-redeclare

/src/repo/lib/article.creation.translation.js
  129:12  warning  'candidates' is already declared in the upper scope on line 122 column 9  no-shadow

/src/repo/lib/util.js
  109:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  155:1  warning  The type 'bool' is undefined         jsdoc/no-undefined-types
  165:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  260:1  warning  The type 'Router' is undefined       jsdoc/no-undefined-types
  280:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/routes/article.creation.morelike.js
  75:32  warning  'ids' is already declared in the upper scope on line 66 column 24  no-shadow

/src/repo/routes/article.creation.translation.js
  14:25  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/spec.yaml
  234:1  warning  This line has a length of 115. Maximum allowed is 100  max-len
  307:1  warning  This line has a length of 120. Maximum allowed is 100  max-len

/src/repo/test/features/app/app.js
  1:11  warning  'describe' is already defined as a built-in global variable  no-redeclare
  1:21  warning  'it' is already defined as a built-in global variable        no-redeclare
  1:25  warning  'before' is already defined as a built-in global variable    no-redeclare
  1:33  warning  'after' is already defined as a built-in global variable     no-redeclare

/src/repo/test/features/app/spec.js
    1:11  warning  'describe' is already defined as a built-in global variable                no-redeclare
    1:21  warning  'it' is already defined as a built-in global variable                      no-redeclare
    1:25  warning  'before' is already defined as a built-in global variable                  no-redeclare
    1:33  warning  'after' is already defined as a built-in global variable                   no-redeclare
   24:30  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  178:14  warning  Found non-literal argument to RegExp Constructor                           security/detect-non-literal-regexp

/src/repo/test/lib/caption.js
  193:1  warning  This line has a length of 136. Maximum allowed is 100  max-len

/src/repo/test/utils/logStream.js
  41:13  warning  'end' is already declared in the upper scope on line 31 column 14  no-shadow
  49:18  warning  'get' is already declared in the upper scope on line 34 column 14  no-shadow

✖ 38 problems (0 errors, 38 warnings)


--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/app.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'bluebird' is undefined.","line":20,"column":1,"nodeType":"Block","endLine":20,"endColumn":1},{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":71,"column":37,"nodeType":"NewExpression","endLine":71,"endColumn":133},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":145,"column":1,"nodeType":"Block","endLine":145,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'bluebird' is undefined.","line":147,"column":1,"nodeType":"Block","endLine":147,"endColumn":1},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":159,"column":31,"nodeType":"CallExpression","endLine":159,"endColumn":61},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":196,"column":1,"nodeType":"Block","endLine":196,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'bluebird' is undefined.","line":197,"column":1,"nodeType":"Block","endLine":197,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'bluebird' is undefined.","line":235,"column":1,"nodeType":"Block","endLine":235,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst http = require('http');\nconst BBPromise = require('bluebird');\nconst express = require('express');\nconst compression = require('compression');\nconst bodyParser = require('body-parser');\nconst fs = BBPromise.promisifyAll(require('fs'));\nconst sUtil = require('./lib/util');\nconst apiUtil = require('./lib/api-util');\nconst packageInfo = require('./package.json');\nconst yaml = require('js-yaml');\nconst addShutdown = require('http-shutdown');\nconst path = require('path');\n\n/**\n * Creates an express app and initialises it\n *\n * @param {Object} options the options to initialise the app with\n * @return {bluebird} the promise resolving to the app object\n */\nfunction initApp(options) {\n\n    // the main application object\n    const app = express();\n\n    // get the options and make them available in the app\n    app.logger = options.logger;    // the logging device\n    app.metrics = options.metrics;  // the metrics\n    app.conf = options.config;      // this app's config options\n    app.info = packageInfo;         // this app's package info\n\n    // ensure some sane defaults\n    if (!app.conf.port) {\n        app.conf.port = 8888;\n    }\n    if (!app.conf.interface) {\n        app.conf.interface = '0.0.0.0';\n    }\n    if (app.conf.compression_level === undefined) {\n        app.conf.compression_level = 3;\n    }\n    if (app.conf.cors === undefined) {\n        app.conf.cors = '*';\n    }\n    if (app.conf.csp === undefined) {\n        app.conf.csp = \"default-src 'self'; object-src 'none'; media-src *; img-src *; style-src *; frame-ancestors 'self'\";\n    }\n\n    // set outgoing proxy\n    if (app.conf.proxy) {\n        process.env.HTTP_PROXY = app.conf.proxy;\n        // if there is a list of domains which should\n        // not be proxied, set it\n        if (app.conf.no_proxy_list) {\n            if (Array.isArray(app.conf.no_proxy_list)) {\n                process.env.NO_PROXY = app.conf.no_proxy_list.join(',');\n            } else {\n                process.env.NO_PROXY = app.conf.no_proxy_list;\n            }\n        }\n    }\n\n    // set up header whitelisting for logging\n    if (!app.conf.log_header_whitelist) {\n        app.conf.log_header_whitelist = [\n            'cache-control', 'content-type', 'content-length', 'if-match',\n            'user-agent', 'x-request-id'\n        ];\n    }\n    app.conf.log_header_whitelist = new RegExp(`^(?:${ app.conf.log_header_whitelist.map((item) => item.trim()).join('|') })$`, 'i');\n\n    // set up the request templates for the APIs\n    apiUtil.setupApiTemplates(app);\n    // set up query templates\n    apiUtil.setupQueryTemplates(app);\n\n    // set up the spec\n    if (!app.conf.spec) {\n        app.conf.spec = `${ __dirname }/spec.yaml`;\n    }\n    if (app.conf.spec.constructor !== Object) {\n        try {\n            app.conf.spec = yaml.safeLoad(fs.readFileSync(app.conf.spec));\n        } catch (e) {\n            app.logger.log('warn/spec', `Could not load the spec: ${ e }`);\n            app.conf.spec = {};\n        }\n    }\n    if (!app.conf.spec.swagger) {\n        app.conf.spec.swagger = '2.0';\n    }\n    if (!app.conf.spec.info) {\n        app.conf.spec.info = {\n            version: app.info.version,\n            title: app.info.name,\n            description: app.info.description\n        };\n    }\n    app.conf.spec.info.version = app.info.version;\n    if (!app.conf.spec.paths) {\n        app.conf.spec.paths = {};\n    }\n\n    // set the CORS and CSP headers\n    app.all('*', (req, res, next) => {\n        if (app.conf.cors !== false) {\n            res.header('access-control-allow-origin', app.conf.cors);\n            res.header('access-control-allow-headers', 'accept, x-requested-with, content-type');\n            res.header('access-control-expose-headers', 'etag');\n        }\n        if (app.conf.csp !== false) {\n            res.header('x-xss-protection', '1; mode=block');\n            res.header('x-content-type-options', 'nosniff');\n            res.header('x-frame-options', 'SAMEORIGIN');\n            res.header('content-security-policy', app.conf.csp);\n            res.header('x-content-security-policy', app.conf.csp);\n            res.header('x-webkit-csp', app.conf.csp);\n        }\n        sUtil.initAndLogRequest(req, app);\n        next();\n    });\n\n    // set up the user agent header string to use for requests\n    app.conf.user_agent = app.conf.user_agent || app.info.name;\n\n    // disable the X-Powered-By header\n    app.set('x-powered-by', false);\n    // disable the ETag header - users should provide them!\n    app.set('etag', false);\n    // enable compression\n    app.use(compression({ level: app.conf.compression_level }));\n    // use the JSON body parser\n    app.use(bodyParser.json());\n    // use the application/x-www-form-urlencoded parser\n    app.use(bodyParser.urlencoded({ extended: true }));\n\n    return BBPromise.resolve(app);\n\n}\n\n/**\n * Loads all routes declared in routes/ into the app\n *\n * @param {Application} app the application object to load routes into\n * @param {string} dir\n * @return {bluebird} a promise resolving to the app object\n */\nfunction loadRoutes(app, dir) {\n\n    // recursively load routes from .js files under routes/\n    return fs.readdirAsync(dir).map((fname) => BBPromise.try(() => {\n            const resolvedPath = path.resolve(dir, fname);\n            const isDirectory = fs.statSync(resolvedPath).isDirectory();\n            if (isDirectory) {\n                loadRoutes(app, resolvedPath);\n            } else if (/\\.js$/.test(fname)) {\n                // import the route file\n                const route = require(`${ dir }/${ fname }`);\n                return route(app);\n            }\n        }).then((route) => {\n            if (route === undefined) {\n                return undefined;\n            }\n            // check that the route exports the object we need\n            if (route.constructor !== Object || !route.path || !route.router ||\n                !(route.api_version || route.skip_domain)) {\n                throw new TypeError(`routes/${ fname } does not export the correct object!`);\n            }\n            // normalise the path to be used as the mount point\n            if (route.path[0] !== '/') {\n                route.path = `/${ route.path }`;\n            }\n            if (route.path[route.path.length - 1] !== '/') {\n                route.path = `${ route.path }/`;\n            }\n            if (!route.skip_domain) {\n                route.path = `/:domain/v${ route.api_version }${ route.path }`;\n            }\n            // wrap the route handlers with Promise.try() blocks\n            sUtil.wrapRouteHandlers(route, app);\n            // all good, use that route\n            app.use(route.path, route.router);\n        })).then(() => {\n        // catch errors\n        sUtil.setErrorHandler(app);\n        // route loading is now complete, return the app object\n        return BBPromise.resolve(app);\n    });\n}\n\n/**\n * Creates and start the service's web server\n *\n * @param {Application} app the app object to use in the service\n * @return {bluebird} a promise creating the web server\n */\nfunction createServer(app) {\n\n    // return a promise which creates an HTTP server,\n    // attaches the app to it, and starts accepting\n    // incoming client requests\n    let server;\n    return new BBPromise((resolve) => {\n        server = http.createServer(app).listen(\n            app.conf.port,\n            app.conf.interface,\n            resolve\n        );\n        server = addShutdown(server);\n    }).then(() => {\n        app.logger.log('info',\n            `Worker ${ process.pid } listening on ${ app.conf.interface || '*' }:${ app.conf.port }`);\n\n        // Don't delay incomplete packets for 40ms (Linux default) on\n        // pipelined HTTP sockets. We write in large chunks or buffers, so\n        // lack of coalescing should not be an issue here.\n        server.on('connection', (socket) => {\n            socket.setNoDelay(true);\n        });\n\n        return server;\n    });\n\n}\n\n/**\n * The service's entry point. It takes over the configuration\n * options and the logger- and metrics-reporting objects from\n * service-runner and starts an HTTP server, attaching the application\n * object to it.\n *\n * @param {Object} options\n * @return {bluebird}\n */\nmodule.exports = function (options) {\n\n    return initApp(options)\n    .then(app => loadRoutes(app, `${ __dirname }/routes`))\n    .then((app) => {\n        // serve static files from static/\n        app.use('/static', express.static(`${ __dirname }/static`));\n        return app;\n    }).then(createServer);\n\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/config.dev.yaml","messages":[{"ruleId":"yml/no-empty-mapping-value","severity":1,"message":"Empty mapping values are forbidden.","line":22,"column":1,"nodeType":"YAMLPair","messageId":"unexpectedEmpty","endLine":22,"endColumn":9},{"ruleId":"yml/no-empty-mapping-value","severity":1,"message":"Empty mapping values are forbidden.","line":93,"column":9,"nodeType":"YAMLPair","messageId":"unexpectedEmpty","endLine":93,"endColumn":14}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"# Number of worker processes to spawn.\n# Set to 0 to run everything in a single process without clustering.\n# Use 'ncpu' to run as many workers as there are CPU units\nnum_workers: 0\n\n# Log error messages and gracefully restart a worker if v8 reports that it\n# uses more heap (note: not RSS) than this many mb.\nworker_heap_limit_mb: 250\n\n# Logger info\nlogging:\n  level: trace\n  streams:\n    - type: stdout # log to stdout\n      named_levels: true # emit log level name instead of index. e.g. INFO vs 30\n#  # Use gelf-stream -> logstash\n#  - type: gelf\n#    host: logstash1003.eqiad.wmnet\n#    port: 12201\n\n# Statsd metrics reporter\nmetrics:\n# type: log\n# host: localhost\n# port: 8125\n\nservices:\n  - name: recommendation-api\n    # a relative path or the name of an npm package, if different from name\n    module: ./app.js\n    # optionally, a version constraint of the npm package\n    # version: ^0.4.0\n    # per-service config\n    conf:\n      port: 6927\n      # interface: localhost # uncomment to only listen on localhost\n      # more per-service config settings\n      # the location of the spec, defaults to spec.yaml if not specified\n      # spec: ./spec.template.yaml\n      # allow cross-domain requests to the API (default '*')\n      cors: \"*\"\n      # to disable use:\n      # cors: false\n      # to restrict to a particular domain, use:\n      # cors: restricted.domain.org\n      # content for the CSP headers\n      # csp: false  # uncomment this line to disable sending them\n      # URL of the outbound proxy to use (complete with protocol)\n      # proxy: http://my.proxy.org:8080\n      # the list of domains for which not to use the proxy defined above\n      # no_proxy_list:\n      #   - domain1.com\n      #   - domain2.org\n      # the list of incoming request headers that can be logged; if left empty,\n      # the following headers are allowed: cache-control, content-length,\n      # content-type, if-match, user-agent, x-request-id\n      # log_header_whitelist:\n      #   - cache-control\n      #   - content-length\n      #   - content-type\n      #   - if-match\n      #   - user-agent\n      #   - x-request-id\n      # the user agent to use when issuing requests\n      # user_agent: service-template-node\n      # the template used for contacting the MW API\n      mwapi_req:\n        method: post\n        uri: https://{{domain}}/w/api.php\n        headers:\n          user-agent: \"{{user-agent}}\"\n        body: \"{{ default(request.query, {}) }}\"\n      # the template used for contacting RESTBase\n      restbase_req:\n        method: \"{{request.method}}\"\n        uri: https://{{domain}}/api/rest_v1/{+path}\n        query: \"{{ default(request.query, {}) }}\"\n        headers: \"{{request.headers}}\"\n        body: \"{{request.body}}\"\n      wdqsapi_req:\n        method: post\n        uri: \"https://query.wikidata.org/sparql\"\n        headers:\n          user-agent: \"{{user-agent}}\"\n        body:\n          format: json\n          query: \"{{request.query}}\"\n      mysql_conn:\n        limit: 2\n        host: localhost:3306\n        name: recommendationapi\n        user: root\n        pass:\n        # If a connection to MySQL fails, how many times to retry?\n        retry: 2\n        # retry delay (in ms) before connecting to MySQL\n        retry_delay: 1000\n      mysql_tables:\n        language: language\n        normalized_rank: normalized_rank\n      wikidata_domain: www.wikidata.org\n      article:\n        # key is the target language, values are source languages\n        translation_models:\n          uz:\n            - ru\n      description_allowed_domains:\n        - www.wikidata.org\n        - test.wikidata.org\n        - wikidata.beta.wmflabs.org\n      caption_allowed_domains:\n        - commons.wikimedia.org\n        - test-commons.wikimedia.org\n        - commons.wikimedia.beta.wmflabs.org\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/config.yaml","messages":[{"ruleId":"yml/no-empty-mapping-value","severity":1,"message":"Empty mapping values are forbidden.","line":22,"column":1,"nodeType":"YAMLPair","messageId":"unexpectedEmpty","endLine":22,"endColumn":9},{"ruleId":"yml/no-empty-mapping-value","severity":1,"message":"Empty mapping values are forbidden.","line":93,"column":9,"nodeType":"YAMLPair","messageId":"unexpectedEmpty","endLine":93,"endColumn":14}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"# Number of worker processes to spawn.\n# Set to 0 to run everything in a single process without clustering.\n# Use 'ncpu' to run as many workers as there are CPU units\nnum_workers: 0\n\n# Log error messages and gracefully restart a worker if v8 reports that it\n# uses more heap (note: not RSS) than this many mb.\nworker_heap_limit_mb: 250\n\n# Logger info\nlogging:\n  level: trace\n  streams:\n    - type: stdout # log to stdout\n      named_levels: true # emit log level name instead of index. e.g. INFO vs 30\n#  # Use gelf-stream -> logstash\n#  - type: gelf\n#    host: logstash1003.eqiad.wmnet\n#    port: 12201\n\n# Statsd metrics reporter\nmetrics:\n# type: log\n# host: localhost\n# port: 8125\n\nservices:\n  - name: recommendation-api\n    # a relative path or the name of an npm package, if different from name\n    module: ./app.js\n    # optionally, a version constraint of the npm package\n    # version: ^0.4.0\n    # per-service config\n    conf:\n      port: 6927\n      # interface: localhost # uncomment to only listen on localhost\n      # more per-service config settings\n      # the location of the spec, defaults to spec.yaml if not specified\n      # spec: ./spec.template.yaml\n      # allow cross-domain requests to the API (default '*')\n      cors: \"*\"\n      # to disable use:\n      # cors: false\n      # to restrict to a particular domain, use:\n      # cors: restricted.domain.org\n      # content for the CSP headers\n      # csp: false  # uncomment this line to disable sending them\n      # URL of the outbound proxy to use (complete with protocol)\n      # proxy: http://my.proxy.org:8080\n      # the list of domains for which not to use the proxy defined above\n      # no_proxy_list:\n      #   - domain1.com\n      #   - domain2.org\n      # the list of incoming request headers that can be logged; if left empty,\n      # the following headers are allowed: cache-control, content-length,\n      # content-type, if-match, user-agent, x-request-id\n      # log_header_whitelist:\n      #   - cache-control\n      #   - content-length\n      #   - content-type\n      #   - if-match\n      #   - user-agent\n      #   - x-request-id\n      # the user agent to use when issuing requests\n      # user_agent: service-template-node\n      # the template used for contacting the MW API\n      mwapi_req:\n        method: post\n        uri: https://{{domain}}/w/api.php\n        headers:\n          user-agent: \"{{user-agent}}\"\n        body: \"{{ default(request.query, {}) }}\"\n      # the template used for contacting RESTBase\n      restbase_req:\n        method: \"{{request.method}}\"\n        uri: https://{{domain}}/api/rest_v1/{+path}\n        query: \"{{ default(request.query, {}) }}\"\n        headers: \"{{request.headers}}\"\n        body: \"{{request.body}}\"\n      wdqsapi_req:\n        method: post\n        uri: \"https://query.wikidata.org/sparql\"\n        headers:\n          user-agent: \"{{user-agent}}\"\n        body:\n          format: json\n          query: \"{{request.query}}\"\n      mysql_conn:\n        limit: 2\n        host: localhost:3306\n        name: recommendationapi\n        user: root\n        pass:\n        # If a connection to MySQL fails, how many times to retry?\n        retry: 2\n        # retry delay (in ms) before connecting to MySQL\n        retry_delay: 1000\n      mysql_tables:\n        language: language\n        normalized_rank: normalized_rank\n      wikidata_domain: www.wikidata.org\n      article:\n        # key is the target language, values are source languages\n        translation_models:\n          uz:\n            - ru\n      description_allowed_domains:\n        - www.wikidata.org\n        - test.wikidata.org\n        - wikidata.beta.wmflabs.org\n      caption_allowed_domains:\n        - commons.wikimedia.org\n        - test-commons.wikimedia.org\n        - commons.wikimedia.beta.wmflabs.org\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/api-util.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":81,"column":1,"nodeType":"Block","endLine":81,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":128,"column":1,"nodeType":"Block","endLine":128,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst preq = require('preq');\nconst sUtil = require('./util');\nconst Template = require('swagger-router').Template;\nconst HTTPError = sUtil.HTTPError;\n\n/**\n * Calls the MW API with the supplied query as its body\n *\n * @param {!Object} app the application object\n * @param {string} domain the domain to issue the request to\n * @param {?Object} query an object with all the query parameters for the MW API\n * @return {!Promise} a promise resolving as the response object from the MW API\n */\nfunction mwApiGet(app, domain, query) {\n\n    query = Object.assign({\n        format: 'json',\n        formatversion: 2\n    }, query);\n\n    const request = app.mwapi_tpl.expand({\n        request: {\n            params: { domain },\n            headers: { 'user-agent': app.conf.user_agent },\n            query\n        }\n    });\n\n    return preq(request).then((response) => {\n        if (response.status < 200 || response.status > 399) {\n            // there was an error when calling the upstream service, propagate that\n            throw new HTTPError({\n                status: response.status,\n                type: 'api_error',\n                title: 'MW API error',\n                detail: response.body\n            });\n        }\n        return response;\n    });\n\n}\n\n/**\n * Calls the REST API with the supplied domain, path and request parameters\n *\n * @param {!Object} app the application object\n * @param {string} domain the domain to issue the request for\n * @param {!string} path the REST API path to contact without the leading slash\n * @param {?Object} [restReq={}] the object containing the REST request details\n * @param {?string} [restReq.method=get] the request method\n * @param {?Object} [restReq.query={}] the query string to send, if any\n * @param {?Object} [restReq.headers={}] the request headers to send\n * @param {?Object} [restReq.body=null] the body of the request, if any\n * @return {!Promise} a promise resolving as the response object from the REST API\n */\nfunction restApiGet(app, domain, path, restReq) {\n\n    restReq = restReq || {};\n    path = path[0] === '/' ? path.slice(1) : path;\n\n    const request = app.restbase_tpl.expand({\n        request: {\n            method: restReq.method,\n            params: { domain, path },\n            query: restReq.query,\n            headers: Object.assign({ 'user-agent': app.conf.user_agent }, restReq.headers),\n            body: restReq.body\n        }\n    });\n\n    return preq(request);\n\n}\n\n/**\n * Sets up the request templates for MW and RESTBase API requests\n *\n * @param {!Application} app the application object\n */\nfunction setupApiTemplates(app) {\n\n    // set up the MW API request template\n    if (!app.conf.mwapi_req) {\n        app.conf.mwapi_req = {\n            uri: 'http://{{domain}}/w/api.php',\n            headers: {\n                'user-agent': '{{user-agent}}'\n            },\n            body: '{{ default(request.query, {}) }}'\n        };\n    }\n    app.mwapi_tpl = new Template(app.conf.mwapi_req);\n\n    // set up the RESTBase request template\n    if (!app.conf.restbase_req) {\n        app.conf.restbase_req = {\n            method: '{{request.method}}',\n            uri: 'http://{{domain}}/api/rest_v1/{+path}',\n            query: '{{ default(request.query, {}) }}',\n            headers: '{{request.headers}}',\n            body: '{{request.body}}'\n        };\n    }\n    app.restbase_tpl = new Template(app.conf.restbase_req);\n\n    if (!app.conf.wdqsapi_req) {\n        app.conf.wdqsapi_req = {\n            method: 'post',\n            uri: 'https://query.wikidata.org/sparql',\n            headers: {\n                'user-agent': '{{user-agent}}'\n            },\n            body: {\n                format: 'json',\n                query: '{{request.query}}'\n            }\n        };\n    }\n    app.wdqsapi_tpl = new Template(app.conf.wdqsapi_req);\n}\n\n/**\n * Sets up templates for queries used in multiple places\n *\n * @param {!Application} app the application object\n */\nfunction setupQueryTemplates(app) {\n    app.queryTemplates = app.queryTemplates || {};\n\n    app.queryTemplates.seed = new Template({\n        parameters: {\n            format: 'json',\n            action: 'query',\n            prop: 'pageprops',\n            ppprop: 'wikibase_item',\n            generator: 'search',\n            gsrlimit: 500,\n            gsrsearch: 'morelike:{{params.seed}}',\n            gsrprop: ''\n        }\n    });\n}\n\nmodule.exports = {\n    mwApiGet,\n    restApiGet,\n    setupApiTemplates,\n    setupQueryTemplates\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/article.creation.morelike.js","messages":[{"ruleId":"no-redeclare","severity":1,"message":"'setTimeout' is already defined as a built-in global variable.","line":1,"column":11,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":21}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* global setTimeout */\n\n'use strict';\n\nconst BBPromise = require('bluebird');\nconst aUtils = require('./api-util');\n\n/**\n * Return article's Wikidata ID from MW API\n *\n * @param {Object} app\n * @param {string} domain\n * @param {string} articleTitle\n * @return {Promise} promise that resolves with article's Wikidata ID or `null`\n */\nfunction getWikidataId(app, domain, articleTitle) {\n    const parameters = {\n        format: 'json',\n        formatversion: 2,\n        action: 'query',\n        prop: 'pageprops',\n        ppprop: 'wikibase_item',\n        titles: articleTitle\n    };\n\n    app.logger.log('debug/article.creation.morelike', {\n        msg: 'Getting article\\'s Wikidata ID from MW API.',\n        articleTitle\n    });\n\n    return aUtils.mwApiGet(app, domain, parameters).then(\n        (response) => {\n            if (!response.body || !response.body.query ||\n                !response.body.query.pages ||\n                !response.body.query.pages[0].pageprops ||\n                !response.body.query.pages[0].pageprops.wikibase_item) {\n                app.logger.log('debug/article.creation.morelike', {\n                    msg: 'No article Wikidta ID retrieved.'\n                });\n                return null;\n            }\n\n            const id = response.body.query.pages[0].pageprops.wikibase_item;\n\n            app.logger.log('debug/article.creation.morelike', {\n                msg: 'Article Wikidata ID retrieved.',\n                id\n            });\n            return id;\n        }\n    );\n}\n\n/**\n * For a Wikidata item return article names from Wikidata's MW API in\n * given languages.\n *\n * @param {Object} app\n * @param {string} wikidataId\n * @param {string[]} languages\n * @return {Promise} promise that resolves with article's names or `null`\n */\nfunction getArticleNames(app, wikidataId, languages) {\n    const domain = app.conf.wikidata_domain;\n    const parameters = {\n        format: 'json',\n        formatversion: 2,\n        action: 'wbgetentities',\n        ids: wikidataId,\n        props: 'sitelinks',\n        sitefilter: languages.map(x => `${ x }wiki`).join('|')\n    };\n\n    app.logger.log('debug/article.creation.morelike', {\n        msg: 'Getting article titles from the MW API.',\n        wikidataId,\n        languages\n    });\n\n    return aUtils.mwApiGet(app, domain, parameters).then(\n        (response) => {\n            if (!response.body || !response.body.entities ||\n                !response.body.entities[wikidataId] ||\n                !response.body.entities[wikidataId].sitelinks) {\n                app.logger.log('debug/article.creation.morelike', {\n                    msg: 'No article names from Wikidta retrieved.',\n                    wikidataId,\n                    languages\n                });\n                return null;\n            }\n\n            const sitelinks = response.body.entities[wikidataId].sitelinks;\n            const articleNames = {};\n\n            languages.forEach((lang) => {\n                const wiki = sitelinks[`${ lang }wiki`];\n                if (wiki && wiki.title) {\n                    if (lang in articleNames) {\n                        articleNames[lang].push(wiki.title);\n                    } else {\n                        articleNames[lang] = [wiki.title];\n                    }\n                }\n            });\n\n            app.logger.log('debug/article.creation.morelike', {\n                msg: 'Article names from Wikidata retrieved.',\n                articleNames\n            });\n\n            return articleNames;\n        }\n    );\n}\n\n/**\n * Get similar articles to articleNames using MediaWiki morelike API\n *\n * @param {Object} app\n * @param {string} language Wiki language\n * @param {string} projectDomain\n * @param {string} wikidataId\n * @param {string[]} articleNames\n * @return {Promise} promise that resolves with the list of wikidata IDs\n */\nfunction getMorelike(app, language, projectDomain, wikidataId, articleNames) {\n    const domain = `${ language }.${ projectDomain }`;\n    const seedQuery = app.queryTemplates.seed.expand({\n        params: {\n            seed: articleNames.join('|')\n        }\n    });\n\n    return aUtils.mwApiGet(app, domain, seedQuery.parameters).then(\n        (response) => {\n            if (!response.body || !response.body.query ||\n                !response.body.query.pages) {\n                app.logger.log('debug/article.creation.morelike', {\n                    msg: 'No article Wikidta ID retrieved.'\n                });\n                return [];\n            }\n\n            const pages = response.body.query.pages;\n            const similarIds = [];\n            for (const id in pages) {\n                if (id) {\n                    const pageprops = pages[id].pageprops;\n                    if (pageprops && pageprops.wikibase_item) {\n                        similarIds.push(pageprops.wikibase_item);\n                    }\n                }\n            }\n            return similarIds;\n        });\n}\n\n/**\n * Get Wikidata item IDs similar to wikidataId using morelike API for languages\n *\n * @param {Object} app\n * @param {string} projectDomain\n * @param {string} wikidataId\n * @param {string[]} languages\n * @return {Promise} promise that resolves with the list of similar Wikidata IDs\n */\nfunction getSimilarArticles(app, projectDomain, wikidataId, languages) {\n    return getArticleNames(app, wikidataId, languages)\n        .then((articleNames) => {\n            const requests = [];\n            for (const language in articleNames) {\n                if (language) {\n                    requests.push(getMorelike(\n                        app, language, projectDomain, wikidataId,\n                        articleNames[language]));\n                }\n            }\n\n            return Promise.all(requests)\n                .then((morelikeItems) => {\n                    const wikidataIds = [];\n                    morelikeItems.forEach((item) => {\n                        wikidataIds.push(...item);\n                    });\n                    return Array.from(new Set(wikidataIds));\n                });\n        });\n}\n\n/**\n * Return filtered Wikidata item IDs that don't have a sitelink to the\n * wiki indicated by language.\n *\n * @param {Object} app\n * @param {string} projectDomain\n * @param {string[]} wikidataIds\n * @param {string} language\n * @return {Promise} promise that resolves with the list of Wikidata IDs\n */\nfunction getMissingArticles(app, projectDomain, wikidataIds, language) {\n    const domain = app.conf.wikidata_domain;\n    const wiki = `${ language }wiki`;\n    // hard-limit set by the API on the number of IDs we can pass to the API\n    const idLimit = 50;\n    const parameters = {\n        format: 'json',\n        formatversion: 2,\n        action: 'wbgetentities',\n        props: 'sitelinks',\n        sitefilter: wiki\n    };\n\n    app.logger.log('debug/article.creation.morelike', {\n        msg: 'Getting article titles from the MW API.',\n        wikidataIds,\n        language\n    });\n\n    // Split Wikidata IDs in to chunks and make separate requests so\n    // that we don't hit the API limit.\n    const requests = [];\n    const requestCount = Math.ceil(wikidataIds.length / idLimit);\n    for (let i = 0; i < requestCount; i++) {\n        const start = i * idLimit;\n        const end = start + idLimit;\n        const ids = wikidataIds.slice(start, end);\n        parameters.ids = ids.join('|');\n        requests.push(\n            aUtils.mwApiGet(app, domain, parameters)\n        );\n    }\n    return Promise.all(requests)\n        .then((responses) => {\n            const results = [];\n            responses.forEach((response) => {\n                const entities = response.body.entities;\n                for (const id in entities) {\n                    if (!entities[id].sitelinks ||\n                        !entities[id].sitelinks[wiki]) {\n                        results.push(id);\n                    }\n                }\n            });\n            app.logger.log('debug/article.creation.morelike', {\n                msg: 'Retrieved missing articles.',\n                results\n            });\n            return results;\n        })\n        .catch((error) => {\n            app.logger.log('error/article.creation.morelike', {\n                msg: 'Could not retrieve missing articles.',\n                error\n            });\n            return null;\n        });\n}\n\n/**\n * Internal function for getting normalized ranks from MySQL\n *\n * In case of MySQL failure, the function is called recursively until no\n * more retries are left.\n *\n * @param {Object} app\n * @param {string[]} wikidataIds\n * @param {string} targetLanguage language used as the target language\n *   in recommendation predictions\n * @param {number} retry number of times to retry MySQL query in case of failure\n * @param {number} retryDelay delay in ms before retrying MySQL connection\n * @param {Function} reject callback\n * @param {Function} resolve callback\n */\nfunction _getArticleNormalizedRanksFromMySQL(\n    app, wikidataIds, targetLanguage, retry, retryDelay, reject, resolve) {\n    const langTable = app.conf.mysql_tables.language;\n    const normalizedRankTable = app.conf.mysql_tables.normalized_rank;\n\n    app.mysqlPool.query(\n        `SELECT max_vals.wikidata_id, rank_table.normalized_rank,\n            source.code as source_language from (SELECT wikidata_id, max(normalized_rank) as normalized_rank\n            FROM ?? INNER JOIN ?? as target ON ??=target.id\n            WHERE wikidata_id in (?)\n            AND target.code=? GROUP BY wikidata_id\n            ORDER BY max(normalized_rank) DESC) as max_vals\n            LEFT JOIN ?? as rank_table\n            ON max_vals.wikidata_id=rank_table.wikidata_id AND\n            max_vals.normalized_rank=rank_table.normalized_rank\n            INNER JOIN ?? as source ON\n            rank_table.source_id=source.id LIMIT 10`,\n        [normalizedRankTable, langTable,\n            `${ normalizedRankTable }.target_id`, wikidataIds,\n            targetLanguage, normalizedRankTable, langTable],\n        (error, results, _) => {\n            if (error) {\n                app.logger.log('error/article.creation.morelike', error);\n                if (retry > 0) {\n                    app.logger.log(\n                        'debug/article.creation.morelike',\n                        {\n                            msg: 'Retrying MySQL query.',\n                            retry,\n                            retryDelay\n                        });\n                    setTimeout(_getArticleNormalizedRanksFromMySQL, retryDelay,\n                        app, wikidataIds, targetLanguage, --retry,\n                        retryDelay, reject, resolve);\n                } else {\n                    reject(error);\n                }\n                return;\n            }\n\n            if (results && results.length) {\n                app.logger.log('debug/article.creation.morelike', {\n                    msg: 'Articles retrieved from DB.',\n                    count: results.length\n                });\n            }\n            results = results.map((x) => {\n                x.wikidata_id = `Q${ x.wikidata_id }`;\n                return x;\n            });\n            resolve(results);\n        });\n}\n\n/**\n * Get recommendation normalized ranks for Wikidata IDs\n *\n * @param {Object} app\n * @param {string[]} wikidataIds\n * @param {string} targetLanguage language used as the target language\n *   in recommendation predictions\n * @return {Promise} promise that resolves with Wikidata IDs and their\n *   recommendation normalized ranks\n */\nfunction getArticleNormalizedRanksFromDb(app, wikidataIds, targetLanguage) {\n    const retry = app.conf.mysql_conn.retry || 2;\n    const retryDelay = app.conf.mysql_conn.retry_delay || 1000;\n\n    wikidataIds = wikidataIds.map((x) => parseInt(x.replace('Q', ''), 10));\n\n    return new BBPromise((resolve, reject) => {\n        _getArticleNormalizedRanksFromMySQL(\n            app, wikidataIds, targetLanguage, retry, retryDelay,\n            reject, resolve);\n    });\n}\n\nmodule.exports = {\n    getWikidataId,\n    getSimilarArticles,\n    getMissingArticles,\n    getArticleNormalizedRanksFromDb\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/article.creation.translation.js","messages":[{"ruleId":"no-shadow","severity":1,"message":"'candidates' is already declared in the upper scope on line 122 column 9.","line":129,"column":12,"nodeType":"Identifier","messageId":"noShadow","endLine":129,"endColumn":22}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst aUtils = require('./api-util');\nconst util = require('./util');\n\n/**\n * Gets articles from the mw api\n *\n * @param {Object} app the application object\n * @param {string} domain the domain to query\n * @param {Object} params the query parameters\n * @return {Promise.<Object>} the resulting map of wikidata id to article title\n */\nfunction getArticles(app, domain, params) {\n    return aUtils.mwApiGet(app, domain, params)\n    .then((response) => {\n        if (Object.prototype.hasOwnProperty.call(response.body, 'error')) {\n            throw new util.HTTPError({\n                status: 500,\n                type: 'internal_server_error',\n                title: 'MediaWiki API error',\n                detail: JSON.stringify(response.body)\n            });\n        }\n        if (!Object.prototype.hasOwnProperty.call(response.body, 'query')) {\n            throw new util.HTTPError({\n                status: 404,\n                type: 'not_found',\n                title: 'no results found',\n                detail: JSON.stringify(response.body)\n            });\n        }\n        return Object.keys(response.body.query.pages)\n        .reduce((accumulator, currentKey) => {\n            const current = response.body.query.pages[currentKey];\n            if (current.ns !== 0) {\n                return accumulator;\n            }\n            if (!current.pageprops || !current.pageprops.wikibase_item) {\n                return accumulator;\n            }\n            if (current.title.includes(':') ||\n                current.title.includes('List') === 0) {\n                return accumulator;\n            }\n            if ('langlinks' in current || 'disambiguation' in current.pageprops) {\n                return accumulator;\n            } else {\n                accumulator.push({\n                    wikidata_id: current.pageprops.wikibase_item,\n                    title: current.title,\n                    sitelink_count: current.langlinkscount ? current.langlinkscount + 1 : 1\n                });\n                return accumulator;\n            }\n        }, []);\n    });\n}\n\n/**\n * Gets articles most closely related to seed\n *\n * @param {Object} app the application object\n * @param {string} source the source language code\n * @param {string} target the target language code\n * @param {string} projectDomain the project domain\n * @param {string} seed the seed to search by\n * @return {Promise.<Object>}\n */\nfunction getArticlesBySeed(app, source, target, projectDomain, seed) {\n    const domain = `${ source }.${ projectDomain }`;\n    const parameters = {\n        format: 'json',\n        action: 'query',\n        prop: 'pageprops|langlinks|langlinkscount',\n        ppprop: 'wikibase_item|disambiguation',\n        lllang: target,\n        lllimit: 500,\n        generator: 'search',\n        gsrlimit: 500,\n        gsrsearch: `morelike:${ seed }`\n    };\n\n    return getArticles(app, domain, parameters);\n}\n\n/**\n * Gets the most popular articles in source wikipedia\n *\n * @param {Object} app the application object\n * @param {string} source the source language code\n * @param {string} target the target language code\n * @param {string} projectDomain the project domain\n * @return {Promise.<Object>}\n */\nfunction getArticlesByPageviews(app, source, target, projectDomain) {\n    const domain = `${ source }.${ projectDomain }`;\n    const parameters = {\n        format: 'json',\n        action: 'query',\n        prop: 'pageprops|langlinks|langlinkscount',\n        ppprop: 'wikibase_item|disambiguation',\n        generator: 'mostviewed',\n        lllang: target,\n        lllimit: 500,\n        gpvimlimit: 500\n    };\n    return getArticles(app, domain, parameters);\n}\n\n/**\n * Recommends articles in source to translate to target\n *\n * @param {Object} app the application object\n * @param {string} source the source language code\n * @param {string} target the target language code\n * @param {string} projectDomain the project domain\n * @param {string} [seed=null] the seed to search by, if any\n * @return {Promise.<Object[]>}\n */\nfunction recommend(app, source, target, projectDomain, seed) {\n    let candidates;\n    if (seed) {\n        candidates = getArticlesBySeed(app, source, target, projectDomain, seed);\n    } else {\n        candidates = getArticlesByPageviews(app, source, target, projectDomain);\n    }\n    return candidates\n    .then((candidates) => candidates.sort((a, b) => b.sitelink_count - a.sitelink_count));\n}\n\nmodule.exports = {\n    recommend\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/caption.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/description.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/suggested-edits-common.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/swagger-ui.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/util.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":109,"column":1,"nodeType":"Block","endLine":109,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'bool' is undefined.","line":155,"column":1,"nodeType":"Block","endLine":155,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":165,"column":1,"nodeType":"Block","endLine":165,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Router' is undefined.","line":260,"column":1,"nodeType":"Block","endLine":260,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":280,"column":1,"nodeType":"Block","endLine":280,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst BBPromise = require('bluebird');\nconst express = require('express');\nconst uuid = require('cassandra-uuid');\nconst bunyan = require('bunyan');\n\n/**\n * Error instance wrapping HTTP error responses\n */\nclass HTTPError extends Error {\n\n    constructor(response) {\n        super();\n        Error.captureStackTrace(this, HTTPError);\n\n        if (response.constructor !== Object) {\n            // just assume this is just the error message\n            response = {\n                status: 500,\n                type: 'internal_error',\n                title: 'InternalError',\n                detail: response\n            };\n        }\n\n        this.name = this.constructor.name;\n        this.message = `${ response.status }`;\n        if (response.type) {\n            this.message += `: ${ response.type }`;\n        }\n\n        Object.assign(this, response);\n    }\n}\n\n/**\n * Generates an object suitable for logging out of a request object\n *\n * @param {!Request} req          the request\n * @param {?RegExp}  whitelistRE  the RegExp used to filter headers\n * @return {!Object} an object containing the key components of the request\n */\nfunction reqForLog(req, whitelistRE) {\n\n    const ret = {\n        url: req.originalUrl,\n        headers: {},\n        method: req.method,\n        params: req.params,\n        query: req.query,\n        body: req.body,\n        remoteAddress: req.connection.remoteAddress,\n        remotePort: req.connection.remotePort\n    };\n\n    if (req.headers && whitelistRE) {\n        Object.keys(req.headers).forEach((hdr) => {\n            if (whitelistRE.test(hdr)) {\n                ret.headers[hdr] = req.headers[hdr];\n            }\n        });\n    }\n\n    return ret;\n\n}\n\n/**\n * Serialises an error object in a form suitable for logging.\n *\n * @param {!Error} err error to serialise\n * @return {!Object} the serialised version of the error\n */\nfunction errForLog(err) {\n\n    const ret = bunyan.stdSerializers.err(err);\n    ret.status = err.status;\n    ret.type = err.type;\n    ret.detail = err.detail;\n\n    // log the stack trace only for 500 errors\n    if (Number.parseInt(ret.status, 10) !== 500) {\n        ret.stack = undefined;\n    }\n\n    return ret;\n\n}\n\n/**\n * Generates a unique request ID.\n *\n * @return {!string} the generated request ID\n */\nfunction generateRequestId() {\n\n    return uuid.TimeUuid.now().toString();\n\n}\n\n/**\n * Wraps all of the given router's handler functions with\n * promised try blocks so as to allow catching all errors,\n * regardless of whether a handler returns/uses promises\n * or not.\n *\n * @param {!Object} route the object containing the router and path to bind it to\n * @param {!Application} app the application object\n */\nfunction wrapRouteHandlers(route, app) {\n\n    route.router.stack.forEach((routerLayer) => {\n        const path = (route.path + routerLayer.route.path.slice(1))\n            .replace(/\\/:/g, '/--')\n            .replace(/^\\//, '')\n            .replace(/[/?]+$/, '');\n        routerLayer.route.stack.forEach((layer) => {\n            const origHandler = layer.handle;\n            layer.handle = function (req, res, next) {\n                const startTime = Date.now();\n                BBPromise.try(() => origHandler(req, res, next))\n                .catch(next)\n                .finally(() => {\n                    let statusCode = parseInt(res.statusCode, 10) || 500;\n                    if (statusCode < 100 || statusCode > 599) {\n                        statusCode = 500;\n                    }\n                    app.metrics.makeMetric({\n                        type: 'Histogram',\n                        name: 'router',\n                        prometheus: {\n                            name: 'recommendation_api_router_request_duration_seconds',\n                            help: 'request duration handled by router',\n                            buckets: [0.01, 0.05, 0.1, 0.5, 1, 5]\n                        },\n                        labels: {\n                            names: ['path', 'method', 'status'],\n                            omitLabelNames: true\n                        }\n                    }).endTiming(startTime, [path || 'root', req.method, statusCode]);\n                });\n            };\n        });\n    });\n\n}\n\n/**\n * Heuristically determine whether the request is a health check, i.e.\n * initiated by service-checker-swagger. Useful for handling MW API\n * errors differently when it's under stress.\n *\n * @param {!Request} req\n * @return {bool}\n */\nfunction isHealthCheck(req) {\n    const userAgent = req.headers['user-agent'];\n    return userAgent && userAgent.startsWith('ServiceChecker-WMF');\n}\n\n/**\n * Generates an error handler for the given applications and installs it.\n *\n * @param {!Application} app the application object to add the handler to\n */\nfunction setErrorHandler(app) {\n\n    app.use((err, req, res, next) => {\n        let errObj;\n        // ensure this is an HTTPError object\n        if (err.constructor === HTTPError) {\n            errObj = err;\n        } else if (err instanceof Error) {\n            // is this an HTTPError defined elsewhere? (preq)\n            if (err.constructor.name === 'HTTPError') {\n                const o = { status: err.status };\n                if (err.body && err.body.constructor === Object) {\n                    Object.keys(err.body).forEach((key) => {\n                        o[key] = err.body[key];\n                    });\n                } else {\n                    o.detail = err.body;\n                }\n                o.message = err.message;\n                errObj = new HTTPError(o);\n            } else {\n                // this is a standard error, convert it\n                errObj = new HTTPError({\n                    status: 500,\n                    type: 'internal_error',\n                    title: err.name,\n                    detail: err.message,\n                    stack: err.stack\n                });\n            }\n        } else if (err.constructor === Object) {\n            // this is a regular object, suppose it's a response\n            errObj = new HTTPError(err);\n        } else {\n            // just assume this is just the error message\n            errObj = new HTTPError({\n                status: 500,\n                type: 'internal_error',\n                title: 'InternalError',\n                detail: err\n            });\n        }\n        // ensure some important error fields are present\n        if (!errObj.status) {\n            errObj.status = 500;\n        }\n        if (!errObj.type) {\n            errObj.type = 'internal_error';\n        }\n        // add the offending URI and method as well\n        if (!errObj.method) {\n            errObj.method = req.method;\n        }\n        if (!errObj.uri) {\n            errObj.uri = req.url;\n        }\n        // some set 'message' or 'description' instead of 'detail'\n        errObj.detail = errObj.detail || errObj.message || errObj.description || '';\n        // adjust the log level based on the status code\n        let level = 'error';\n        if (Number.parseInt(errObj.status, 10) < 400) {\n            level = 'trace';\n        } else if (Number.parseInt(errObj.status, 10) < 500) {\n            level = 'info';\n        }\n        // log the error\n        const component = (errObj.component ? errObj.component : errObj.status);\n        (req.logger || app.logger).log(`${ level }/${ component }`, errForLog(errObj));\n        // Reduce false alarms to SRE stemming from health check\n        // requests. These kinds of '500+' errors may happen when the\n        // MediaWiki API is under load and fails to return a success.\n        // See https://phabricator.wikimedia.org/T247732#6099579\n        if (isHealthCheck(req) && err.healthCheckStatus) {\n            errObj.status = err.healthCheckStatus;\n        }\n        // let through only non-sensitive info\n        const respBody = {\n            status: errObj.status,\n            type: errObj.type,\n            title: errObj.title,\n            detail: errObj.detail,\n            method: errObj.method,\n            uri: errObj.uri\n        };\n        res.status(errObj.status).json(respBody);\n    });\n\n}\n\n/**\n * Creates a new router with some default options.\n *\n * @param {?Object} [opts] additional options to pass to express.Router()\n * @return {!Router} a new router object\n */\nfunction createRouter(opts) {\n\n    const options = {\n        mergeParams: true\n    };\n\n    if (opts && opts.constructor === Object) {\n        Object.assign(options, opts);\n    }\n\n    return new express.Router(options);\n\n}\n\n/**\n * Adds logger to the request and logs it.\n *\n * @param {!*} req request object\n * @param {!Application} app application object\n */\nfunction initAndLogRequest(req, app) {\n    req.headers = req.headers || {};\n    req.headers['x-request-id'] = req.headers['x-request-id'] || generateRequestId();\n    req.logger = app.logger.child({\n        request_id: req.headers['x-request-id'],\n        request: reqForLog(req, app.conf.log_header_whitelist)\n    });\n    req.logger.log('trace/req', { msg: 'incoming request' });\n}\n\nmodule.exports = {\n    HTTPError,\n    initAndLogRequest,\n    wrapRouteHandlers,\n    setErrorHandler,\n    router: createRouter\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/article.creation.morelike.js","messages":[{"ruleId":"no-shadow","severity":1,"message":"'ids' is already declared in the upper scope on line 66 column 24.","line":75,"column":32,"nodeType":"Identifier","messageId":"noShadow","endLine":75,"endColumn":35}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst BBPromise = require('bluebird');\nconst mysql = require('mysql');\nconst util = require('../lib/util');\nconst aUtil = require('../lib/article.creation.morelike');\n\nconst router = util.router();\nlet app;\n\nfunction mwApiFailure(e) {\n    app.logger.log('error/article.creation.morelike', { e });\n    return BBPromise.reject(new util.HTTPError({\n        status: 503,\n        healthCheckStatus: 404,  // extra flag to reduce false alarms\n        message: 'The MediaWiki API failed. Please try again later.'\n    }));\n}\n\n/**\n * GET /{seed}\n * Gets missing articles (from the current wiki) similar to seed.\n *\n * Similar articles are retrieved using a CirrusSearch morelike query.\n * Articles are prioritized using translation recommendations\n * predictions.\n */\nrouter.get('/:seed', (req, res) => {\n    const domain = req.params.domain;\n    const domainParts = domain.split('.');\n    const language = domainParts[0];  // e.g. en\n    const projectDomain = domainParts.splice(1).join('.');\n\n    let sourceLanguages = [];\n    let availableModels = [];\n\n    if (app.conf.article && app.conf.article.translation_models) {\n        sourceLanguages = app.conf.article.translation_models[language] || [];\n        availableModels = Object.keys(app.conf.article.translation_models);\n    }\n\n    if (!sourceLanguages.length) {\n        app.logger.log('error/article.creation.morelike',\n            `Article translation model for \"${ language }\" doesn't exist.`);\n\n        const errorObject = new util.HTTPError({\n            status: 501,\n            message: `'morelike' article recommendations are not enabled on\n                this Wikipedia. Currently enabled on:\n                ${ availableModels.join(', ') || 'none' }.`\n        });\n        return BBPromise.reject(errorObject);\n    }\n\n    return aUtil.getWikidataId(app, domain, req.params.seed)\n        .catch(mwApiFailure)\n        .then((id) => {\n            if (!id) {\n                return BBPromise.reject(new util.HTTPError({\n                    status: 404,\n                    message: 'No such article found.'\n                }));\n            }\n            return aUtil.getSimilarArticles(app, projectDomain, id, sourceLanguages)\n                .catch(mwApiFailure)\n                .then((ids) => {\n                    if (!ids.length) {\n                        return BBPromise.reject(new util.HTTPError({\n                            status: 404,\n                            message: 'No similar articles to seed found.'\n                        }));\n                    }\n                    return aUtil.getMissingArticles(app, projectDomain, ids, language)\n                        .catch(mwApiFailure)\n                        .then((ids) => {\n                            if (ids === null) {\n                                return  mwApiFailure();\n                            } else if (!ids.length) {\n                                return BBPromise.reject(new util.HTTPError({\n                                    status: 404,\n                                    message: 'All similar articles have been created.'\n                                }));\n                            } else {\n                                return aUtil.getArticleNormalizedRanksFromDb(\n                                    app, ids, language\n                                ).then((results) => {\n                                    res.json(results);\n                                }).catch((error) => BBPromise.reject(new util.HTTPError({\n                                        status: 503,\n                                        message: 'Cannot retrieve normalized ranks from' +\n                                            ' the database. Please try again later.'\n                                    })));\n                            }\n                        });\n                });\n        });\n});\n\nmodule.exports = function (appObj) {\n    app = appObj;\n\n    const mysqlConf = app.conf.mysql_conn;\n    const hostPort = mysqlConf.host.split(':');\n    app.mysqlPool = mysql.createPool({\n        connectionLimit: mysqlConf.limit,\n        host: hostPort[0],\n        port: hostPort[1] || 3306,\n        user: mysqlConf.user,\n        password: mysqlConf.pass,\n        database: mysqlConf.name\n    });\n\n    return {\n        path: '/article/creation/morelike',\n        api_version: 1,\n        router\n    };\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/article.creation.translation.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":14,"column":25,"nodeType":"Literal","endLine":14,"endColumn":51}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst util = require('../lib/util');\nconst tUtil = require('../lib/article.creation.translation');\n\nconst router = util.router();\nlet app;\n\n/**\n * Regular expression used for validating the source parameter\n *\n * @type {RegExp}\n */\nconst sourceValidator = /^[a-zA-Z]+(-[a-zA-Z]+)*$/;\n\nfunction recommend(req, res, source, target, projectDomain, seed) {\n    if (!sourceValidator.test(source)) {\n        throw new util.HTTPError({\n            status: 400,\n            type: 'bad_request',\n            title: 'Bad request',\n            detail: 'source parameter was invalid'\n        });\n    }\n\n    let count = 24;\n    if (req.query && req.query.count) {\n        count = parseInt(req.query.count, 10);\n        if (isNaN(count) || count < 1 || count > 500) {\n            throw new util.HTTPError({\n                status: 400,\n                type: 'bad_request',\n                title: 'Bad request',\n                detail: 'count parameter was invalid'\n            });\n        }\n    }\n\n    return tUtil.recommend(app, source, target, projectDomain, seed)\n    .then((result) => {\n        result = result.slice(0, count);\n        res.json({\n            count: result.length,\n            items: result\n        });\n    });\n}\n\n/**\n * GET /{source}/{seed}\n * Gets the articles existing in source but missing in domain based on seed.\n */\nrouter.get('/:source/:seed?', (req, res) => {\n    const domainParts = req.params.domain.split('.');\n    const target = domainParts[0];\n    const projectDomain = domainParts.splice(1).join('.');\n    return recommend(req, res, req.params.source, target,\n        projectDomain, req.params.seed);\n});\n\nmodule.exports = function (appObj) {\n\n    app = appObj;\n\n    return {\n        path: '/article/creation/translation',\n        api_version: 1,\n        router\n    };\n\n};\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/caption.addition.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/caption.translation.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/description.addition.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/description.translation.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/routes/root.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/server.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/spec.yaml","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 115. Maximum allowed is 100.","line":234,"column":1,"nodeType":"Program","messageId":"max","endLine":234,"endColumn":116},{"ruleId":"max-len","severity":1,"message":"This line has a length of 120. Maximum allowed is 100.","line":307,"column":1,"nodeType":"Program","messageId":"max","endLine":307,"endColumn":121}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"swagger: \"2.0\"\ninfo:\n  termsOfService: https://wikimediafoundation.org/wiki/Terms_of_Use\n  contact:\n    name: the Wikimedia Research team\n    url: https://www.mediawiki.org/wiki/Wikimedia_Research\n  license:\n    name: Apache2\n    url: http://www.apache.org/licenses/LICENSE-2.0\npaths:\n  # from routes/root.js\n  /robots.txt:\n    get:\n      tags:\n        - Root\n        - Robots\n      description: Gets robots.txt\n      x-amples:\n        - title: robots.txt check\n          request: {}\n          response:\n            status: 200\n            headers:\n              user-agent: \"*\"\n              disallow: /\n  /:\n    get:\n      tags:\n        - Root\n      description: The root service end-point\n      produces:\n        - application/json\n      x-amples:\n        - title: root with no query params\n          request: {}\n          response:\n            status: 404\n        - title: spec from root\n          request:\n            query:\n              spec: true\n          response:\n            status: 200\n        - title: doc from root\n          request:\n            query:\n              doc: true\n          response:\n            status: 200\n        - title: root with wrong query param\n          request:\n            query:\n              fooo: true\n          response:\n            status: 404\n  # from routes/article.creation.translation.js\n  /{domain}/v1/article/creation/translation/{source}{/seed}:\n    get:\n      tags:\n        - Recommend\n      summary: Recommend missing articles\n      description: |\n        Recommends articles to be translated from the source\n        to the domain language.\n        Stability: [unstable](https://www.mediawiki.org/wiki/API_versioning#Unstable)\n      produces:\n        - applicaiton/json\n      parameters:\n        - name: source\n          in: path\n          description: The source language code\n          type: string\n          required: true\n        - name: domain\n          in: path\n          description: The target domain\n          type: string\n          required: true\n        - name: seed\n          in: path\n          description: The article to use as a search seed\n          type: string\n          required: false\n        - name: count\n          in: query\n          description: The max number of articles to return\n          type: int\n          required: false\n          default: 24\n      x-amples:\n        #        # TODO: Find a stable source of pageview data (T233381)\n        #        - title: article.creation.translation - normal source and target\n        #          request:\n        #            params:\n        #              source: it\n        #              domain: de.wikipedia.org\n        #          response:\n        #            status: 200\n        #            headers:\n        #              content-type: application/json\n        - title: article.creation.translation - normal source and target with seed\n          request:\n            params:\n              source: en\n              domain: de.wikipedia.org\n              seed: Apple\n          response:\n            status: 200\n            headers:\n              content-type: application/json\n        #        # TODO: Find a solution for this case\n        #        - title: article.creation.translation - bad source\n        #          request:\n        #            params:\n        #              source: qqq\n        #              domain: de.wikipedia.org\n        #          response:\n        #            status: 504\n        #            headers:\n        #              content-type: application/json\n        - title: article.creation.translation - bad seed\n          request:\n            params:\n              source: en\n              domain: de.wikipedia.org\n              seed: thishsouldnotreturnanyresultsfromthesearchapi\n          response:\n            status: 404\n            headers:\n              content-type: application/json\n        - title: article.creation.translation - invalid count\n          request:\n            params:\n              source: en\n              domain: de.wikipedia.org\n            query:\n              count: -123\n          response:\n            status: 400\n        - title: article.creation.translation - incorrectly formatted source\n          request:\n            params:\n              source: en-\n              domain: de.wikipedia.org\n          response:\n            status: 400\n  /{domain}/v1/article/creation/morelike/{seed}:\n    get:\n      tags:\n        - Recommendation\n      summary: Recommend missing articles\n      description: |\n        Recommends articles similar to the seed article but are missing\n        from the domain language Wikipedia.\n\n        Stability: [unstable](https://www.mediawiki.org/wiki/API_versioning#Unstable)\n      produces:\n        - application/json\n        - application/problem+json\n      parameters:\n        - name: seed\n          in: path\n          description: The article title used to search similar but missing articles\n          type: string\n          required: true\n      x-amples:\n        - title: article.creation.morelike - good article title\n          # Disabling for now because the test environment may not have\n          # access to MySQL. See feature tests for a replacement.\n          skip-locally: true\n          request:\n            params:\n              seed: Palov\n              domain: uz.wikipedia.org\n          response:\n            status: 200\n            headers:\n              content-type: application/json\n        - title: article.creation.morelike - bad article title\n          request:\n            params:\n              seed: Palov-missing\n              domain: uz.wikipedia.org\n          response:\n            status: 404\n            headers:\n              content-type: application/json\n        - title: article.creation.morelike - missing models\n          request:\n            params:\n              seed: Palov\n              domain: blah.wikipedia.org\n          response:\n            status: 501\n            headers:\n              content-type: application/json\n\n  /{domain}/v1/caption/addition/{target}:\n    get:\n      tags:\n        - Recommendation\n      summary: Recommend files missing a caption (label) in the target language\n      produces:\n        - application/json\n        - application/problem+json\n      parameters:\n        - name: target\n          in: path\n          description: The target wiki language code for suggestions\n          type: string\n          required: true\n      x-amples:\n        - title: Caption addition suggestions\n          request:\n            params:\n              domain: commons.wikimedia.org\n              target: zh\n            response:\n              status: 200\n              headers:\n                content-type: application/json\n              body:\n                - pageid: /.+/\n                  ns: 6\n                  title: /.+/\n                  mime: /image.*/\n                  structured:\n                    captions: /.+/\n\n  /{domain}/v1/caption/translation/from/{source}/to/{target}:\n    get:\n      tags:\n        - Recommendation\n      summary: Recommend files with a caption (label) in the source language but missing one in the target language\n      produces:\n        - application/json\n        - application/problem+json\n      parameters:\n        - name: source\n          in: path\n          description: The source wiki language code for suggestions\n          type: string\n          required: true\n        - name: target\n          in: path\n          description: The target wiki language code for suggestions\n          type: string\n          required: true\n      x-amples:\n        - title: Caption translation suggestions\n          request:\n            params:\n              domain: commons.wikimedia.org\n              source: en\n              target: zh\n            response:\n              status: 200\n              headers:\n                content-type: application/json\n              body:\n                - pageid: /.+/\n                  ns: 6\n                  title: /.+/\n                  mime: /image.*/\n                  structured:\n                    captions: /.+/\n\n  /{domain}/v1/description/addition/{target}:\n    get:\n      tags:\n        - Recommendation\n      summary: Recommend Wikibase items missing a description in the target language\n      produces:\n        - application/json\n        - application/problem+json\n      parameters:\n        - name: target\n          in: path\n          description: The target wiki language code for suggestions\n          type: string\n          required: true\n      x-amples:\n        - title: Description addition suggestions\n          request:\n            params:\n              domain: www.wikidata.org\n              target: zh\n            response:\n              status: 200\n              headers:\n                content-type: application/json\n              body:\n                - pageid: /.+/\n                  ns: 0\n                  title: /.+/\n                  wikibase_item:\n                    type: item\n                    id: /.+/\n                    labels: /.+/\n                    descriptions: /.+/\n                    sitelinks: /.+/\n\n  /{domain}/v1/description/translation/from/{source}/to/{target}:\n    get:\n      tags:\n        - Recommendation\n      summary: Recommend Wikibase items with a description in the source language but missing one in the target language\n      produces:\n        - application/json\n        - application/problem+json\n      parameters:\n        - name: source\n          in: path\n          description: The source wiki language code for suggestions\n          type: string\n          required: true\n        - name: target\n          in: path\n          description: The target wiki language code for suggestions\n          type: string\n          required: true\n      x-amples:\n        - title: Description translation suggestions\n          request:\n            params:\n              domain: www.wikidata.org\n              source: en\n              target: zh\n            response:\n              status: 200\n              headers:\n                content-type: application/json\n              body:\n                - pageid: /.+/\n                  ns: 0\n                  title: /.+/\n                  wikibase_item:\n                    type: item\n                    id: /.+/\n                    labels: /.+/\n                    descriptions: /.+/\n                    sitelinks: /.+/\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/targets.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/features/app/app.js","messages":[{"ruleId":"no-redeclare","severity":1,"message":"'describe' is already defined as a built-in global variable.","line":1,"column":11,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":19},{"ruleId":"no-redeclare","severity":1,"message":"'it' is already defined as a built-in global variable.","line":1,"column":21,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":23},{"ruleId":"no-redeclare","severity":1,"message":"'before' is already defined as a built-in global variable.","line":1,"column":25,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":31},{"ruleId":"no-redeclare","severity":1,"message":"'after' is already defined as a built-in global variable.","line":1,"column":33,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":38}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* global describe, it, before, after */\n\n'use strict';\n\nconst preq   = require('preq');\nconst assert = require('../../utils/assert.js');\nconst server = require('../../utils/server.js');\n\nif (!server.stopHookAdded) {\n    server.stopHookAdded = true;\n    after(() => server.stop());\n}\n\ndescribe('express app', function () {\n\n    this.timeout(20000);\n\n    before(() => server.start());\n\n    it('should get robots.txt', () => preq.get({\n            uri: `${ server.config.uri }robots.txt`\n        }).then((res) => {\n            assert.deepEqual(res.status, 200);\n            assert.deepEqual(res.headers.disallow, '/');\n        }));\n\n    it('should set CORS headers', () => {\n        if (server.config.service.conf.cors === false) {\n            return true;\n        }\n        return preq.get({\n            uri: `${ server.config.uri }robots.txt`\n        }).then((res) => {\n            assert.deepEqual(res.status, 200);\n            assert.deepEqual(res.headers['access-control-allow-origin'], '*');\n            assert.deepEqual(!!res.headers['access-control-allow-headers'], true);\n            assert.deepEqual(!!res.headers['access-control-expose-headers'], true);\n        });\n    });\n\n    it('should set CSP headers', () => {\n        if (server.config.service.conf.csp === false) {\n            return true;\n        }\n        return preq.get({\n            uri: `${ server.config.uri }robots.txt`\n        }).then((res) => {\n            assert.deepEqual(res.status, 200);\n            assert.deepEqual(res.headers['x-xss-protection'], '1; mode=block');\n            assert.deepEqual(res.headers['x-content-type-options'], 'nosniff');\n            assert.deepEqual(res.headers['x-frame-options'], 'SAMEORIGIN');\n\n            assert.deepEqual(res.headers['content-security-policy'], 'default-src \\'self\\'; object-src \\'none\\'; media-src *; img-src *; style-src *; frame-ancestors \\'self\\'');\n            assert.deepEqual(res.headers['x-content-security-policy'], 'default-src \\'self\\'; object-src \\'none\\'; media-src *; img-src *; style-src *; frame-ancestors \\'self\\'');\n            assert.deepEqual(res.headers['x-webkit-csp'], 'default-src \\'self\\'; object-src \\'none\\'; media-src *; img-src *; style-src *; frame-ancestors \\'self\\'');\n\n        });\n    });\n\n    it('should get static content gzipped', () => preq.get({\n            uri: `${ server.config.uri }static/index.html`,\n            headers: {\n                'accept-encoding': 'gzip, deflate'\n            }\n        }).then((res) => {\n            assert.deepEqual(res.status, 200);\n            // if there is no content-length, the reponse was gzipped\n            assert.deepEqual(res.headers['content-length'], undefined,\n                'Did not expect the content-length header!');\n        }));\n\n    it('should get static content uncompressed', () => preq.get({\n            uri: `${ server.config.uri }static/index.html`,\n            headers: {\n                'accept-encoding': ''\n            }\n        }).then((res) => {\n            const contentEncoding = res.headers['content-encoding'];\n            assert.deepEqual(res.status, 200);\n            assert.deepEqual(contentEncoding, undefined, 'Did not expect gzipped contents!');\n        }));\n});\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/features/app/spec.js","messages":[{"ruleId":"no-redeclare","severity":1,"message":"'describe' is already defined as a built-in global variable.","line":1,"column":11,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":19},{"ruleId":"no-redeclare","severity":1,"message":"'it' is already defined as a built-in global variable.","line":1,"column":21,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":23},{"ruleId":"no-redeclare","severity":1,"message":"'before' is already defined as a built-in global variable.","line":1,"column":25,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":31},{"ruleId":"no-redeclare","severity":1,"message":"'after' is already defined as a built-in global variable.","line":1,"column":33,"nodeType":"Block","messageId":"redeclaredAsBuiltin","endLine":1,"endColumn":38},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":24,"column":30,"nodeType":"CallExpression","endLine":24,"endColumn":55},{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":178,"column":14,"nodeType":"NewExpression","endLine":178,"endColumn":47}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* global describe, it, before, after */\n\n'use strict';\n\nconst preq   = require('preq');\nconst assert = require('../../utils/assert.js');\nconst server = require('../../utils/server.js');\nconst URI    = require('swagger-router').URI;\nconst yaml   = require('js-yaml');\nconst fs     = require('fs');\n\nif (!server.stopHookAdded) {\n    server.stopHookAdded = true;\n    after(() => server.stop());\n}\n\nfunction staticSpecLoad() {\n\n    let spec;\n    const myService = server.config.conf.services[server.config.conf.services.length - 1].conf;\n    const specPath = `${ __dirname }/../../../${ myService.spec ? myService.spec : 'spec.yaml' }`;\n\n    try {\n        spec = yaml.safeLoad(fs.readFileSync(specPath));\n    } catch (e) {\n        // this error will be detected later, so ignore it\n        spec = { paths: {}, 'x-default-params': {} };\n    }\n\n    return spec;\n\n}\n\nfunction validateExamples(pathStr, defParams, mSpec) {\n\n    const uri = new URI(pathStr, {}, true);\n\n    if (!mSpec) {\n        try {\n            uri.expand(defParams);\n            return true;\n        } catch (e) {\n            throw new Error(`Missing parameter for route ${ pathStr } : ${ e.message }`);\n        }\n    }\n\n    if (!Array.isArray(mSpec)) {\n        throw new Error(`Route ${ pathStr } : x-amples must be an array!`);\n    }\n\n    mSpec.forEach((ex, idx) => {\n        if (!ex.title) {\n            throw new Error(`Route ${ pathStr }, example ${ idx }: title missing!`);\n        }\n        ex.request = ex.request || {};\n        try {\n            uri.expand(Object.assign({}, defParams, ex.request.params || {}));\n        } catch (e) {\n            throw new Error(\n                `Route ${ pathStr }, example ${ idx } (${ ex.title }): missing parameter: ${ e.message }`\n            );\n        }\n    });\n\n    return true;\n\n}\n\nfunction constructTestCase(title, path, method, request, response) {\n\n    return {\n        title,\n        request: {\n            uri: server.config.uri + (path[0] === '/' ? path.slice(1) : path),\n            method,\n            headers: request.headers || {},\n            query: request.query,\n            body: request.body,\n            followRedirect: false\n        },\n        response: {\n            status: response.status || 200,\n            headers: response.headers || {},\n            body: response.body\n        }\n    };\n\n}\n\nfunction constructTests(paths, defParams) {\n\n    const ret = [];\n\n    Object.keys(paths).forEach((pathStr) => {\n        Object.keys(paths[pathStr]).forEach((method) => {\n            const p = paths[pathStr][method];\n            if ({}.hasOwnProperty.call(p, 'x-monitor') && !p['x-monitor']) {\n                return;\n            }\n            const uri = new URI(pathStr, {}, true);\n            if (!p['x-amples']) {\n                ret.push(constructTestCase(\n                    pathStr,\n                    uri.toString({ params: defParams }),\n                    method,\n                    {},\n                    {}\n                ));\n                return;\n            }\n            p['x-amples'].forEach((ex) => {\n                ex.request = ex.request || {};\n                if (!ex['skip-locally']) {\n                    ret.push(constructTestCase(\n                        ex.title,\n                        uri.toString({\n                            params: Object.assign(\n                                {},\n                                defParams,\n                                ex.request.params || {}\n                            )\n                        }),\n                        method,\n                        ex.request,\n                        ex.response || {}\n                    ));\n                }\n            });\n        });\n    });\n\n    return ret;\n\n}\n\nfunction cmp(result, expected, errMsg) {\n\n    if (expected === null || expected === undefined) {\n        // nothing to expect, so we can return\n        return true;\n    }\n    if (result === null || result === undefined) {\n        result = '';\n    }\n\n    if (expected.constructor === Object) {\n        Object.keys(expected).forEach((key) => {\n            const val = expected[key];\n            assert.deepEqual({}.hasOwnProperty.call(result, key), true,\n                `Body field ${ key } not found in response!`);\n            cmp(result[key], val, `${ key } body field mismatch!`);\n        });\n        return true;\n    } else if (expected.constructor === Array) {\n        if (result.constructor !== Array) {\n            assert.deepEqual(result, expected, errMsg);\n            return true;\n        }\n        // only one item in expected - compare them all\n        if (expected.length === 1 && result.length > 1) {\n            result.forEach((item) => {\n                cmp(item, expected[0], errMsg);\n            });\n            return true;\n        }\n        // more than one item expected, check them one by one\n        if (expected.length !== result.length) {\n            assert.deepEqual(result, expected, errMsg);\n            return true;\n        }\n        expected.forEach((item, idx) => {\n            cmp(result[idx], item, errMsg);\n        });\n        return true;\n    }\n\n    if (expected.length > 1 && expected[0] === '/' && expected[expected.length - 1] === '/') {\n        if ((new RegExp(expected.slice(1, -1))).test(result)) {\n            return true;\n        }\n    } else if (expected.length === 0 && result.length === 0) {\n        return true;\n    } else if (result === expected || result.startsWith(expected)) {\n        return true;\n    }\n\n    assert.deepEqual(result, expected, errMsg);\n    return true;\n\n}\n\nfunction validateTestResponse(testCase, res) {\n\n    const expRes = testCase.response;\n\n    // check the status\n    assert.status(res, expRes.status);\n    // check the headers\n    Object.keys(expRes.headers).forEach((key) => {\n        const val = expRes.headers[key];\n        assert.deepEqual({}.hasOwnProperty.call(res.headers, key), true,\n            `Header ${ key } not found in response!`);\n        cmp(res.headers[key], val, `${ key } header mismatch!`);\n    });\n    // check the body\n    if (!expRes.body) {\n        return true;\n    }\n    res.body = res.body || '';\n    if (Buffer.isBuffer(res.body)) {\n        res.body = res.body.toString();\n    }\n    if (expRes.body.constructor !== res.body.constructor) {\n        if (expRes.body.constructor === String) {\n            res.body = JSON.stringify(res.body);\n        } else {\n            res.body = JSON.parse(res.body);\n        }\n    }\n    // check that the body type is the same\n    if (expRes.body.constructor !== res.body.constructor) {\n        throw new Error(\n            `Expected body type ${ expRes.body.constructor } but got ${ res.body.constructor }`\n        );\n    }\n\n    // compare the bodies\n    cmp(res.body, expRes.body, 'Body mismatch!');\n\n    return true;\n\n}\n\ndescribe('Swagger spec', function () {\n\n    // the variable holding the spec\n    let spec = staticSpecLoad();\n    // default params, if given\n    let defParams = spec['x-default-params'] || {};\n\n    this.timeout(20000);\n\n    before(() => server.start());\n\n    it('get the spec', () => preq.get(`${ server.config.uri }?spec`)\n        .then((res) => {\n            assert.status(200);\n            assert.contentType(res, 'application/json');\n            assert.notDeepEqual(res.body, undefined, 'No body received!');\n            spec = res.body;\n        }));\n\n    it('spec validation', () => {\n        if (spec['x-default-params']) {\n            defParams = spec['x-default-params'];\n        }\n        // check the high-level attributes\n        ['info', 'swagger', 'paths'].forEach((prop) => {\n            assert.deepEqual(!!spec[prop], true, `No ${ prop } field present!`);\n        });\n        // no paths - no love\n        assert.deepEqual(!!Object.keys(spec.paths), true, 'No paths given in the spec!');\n        // now check each path\n        Object.keys(spec.paths).forEach((pathStr) => {\n            assert.deepEqual(!!pathStr, true, 'A path cannot have a length of zero!');\n            const path = spec.paths[pathStr];\n            assert.deepEqual(!!Object.keys(path), true, `No methods defined for path: ${ pathStr }`);\n            Object.keys(path).forEach((method) => {\n                const mSpec = path[method];\n                if ({}.hasOwnProperty.call(mSpec, 'x-monitor') && !mSpec['x-monitor']) {\n                    return;\n                }\n                validateExamples(pathStr, defParams, mSpec['x-amples']);\n            });\n        });\n    });\n\n    describe('routes', () => {\n\n        constructTests(spec.paths, defParams).forEach((testCase) => {\n            it(testCase.title, () => preq(testCase.request)\n                .then((res) => {\n                    validateTestResponse(testCase, res);\n                }, (err) => {\n                    validateTestResponse(testCase, err);\n                }));\n        });\n\n    });\n});\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/features/v1/article.creation.morelike.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/lib/article.creation.morelike.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/lib/caption.js","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 136. Maximum allowed is 100.","line":193,"column":1,"nodeType":"Program","messageId":"max","endLine":193,"endColumn":137}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst rewire = require('rewire');\nconst lib = rewire('../../lib/caption');\nconst assert = require('../utils/assert');\n\ndescribe('lib:caption', () => {\n\n    describe('convertGlobalUsagePageIdsToInts', () => {\n        const convertGlobalUsagePageIdsToInts = lib.__get__('convertGlobalUsagePageIdsToInts');\n\n        it('converts page ID string to int', () => {\n            const image = {\n                pageid: 1,\n                ns: 6,\n                title: 'File:Foo.jpg',\n                mime: 'image/jpeg',\n                structured: { captions: {} },\n                globalusage: [{\n                    title: 'Foo',\n                    wiki: 'en.wikipedia.org',\n                    pageid: '5'\n                }]\n            };\n            convertGlobalUsagePageIdsToInts(image);\n            assert.deepEqual(image.globalusage[0].pageid, 5);\n        });\n    });\n\n    describe('consolidateImageData', () => {\n        const consolidateImageData = lib.__get__('consolidateImageData');\n\n        it('consolidates entity data to expected items', () => {\n            const images = { 1: {}, 2: {}, 3: {} };\n            const entities = {\n                M1: { labels: { en: { value: 'en' } } },\n                M2: { labels: { es: { value: 'es' } } },\n                M3: { labels: { de: { value: 'de' } } }\n            };\n            consolidateImageData(images, entities);\n            assert.deepEqual(images['1'].structured.captions.en, 'en');\n            assert.deepEqual(images['2'].structured.captions.es, 'es');\n            assert.deepEqual(images['3'].structured.captions.de, 'de');\n        });\n\n        it('handles undefined entities object', () => {\n            assert.doesNotThrow(consolidateImageData, {}, undefined);\n        });\n\n    });\n\n    describe('filterPages', () => {\n        const filterPages = lib.__get__('filterPages');\n\n        it('filters non-image MIME types', () => {\n            const pages = {\n                1: { imageinfo: [ { mime: 'audio/ogg' } ] },\n                2: { imageinfo: [ { mime: 'image/jpeg' } ] },\n                3: { imageinfo: [ { mime: 'text/html' } ] },\n                4: {\n                    imageinfo: [ { mime: 'image/jpeg' } ],\n                    protection: [ { type: 'edit', level: 'sysop' } ]\n                }\n            };\n            const result = filterPages(pages);\n            assert.deepEqual(Object.keys(result).length, 1);\n            assert.ok(result['2']);\n        });\n\n        it('handles undefined pages object', () => {\n            assert.doesNotThrow(filterPages, undefined);\n        });\n\n    });\n\n    describe('makeResult', () => {\n\n        const image = {\n            pageid: 1,\n            ns: 6,\n            title: 'File:Foo',\n            structured: {\n                captions: {\n                    en: 'Foo',\n                    pt: 'Bar'\n                }\n            },\n            imageinfo: [{\n                mime: 'image/jpeg'\n            }],\n            globalusage: [{\n                title: 'Bar',\n                wiki: 'pt.wikipedia.org',\n                pageid: 3\n            }]\n        };\n\n        const expected = {\n            pageid: 1,\n            ns: 6,\n            title: 'File:Foo',\n            mime: 'image/jpeg',\n            structured: {\n                captions: {\n                    en: 'Foo',\n                    pt: 'Bar'\n                }\n            },\n            globalusage: {\n                pt: [{\n                    title: 'Bar',\n                    wiki: 'pt.wikipedia.org',\n                    pageid: 3\n                }]\n            }\n        };\n\n        const makeResult = lib.__get__('makeResult');\n\n        it('result is structured as expected', () => {\n            assert.deepEqual(makeResult(image, 'pt', 'en'), expected);\n        });\n\n    });\n\n    describe('makeResults', () => {\n\n        const images = {\n            1: {\n                pageid: 1,\n                ns: 6,\n                title: 'File:Foo',\n                imageinfo: [{\n                    mime: 'image/jpeg'\n                }],\n                globalusage: [{\n                    title: 'Bar',\n                    wiki: 'pt.wikipedia.org',\n                    pageid: 3\n                }]\n            },\n            2: {\n                pageid: 2,\n                ns: 6,\n                title: 'File:Bar',\n                imageinfo: [{\n                    mime: 'image/jpeg'\n                }],\n                globalusage: [{\n                    title: 'Foo',\n                    wiki: 'pt.wikipedia.org',\n                    pageid: 4\n                }]\n            },\n            3: {\n                pageid: 3,\n                ns: 6,\n                title: 'File:Baz',\n                imageinfo: [{\n                    mime: 'image/jpeg'\n                }],\n                globalusage: [{\n                    title: 'Baz',\n                    wiki: 'pt.wikipedia.org',\n                    pageid: 5\n                }]\n            }\n        };\n\n        const entities = {\n            M1: {\n                labels: {\n                    en: {\n                        value: 'Foo'\n                    },\n                    pt: {\n                        value: 'Bar'\n                    }\n                }\n            },\n            M2: {\n                labels: {}\n            },\n            M3: {\n                labels: {\n                    en: {\n                        value: 'Baz'\n                    }\n                }\n            }\n        };\n\n        const cond = (image, targetLang, sourceLang) => image.structured.captions[sourceLang] && !image.structured.captions[targetLang];\n\n        const makeResults = lib.__get__('makeResults');\n\n        it('makes results as expected', () => {\n            const result = makeResults(images, entities, cond, 'pt', 'en');\n            const expected = [{\n                pageid: 3,\n                ns: 6,\n                title: 'File:Baz',\n                mime: 'image/jpeg',\n                structured: {\n                    captions: {\n                        en: 'Baz'\n                    }\n                },\n                globalusage: {\n                    pt: [{\n                        title: 'Baz',\n                        wiki: 'pt.wikipedia.org',\n                        pageid: 5\n                    }]\n                }\n            }];\n            assert.deepEqual(result, expected);\n        });\n    });\n\n});\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/lib/description.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/lib/suggested-edits-common.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/utils/assert.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/utils/logStream.js","messages":[{"ruleId":"no-shadow","severity":1,"message":"'end' is already declared in the upper scope on line 31 column 14.","line":41,"column":13,"nodeType":"Identifier","messageId":"noShadow","endLine":41,"endColumn":16},{"ruleId":"no-shadow","severity":1,"message":"'get' is already declared in the upper scope on line 34 column 14.","line":49,"column":18,"nodeType":"Identifier","messageId":"noShadow","endLine":49,"endColumn":21}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst bunyan = require('bunyan');\n\nfunction logStream(logStdout) {\n\n    const log = [];\n    const parrot = bunyan.createLogger({\n        name: 'test-logger',\n        level: 'warn'\n    });\n\n    function write(chunk, encoding, callback) {\n        try {\n            const entry = JSON.parse(chunk);\n            const levelMatch = /^(\\w+)/.exec(entry.levelPath);\n            if (logStdout && levelMatch) {\n                const level = levelMatch[1];\n                if (parrot[level]) {\n                    parrot[level](entry);\n                }\n            }\n        } catch (e) {\n            console.error('something went wrong trying to parrot a log entry', e, chunk);\n        }\n\n        log.push(chunk);\n    }\n\n    // to implement the stream writer interface\n    function end(chunk, encoding, callback) {\n    }\n\n    function get() {\n        return log;\n    }\n\n    function slice() {\n\n        const begin = log.length;\n        let end   = null;\n\n        function halt() {\n            if (end === null) {\n                end = log.length;\n            }\n        }\n\n        function get() {\n            return log.slice(begin, end);\n        }\n\n        /* Disable eslint object-shorthand until Node 4 support is dropped */\n\n        return {\n            halt: halt,\n            get: get\n        };\n\n    }\n\n    return {\n        write: write,\n        end: end,\n        slice: slice,\n        get: get\n    };\n}\n\nmodule.exports = logStream;\n","usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/test/utils/server.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}]

--- end ---
$ /usr/bin/npm ci
--- stderr ---
npm WARN skipping integrity check for git dependency ssh://git@github.com/wikimedia/swagger-ui.git 
npm WARN deprecated kad-fs@0.0.4: This package is no longer maintained.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated kad-memstore@0.0.1: This package is no longer maintained.
npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated sinon@7.5.0: 16.1.1
--- stdout ---

added 801 packages, and audited 802 packages in 1m

113 packages are looking for funding
  run `npm fund` for details

20 vulnerabilities (1 low, 12 moderate, 3 high, 4 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> recommendation-api@0.7.0 test
> PREQ_CONNECT_TIMEOUT=15 mocha && npm run lint



starting test server
  express app
    ✓ should get robots.txt
    ✓ should set CORS headers
    ✓ should set CSP headers
    ✓ should get static content gzipped
    ✓ should get static content uncompressed

  Swagger spec
    ✓ get the spec
    ✓ spec validation
    routes
      ✓ robots.txt check
      ✓ root with no query params
      ✓ spec from root
      ✓ doc from root
      ✓ root with wrong query param
      ✓ article.creation.translation - normal source and target with seed (1472ms)
      ✓ article.creation.translation - bad seed (143ms)
      ✓ article.creation.translation - invalid count
      ✓ article.creation.translation - incorrectly formatted source
      ✓ article.creation.morelike - bad article title (59ms)
      ✓ article.creation.morelike - missing models (120ms)
      ✓ Caption addition suggestions (1271ms)
      ✓ Caption translation suggestions (1415ms)
      ✓ Description addition suggestions (703ms)
      ✓ Description translation suggestions (675ms)

  article.creation.morelike
    ✓ should return recommendations for good article title (3028ms)

  Get missing articles
    ✓ correctly filters out existing articles

  lib:caption
    convertGlobalUsagePageIdsToInts
      ✓ converts page ID string to int
    consolidateImageData
      ✓ consolidates entity data to expected items
      ✓ handles undefined entities object
    filterPages
      ✓ filters non-image MIME types
      ✓ handles undefined pages object
    makeResult
      ✓ result is structured as expected
    makeResults
      ✓ makes results as expected

  lib:description
    wikiLangToDBName
      ✓ appends "wiki"
      ✓ converts hyphens to underscores
      ✓ converts be-tarask to be_x_oldwiki
    buildResponse
      ✓ gracefully handles undefined inputs
      ✓ gracefully handles empty inputs

  lib:suggested-edits-common
    checkRequestDomain
      ✓ allows allowed domains
      ✓ disallows disallowed domains
      ✓ handles undefined
    getWikiLangForLangCode
      ✓ translates language variants to base wiki language codes
      ✓ passed through other inputs
      ✓ handles undefined

stopping test server

  42 passing (9s)


> recommendation-api@0.7.0 lint
> node_modules/.bin/eslint --ext .js .


/src/repo/app.js
   20:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
   71:37  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  145:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  147:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  159:31  warning  Found non-literal argument in require             security/detect-non-literal-require
  196:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  197:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  235:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types

/src/repo/lib/api-util.js
   81:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  128:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/lib/article.creation.morelike.js
  1:11  warning  'setTimeout' is already defined as a built-in global variable  no-redeclare

/src/repo/lib/article.creation.translation.js
  129:12  warning  'candidates' is already declared in the upper scope on line 122 column 9  no-shadow

/src/repo/lib/util.js
  109:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  155:1  warning  The type 'bool' is undefined         jsdoc/no-undefined-types
  165:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  260:1  warning  The type 'Router' is undefined       jsdoc/no-undefined-types
  280:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/routes/article.creation.morelike.js
  75:32  warning  'ids' is already declared in the upper scope on line 66 column 24  no-shadow

/src/repo/routes/article.creation.translation.js
  14:25  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/test/features/app/app.js
  1:11  warning  'describe' is already defined as a built-in global variable  no-redeclare
  1:21  warning  'it' is already defined as a built-in global variable        no-redeclare
  1:25  warning  'before' is already defined as a built-in global variable    no-redeclare
  1:33  warning  'after' is already defined as a built-in global variable     no-redeclare

/src/repo/test/features/app/spec.js
    1:11  warning  'describe' is already defined as a built-in global variable                no-redeclare
    1:21  warning  'it' is already defined as a built-in global variable                      no-redeclare
    1:25  warning  'before' is already defined as a built-in global variable                  no-redeclare
    1:33  warning  'after' is already defined as a built-in global variable                   no-redeclare
   24:30  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  178:14  warning  Found non-literal argument to RegExp Constructor                           security/detect-non-literal-regexp

/src/repo/test/lib/caption.js
  193:1  warning  This line has a length of 136. Maximum allowed is 100  max-len

/src/repo/test/utils/logStream.js
  41:13  warning  'end' is already declared in the upper scope on line 31 column 14  no-shadow
  49:18  warning  'get' is already declared in the upper scope on line 34 column 14  no-shadow

✖ 32 problems (0 errors, 32 warnings)


--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "ajv": {
      "name": "ajv",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097685,
          "name": "ajv",
          "dependency": "ajv",
          "title": "Prototype Pollution in Ajv",
          "url": "https://github.com/advisories/GHSA-v88g-cgmw-v5xw",
          "severity": "moderate",
          "cwe": [
            "CWE-915",
            "CWE-1321"
          ],
          "cvss": {
            "score": 5.6,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"
          },
          "range": "<6.12.3"
        }
      ],
      "effects": [
        "ajv-keywords",
        "eslint",
        "table"
      ],
      "range": "<6.12.3",
      "nodes": [
        "node_modules/rewire/node_modules/ajv",
        "node_modules/table/node_modules/ajv"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "ajv-keywords": {
      "name": "ajv-keywords",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv"
      ],
      "effects": [],
      "range": "2.1.1",
      "nodes": [
        "node_modules/table/node_modules/ajv-keywords"
      ],
      "fixAvailable": true
    },
    "debug": {
      "name": "debug",
      "severity": "low",
      "isDirect": false,
      "via": [
        {
          "source": 1096793,
          "name": "debug",
          "dependency": "debug",
          "title": "Regular Expression Denial of Service in debug",
          "url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c",
          "severity": "low",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 3.7,
            "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": ">=3.2.0 <3.2.7"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "3.2.0 - 3.2.6",
      "nodes": [
        "node_modules/mocha/node_modules/debug"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "eslint": {
      "name": "eslint",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv",
        "table"
      ],
      "effects": [
        "rewire"
      ],
      "range": "2.5.0 - 2.5.2 || 4.2.0 - 5.0.0-rc.0",
      "nodes": [
        "node_modules/rewire/node_modules/eslint"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "flat": {
      "name": "flat",
      "severity": "critical",
      "isDirect": false,
      "via": [
        {
          "source": 1089152,
          "name": "flat",
          "dependency": "flat",
          "title": "flat vulnerable to Prototype Pollution",
          "url": "https://github.com/advisories/GHSA-2j2x-2gpw-g8fm",
          "severity": "critical",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 9.8,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<5.0.1"
        }
      ],
      "effects": [
        "yargs-unparser"
      ],
      "range": "<5.0.1",
      "nodes": [
        "node_modules/flat"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "limitation": {
      "name": "limitation",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "wikimedia-kad-fork"
      ],
      "effects": [
        "service-runner"
      ],
      "range": ">=0.2.3",
      "nodes": [
        "node_modules/limitation"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "minimatch": {
      "name": "minimatch",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1096485,
          "name": "minimatch",
          "dependency": "minimatch",
          "title": "minimatch ReDoS vulnerability",
          "url": "https://github.com/advisories/GHSA-f8q6-p94x-37v3",
          "severity": "high",
          "cwe": [
            "CWE-400",
            "CWE-1333"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<3.0.5"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "<3.0.5",
      "nodes": [
        "node_modules/mocha/node_modules/minimatch"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "mocha": {
      "name": "mocha",
      "severity": "critical",
      "isDirect": true,
      "via": [
        "debug",
        "minimatch",
        "yargs-unparser"
      ],
      "effects": [],
      "range": "5.1.0 - 9.2.1",
      "nodes": [
        "node_modules/mocha"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    },
    "ms": {
      "name": "ms",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1094419,
          "name": "ms",
          "dependency": "ms",
          "title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
          "url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
          "severity": "moderate",
          "cwe": [
            "CWE-1333"
          ],
          "cvss": {
            "score": 5.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
          },
          "range": "<2.0.0"
        }
      ],
      "effects": [
        "wikimedia-kad-fork"
      ],
      "range": "<2.0.0",
      "nodes": [
        "node_modules/wikimedia-kad-fork/node_modules/ms"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "preq": {
      "name": "preq",
      "severity": "high",
      "isDirect": true,
      "via": [
        "request",
        "requestretry"
      ],
      "effects": [],
      "range": "*",
      "nodes": [
        "node_modules/preq"
      ],
      "fixAvailable": false
    },
    "request": {
      "name": "request",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1096727,
          "name": "request",
          "dependency": "request",
          "title": "Server-Side Request Forgery in Request",
          "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 6.1,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
          },
          "range": "<=2.88.2"
        },
        "tough-cookie"
      ],
      "effects": [
        "preq",
        "requestretry"
      ],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": false
    },
    "requestretry": {
      "name": "requestretry",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1090420,
          "name": "requestretry",
          "dependency": "requestretry",
          "title": "Cookie exposure in requestretry",
          "url": "https://github.com/advisories/GHSA-hjp8-2cm3-cc45",
          "severity": "high",
          "cwe": [
            "CWE-200"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
          },
          "range": "<7.0.0"
        },
        "request"
      ],
      "effects": [
        "preq"
      ],
      "range": "*",
      "nodes": [
        "node_modules/requestretry"
      ],
      "fixAvailable": false
    },
    "rewire": {
      "name": "rewire",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "eslint"
      ],
      "effects": [],
      "range": "4.0.0 - 4.0.1",
      "nodes": [
        "node_modules/rewire"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "service-runner": {
      "name": "service-runner",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        "limitation",
        "tar"
      ],
      "effects": [],
      "range": ">=3.0.0",
      "nodes": [
        "node_modules/service-runner"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "swagger-ui": {
      "name": "swagger-ui",
      "severity": "critical",
      "isDirect": true,
      "via": [
        {
          "source": 1085691,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Reverse Tabnapping in swagger-ui",
          "url": "https://github.com/advisories/GHSA-x9p2-fxq6-2m5f",
          "severity": "moderate",
          "cwe": [
            "CWE-1022"
          ],
          "cvss": {
            "score": 4.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
          },
          "range": "<3.18.0"
        },
        {
          "source": 1086900,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-Site Scripting in swagger-ui",
          "url": "https://github.com/advisories/GHSA-388g-jwpg-x6j4",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N"
          },
          "range": "<3.0.13"
        },
        {
          "source": 1088760,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Spoofing attack in swagger-ui",
          "url": "https://github.com/advisories/GHSA-cr3q-pqgq-m8c2",
          "severity": "moderate",
          "cwe": [
            "CWE-20"
          ],
          "cvss": {
            "score": 4.3,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
          },
          "range": "<4.1.3"
        },
        {
          "source": 1088813,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-site scripting in Swagger-UI",
          "url": "https://github.com/advisories/GHSA-c427-hjc3-wrfw",
          "severity": "critical",
          "cwe": [
            "CWE-79",
            "CWE-352"
          ],
          "cvss": {
            "score": 9.8,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
          },
          "range": "<3.23.11"
        },
        {
          "source": 1092161,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Server side request forgery in SwaggerUI",
          "url": "https://github.com/advisories/GHSA-qrmm-w75w-3wpx",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 0,
            "vectorString": null
          },
          "range": "<4.1.3"
        },
        {
          "source": 1094217,
          "name": "swagger-ui",
          "dependency": "swagger-ui",
          "title": "Cross-Site Scripting in swagger-ui",
          "url": "https://github.com/advisories/GHSA-4f9m-pxwh-68hg",
          "severity": "moderate",
          "cwe": [
            "CWE-79"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<3.20.9"
        }
      ],
      "effects": [],
      "range": "<=4.1.2",
      "nodes": [
        "node_modules/swagger-ui"
      ],
      "fixAvailable": false
    },
    "table": {
      "name": "table",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ajv"
      ],
      "effects": [
        "eslint"
      ],
      "range": "3.7.10 - 4.0.2",
      "nodes": [
        "node_modules/table"
      ],
      "fixAvailable": {
        "name": "rewire",
        "version": "7.0.0",
        "isSemVerMajor": true
      }
    },
    "tar": {
      "name": "tar",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097493,
          "name": "tar",
          "dependency": "tar",
          "title": "Denial of service while parsing a tar file due to lack of folders count validation",
          "url": "https://github.com/advisories/GHSA-f5x3-32g6-xq36",
          "severity": "moderate",
          "cwe": [
            "CWE-400"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
          },
          "range": "<6.2.1"
        }
      ],
      "effects": [
        "service-runner"
      ],
      "range": "<6.2.1",
      "nodes": [
        "node_modules/tar"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "tough-cookie": {
      "name": "tough-cookie",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097682,
          "name": "tough-cookie",
          "dependency": "tough-cookie",
          "title": "tough-cookie Prototype Pollution vulnerability",
          "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
          "severity": "moderate",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<4.1.3"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<4.1.3",
      "nodes": [
        "node_modules/tough-cookie"
      ],
      "fixAvailable": false
    },
    "wikimedia-kad-fork": {
      "name": "wikimedia-kad-fork",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        "ms"
      ],
      "effects": [
        "limitation"
      ],
      "range": "*",
      "nodes": [
        "node_modules/wikimedia-kad-fork"
      ],
      "fixAvailable": {
        "name": "service-runner",
        "version": "5.0.0",
        "isSemVerMajor": true
      }
    },
    "yargs-unparser": {
      "name": "yargs-unparser",
      "severity": "critical",
      "isDirect": false,
      "via": [
        "flat"
      ],
      "effects": [
        "mocha"
      ],
      "range": "<=1.6.3",
      "nodes": [
        "node_modules/yargs-unparser"
      ],
      "fixAvailable": {
        "name": "mocha",
        "version": "10.6.0",
        "isSemVerMajor": true
      }
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 1,
      "moderate": 12,
      "high": 3,
      "critical": 4,
      "total": 20
    },
    "dependencies": {
      "prod": 203,
      "dev": 590,
      "optional": 9,
      "peer": 1,
      "peerOptional": 0,
      "total": 801
    }
  }
}

--- end ---
Attempting to npm audit fix
$ /usr/bin/npm audit fix --dry-run --only=dev --json
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---
{
  "added": 0,
  "removed": 0,
  "changed": 0,
  "audited": 802,
  "funding": 113,
  "audit": {
    "auditReportVersion": 2,
    "vulnerabilities": {
      "ajv": {
        "name": "ajv",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1097685,
            "name": "ajv",
            "dependency": "ajv",
            "title": "Prototype Pollution in Ajv",
            "url": "https://github.com/advisories/GHSA-v88g-cgmw-v5xw",
            "severity": "moderate",
            "cwe": [
              "CWE-915",
              "CWE-1321"
            ],
            "cvss": {
              "score": 5.6,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"
            },
            "range": "<6.12.3"
          }
        ],
        "effects": [
          "ajv-keywords",
          "eslint",
          "table"
        ],
        "range": "<6.12.3",
        "nodes": [
          "node_modules/rewire/node_modules/ajv",
          "node_modules/table/node_modules/ajv"
        ],
        "fixAvailable": {
          "name": "rewire",
          "version": "7.0.0",
          "isSemVerMajor": true
        }
      },
      "ajv-keywords": {
        "name": "ajv-keywords",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "ajv"
        ],
        "effects": [],
        "range": "2.1.1",
        "nodes": [
          "node_modules/table/node_modules/ajv-keywords"
        ],
        "fixAvailable": true
      },
      "debug": {
        "name": "debug",
        "severity": "low",
        "isDirect": false,
        "via": [
          {
            "source": 1096793,
            "name": "debug",
            "dependency": "debug",
            "title": "Regular Expression Denial of Service in debug",
            "url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c",
            "severity": "low",
            "cwe": [
              "CWE-400"
            ],
            "cvss": {
              "score": 3.7,
              "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
            },
            "range": ">=3.2.0 <3.2.7"
          }
        ],
        "effects": [
          "mocha"
        ],
        "range": "3.2.0 - 3.2.6",
        "nodes": [
          "node_modules/mocha/node_modules/debug"
        ],
        "fixAvailable": {
          "name": "mocha",
          "version": "10.6.0",
          "isSemVerMajor": true
        }
      },
      "eslint": {
        "name": "eslint",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "ajv",
          "table"
        ],
        "effects": [
          "rewire"
        ],
        "range": "2.5.0 - 2.5.2 || 4.2.0 - 5.0.0-rc.0",
        "nodes": [
          "node_modules/rewire/node_modules/eslint"
        ],
        "fixAvailable": {
          "name": "rewire",
          "version": "7.0.0",
          "isSemVerMajor": true
        }
      },
      "flat": {
        "name": "flat",
        "severity": "critical",
        "isDirect": false,
        "via": [
          {
            "source": 1089152,
            "name": "flat",
            "dependency": "flat",
            "title": "flat vulnerable to Prototype Pollution",
            "url": "https://github.com/advisories/GHSA-2j2x-2gpw-g8fm",
            "severity": "critical",
            "cwe": [
              "CWE-1321"
            ],
            "cvss": {
              "score": 9.8,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
            },
            "range": "<5.0.1"
          }
        ],
        "effects": [
          "yargs-unparser"
        ],
        "range": "<5.0.1",
        "nodes": [
          "node_modules/flat"
        ],
        "fixAvailable": {
          "name": "mocha",
          "version": "10.6.0",
          "isSemVerMajor": true
        }
      },
      "limitation": {
        "name": "limitation",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "wikimedia-kad-fork"
        ],
        "effects": [
          "service-runner"
        ],
        "range": ">=0.2.3",
        "nodes": [
          "node_modules/limitation"
        ],
        "fixAvailable": {
          "name": "service-runner",
          "version": "5.0.0",
          "isSemVerMajor": true
        }
      },
      "minimatch": {
        "name": "minimatch",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1096485,
            "name": "minimatch",
            "dependency": "minimatch",
            "title": "minimatch ReDoS vulnerability",
            "url": "https://github.com/advisories/GHSA-f8q6-p94x-37v3",
            "severity": "high",
            "cwe": [
              "CWE-400",
              "CWE-1333"
            ],
            "cvss": {
              "score": 7.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
            },
            "range": "<3.0.5"
          }
        ],
        "effects": [
          "mocha"
        ],
        "range": "<3.0.5",
        "nodes": [
          "node_modules/mocha/node_modules/minimatch"
        ],
        "fixAvailable": {
          "name": "mocha",
          "version": "10.6.0",
          "isSemVerMajor": true
        }
      },
      "mocha": {
        "name": "mocha",
        "severity": "critical",
        "isDirect": true,
        "via": [
          "debug",
          "minimatch",
          "yargs-unparser"
        ],
        "effects": [],
        "range": "5.1.0 - 9.2.1",
        "nodes": [
          "node_modules/mocha"
        ],
        "fixAvailable": {
          "name": "mocha",
          "version": "10.6.0",
          "isSemVerMajor": true
        }
      },
      "ms": {
        "name": "ms",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1094419,
            "name": "ms",
            "dependency": "ms",
            "title": "Vercel ms Inefficient Regular Expression Complexity vulnerability",
            "url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f",
            "severity": "moderate",
            "cwe": [
              "CWE-1333"
            ],
            "cvss": {
              "score": 5.3,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
            },
            "range": "<2.0.0"
          }
        ],
        "effects": [
          "wikimedia-kad-fork"
        ],
        "range": "<2.0.0",
        "nodes": [
          "node_modules/wikimedia-kad-fork/node_modules/ms"
        ],
        "fixAvailable": {
          "name": "service-runner",
          "version": "5.0.0",
          "isSemVerMajor": true
        }
      },
      "preq": {
        "name": "preq",
        "severity": "high",
        "isDirect": true,
        "via": [
          "request",
          "requestretry"
        ],
        "effects": [],
        "range": "*",
        "nodes": [
          "node_modules/preq"
        ],
        "fixAvailable": false
      },
      "request": {
        "name": "request",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1096727,
            "name": "request",
            "dependency": "request",
            "title": "Server-Side Request Forgery in Request",
            "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
            "severity": "moderate",
            "cwe": [
              "CWE-918"
            ],
            "cvss": {
              "score": 6.1,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
            },
            "range": "<=2.88.2"
          },
          "tough-cookie"
        ],
        "effects": [
          "preq",
          "requestretry"
        ],
        "range": "*",
        "nodes": [
          "node_modules/request"
        ],
        "fixAvailable": false
      },
      "requestretry": {
        "name": "requestretry",
        "severity": "high",
        "isDirect": false,
        "via": [
          {
            "source": 1090420,
            "name": "requestretry",
            "dependency": "requestretry",
            "title": "Cookie exposure in requestretry",
            "url": "https://github.com/advisories/GHSA-hjp8-2cm3-cc45",
            "severity": "high",
            "cwe": [
              "CWE-200"
            ],
            "cvss": {
              "score": 7.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
            },
            "range": "<7.0.0"
          },
          "request"
        ],
        "effects": [
          "preq"
        ],
        "range": "*",
        "nodes": [
          "node_modules/requestretry"
        ],
        "fixAvailable": false
      },
      "rewire": {
        "name": "rewire",
        "severity": "moderate",
        "isDirect": true,
        "via": [
          "eslint"
        ],
        "effects": [],
        "range": "4.0.0 - 4.0.1",
        "nodes": [
          "node_modules/rewire"
        ],
        "fixAvailable": {
          "name": "rewire",
          "version": "7.0.0",
          "isSemVerMajor": true
        }
      },
      "service-runner": {
        "name": "service-runner",
        "severity": "moderate",
        "isDirect": true,
        "via": [
          "limitation",
          "tar"
        ],
        "effects": [],
        "range": ">=3.0.0",
        "nodes": [
          "node_modules/service-runner"
        ],
        "fixAvailable": {
          "name": "service-runner",
          "version": "5.0.0",
          "isSemVerMajor": true
        }
      },
      "swagger-ui": {
        "name": "swagger-ui",
        "severity": "critical",
        "isDirect": true,
        "via": [
          {
            "source": 1085691,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Reverse Tabnapping in swagger-ui",
            "url": "https://github.com/advisories/GHSA-x9p2-fxq6-2m5f",
            "severity": "moderate",
            "cwe": [
              "CWE-1022"
            ],
            "cvss": {
              "score": 4.3,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
            },
            "range": "<3.18.0"
          },
          {
            "source": 1086900,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Cross-Site Scripting in swagger-ui",
            "url": "https://github.com/advisories/GHSA-388g-jwpg-x6j4",
            "severity": "moderate",
            "cwe": [
              "CWE-79"
            ],
            "cvss": {
              "score": 6.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N"
            },
            "range": "<3.0.13"
          },
          {
            "source": 1088760,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Spoofing attack in swagger-ui",
            "url": "https://github.com/advisories/GHSA-cr3q-pqgq-m8c2",
            "severity": "moderate",
            "cwe": [
              "CWE-20"
            ],
            "cvss": {
              "score": 4.3,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
            },
            "range": "<4.1.3"
          },
          {
            "source": 1088813,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Cross-site scripting in Swagger-UI",
            "url": "https://github.com/advisories/GHSA-c427-hjc3-wrfw",
            "severity": "critical",
            "cwe": [
              "CWE-79",
              "CWE-352"
            ],
            "cvss": {
              "score": 9.8,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
            },
            "range": "<3.23.11"
          },
          {
            "source": 1092161,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Server side request forgery in SwaggerUI",
            "url": "https://github.com/advisories/GHSA-qrmm-w75w-3wpx",
            "severity": "moderate",
            "cwe": [
              "CWE-918"
            ],
            "cvss": {
              "score": 0,
              "vectorString": null
            },
            "range": "<4.1.3"
          },
          {
            "source": 1094217,
            "name": "swagger-ui",
            "dependency": "swagger-ui",
            "title": "Cross-Site Scripting in swagger-ui",
            "url": "https://github.com/advisories/GHSA-4f9m-pxwh-68hg",
            "severity": "moderate",
            "cwe": [
              "CWE-79"
            ],
            "cvss": {
              "score": 6.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
            },
            "range": "<3.20.9"
          }
        ],
        "effects": [],
        "range": "<=4.1.2",
        "nodes": [
          "node_modules/swagger-ui"
        ],
        "fixAvailable": false
      },
      "table": {
        "name": "table",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "ajv"
        ],
        "effects": [
          "eslint"
        ],
        "range": "3.7.10 - 4.0.2",
        "nodes": [
          "node_modules/table"
        ],
        "fixAvailable": {
          "name": "rewire",
          "version": "7.0.0",
          "isSemVerMajor": true
        }
      },
      "tar": {
        "name": "tar",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1097493,
            "name": "tar",
            "dependency": "tar",
            "title": "Denial of service while parsing a tar file due to lack of folders count validation",
            "url": "https://github.com/advisories/GHSA-f5x3-32g6-xq36",
            "severity": "moderate",
            "cwe": [
              "CWE-400"
            ],
            "cvss": {
              "score": 6.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
            },
            "range": "<6.2.1"
          }
        ],
        "effects": [
          "service-runner"
        ],
        "range": "<6.2.1",
        "nodes": [
          "node_modules/tar"
        ],
        "fixAvailable": {
          "name": "service-runner",
          "version": "5.0.0",
          "isSemVerMajor": true
        }
      },
      "tough-cookie": {
        "name": "tough-cookie",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          {
            "source": 1097682,
            "name": "tough-cookie",
            "dependency": "tough-cookie",
            "title": "tough-cookie Prototype Pollution vulnerability",
            "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
            "severity": "moderate",
            "cwe": [
              "CWE-1321"
            ],
            "cvss": {
              "score": 6.5,
              "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
            },
            "range": "<4.1.3"
          }
        ],
        "effects": [
          "request"
        ],
        "range": "<4.1.3",
        "nodes": [
          "node_modules/tough-cookie"
        ],
        "fixAvailable": false
      },
      "wikimedia-kad-fork": {
        "name": "wikimedia-kad-fork",
        "severity": "moderate",
        "isDirect": false,
        "via": [
          "ms"
        ],
        "effects": [
          "limitation"
        ],
        "range": "*",
        "nodes": [
          "node_modules/wikimedia-kad-fork"
        ],
        "fixAvailable": {
          "name": "service-runner",
          "version": "5.0.0",
          "isSemVerMajor": true
        }
      },
      "yargs-unparser": {
        "name": "yargs-unparser",
        "severity": "critical",
        "isDirect": false,
        "via": [
          "flat"
        ],
        "effects": [
          "mocha"
        ],
        "range": "<=1.6.3",
        "nodes": [
          "node_modules/yargs-unparser"
        ],
        "fixAvailable": {
          "name": "mocha",
          "version": "10.6.0",
          "isSemVerMajor": true
        }
      }
    },
    "metadata": {
      "vulnerabilities": {
        "info": 0,
        "low": 1,
        "moderate": 12,
        "high": 3,
        "critical": 4,
        "total": 20
      },
      "dependencies": {
        "prod": 203,
        "dev": 590,
        "optional": 9,
        "peer": 1,
        "peerOptional": 0,
        "total": 801
      }
    }
  }
}

--- end ---
{"added": 0, "removed": 0, "changed": 0, "audited": 802, "funding": 113, "audit": {"auditReportVersion": 2, "vulnerabilities": {"ajv": {"name": "ajv", "severity": "moderate", "isDirect": false, "via": [{"source": 1097685, "name": "ajv", "dependency": "ajv", "title": "Prototype Pollution in Ajv", "url": "https://github.com/advisories/GHSA-v88g-cgmw-v5xw", "severity": "moderate", "cwe": ["CWE-915", "CWE-1321"], "cvss": {"score": 5.6, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"}, "range": "<6.12.3"}], "effects": ["ajv-keywords", "eslint", "table"], "range": "<6.12.3", "nodes": ["node_modules/rewire/node_modules/ajv", "node_modules/table/node_modules/ajv"], "fixAvailable": {"name": "rewire", "version": "7.0.0", "isSemVerMajor": true}}, "ajv-keywords": {"name": "ajv-keywords", "severity": "moderate", "isDirect": false, "via": ["ajv"], "effects": [], "range": "2.1.1", "nodes": ["node_modules/table/node_modules/ajv-keywords"], "fixAvailable": true}, "debug": {"name": "debug", "severity": "low", "isDirect": false, "via": [{"source": 1096793, "name": "debug", "dependency": "debug", "title": "Regular Expression Denial of Service in debug", "url": "https://github.com/advisories/GHSA-gxpj-cx7g-858c", "severity": "low", "cwe": ["CWE-400"], "cvss": {"score": 3.7, "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"}, "range": ">=3.2.0 <3.2.7"}], "effects": ["mocha"], "range": "3.2.0 - 3.2.6", "nodes": ["node_modules/mocha/node_modules/debug"], "fixAvailable": {"name": "mocha", "version": "10.6.0", "isSemVerMajor": true}}, "eslint": {"name": "eslint", "severity": "moderate", "isDirect": false, "via": ["ajv", "table"], "effects": ["rewire"], "range": "2.5.0 - 2.5.2 || 4.2.0 - 5.0.0-rc.0", "nodes": ["node_modules/rewire/node_modules/eslint"], "fixAvailable": {"name": "rewire", "version": "7.0.0", "isSemVerMajor": true}}, "flat": {"name": "flat", "severity": "critical", "isDirect": false, "via": [{"source": 1089152, "name": "flat", "dependency": "flat", "title": "flat vulnerable to Prototype Pollution", "url": "https://github.com/advisories/GHSA-2j2x-2gpw-g8fm", "severity": "critical", "cwe": ["CWE-1321"], "cvss": {"score": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, "range": "<5.0.1"}], "effects": ["yargs-unparser"], "range": "<5.0.1", "nodes": ["node_modules/flat"], "fixAvailable": {"name": "mocha", "version": "10.6.0", "isSemVerMajor": true}}, "limitation": {"name": "limitation", "severity": "moderate", "isDirect": false, "via": ["wikimedia-kad-fork"], "effects": ["service-runner"], "range": ">=0.2.3", "nodes": ["node_modules/limitation"], "fixAvailable": {"name": "service-runner", "version": "5.0.0", "isSemVerMajor": true}}, "minimatch": {"name": "minimatch", "severity": "high", "isDirect": false, "via": [{"source": 1096485, "name": "minimatch", "dependency": "minimatch", "title": "minimatch ReDoS vulnerability", "url": "https://github.com/advisories/GHSA-f8q6-p94x-37v3", "severity": "high", "cwe": ["CWE-400", "CWE-1333"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}, "range": "<3.0.5"}], "effects": ["mocha"], "range": "<3.0.5", "nodes": ["node_modules/mocha/node_modules/minimatch"], "fixAvailable": {"name": "mocha", "version": "10.6.0", "isSemVerMajor": true}}, "mocha": {"name": "mocha", "severity": "critical", "isDirect": true, "via": ["debug", "minimatch", "yargs-unparser"], "effects": [], "range": "5.1.0 - 9.2.1", "nodes": ["node_modules/mocha"], "fixAvailable": {"name": "mocha", "version": "10.6.0", "isSemVerMajor": true}}, "ms": {"name": "ms", "severity": "moderate", "isDirect": false, "via": [{"source": 1094419, "name": "ms", "dependency": "ms", "title": "Vercel ms Inefficient Regular Expression Complexity vulnerability", "url": "https://github.com/advisories/GHSA-w9mr-4mfr-499f", "severity": "moderate", "cwe": ["CWE-1333"], "cvss": {"score": 5.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"}, "range": "<2.0.0"}], "effects": ["wikimedia-kad-fork"], "range": "<2.0.0", "nodes": ["node_modules/wikimedia-kad-fork/node_modules/ms"], "fixAvailable": {"name": "service-runner", "version": "5.0.0", "isSemVerMajor": true}}, "preq": {"name": "preq", "severity": "high", "isDirect": true, "via": ["request", "requestretry"], "effects": [], "range": "*", "nodes": ["node_modules/preq"], "fixAvailable": false}, "request": {"name": "request", "severity": "moderate", "isDirect": false, "via": [{"source": 1096727, "name": "request", "dependency": "request", "title": "Server-Side Request Forgery in Request", "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6", "severity": "moderate", "cwe": ["CWE-918"], "cvss": {"score": 6.1, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}, "range": "<=2.88.2"}, "tough-cookie"], "effects": ["preq", "requestretry"], "range": "*", "nodes": ["node_modules/request"], "fixAvailable": false}, "requestretry": {"name": "requestretry", "severity": "high", "isDirect": false, "via": [{"source": 1090420, "name": "requestretry", "dependency": "requestretry", "title": "Cookie exposure in requestretry", "url": "https://github.com/advisories/GHSA-hjp8-2cm3-cc45", "severity": "high", "cwe": ["CWE-200"], "cvss": {"score": 7.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"}, "range": "<7.0.0"}, "request"], "effects": ["preq"], "range": "*", "nodes": ["node_modules/requestretry"], "fixAvailable": false}, "rewire": {"name": "rewire", "severity": "moderate", "isDirect": true, "via": ["eslint"], "effects": [], "range": "4.0.0 - 4.0.1", "nodes": ["node_modules/rewire"], "fixAvailable": {"name": "rewire", "version": "7.0.0", "isSemVerMajor": true}}, "service-runner": {"name": "service-runner", "severity": "moderate", "isDirect": true, "via": ["limitation", "tar"], "effects": [], "range": ">=3.0.0", "nodes": ["node_modules/service-runner"], "fixAvailable": {"name": "service-runner", "version": "5.0.0", "isSemVerMajor": true}}, "swagger-ui": {"name": "swagger-ui", "severity": "critical", "isDirect": true, "via": [{"source": 1085691, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Reverse Tabnapping in swagger-ui", "url": "https://github.com/advisories/GHSA-x9p2-fxq6-2m5f", "severity": "moderate", "cwe": ["CWE-1022"], "cvss": {"score": 4.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"}, "range": "<3.18.0"}, {"source": 1086900, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Cross-Site Scripting in swagger-ui", "url": "https://github.com/advisories/GHSA-388g-jwpg-x6j4", "severity": "moderate", "cwe": ["CWE-79"], "cvss": {"score": 6.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N"}, "range": "<3.0.13"}, {"source": 1088760, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Spoofing attack in swagger-ui", "url": "https://github.com/advisories/GHSA-cr3q-pqgq-m8c2", "severity": "moderate", "cwe": ["CWE-20"], "cvss": {"score": 4.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"}, "range": "<4.1.3"}, {"source": 1088813, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Cross-site scripting in Swagger-UI", "url": "https://github.com/advisories/GHSA-c427-hjc3-wrfw", "severity": "critical", "cwe": ["CWE-79", "CWE-352"], "cvss": {"score": 9.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}, "range": "<3.23.11"}, {"source": 1092161, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Server side request forgery in SwaggerUI", "url": "https://github.com/advisories/GHSA-qrmm-w75w-3wpx", "severity": "moderate", "cwe": ["CWE-918"], "cvss": {"score": 0, "vectorString": null}, "range": "<4.1.3"}, {"source": 1094217, "name": "swagger-ui", "dependency": "swagger-ui", "title": "Cross-Site Scripting in swagger-ui", "url": "https://github.com/advisories/GHSA-4f9m-pxwh-68hg", "severity": "moderate", "cwe": ["CWE-79"], "cvss": {"score": 6.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"}, "range": "<3.20.9"}], "effects": [], "range": "<=4.1.2", "nodes": ["node_modules/swagger-ui"], "fixAvailable": false}, "table": {"name": "table", "severity": "moderate", "isDirect": false, "via": ["ajv"], "effects": ["eslint"], "range": "3.7.10 - 4.0.2", "nodes": ["node_modules/table"], "fixAvailable": {"name": "rewire", "version": "7.0.0", "isSemVerMajor": true}}, "tar": {"name": "tar", "severity": "moderate", "isDirect": false, "via": [{"source": 1097493, "name": "tar", "dependency": "tar", "title": "Denial of service while parsing a tar file due to lack of folders count validation", "url": "https://github.com/advisories/GHSA-f5x3-32g6-xq36", "severity": "moderate", "cwe": ["CWE-400"], "cvss": {"score": 6.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"}, "range": "<6.2.1"}], "effects": ["service-runner"], "range": "<6.2.1", "nodes": ["node_modules/tar"], "fixAvailable": {"name": "service-runner", "version": "5.0.0", "isSemVerMajor": true}}, "tough-cookie": {"name": "tough-cookie", "severity": "moderate", "isDirect": false, "via": [{"source": 1097682, "name": "tough-cookie", "dependency": "tough-cookie", "title": "tough-cookie Prototype Pollution vulnerability", "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 6.5, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"}, "range": "<4.1.3"}], "effects": ["request"], "range": "<4.1.3", "nodes": ["node_modules/tough-cookie"], "fixAvailable": false}, "wikimedia-kad-fork": {"name": "wikimedia-kad-fork", "severity": "moderate", "isDirect": false, "via": ["ms"], "effects": ["limitation"], "range": "*", "nodes": ["node_modules/wikimedia-kad-fork"], "fixAvailable": {"name": "service-runner", "version": "5.0.0", "isSemVerMajor": true}}, "yargs-unparser": {"name": "yargs-unparser", "severity": "critical", "isDirect": false, "via": ["flat"], "effects": ["mocha"], "range": "<=1.6.3", "nodes": ["node_modules/yargs-unparser"], "fixAvailable": {"name": "mocha", "version": "10.6.0", "isSemVerMajor": true}}}, "metadata": {"vulnerabilities": {"info": 0, "low": 1, "moderate": 12, "high": 3, "critical": 4, "total": 20}, "dependencies": {"prod": 203, "dev": 590, "optional": 9, "peer": 1, "peerOptional": 0, "total": 801}}}}
$ /usr/bin/npm audit fix --only=dev
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---

up to date, audited 802 packages in 7s

113 packages are looking for funding
  run `npm fund` for details

# npm audit report

ajv  <6.12.3
Severity: moderate
Prototype Pollution in Ajv - https://github.com/advisories/GHSA-v88g-cgmw-v5xw
fix available via `npm audit fix --force`
Will install rewire@7.0.0, which is a breaking change
node_modules/rewire/node_modules/ajv
node_modules/table/node_modules/ajv
  ajv-keywords  2.1.1
  Depends on vulnerable versions of ajv
  node_modules/table/node_modules/ajv-keywords
  eslint  2.5.0 - 2.5.2 || 4.2.0 - 5.0.0-rc.0
  Depends on vulnerable versions of ajv
  Depends on vulnerable versions of table
  node_modules/rewire/node_modules/eslint
    rewire  4.0.0 - 4.0.1
    Depends on vulnerable versions of eslint
    node_modules/rewire
  table  3.7.10 - 4.0.2
  Depends on vulnerable versions of ajv
  node_modules/table

debug  3.2.0 - 3.2.6
Regular Expression Denial of Service in debug - https://github.com/advisories/GHSA-gxpj-cx7g-858c
fix available via `npm audit fix --force`
Will install mocha@10.6.0, which is a breaking change
node_modules/mocha/node_modules/debug
  mocha  5.1.0 - 9.2.1
  Depends on vulnerable versions of debug
  Depends on vulnerable versions of minimatch
  Depends on vulnerable versions of yargs-unparser
  node_modules/mocha

flat  <5.0.1
Severity: critical
flat vulnerable to Prototype Pollution - https://github.com/advisories/GHSA-2j2x-2gpw-g8fm
fix available via `npm audit fix --force`
Will install mocha@10.6.0, which is a breaking change
node_modules/flat
  yargs-unparser  <=1.6.3
  Depends on vulnerable versions of flat
  node_modules/yargs-unparser

minimatch  <3.0.5
Severity: high
minimatch ReDoS vulnerability - https://github.com/advisories/GHSA-f8q6-p94x-37v3
fix available via `npm audit fix --force`
Will install mocha@10.6.0, which is a breaking change
node_modules/mocha/node_modules/minimatch

ms  <2.0.0
Severity: moderate
Vercel ms Inefficient Regular Expression Complexity vulnerability - https://github.com/advisories/GHSA-w9mr-4mfr-499f
fix available via `npm audit fix --force`
Will install service-runner@5.0.0, which is a breaking change
node_modules/wikimedia-kad-fork/node_modules/ms
  wikimedia-kad-fork  *
  Depends on vulnerable versions of ms
  node_modules/wikimedia-kad-fork
    limitation  >=0.2.3
    Depends on vulnerable versions of wikimedia-kad-fork
    node_modules/limitation
      service-runner  >=3.0.0
      Depends on vulnerable versions of limitation
      Depends on vulnerable versions of tar
      node_modules/service-runner

request  *
Severity: moderate
Server-Side Request Forgery in Request - https://github.com/advisories/GHSA-p8p7-x288-28g6
Depends on vulnerable versions of tough-cookie
No fix available
node_modules/request
  preq  *
  Depends on vulnerable versions of request
  Depends on vulnerable versions of requestretry
  node_modules/preq
  requestretry  *
  Depends on vulnerable versions of request
  node_modules/requestretry


swagger-ui  <=4.1.2
Severity: critical
Reverse Tabnapping in swagger-ui - https://github.com/advisories/GHSA-x9p2-fxq6-2m5f
Cross-Site Scripting in swagger-ui - https://github.com/advisories/GHSA-388g-jwpg-x6j4
Spoofing attack in swagger-ui - https://github.com/advisories/GHSA-cr3q-pqgq-m8c2
Cross-site scripting in Swagger-UI - https://github.com/advisories/GHSA-c427-hjc3-wrfw
Server side request forgery in SwaggerUI - https://github.com/advisories/GHSA-qrmm-w75w-3wpx
Cross-Site Scripting in swagger-ui - https://github.com/advisories/GHSA-4f9m-pxwh-68hg
No fix available
node_modules/swagger-ui

tar  <6.2.1
Severity: moderate
Denial of service while parsing a tar file due to lack of folders count validation - https://github.com/advisories/GHSA-f5x3-32g6-xq36
fix available via `npm audit fix --force`
Will install service-runner@5.0.0, which is a breaking change
node_modules/tar

tough-cookie  <4.1.3
Severity: moderate
tough-cookie Prototype Pollution vulnerability - https://github.com/advisories/GHSA-72xf-g2v4-qvf3
No fix available
node_modules/tough-cookie

20 vulnerabilities (1 low, 12 moderate, 3 high, 4 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
Verifying that tests still pass
$ /usr/bin/npm ci
--- stderr ---
npm WARN skipping integrity check for git dependency ssh://git@github.com/wikimedia/swagger-ui.git 
npm WARN deprecated kad-fs@0.0.4: This package is no longer maintained.
npm WARN deprecated har-validator@5.1.5: this library is no longer supported
npm WARN deprecated mkdirp@0.5.4: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated kad-memstore@0.0.1: This package is no longer maintained.
npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.
npm WARN deprecated debug@3.2.6: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated sinon@7.5.0: 16.1.1
--- stdout ---

added 801 packages, and audited 802 packages in 1m

113 packages are looking for funding
  run `npm fund` for details

20 vulnerabilities (1 low, 12 moderate, 3 high, 4 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> recommendation-api@0.7.0 test
> PREQ_CONNECT_TIMEOUT=15 mocha && npm run lint



starting test server
  express app
    ✓ should get robots.txt (55ms)
    ✓ should set CORS headers
    ✓ should set CSP headers
    ✓ should get static content gzipped (41ms)
    ✓ should get static content uncompressed

  Swagger spec
    ✓ get the spec
    ✓ spec validation
    routes
      ✓ robots.txt check
      ✓ root with no query params
      ✓ spec from root
      ✓ doc from root
      ✓ root with wrong query param
      ✓ article.creation.translation - normal source and target with seed (456ms)
      ✓ article.creation.translation - bad seed (87ms)
      ✓ article.creation.translation - invalid count
      ✓ article.creation.translation - incorrectly formatted source
      ✓ article.creation.morelike - bad article title (65ms)
      ✓ article.creation.morelike - missing models (121ms)
      ✓ Caption addition suggestions (1904ms)
      ✓ Caption translation suggestions (1103ms)
      ✓ Description addition suggestions (899ms)
      ✓ Description translation suggestions (783ms)

  article.creation.morelike
    ✓ should return recommendations for good article title (2089ms)

  Get missing articles
    ✓ correctly filters out existing articles

  lib:caption
    convertGlobalUsagePageIdsToInts
      ✓ converts page ID string to int
    consolidateImageData
      ✓ consolidates entity data to expected items
      ✓ handles undefined entities object
    filterPages
      ✓ filters non-image MIME types
      ✓ handles undefined pages object
    makeResult
      ✓ result is structured as expected
    makeResults
      ✓ makes results as expected

  lib:description
    wikiLangToDBName
      ✓ appends "wiki"
      ✓ converts hyphens to underscores
      ✓ converts be-tarask to be_x_oldwiki
    buildResponse
      ✓ gracefully handles undefined inputs
      ✓ gracefully handles empty inputs

  lib:suggested-edits-common
    checkRequestDomain
      ✓ allows allowed domains
      ✓ disallows disallowed domains
      ✓ handles undefined
    getWikiLangForLangCode
      ✓ translates language variants to base wiki language codes
      ✓ passed through other inputs
      ✓ handles undefined

stopping test server

  42 passing (8s)


> recommendation-api@0.7.0 lint
> node_modules/.bin/eslint --ext .js .


/src/repo/app.js
   20:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
   71:37  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  145:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  147:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  159:31  warning  Found non-literal argument in require             security/detect-non-literal-require
  196:1   warning  The type 'Application' is undefined               jsdoc/no-undefined-types
  197:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types
  235:1   warning  The type 'bluebird' is undefined                  jsdoc/no-undefined-types

/src/repo/lib/api-util.js
   81:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  128:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/lib/article.creation.morelike.js
  1:11  warning  'setTimeout' is already defined as a built-in global variable  no-redeclare

/src/repo/lib/article.creation.translation.js
  129:12  warning  'candidates' is already declared in the upper scope on line 122 column 9  no-shadow

/src/repo/lib/util.js
  109:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  155:1  warning  The type 'bool' is undefined         jsdoc/no-undefined-types
  165:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types
  260:1  warning  The type 'Router' is undefined       jsdoc/no-undefined-types
  280:1  warning  The type 'Application' is undefined  jsdoc/no-undefined-types

/src/repo/routes/article.creation.morelike.js
  75:32  warning  'ids' is already declared in the upper scope on line 66 column 24  no-shadow

/src/repo/routes/article.creation.translation.js
  14:25  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/test/features/app/app.js
  1:11  warning  'describe' is already defined as a built-in global variable  no-redeclare
  1:21  warning  'it' is already defined as a built-in global variable        no-redeclare
  1:25  warning  'before' is already defined as a built-in global variable    no-redeclare
  1:33  warning  'after' is already defined as a built-in global variable     no-redeclare

/src/repo/test/features/app/spec.js
    1:11  warning  'describe' is already defined as a built-in global variable                no-redeclare
    1:21  warning  'it' is already defined as a built-in global variable                      no-redeclare
    1:25  warning  'before' is already defined as a built-in global variable                  no-redeclare
    1:33  warning  'after' is already defined as a built-in global variable                   no-redeclare
   24:30  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  178:14  warning  Found non-literal argument to RegExp Constructor                           security/detect-non-literal-regexp

/src/repo/test/lib/caption.js
  193:1  warning  This line has a length of 136. Maximum allowed is 100  max-len

/src/repo/test/utils/logStream.js
  41:13  warning  'end' is already declared in the upper scope on line 31 column 14  no-shadow
  49:18  warning  'get' is already declared in the upper scope on line 34 column 14  no-shadow

✖ 32 problems (0 errors, 32 warnings)


--- end ---
{}
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
build: Updating eslint-config-wikimedia to 0.28.2

$ git add .
--- stdout ---

--- end ---
$ git commit -F /tmp/tmpxox6zogy
--- stdout ---
[master 8a6c300] build: Updating eslint-config-wikimedia to 0.28.2
 13 files changed, 408 insertions(+), 317 deletions(-)

--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From 8a6c300b1a0049eb72c2456a5fb55e52693675f3 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Thu, 4 Jul 2024 16:37:53 +0000
Subject: [PATCH] build: Updating eslint-config-wikimedia to 0.28.2

Change-Id: I18fc743c58c0a953f2dcac89ed146f3362eb9252
---
 app.js                                        |  10 +-
 lib/article.creation.morelike.js              |   4 +-
 lib/article.creation.translation.js           |   6 +-
 lib/description.js                            |  15 +-
 package-lock.json                             | 608 +++++++++++-------
 package.json                                  |   2 +-
 routes/article.creation.morelike.js           |   6 +-
 test/features/app/app.js                      |  18 +-
 test/features/app/spec.js                     |  16 +-
 test/features/v1/article.creation.morelike.js |   6 +-
 test/lib/caption.js                           |   4 +-
 test/lib/suggested-edits-common.js            |  18 +-
 test/utils/server.js                          |  12 +-
 13 files changed, 408 insertions(+), 317 deletions(-)

diff --git a/app.js b/app.js
index 6eac1c1..2e61fe4 100644
--- a/app.js
+++ b/app.js
@@ -68,9 +68,7 @@ function initApp(options) {
             'user-agent', 'x-request-id'
         ];
     }
-    app.conf.log_header_whitelist = new RegExp(`^(?:${ app.conf.log_header_whitelist.map((item) => {
-        return item.trim();
-    }).join('|') })$`, 'i');
+    app.conf.log_header_whitelist = new RegExp(`^(?:${ app.conf.log_header_whitelist.map((item) => item.trim()).join('|') })$`, 'i');
 
     // set up the request templates for the APIs
     apiUtil.setupApiTemplates(app);
@@ -151,8 +149,7 @@ function initApp(options) {
 function loadRoutes(app, dir) {
 
     // recursively load routes from .js files under routes/
-    return fs.readdirAsync(dir).map((fname) => {
-        return BBPromise.try(() => {
+    return fs.readdirAsync(dir).map((fname) => BBPromise.try(() => {
             const resolvedPath = path.resolve(dir, fname);
             const isDirectory = fs.statSync(resolvedPath).isDirectory();
             if (isDirectory) {
@@ -185,8 +182,7 @@ function loadRoutes(app, dir) {
             sUtil.wrapRouteHandlers(route, app);
             // all good, use that route
             app.use(route.path, route.router);
-        });
-    }).then(() => {
+        })).then(() => {
         // catch errors
         sUtil.setErrorHandler(app);
         // route loading is now complete, return the app object
diff --git a/lib/article.creation.morelike.js b/lib/article.creation.morelike.js
index 87c5d9c..d0d0a79 100644
--- a/lib/article.creation.morelike.js
+++ b/lib/article.creation.morelike.js
@@ -340,9 +340,7 @@ function getArticleNormalizedRanksFromDb(app, wikidataIds, targetLanguage) {
     const retry = app.conf.mysql_conn.retry || 2;
     const retryDelay = app.conf.mysql_conn.retry_delay || 1000;
 
-    wikidataIds = wikidataIds.map((x) => {
-        return parseInt(x.replace('Q', ''), 10);
-    });
+    wikidataIds = wikidataIds.map((x) => parseInt(x.replace('Q', ''), 10));
 
     return new BBPromise((resolve, reject) => {
         _getArticleNormalizedRanksFromMySQL(
diff --git a/lib/article.creation.translation.js b/lib/article.creation.translation.js
index ae9e21c..9e53b81 100644
--- a/lib/article.creation.translation.js
+++ b/lib/article.creation.translation.js
@@ -126,11 +126,7 @@ function recommend(app, source, target, projectDomain, seed) {
         candidates = getArticlesByPageviews(app, source, target, projectDomain);
     }
     return candidates
-    .then((candidates) => {
-        return candidates.sort((a, b) => {
-            return b.sitelink_count - a.sitelink_count;
-        });
-    });
+    .then((candidates) => candidates.sort((a, b) => b.sitelink_count - a.sitelink_count));
 }
 
 module.exports = {
diff --git a/lib/description.js b/lib/description.js
index 2ac31e4..c03b184 100644
--- a/lib/description.js
+++ b/lib/description.js
@@ -74,13 +74,11 @@ function wikibaseItemHasSiteLink(page, dbName) {
  * @return {!Object} filtered pages
  */
 function filterPages(pages) {
-    return pages.filter(page => {
-        return hasPageProps(page) &&
+    return pages.filter(page => hasPageProps(page) &&
             !isDisambiguationPage(page) &&
             hasWikibaseItem(page) &&
             !hasDescription(page) &&
-            !isPageProtected(page);
-        }
+            !isPageProtected(page)
     );
 }
 
@@ -178,16 +176,13 @@ function buildResponse(
         return [];
     }
     wikiPages.forEach((page) => {
-        const wikidataPage = wikidataPages.find(p =>
-            p.title === page.pageprops.wikibase_item);
+        const wikidataPage = wikidataPages.find(p => p.title === page.pageprops.wikibase_item);
         page.wikibase_item = entities[page.pageprops.wikibase_item];
         page.wikibase_item.protection = wikidataPage.protection;
         delete page.pageprops;
     });
-    return wikiPages.filter((page) => {
-        return !isWikibaseItemPageProtected(page) &&
-            isValidResult(page, targetLang, targetWikiLang, sourceLang, sourceWikiLang);
-    });
+    return wikiPages.filter((page) => !isWikibaseItemPageProtected(page) &&
+            isValidResult(page, targetLang, targetWikiLang, sourceLang, sourceWikiLang));
 }
 
 /**
diff --git a/package-lock.json b/package-lock.json
index 16fc0a6..578e8f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -27,7 +27,7 @@
 			},
 			"devDependencies": {
 				"ajv": "^6.9.1",
-				"eslint-config-wikimedia": "0.27.0",
+				"eslint-config-wikimedia": "0.28.2",
 				"extend": "^3.0.2",
 				"mocha": "^6.0.1",
 				"mocha-lcov-reporter": "^1.3.0",
@@ -200,9 +200,9 @@
 			}
 		},
 		"node_modules/@babel/helper-validator-identifier": {
-			"version": "7.22.20",
-			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-			"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+			"version": "7.24.7",
+			"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
+			"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
 			"dev": true,
 			"engines": {
 				"node": ">=6.9.0"
@@ -387,11 +387,14 @@
 			}
 		},
 		"node_modules/@es-joy/jsdoccomment": {
-			"version": "0.42.0",
-			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.42.0.tgz",
-			"integrity": "sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==",
+			"version": "0.43.1",
+			"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz",
+			"integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==",
 			"dev": true,
 			"dependencies": {
+				"@types/eslint": "^8.56.5",
+				"@types/estree": "^1.0.5",
+				"@typescript-eslint/types": "^7.2.0",
 				"comment-parser": "1.4.1",
 				"esquery": "^1.5.0",
 				"jsdoc-type-pratt-parser": "~4.0.0"
@@ -416,9 +419,9 @@
 			}
 		},
 		"node_modules/@eslint-community/regexpp": {
-			"version": "4.10.0",
-			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
-			"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+			"version": "4.11.0",
+			"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz",
+			"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==",
 			"dev": true,
 			"engines": {
 				"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
@@ -678,6 +681,22 @@
 			"integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
 			"dev": true
 		},
+		"node_modules/@types/eslint": {
+			"version": "8.56.10",
+			"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
+			"integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
+			"dev": true,
+			"dependencies": {
+				"@types/estree": "*",
+				"@types/json-schema": "*"
+			}
+		},
+		"node_modules/@types/estree": {
+			"version": "1.0.5",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+			"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+			"dev": true
+		},
 		"node_modules/@types/json-schema": {
 			"version": "7.0.15",
 			"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -690,23 +709,17 @@
 			"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
 			"dev": true
 		},
-		"node_modules/@types/semver": {
-			"version": "7.5.8",
-			"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
-			"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
-			"dev": true
-		},
 		"node_modules/@typescript-eslint/scope-manager": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
-			"integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz",
+			"integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/visitor-keys": "5.62.0"
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/visitor-keys": "7.15.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -714,12 +727,12 @@
 			}
 		},
 		"node_modules/@typescript-eslint/types": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
-			"integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz",
+			"integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==",
 			"dev": true,
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -727,21 +740,22 @@
 			}
 		},
 		"node_modules/@typescript-eslint/typescript-estree": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
-			"integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz",
+			"integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/visitor-keys": "5.62.0",
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/visitor-keys": "7.15.0",
 				"debug": "^4.3.4",
 				"globby": "^11.1.0",
 				"is-glob": "^4.0.3",
-				"semver": "^7.3.7",
-				"tsutils": "^3.21.0"
+				"minimatch": "^9.0.4",
+				"semver": "^7.6.0",
+				"ts-api-utils": "^1.3.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -753,10 +767,19 @@
 				}
 			}
 		},
+		"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0"
+			}
+		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -770,6 +793,21 @@
 				}
 			}
 		},
+		"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+			"version": "9.0.5",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+			"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
+			}
+		},
 		"node_modules/@typescript-eslint/typescript-estree/node_modules/ms": {
 			"version": "2.1.2",
 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -777,64 +815,38 @@
 			"dev": true
 		},
 		"node_modules/@typescript-eslint/utils": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
-			"integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz",
+			"integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==",
 			"dev": true,
 			"dependencies": {
-				"@eslint-community/eslint-utils": "^4.2.0",
-				"@types/json-schema": "^7.0.9",
-				"@types/semver": "^7.3.12",
-				"@typescript-eslint/scope-manager": "5.62.0",
-				"@typescript-eslint/types": "5.62.0",
-				"@typescript-eslint/typescript-estree": "5.62.0",
-				"eslint-scope": "^5.1.1",
-				"semver": "^7.3.7"
+				"@eslint-community/eslint-utils": "^4.4.0",
+				"@typescript-eslint/scope-manager": "7.15.0",
+				"@typescript-eslint/types": "7.15.0",
+				"@typescript-eslint/typescript-estree": "7.15.0"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
 				"url": "https://opencollective.com/typescript-eslint"
 			},
 			"peerDependencies": {
-				"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-			}
-		},
-		"node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
-			"version": "5.1.1",
-			"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
-			"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
-			"dev": true,
-			"dependencies": {
-				"esrecurse": "^4.3.0",
-				"estraverse": "^4.1.1"
-			},
-			"engines": {
-				"node": ">=8.0.0"
-			}
-		},
-		"node_modules/@typescript-eslint/utils/node_modules/estraverse": {
-			"version": "4.3.0",
-			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-			"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-			"dev": true,
-			"engines": {
-				"node": ">=4.0"
+				"eslint": "^8.56.0"
 			}
 		},
 		"node_modules/@typescript-eslint/visitor-keys": {
-			"version": "5.62.0",
-			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
-			"integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+			"version": "7.15.0",
+			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz",
+			"integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/types": "5.62.0",
-				"eslint-visitor-keys": "^3.3.0"
+				"@typescript-eslint/types": "7.15.0",
+				"eslint-visitor-keys": "^3.4.3"
 			},
 			"engines": {
-				"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+				"node": "^18.18.0 || >=20.0.0"
 			},
 			"funding": {
 				"type": "opencollective",
@@ -860,9 +872,9 @@
 			}
 		},
 		"node_modules/acorn": {
-			"version": "8.11.2",
-			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
-			"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
+			"version": "8.12.1",
+			"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+			"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
 			"dev": true,
 			"bin": {
 				"acorn": "bin/acorn"
@@ -1270,12 +1282,12 @@
 			}
 		},
 		"node_modules/braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
 			"dev": true,
 			"dependencies": {
-				"fill-range": "^7.0.1"
+				"fill-range": "^7.1.1"
 			},
 			"engines": {
 				"node": ">=8"
@@ -1320,9 +1332,9 @@
 			}
 		},
 		"node_modules/browserslist-config-wikimedia": {
-			"version": "0.6.1",
-			"resolved": "https://registry.npmjs.org/browserslist-config-wikimedia/-/browserslist-config-wikimedia-0.6.1.tgz",
-			"integrity": "sha512-F3O+12ud7ZwBaiB/RZIMGDgz3nEuXz8RhtdPB4Lkd/WVP5Vy77EqBWRMz4vJ64x8LTTH3BOaHCD2ZuUcgShqyQ==",
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/browserslist-config-wikimedia/-/browserslist-config-wikimedia-0.7.0.tgz",
+			"integrity": "sha512-CTa0lv78dXKEgrYsOLCkqO+9UUS3CV9MWEOYHcymgEvx4mYxB80sCoKRCR7wW2SOMNxjaP9hohrZripjnKuRTA==",
 			"dev": true
 		},
 		"node_modules/buffer-from": {
@@ -1343,15 +1355,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/builtins": {
-			"version": "5.0.1",
-			"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
-			"integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
-			"dev": true,
-			"dependencies": {
-				"semver": "^7.0.0"
-			}
-		},
 		"node_modules/bunyan": {
 			"version": "1.8.15",
 			"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz",
@@ -1801,9 +1804,9 @@
 			"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
 		},
 		"node_modules/core-js-compat": {
-			"version": "3.37.0",
-			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.0.tgz",
-			"integrity": "sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==",
+			"version": "3.37.1",
+			"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz",
+			"integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==",
 			"dev": true,
 			"dependencies": {
 				"browserslist": "^4.23.0"
@@ -2072,6 +2075,19 @@
 				"node": ">= 0.8"
 			}
 		},
+		"node_modules/enhanced-resolve": {
+			"version": "5.17.0",
+			"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
+			"integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
+			"dev": true,
+			"dependencies": {
+				"graceful-fs": "^4.2.4",
+				"tapable": "^2.2.0"
+			},
+			"engines": {
+				"node": ">=10.13.0"
+			}
+		},
 		"node_modules/error-ex": {
 			"version": "1.3.2",
 			"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2276,9 +2292,9 @@
 			}
 		},
 		"node_modules/eslint-compat-utils": {
-			"version": "0.5.0",
-			"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz",
-			"integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==",
+			"version": "0.5.1",
+			"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz",
+			"integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==",
 			"dev": true,
 			"dependencies": {
 				"semver": "^7.5.4"
@@ -2291,28 +2307,28 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.27.0",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.27.0.tgz",
-			"integrity": "sha512-KkZ54+MUnggz17C/RCEMXQSpiiqZRF7p9fjrz4phaaeKlTrjg0B+QbM5zcDWcjGiAWaJUptHaH17+RZldadkUw==",
+			"version": "0.28.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.28.2.tgz",
+			"integrity": "sha512-5+rdnT7wH1gpKAO6tHYThg78eMhZMruJzvqku3Y5iaEY/A7kSKLFpA/vOj/snys9fKjDHC9BXmArQh+agkOoJQ==",
 			"dev": true,
 			"dependencies": {
-				"browserslist-config-wikimedia": "^0.6.1",
+				"browserslist-config-wikimedia": "^0.7.0",
 				"eslint": "^8.57.0",
 				"eslint-plugin-compat": "^4.2.0",
 				"eslint-plugin-es-x": "^7.6.0",
-				"eslint-plugin-jest": "^27.9.0",
-				"eslint-plugin-jsdoc": "48.2.1",
-				"eslint-plugin-json-es": "^1.5.7",
-				"eslint-plugin-mediawiki": "^0.6.0",
-				"eslint-plugin-mocha": "^10.4.1",
-				"eslint-plugin-n": "^16.6.2",
-				"eslint-plugin-no-jquery": "^2.7.0",
+				"eslint-plugin-jest": "^28.5.0",
+				"eslint-plugin-jsdoc": "48.2.5",
+				"eslint-plugin-json-es": "^1.6.0",
+				"eslint-plugin-mediawiki": "^0.7.0",
+				"eslint-plugin-mocha": "^10.4.3",
+				"eslint-plugin-n": "^17.7.0",
+				"eslint-plugin-no-jquery": "^3.0.1",
 				"eslint-plugin-qunit": "^8.1.1",
 				"eslint-plugin-security": "^1.7.1",
-				"eslint-plugin-unicorn": "^51.0.1",
-				"eslint-plugin-vue": "^9.23.0",
+				"eslint-plugin-unicorn": "^53.0.0",
+				"eslint-plugin-vue": "^9.26.0",
 				"eslint-plugin-wdio": "^8.24.12",
-				"eslint-plugin-yml": "^1.13.2"
+				"eslint-plugin-yml": "^1.14.0"
 			}
 		},
 		"node_modules/eslint-plugin-compat": {
@@ -2337,39 +2353,40 @@
 			}
 		},
 		"node_modules/eslint-plugin-es-x": {
-			"version": "7.6.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.6.0.tgz",
-			"integrity": "sha512-I0AmeNgevgaTR7y2lrVCJmGYF0rjoznpDvqV/kIkZSZbZ8Rw3eu4cGlvBBULScfkSOCzqKbff5LR4CNrV7mZHA==",
+			"version": "7.8.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz",
+			"integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==",
 			"dev": true,
+			"funding": [
+				"https://github.com/sponsors/ota-meshi",
+				"https://opencollective.com/eslint"
+			],
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.1.2",
-				"@eslint-community/regexpp": "^4.6.0",
-				"eslint-compat-utils": "^0.5.0"
+				"@eslint-community/regexpp": "^4.11.0",
+				"eslint-compat-utils": "^0.5.1"
 			},
 			"engines": {
 				"node": "^14.18.0 || >=16.0.0"
 			},
-			"funding": {
-				"url": "https://github.com/sponsors/ota-meshi"
-			},
 			"peerDependencies": {
 				"eslint": ">=8"
 			}
 		},
 		"node_modules/eslint-plugin-jest": {
-			"version": "27.9.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
-			"integrity": "sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==",
+			"version": "28.6.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz",
+			"integrity": "sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg==",
 			"dev": true,
 			"dependencies": {
-				"@typescript-eslint/utils": "^5.10.0"
+				"@typescript-eslint/utils": "^6.0.0 || ^7.0.0"
 			},
 			"engines": {
-				"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+				"node": "^16.10.0 || ^18.12.0 || >=20.0.0"
 			},
 			"peerDependencies": {
-				"@typescript-eslint/eslint-plugin": "^5.0.0 || ^6.0.0 || ^7.0.0",
-				"eslint": "^7.0.0 || ^8.0.0",
+				"@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0",
+				"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0",
 				"jest": "*"
 			},
 			"peerDependenciesMeta": {
@@ -2382,19 +2399,19 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc": {
-			"version": "48.2.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.1.tgz",
-			"integrity": "sha512-iUvbcyDZSO/9xSuRv2HQBw++8VkV/pt3UWtX9cpPH0l7GKPq78QC/6+PmyQHHvNZaTjAce6QVciEbnc6J/zH5g==",
+			"version": "48.2.5",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.5.tgz",
+			"integrity": "sha512-ZeTfKV474W1N9niWfawpwsXGu+ZoMXu4417eBROX31d7ZuOk8zyG66SO77DpJ2+A9Wa2scw/jRqBPnnQo7VbcQ==",
 			"dev": true,
 			"dependencies": {
-				"@es-joy/jsdoccomment": "~0.42.0",
+				"@es-joy/jsdoccomment": "~0.43.0",
 				"are-docs-informative": "^0.0.2",
 				"comment-parser": "1.4.1",
 				"debug": "^4.3.4",
 				"escape-string-regexp": "^4.0.0",
 				"esquery": "^1.5.0",
 				"is-builtin-module": "^3.2.1",
-				"semver": "^7.6.0",
+				"semver": "^7.6.1",
 				"spdx-expression-parse": "^4.0.0"
 			},
 			"engines": {
@@ -2405,9 +2422,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-jsdoc/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -2438,9 +2455,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-json-es": {
-			"version": "1.5.7",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.5.7.tgz",
-			"integrity": "sha512-ehBHcCcJo4iViYx6vp3T+SmwzLIlVDzZNoVxN/txZIiPwDQ26mnYaN5iJ3imqN4l1b8z6rbxEH2kB9XDGxeU/w==",
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-json-es/-/eslint-plugin-json-es-1.6.0.tgz",
+			"integrity": "sha512-xVn6hufGQH1Aa+yqOhQ43Cq28GuitTcMpQh+uaUh27U2qnVLBrvkN+2xQSnv6zpdLEPS35JCNhq4kvhR+PQCgw==",
 			"dev": true,
 			"dependencies": {
 				"eslint-visitor-keys": "^3.3.0",
@@ -2451,9 +2468,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mediawiki": {
-			"version": "0.6.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.6.0.tgz",
-			"integrity": "sha512-a2Zm18N5nPyflBajM2ZWATxucIpYPEmOSjFzUR1OBH3hAL0GY9fx1mpezEwzqAQ862d+kPkolgQOzktnZe8nKA==",
+			"version": "0.7.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mediawiki/-/eslint-plugin-mediawiki-0.7.0.tgz",
+			"integrity": "sha512-1Y2nsFDPp96xOZCB5ivZAgqYe9i6w2u64VoCIaAzPyZnd/2h8VQR3CtD+u4Yk/KrpbKq9AAJjrs5LS8VAz6KOA==",
 			"dev": true,
 			"dependencies": {
 				"eslint-plugin-vue": "^9.23.0",
@@ -2464,9 +2481,9 @@
 			}
 		},
 		"node_modules/eslint-plugin-mocha": {
-			"version": "10.4.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.2.tgz",
-			"integrity": "sha512-cur4dVYnSEWTBwdqIBQFxa/9siAhesu0TX+lbJ4ClE9j0eNMNe6BSx3vkFFNz6tGoveyMyELFXa30f3fvuAVDg==",
+			"version": "10.4.3",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.4.3.tgz",
+			"integrity": "sha512-emc4TVjq5Ht0/upR+psftuz6IBG5q279p+1dSRDeHf+NS9aaerBi3lXKo1SEzwC29hFIW21gO89CEWSvRsi8IQ==",
 			"dev": true,
 			"dependencies": {
 				"eslint-utils": "^3.0.0",
@@ -2481,40 +2498,73 @@
 			}
 		},
 		"node_modules/eslint-plugin-n": {
-			"version": "16.6.2",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
-			"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
+			"version": "17.9.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.9.0.tgz",
+			"integrity": "sha512-CPSaXDXdrT4nsrOrO4mT4VB6FMUkoySRkHWuuJJHVqsIEjIeZgMY1H7AzSwPbDScikBmLN82KeM1u7ixV7PzGg==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.4.0",
-				"builtins": "^5.0.1",
+				"enhanced-resolve": "^5.17.0",
 				"eslint-plugin-es-x": "^7.5.0",
 				"get-tsconfig": "^4.7.0",
-				"globals": "^13.24.0",
+				"globals": "^15.0.0",
 				"ignore": "^5.2.4",
-				"is-builtin-module": "^3.2.1",
-				"is-core-module": "^2.12.1",
-				"minimatch": "^3.1.2",
-				"resolve": "^1.22.2",
+				"minimatch": "^9.0.0",
 				"semver": "^7.5.3"
 			},
 			"engines": {
-				"node": ">=16.0.0"
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 			},
 			"funding": {
-				"url": "https://github.com/sponsors/mysticatea"
+				"url": "https://opencollective.com/eslint"
 			},
 			"peerDependencies": {
-				"eslint": ">=7.0.0"
+				"eslint": ">=8.23.0"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+			"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+			"dev": true,
+			"dependencies": {
+				"balanced-match": "^1.0.0"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/globals": {
+			"version": "15.8.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz",
+			"integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==",
+			"dev": true,
+			"engines": {
+				"node": ">=18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint-plugin-n/node_modules/minimatch": {
+			"version": "9.0.5",
+			"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+			"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+			"dev": true,
+			"dependencies": {
+				"brace-expansion": "^2.0.1"
+			},
+			"engines": {
+				"node": ">=16 || 14 >=14.17"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/isaacs"
 			}
 		},
 		"node_modules/eslint-plugin-no-jquery": {
-			"version": "2.7.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz",
-			"integrity": "sha512-Aeg7dA6GTH1AcWLlBtWNzOU9efK5KpNi7b0EhBO0o0M+awyzguUUo8gF6hXGjQ9n5h8/uRtYv9zOqQkeC5CG0w==",
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-3.0.2.tgz",
+			"integrity": "sha512-n/+6p6PFhWDNPVLJj1463hw4OTIRBbROGcbhmtOHTgw7yihSKzkwZiQ00EJTneyeR3jRiw5lpWSMCCBhtb8t2g==",
 			"dev": true,
 			"peerDependencies": {
-				"eslint": ">=2.3.0"
+				"eslint": ">=8.0.0"
 			}
 		},
 		"node_modules/eslint-plugin-qunit": {
@@ -2540,17 +2590,17 @@
 			}
 		},
 		"node_modules/eslint-plugin-unicorn": {
-			"version": "51.0.1",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz",
-			"integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==",
+			"version": "53.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
+			"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
 			"dev": true,
 			"dependencies": {
-				"@babel/helper-validator-identifier": "^7.22.20",
+				"@babel/helper-validator-identifier": "^7.24.5",
 				"@eslint-community/eslint-utils": "^4.4.0",
-				"@eslint/eslintrc": "^2.1.4",
+				"@eslint/eslintrc": "^3.0.2",
 				"ci-info": "^4.0.0",
 				"clean-regexp": "^1.0.0",
-				"core-js-compat": "^3.34.0",
+				"core-js-compat": "^3.37.0",
 				"esquery": "^1.5.0",
 				"indent-string": "^4.0.0",
 				"is-builtin-module": "^3.2.1",
@@ -2559,11 +2609,11 @@
 				"read-pkg-up": "^7.0.1",
 				"regexp-tree": "^0.1.27",
 				"regjsparser": "^0.10.0",
-				"semver": "^7.5.4",
+				"semver": "^7.6.1",
 				"strip-indent": "^3.0.0"
 			},
 			"engines": {
-				"node": ">=16"
+				"node": ">=18.18"
 			},
 			"funding": {
 				"url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
@@ -2572,6 +2622,105 @@
 				"eslint": ">=8.56.0"
 			}
 		},
+		"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
+			"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
+			"dev": true,
+			"dependencies": {
+				"ajv": "^6.12.4",
+				"debug": "^4.3.2",
+				"espree": "^10.0.1",
+				"globals": "^14.0.0",
+				"ignore": "^5.2.0",
+				"import-fresh": "^3.2.1",
+				"js-yaml": "^4.1.0",
+				"minimatch": "^3.1.2",
+				"strip-json-comments": "^3.1.1"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/argparse": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+			"dev": true
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/debug": {
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+			"dev": true,
+			"dependencies": {
+				"ms": "2.1.2"
+			},
+			"engines": {
+				"node": ">=6.0"
+			},
+			"peerDependenciesMeta": {
+				"supports-color": {
+					"optional": true
+				}
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/eslint-visitor-keys": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
+			"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
+			"dev": true,
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/espree": {
+			"version": "10.1.0",
+			"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
+			"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
+			"dev": true,
+			"dependencies": {
+				"acorn": "^8.12.0",
+				"acorn-jsx": "^5.3.2",
+				"eslint-visitor-keys": "^4.0.0"
+			},
+			"engines": {
+				"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+			},
+			"funding": {
+				"url": "https://opencollective.com/eslint"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/globals": {
+			"version": "14.0.0",
+			"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+			"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=18"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/eslint-plugin-unicorn/node_modules/js-yaml": {
+			"version": "4.1.0",
+			"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+			"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+			"dev": true,
+			"dependencies": {
+				"argparse": "^2.0.1"
+			},
+			"bin": {
+				"js-yaml": "bin/js-yaml.js"
+			}
+		},
 		"node_modules/eslint-plugin-unicorn/node_modules/jsesc": {
 			"version": "3.0.2",
 			"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
@@ -2584,10 +2733,16 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/eslint-plugin-unicorn/node_modules/ms": {
+			"version": "2.1.2",
+			"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+			"dev": true
+		},
 		"node_modules/eslint-plugin-vue": {
-			"version": "9.25.0",
-			"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.25.0.tgz",
-			"integrity": "sha512-tDWlx14bVe6Bs+Nnh3IGrD+hb11kf2nukfm6jLsmJIhmiRQ1SUaksvwY9U5MvPB0pcrg0QK0xapQkfITs3RKOA==",
+			"version": "9.27.0",
+			"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz",
+			"integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==",
 			"dev": true,
 			"dependencies": {
 				"@eslint-community/eslint-utils": "^4.4.0",
@@ -2596,7 +2751,7 @@
 				"nth-check": "^2.1.1",
 				"postcss-selector-parser": "^6.0.15",
 				"semver": "^7.6.0",
-				"vue-eslint-parser": "^9.4.2",
+				"vue-eslint-parser": "^9.4.3",
 				"xml-name-validator": "^4.0.0"
 			},
 			"engines": {
@@ -3048,9 +3203,9 @@
 			}
 		},
 		"node_modules/fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
 			"dev": true,
 			"dependencies": {
 				"to-regex-range": "^5.0.1"
@@ -3374,9 +3529,9 @@
 			}
 		},
 		"node_modules/get-tsconfig": {
-			"version": "4.7.2",
-			"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz",
-			"integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==",
+			"version": "4.7.5",
+			"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+			"integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
 			"dev": true,
 			"dependencies": {
 				"resolve-pkg-maps": "^1.0.0"
@@ -4768,17 +4923,6 @@
 			"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
 			"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
 		},
-		"node_modules/lru-cache": {
-			"version": "6.0.0",
-			"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-			"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-			"dependencies": {
-				"yallist": "^4.0.0"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/make-dir": {
 			"version": "2.1.0",
 			"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -4841,12 +4985,12 @@
 			}
 		},
 		"node_modules/micromatch": {
-			"version": "4.0.5",
-			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-			"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+			"version": "4.0.7",
+			"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
+			"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
 			"dev": true,
 			"dependencies": {
-				"braces": "^3.0.2",
+				"braces": "^3.0.3",
 				"picomatch": "^2.3.1"
 			},
 			"engines": {
@@ -6046,9 +6190,9 @@
 			}
 		},
 		"node_modules/postcss-selector-parser": {
-			"version": "6.0.16",
-			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
-			"integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==",
+			"version": "6.1.0",
+			"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
+			"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
 			"dev": true,
 			"dependencies": {
 				"cssesc": "^3.0.0",
@@ -7154,12 +7298,9 @@
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
 		},
 		"node_modules/semver": {
-			"version": "7.6.0",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
-			"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-			"dependencies": {
-				"lru-cache": "^6.0.0"
-			},
+			"version": "7.6.2",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+			"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
 			"bin": {
 				"semver": "bin/semver.js"
 			},
@@ -7810,6 +7951,15 @@
 				"node": ">=4"
 			}
 		},
+		"node_modules/tapable": {
+			"version": "2.2.1",
+			"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+			"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+			"dev": true,
+			"engines": {
+				"node": ">=6"
+			}
+		},
 		"node_modules/tar": {
 			"version": "4.4.19",
 			"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
@@ -8057,25 +8207,16 @@
 				"node": ">=0.8"
 			}
 		},
-		"node_modules/tslib": {
-			"version": "1.14.1",
-			"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-			"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-			"dev": true
-		},
-		"node_modules/tsutils": {
-			"version": "3.21.0",
-			"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
-			"integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+		"node_modules/ts-api-utils": {
+			"version": "1.3.0",
+			"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
+			"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
 			"dev": true,
-			"dependencies": {
-				"tslib": "^1.8.1"
-			},
 			"engines": {
-				"node": ">= 6"
+				"node": ">=16"
 			},
 			"peerDependencies": {
-				"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+				"typescript": ">=4.2.0"
 			}
 		},
 		"node_modules/tunnel-agent": {
@@ -8211,9 +8352,9 @@
 			"dev": true
 		},
 		"node_modules/typescript": {
-			"version": "5.4.5",
-			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
-			"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+			"version": "5.5.3",
+			"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
+			"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
 			"dev": true,
 			"peer": true,
 			"bin": {
@@ -8349,9 +8490,9 @@
 			}
 		},
 		"node_modules/vue-eslint-parser": {
-			"version": "9.4.2",
-			"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
-			"integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
+			"version": "9.4.3",
+			"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+			"integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
 			"dev": true,
 			"dependencies": {
 				"debug": "^4.3.4",
@@ -8373,9 +8514,9 @@
 			}
 		},
 		"node_modules/vue-eslint-parser/node_modules/debug": {
-			"version": "4.3.4",
-			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
-			"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+			"version": "4.3.5",
+			"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
+			"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
 			"dev": true,
 			"dependencies": {
 				"ms": "2.1.2"
@@ -8616,11 +8757,6 @@
 			"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
 			"dev": true
 		},
-		"node_modules/yallist": {
-			"version": "4.0.0",
-			"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-			"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-		},
 		"node_modules/yaml": {
 			"version": "2.4.1",
 			"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
diff --git a/package.json b/package.json
index a940615..533669c 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
 	},
 	"devDependencies": {
 		"ajv": "^6.9.1",
-		"eslint-config-wikimedia": "0.27.0",
+		"eslint-config-wikimedia": "0.28.2",
 		"extend": "^3.0.2",
 		"mocha": "^6.0.1",
 		"mocha-lcov-reporter": "^1.3.0",
diff --git a/routes/article.creation.morelike.js b/routes/article.creation.morelike.js
index aea403f..1abc1d8 100644
--- a/routes/article.creation.morelike.js
+++ b/routes/article.creation.morelike.js
@@ -85,13 +85,11 @@ router.get('/:seed', (req, res) => {
                                     app, ids, language
                                 ).then((results) => {
                                     res.json(results);
-                                }).catch((error) => {
-                                    return BBPromise.reject(new util.HTTPError({
+                                }).catch((error) => BBPromise.reject(new util.HTTPError({
                                         status: 503,
                                         message: 'Cannot retrieve normalized ranks from' +
                                             ' the database. Please try again later.'
-                                    }));
-                                });
+                                    })));
                             }
                         });
                 });
diff --git a/test/features/app/app.js b/test/features/app/app.js
index 0ac8f00..7e3d3e5 100644
--- a/test/features/app/app.js
+++ b/test/features/app/app.js
@@ -17,14 +17,12 @@ describe('express app', function () {
 
     before(() => server.start());
 
-    it('should get robots.txt', () => {
-        return preq.get({
+    it('should get robots.txt', () => preq.get({
             uri: `${ server.config.uri }robots.txt`
         }).then((res) => {
             assert.deepEqual(res.status, 200);
             assert.deepEqual(res.headers.disallow, '/');
-        });
-    });
+        }));
 
     it('should set CORS headers', () => {
         if (server.config.service.conf.cors === false) {
@@ -59,8 +57,7 @@ describe('express app', function () {
         });
     });
 
-    it('should get static content gzipped', () => {
-        return preq.get({
+    it('should get static content gzipped', () => preq.get({
             uri: `${ server.config.uri }static/index.html`,
             headers: {
                 'accept-encoding': 'gzip, deflate'
@@ -70,11 +67,9 @@ describe('express app', function () {
             // if there is no content-length, the reponse was gzipped
             assert.deepEqual(res.headers['content-length'], undefined,
                 'Did not expect the content-length header!');
-        });
-    });
+        }));
 
-    it('should get static content uncompressed', () => {
-        return preq.get({
+    it('should get static content uncompressed', () => preq.get({
             uri: `${ server.config.uri }static/index.html`,
             headers: {
                 'accept-encoding': ''
@@ -83,6 +78,5 @@ describe('express app', function () {
             const contentEncoding = res.headers['content-encoding'];
             assert.deepEqual(res.status, 200);
             assert.deepEqual(contentEncoding, undefined, 'Did not expect gzipped contents!');
-        });
-    });
+        }));
 });
diff --git a/test/features/app/spec.js b/test/features/app/spec.js
index 60b500d..84ca636 100644
--- a/test/features/app/spec.js
+++ b/test/features/app/spec.js
@@ -240,19 +240,15 @@ describe('Swagger spec', function () {
 
     this.timeout(20000);
 
-    before(() => {
-        return server.start();
-    });
+    before(() => server.start());
 
-    it('get the spec', () => {
-        return preq.get(`${ server.config.uri }?spec`)
+    it('get the spec', () => preq.get(`${ server.config.uri }?spec`)
         .then((res) => {
             assert.status(200);
             assert.contentType(res, 'application/json');
             assert.notDeepEqual(res.body, undefined, 'No body received!');
             spec = res.body;
-        });
-    });
+        }));
 
     it('spec validation', () => {
         if (spec['x-default-params']) {
@@ -282,14 +278,12 @@ describe('Swagger spec', function () {
     describe('routes', () => {
 
         constructTests(spec.paths, defParams).forEach((testCase) => {
-            it(testCase.title, () => {
-                return preq(testCase.request)
+            it(testCase.title, () => preq(testCase.request)
                 .then((res) => {
                     validateTestResponse(testCase, res);
                 }, (err) => {
                     validateTestResponse(testCase, err);
-                });
-            });
+                }));
         });
 
     });
diff --git a/test/features/v1/article.creation.morelike.js b/test/features/v1/article.creation.morelike.js
index c280826..dab1388 100644
--- a/test/features/v1/article.creation.morelike.js
+++ b/test/features/v1/article.creation.morelike.js
@@ -29,8 +29,7 @@ before(() => server.start());
 describe('article.creation.morelike', function () {
     this.timeout(20000);
 
-    it('should return recommendations for good article title', () => {
-        return preq.get(
+    it('should return recommendations for good article title', () => preq.get(
             `${ server.config.uri }uz.wikipedia.org/v1/article/creation/morelike/Palov`
         ).then((res) => {
             assert.status(res, 200);
@@ -43,6 +42,5 @@ describe('article.creation.morelike', function () {
                     { wikidata_id: 'Q127418', normalized_rank: 0.891431 }
                 ]
             );
-        });
-    });
+        }));
 });
diff --git a/test/lib/caption.js b/test/lib/caption.js
index 39392d1..1378664 100644
--- a/test/lib/caption.js
+++ b/test/lib/caption.js
@@ -190,9 +190,7 @@ describe('lib:caption', () => {
             }
         };
 
-        const cond = (image, targetLang, sourceLang) => {
-            return image.structured.captions[sourceLang] && !image.structured.captions[targetLang];
-        };
+        const cond = (image, targetLang, sourceLang) => image.structured.captions[sourceLang] && !image.structured.captions[targetLang];
 
         const makeResults = lib.__get__('makeResults');
 
diff --git a/test/lib/suggested-edits-common.js b/test/lib/suggested-edits-common.js
index e8b36f1..e92c212 100644
--- a/test/lib/suggested-edits-common.js
+++ b/test/lib/suggested-edits-common.js
@@ -57,23 +57,17 @@ describe('lib:suggested-edits-common', () => {
 
     describe('getWikiLangForLangCode', () => {
 
-        it('translates language variants to base wiki language codes', () => {
-            return lib.getWikiLangForLangCode(app, req, 'zh-hans').then((res) => {
+        it('translates language variants to base wiki language codes', () => lib.getWikiLangForLangCode(app, req, 'zh-hans').then((res) => {
                 assert.deepEqual(res, 'zh');
-            });
-        });
+            }));
 
-        it('passed through other inputs', () => {
-            return lib.getWikiLangForLangCode(app, req, 'foo').then((res) => {
+        it('passed through other inputs', () => lib.getWikiLangForLangCode(app, req, 'foo').then((res) => {
                 assert.deepEqual(res, 'foo');
-            });
-        });
+            }));
 
-        it('handles undefined', () => {
-            return lib.getWikiLangForLangCode(app, req, undefined).then((res) => {
+        it('handles undefined', () => lib.getWikiLangForLangCode(app, req, undefined).then((res) => {
                 assert.deepEqual(res, undefined);
-            });
-        });
+            }));
 
     });
 
diff --git a/test/utils/server.js b/test/utils/server.js
index 70099e1..44cb649 100644
--- a/test/utils/server.js
+++ b/test/utils/server.js
@@ -29,9 +29,7 @@ config.conf.logging = {
 // make a deep copy of it for later reference
 const origConfig = extend(true, {}, config);
 
-module.exports.stop = () => {
-    return BBPromise.resolve();
-};
+module.exports.stop = () => BBPromise.resolve();
 let options = null;
 const runner = new ServiceRunner();
 
@@ -50,13 +48,9 @@ function start(_options) {
             .then((serviceReturns) => {
                 module.exports.stop = () => {
                     console.log('stopping test server');
-                    serviceReturns.forEach(servers =>
-                        servers.forEach(server =>
-                            server.shutdown()));
+                    serviceReturns.forEach(servers => servers.forEach(server => server.shutdown()));
                     return runner.stop().then(() => {
-                        module.exports.stop = () => {
-                            return BBPromise.resolve();
-                        };
+                        module.exports.stop = () => BBPromise.resolve();
                     });
                 };
                 return true;
-- 
2.39.2


--- end ---
Source code is licensed under the AGPL.