$ date
--- stdout ---
Thu Apr 18 12:00:08 UTC 2024
--- end ---
$ git clone file:///srv/git/mediawiki-services-eventstreams.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 ---
30fa4592e015f38e58740033114cfd72861b9f6c refs/heads/master
--- end ---
$ /usr/bin/npm i --package-lock-only
--- stderr ---
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'eslint-plugin-jsdoc@39.2.2',
npm WARN EBADENGINE required: { node: '^14 || ^16 || ^17' },
npm WARN EBADENGINE current: { node: 'v18.19.0', npm: '9.2.0' }
npm WARN EBADENGINE }
npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: '@es-joy/jsdoccomment@0.23.6',
npm WARN EBADENGINE required: { node: '^12 || ^14 || ^16 || ^17' },
npm WARN EBADENGINE current: { node: 'v18.19.0', npm: '9.2.0' }
npm WARN EBADENGINE }
--- stdout ---
up to date, audited 582 packages in 6s
67 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
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 ---
Editing .gitignore to remove package-lock.json
$ /usr/bin/npm audit --json
--- stdout ---
{
"auditReportVersion": 2,
"vulnerabilities": {
"@wikimedia/url-get": {
"name": "@wikimedia/url-get",
"severity": "moderate",
"isDirect": true,
"via": [
"preq"
],
"effects": [],
"range": "*",
"nodes": [
"node_modules/@wikimedia/url-get"
],
"fixAvailable": false
},
"limitation": {
"name": "limitation",
"severity": "moderate",
"isDirect": false,
"via": [
"wikimedia-kad-fork"
],
"effects": [],
"range": ">=0.2.3",
"nodes": [
"node_modules/limitation"
],
"fixAvailable": 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": true
},
"preq": {
"name": "preq",
"severity": "high",
"isDirect": true,
"via": [
"request",
"requestretry"
],
"effects": [
"@wikimedia/url-get"
],
"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
},
"tough-cookie": {
"name": "tough-cookie",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1096643,
"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": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 6,
"high": 2,
"critical": 0,
"total": 8
},
"dependencies": {
"prod": 210,
"dev": 361,
"optional": 12,
"peer": 0,
"peerOptional": 0,
"total": 581
}
}
}
--- end ---
Upgrading n:eslint-config-wikimedia from ^0.25.1 -> 0.27.0
$ /usr/bin/npm install
--- stderr ---
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 kad-memstore@0.0.1: This package is no longer maintained.
npm WARN deprecated gc-stats@1.4.1: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
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
--- stdout ---
added 608 packages, and audited 609 packages in 2m
75 packages are looking for funding
run `npm fund` for details
8 vulnerabilities (6 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
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
66:37 warning Found non-literal argument to RegExp Constructor security/detect-non-literal-regexp
141:1 warning The type 'Application' is undefined jsdoc/no-undefined-types
156:31 warning Found non-literal argument in require security/detect-non-literal-require
195:1 warning The type 'Application' is undefined jsdoc/no-undefined-types
/src/repo/config.dev.yaml
90:1 warning This line has a length of 179. Maximum allowed is 100 max-len
/src/repo/config.prod.yaml
91:1 warning This line has a length of 177. Maximum allowed is 100 max-len
/src/repo/config.yaml
90:1 warning This line has a length of 179. Maximum allowed is 100 max-len
/src/repo/lib/eventstreams-util.js
117:1 warning This line has a length of 101. Maximum allowed is 100 max-len
118:1 warning This line has a length of 104. Maximum allowed is 100 max-len
/src/repo/lib/util.js
99:1 warning The type 'Application' is undefined jsdoc/no-undefined-types
143:1 warning The type 'Application' is undefined jsdoc/no-undefined-types
223:1 warning The type 'Router' is undefined jsdoc/no-undefined-types
243:1 warning The type 'Application' is undefined jsdoc/no-undefined-types
/src/repo/routes/stream.js
28:1 warning Missing JSDoc @return declaration jsdoc/require-returns
249:1 warning Missing JSDoc @return declaration jsdoc/require-returns
424:1 warning This line has a length of 103. Maximum allowed is 100 max-len
/src/repo/server.js
1:1 error ES2023 Hashbang comments are forbidden es-x/no-hashbang
/src/repo/stream-config.yaml
5:1 warning This line has a length of 169. Maximum allowed is 100 max-len
/src/repo/test/features/app/spec.js
70:25 error 'spec' is already declared in the upper scope on line 11 column 5 no-shadow
154:14 warning Found non-literal argument to RegExp Constructor security/detect-non-literal-regexp
265:16 error 'spec' is already declared in the upper scope on line 11 column 5 no-shadow
/src/repo/test/features/info/info.js
1:11 error 'describe' is already defined as a built-in global variable no-redeclare
1:21 error 'it' is already defined as a built-in global variable no-redeclare
1:25 error 'before' is already defined as a built-in global variable no-redeclare
1:33 error 'after' is already defined as a built-in global variable no-redeclare
/src/repo/test/utils/assert.js
23:1 warning The type 'integer' is undefined jsdoc/no-undefined-types
/src/repo/test/utils/server.js
9:9 error Unexpected dangling '_' in '_spec' no-underscore-dangle
13:14 error Unexpected dangling '_' in '_running' no-underscore-dangle
19:30 error Unexpected dangling '_' in '_impl' no-underscore-dangle
19:30 error Unexpected dangling '_' in '_runner' no-underscore-dangle
20:27 error Unexpected dangling '_' in '_impl' no-underscore-dangle
20:27 error Unexpected dangling '_' in '_runner' no-underscore-dangle
22:14 error Unexpected dangling '_' in '_spec' no-underscore-dangle
30:17 error Unexpected dangling '_' in '_spec' no-underscore-dangle
34:17 error Unexpected dangling '_' in '_spec' no-underscore-dangle
40:27 error Unexpected dangling '_' in '_impl' no-underscore-dangle
40:27 error Unexpected dangling '_' in '_runner' no-underscore-dangle
41:27 error Unexpected dangling '_' in '_spec' no-underscore-dangle
49:19 error Unexpected dangling '_' in '_impl' no-underscore-dangle
49:19 error Unexpected dangling '_' in '_runner' no-underscore-dangle
50:19 error Unexpected dangling '_' in '_spec' no-underscore-dangle
/src/repo/ui/src/apis/EventStreamsApi.js
1:1 error Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
/src/repo/ui/src/config/index.js
1:1 error Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
/src/repo/ui/src/main.js
1:1 error Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
/src/repo/ui/src/router/index.js
1:1 error Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
/src/repo/ui/src/utils/index.js
57:1 error Parsing error: 'import' and 'export' may appear only with 'sourceType: module'
✖ 46 problems (27 errors, 19 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":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/.travis.yml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/app.js","messages":[{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":66,"column":37,"nodeType":"NewExpression","endLine":68,"endColumn":28},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":141,"column":1,"nodeType":"Block","endLine":141,"endColumn":1},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":156,"column":31,"nodeType":"CallExpression","endLine":156,"endColumn":61},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":195,"column":1,"nodeType":"Block","endLine":195,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst http = require('http');\nconst BBPromise = require('bluebird');\nconst express = require('express');\n// === EventStreams modification ===\n// const compression = require('compression');\n// === End EventStreams modification ===\nconst bodyParser = require('body-parser');\nconst fs = BBPromise.promisifyAll(require('fs'));\nconst sUtil = require('./lib/util');\nconst packageInfo = require('./package.json');\nconst yaml = require('js-yaml');\nconst addShutdown = require('http-shutdown');\nconst path = require('path');\n\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 app.conf.port = app.conf.port || 8888;\n app.conf.interface = app.conf.interface || '0.0.0.0';\n app.conf.compression_level =\n app.conf.compression_level === undefined ? 3 : app.conf.compression_level;\n app.conf.cors = app.conf.cors === undefined ? '*' : app.conf.cors;\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) => {\n return item.trim();\n }).join('|') })$`, 'i');\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.openapi) {\n app.conf.spec.openapi = '3.0.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\n // === EventStreams modification ===\n // Don't use compression, streams never end.\n // app.use(compression({ level: app.conf.compression_level }));\n // === End EventStreams modification ===\n\n // use the JSON body parser\n app.use(bodyParser.json({ limit: app.conf.max_body_size || '100kb' }));\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 routes folder\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) => {\n return 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 });\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/**\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 the options to initialise the app with\n * @return {bluebird} HTTP server\n */\nmodule.exports = (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":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"max-len","severity":1,"message":"This line has a length of 179. Maximum allowed is 100.","line":90,"column":1,"nodeType":"Program","messageId":"max","endLine":90,"endColumn":180}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"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: debug\n streams:\n - type: debug\n# streams:\n# # Use gelf-stream -> logstash\n# - type: gelf\n# host: logstash1003.eqiad.wmnet\n# port: 12201\n\n# metrics:\n# - type: prometheus\n# port: 9102\n\nservices:\n - name: eventstreams\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: 8092\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.yaml\n # allow cross-domain requests to the API (default '*')\n cors: \"*\"\n\n # If enabled, a HTML based stream GUI will be made available at /v2/ui.\n # Defaults to true if undefined.\n # stream_ui_enabled: true\n\n # Connection limiting. EventStreams connections are\n # expected to be long lived, so we can't use typical\n # connections per second type ratelimiting. Instead,\n # we limit the number of connections per X-Client-IP.\n # client_ip_connection_limit: 2\n\n # A URI from which 'stream configuration' will be fetched.\n #\n # The object returned by this URI should map a stream name to its config settings,\n # which must include topics. If schema_title is set, it will be mentioned\n # in the generated OpenAPI spec.\n #\n stream_config_uri: ./stream-config.yaml\n # OR:\n # stream_config_uri: https://meta.wikimedia.org/w/api.php?action=streamconfigs\n\n # If provided, these options will be passed when opening stream_config_uri\n # for reading. If stream_config_uri is a local file path, fs.readFile options should be\n # given. If stream_config_uri is a remote http URI, preq.get options should be used.\n # stream_config_uri_options: {'headers': {'Host': 'streamconfig.service.org'}}\n\n # If set, the stream configs are expected to live in a subobject\n # of the result object returned from stream_config_uri. The stream\n # configs object will be extracted at this path.\n # This should be a path string that Lodash#get understands.\n # stream_config_object_path: 'streams'\n\n # How long in seconds stream configs live in cache before being recached.\n # 0 or unset means no expiration.\n # stream_config_ttl: 60\n\n # Default stream config settings to apply to the configs fetched from stream_config_uri.\n # EventStreams uses some extra settings:\n # - stream_aliases\n # An array of stream route aliases from which to allow subscription to the stream.\n # You might use this if you want to expose a stream at an additional name\n # than its canonical stream name.\n # - description\n # Will be added to the OpenAPI spec's description of the stream route.\n # - $schema\n # If set, will be used to augmment the OpenAPI spec's response schema and examples.\n # stream_config_defaults:\n # mediawiki.page-create:\n # stream_aliases: [page-create]\n # description: |-\n # mediawiki.page-create events. This page create stream is just the first revision create event for each page. As such, it reuses the mediawiki/revision/create schema.\n # mediawiki.revision-create:\n # stream_aliases: [revision-create]\n # description: |-\n # mediawiki.revision-create events.\n\n # If set, these URIs will be prepended to any relative schema URI\n # extracted from the $schema stream config setting, or the URI constructed\n # using the schema_title stream config setting.. The resulting URLs will\n # be searched until a schema is found. Change this\n # to match paths to your schema repositories.\n # schema_base_uris: [\n # https://schema.wikimedia.org/repositories/primary/jsonschema,\n # https://schema.wikimedia.org/repositories/secondary/jsonschema\n # ]\n # If provided, these options will be passed when opening schema URIs.\n # for reading. If a schema URI is a local file path, fs.readFile options should be\n # given. If a schema URI is a remote http URI, preq.get options should be used.\n # schema_uri_options: {'headers': {'Host': 'schema.service.org'}}\n\n # If given, and no $schema is found in a stream config, a schema URI will\n # attempt to be constructed a /${schema_title}/${schema_latest_version}.\n # If this is not set, no schema URI will be inferred using schema_title.\n # schema_latest_version: 'latest'\n\n # Explicit list of allowed streams. If this is not set, all streams fetched from\n # the stream_config_uri will be exposed. Otherwise, only streams in this list\n # will be exposed.\n # allowed_streams:\n # - mediawiki.page-create\n # - mediawiki.revision-create\n\n # kafka configs go here.\n kafka:\n metadata.broker.list: \"localhost:9092\"\n statistics.interval.ms: 5000\n\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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.prod.yaml","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 177. Maximum allowed is 100.","line":91,"column":1,"nodeType":"Program","messageId":"max","endLine":91,"endColumn":178}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"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: 500\n\n# Logger info\nlogging:\n level: info\n streams:\n - type: debug\n # streams:\n # # Use gelf-stream -> logstash\n # - type: gelf\n # host: logstash1003.eqiad.wmnet\n # port: 12201\n\n# Statsd metrics reporter\nmetrics:\n - type: prometheus\n port: 9102\n\nservices:\n - name: eventstreams\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: 8092\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.yaml\n # allow cross-domain requests to the API (default '*')\n cors: \"*\"\n\n # If enabled, a HTML based stream GUI will be made available at /v2/ui.\n # Defaults to true if undefined.\n # stream_ui_enabled: true\n\n # Connection limiting. EventStreams connections are\n # expected to be long lived, so we can't use typical\n # connections per second type ratelimiting. Instead,\n # we limit the number of connections per X-Client-IP.\n client_ip_connection_limit: 2\n\n # A URI from which 'stream configuration' will be fetched.\n #\n # The object returned by this URI should map a stream name to its config settings,\n # which must include topics. If schema_title is set, it will be mentioned\n # in the generated OpenAPI spec.\n #\n # stream_config_uri: ./stream-config.yaml\n # OR:\n stream_config_uri: https://meta.wikimedia.org/w/api.php?action=streamconfigs\n\n # If provided, these options will be passed when opening stream_config_uri\n # for reading. If this is a local file path, fs.readFile options should be\n # given. If this is a remote http URI, preq.get options should be used.\n # stream_config_uri_options: {'headers': {'Host': 'streamconfig.service.org'}}\n\n # If set, the stream configs are expected to live in a subobject\n # of the result object returned from stream_config_uri. The stream\n # configs object will be extracted at this path.\n # This should be a path string that Lodash#get understands.\n stream_config_object_path: streams\n\n # How long in seconds stream configs live in cache before being recached.\n # 0 or unset means no expiration.\n stream_config_ttl: 300\n\n # Default stream config settings to apply to the configs fetched from stream_config_uri.\n # EventStreams uses some extra settings:\n # - stream_aliases\n # An array of stream route aliases from which to allow subscription to the stream.\n # You might use this if you want to expose a stream at an additional name\n # than its canonical stream name.\n # - description\n # Will be added to the OpenAPI spec's description of the stream route.\n # - $schema\n # If set, will be used to augmment the OpenAPI spec's response schema and examples.\n stream_config_defaults:\n mediawiki.page-create:\n stream_aliases: [ page-create ]\n description: |-\n mediawiki.page-create events. This page create stream is just the first revision create event for each page. As such, it reuses the mediawiki/revision/create schema.\n mediawiki.page-delete:\n stream_aliases: [ page-delete ]\n mediawiki.page-move:\n stream_aliases: [ page-move ]\n mediawiki.page-undelete:\n stream_aliases: [ page-undelete ]\n mediawiki.page-properties-change:\n stream_aliases: [ page-properties-change ]\n mediawiki.revision-create:\n stream_aliases: [ revision-create ]\n mediawiki.recentchange:\n stream_aliases: [ recentchange-delete ]\n\n # If set, these URIs will be prepended to any relative schema URI\n # extracted from the $schema stream config setting, or the URI constructed\n # using the schema_title stream config setting.. The resulting URLs will\n # be searched until a schema is found. Change this\n # to match paths to your schema repositories.\n schema_base_uris:\n - https://schema.wikimedia.org/repositories/primary/jsonschema\n - https://schema.wikimedia.org/repositories/secondary/jsonschema\n\n # If provided, these options will be passed when opening schema URIs.\n # for reading. If a schema URI is a local file path, fs.readFile options should be\n # given. If a schema URI is a remote http URI, preq.get options should be used.\n # schema_uri_options: {'headers': {'Host': 'schema.service.org'}}\n\n # If given, and no $schema is found in a stream config, a schema URI will\n # attempt to be constructed a /${schema_title}/${schema_latest_version}.\n # If this is not set, no schema URI will be inferred using schema_title.\n schema_latest_version: latest\n\n # Explicit list of allowed streams. If this is not set, all streams fetched from\n # the stream_config_uri will be exposed. Otherwise, only streams in this list\n # will be exposed.\n allowed_streams:\n - mediawiki.page-create\n - mediawiki.page-delete\n - mediawiki.page-move\n - mediawiki.page-undelete\n - mediawiki.page-properties-change\n - mediawiki.revision-create\n - mediawiki.recentchange\n\n # kafka configs go here.\n kafka:\n metadata.broker.list: \"localhost:9092\"\n statistics.interval.ms: 60000\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"max-len","severity":1,"message":"This line has a length of 179. Maximum allowed is 100.","line":90,"column":1,"nodeType":"Program","messageId":"max","endLine":90,"endColumn":180}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"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: debug\n streams:\n - type: debug\n# streams:\n# # Use gelf-stream -> logstash\n# - type: gelf\n# host: logstash1003.eqiad.wmnet\n# port: 12201\n\n# metrics:\n# - type: prometheus\n# port: 9102\n\nservices:\n - name: eventstreams\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: 8092\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.yaml\n # allow cross-domain requests to the API (default '*')\n cors: \"*\"\n\n # If enabled, a HTML based stream GUI will be made available at /v2/ui.\n # Defaults to true if undefined.\n # stream_ui_enabled: true\n\n # Connection limiting. EventStreams connections are\n # expected to be long lived, so we can't use typical\n # connections per second type ratelimiting. Instead,\n # we limit the number of connections per X-Client-IP.\n # client_ip_connection_limit: 2\n\n # A URI from which 'stream configuration' will be fetched.\n #\n # The object returned by this URI should map a stream name to its config settings,\n # which must include topics. If schema_title is set, it will be mentioned\n # in the generated OpenAPI spec.\n #\n stream_config_uri: ./stream-config.yaml\n # OR:\n # stream_config_uri: https://meta.wikimedia.org/w/api.php?action=streamconfigs\n\n # If provided, these options will be passed when opening stream_config_uri\n # for reading. If stream_config_uri is a local file path, fs.readFile options should be\n # given. If stream_config_uri is a remote http URI, preq.get options should be used.\n # stream_config_uri_options: {'headers': {'Host': 'streamconfig.service.org'}}\n\n # If set, the stream configs are expected to live in a subobject\n # of the result object returned from stream_config_uri. The stream\n # configs object will be extracted at this path.\n # This should be a path string that Lodash#get understands.\n # stream_config_object_path: 'streams'\n\n # How long in seconds stream configs live in cache before being recached.\n # 0 or unset means no expiration.\n # stream_config_ttl: 60\n\n # Default stream config settings to apply to the configs fetched from stream_config_uri.\n # EventStreams uses some extra settings:\n # - stream_aliases\n # An array of stream route aliases from which to allow subscription to the stream.\n # You might use this if you want to expose a stream at an additional name\n # than its canonical stream name.\n # - description\n # Will be added to the OpenAPI spec's description of the stream route.\n # - $schema\n # If set, will be used to augmment the OpenAPI spec's response schema and examples.\n # stream_config_defaults:\n # mediawiki.page-create:\n # stream_aliases: [page-create]\n # description: |-\n # mediawiki.page-create events. This page create stream is just the first revision create event for each page. As such, it reuses the mediawiki/revision/create schema.\n # mediawiki.revision-create:\n # stream_aliases: [revision-create]\n # description: |-\n # mediawiki.revision-create events.\n\n # If set, these URIs will be prepended to any relative schema URI\n # extracted from the $schema stream config setting, or the URI constructed\n # using the schema_title stream config setting.. The resulting URLs will\n # be searched until a schema is found. Change this\n # to match paths to your schema repositories.\n # schema_base_uris: [\n # https://schema.wikimedia.org/repositories/primary/jsonschema,\n # https://schema.wikimedia.org/repositories/secondary/jsonschema\n # ]\n # If provided, these options will be passed when opening schema URIs.\n # for reading. If a schema URI is a local file path, fs.readFile options should be\n # given. If a schema URI is a remote http URI, preq.get options should be used.\n # schema_uri_options: {'headers': {'Host': 'schema.service.org'}}\n\n # If given, and no $schema is found in a stream config, a schema URI will\n # attempt to be constructed a /${schema_title}/${schema_latest_version}.\n # If this is not set, no schema URI will be inferred using schema_title.\n # schema_latest_version: 'latest'\n\n # Explicit list of allowed streams. If this is not set, all streams fetched from\n # the stream_config_uri will be exposed. Otherwise, only streams in this list\n # will be exposed.\n # allowed_streams:\n # - mediawiki.page-create\n # - mediawiki.revision-create\n\n # kafka configs go here.\n kafka:\n metadata.broker.list: \"localhost:9092\"\n statistics.interval.ms: 5000\n\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/eventstreams-util.js","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 101. Maximum allowed is 100.","line":117,"column":1,"nodeType":"Program","messageId":"max","endLine":117,"endColumn":102},{"ruleId":"max-len","severity":1,"message":"This line has a length of 104. Maximum allowed is 100.","line":118,"column":1,"nodeType":"Program","messageId":"max","endLine":118,"endColumn":105}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst _ = require('lodash');\n\nconst {\n objectFactory\n} = require('@wikimedia/url-get');\n\n/**\n * Custom message deserializer for eventstreams.\n * Augments the deserialized message with kafka\n * metadata in the .meta subobject.\n *\n * @param {Object} kafkaMessage\n * @return {Object}\n */\nfunction deserializer(kafkaMessage) {\n kafkaMessage.message = objectFactory(kafkaMessage.value);\n\n if (!kafkaMessage.message.meta) {\n kafkaMessage.message.meta = {};\n }\n kafkaMessage.message.meta.topic = kafkaMessage.topic;\n kafkaMessage.message.meta.partition = kafkaMessage.partition;\n kafkaMessage.message.meta.offset = kafkaMessage.offset;\n if (kafkaMessage.key) {\n kafkaMessage.message.meta.key = kafkaMessage.key;\n }\n\n return kafkaMessage;\n}\n\n/**\n * Time sensitive, one off, implementation to support T354456.\n *\n * Redact message content based on deny list of page titles,\n * populated with the full page name.\n *\n * TODO: re-evaluate this approach after integration testing.\n *\n * @param {{string: string[]}} redacted_pages Map of wiki to list of redacted pages.\n * @param {Object} options\n * @param {Object} options.logger Logger\n * @param {string} options.clientIp\n * @param {string} options.userAgent\n * @param {string} options.referer\n * @return {function(Object): Object}\n */\nfunction makeMediaWikiRedactorDeserializer(redacted_pages, options = {}) {\n // Time sensitive, one off, implementation to support T354456.\n // Users are expected to provide *normalized* page names. This data is not available\n // in some legacy streams. Apply some normalization before comparing user configs\n // to kafka messages. This won't cover all MediaWiki cases, it's used only to have\n // consistent naming within this method.\n //\n // We can't match on page IDs, because not all streams ship it (e.g. mediawiki.recentchange).\n // We could create lookup up tables by querying the MediaWiki Action/REST APIs at service\n // startup, but given the use case here we might get away with simple logic (at the cost of\n // generating false positives).\n //\n // 1. Trim any space at the beginning and end of the string. E.g. \" a b \" -> \"a b\".\n // 2. Replace all space occurrences with an underscore, to match page titles (uri) naming.\n // E.g. 'a b c' -> 'a_b_c'.\n // 3. Ignore site-specific capitalization rules, and preemptively lower case page titles\n // to workaround case sensitivity settings.\n // We compare with titles regardless of capitalization.\n // This might increase false positives, but decrease the risk of PII leaks.\n // References:\n // - https://www.mediawiki.org/wiki/Manual:Page_title\n // - https://wikitech.wikimedia.org/wiki/Analytics/Data_Lake/Traffic/Pageviews/Redirects\n const normalizePageTitle = (pageTitle, defaultReturnValue = null) => {\n if (pageTitle === null || pageTitle === undefined) {\n return defaultReturnValue;\n }\n // Make sure page_title is a string\n pageTitle = String(pageTitle);\n pageTitle = pageTitle.trim();\n return pageTitle.replace(/ +/g, '_').toLowerCase();\n };\n\n for (const domain in redacted_pages) {\n redacted_pages[domain] = redacted_pages[domain].flatMap(\n (pageTitle) => normalizePageTitle(pageTitle, [])\n );\n }\n const domains = Object.keys(redacted_pages);\n return (kafkaMessage) => {\n const km = deserializer(kafkaMessage);\n const { message } = km;\n const domain = message.meta.domain;\n if (!domain || !domains.includes(domain)) {\n return km;\n }\n\n let pageTitle;\n if ( // case mediawiki.page_change.v1\n message.meta.stream === 'mediawiki.page_change.v1' &&\n domains.includes(message.meta.domain) &&\n redacted_pages[message.meta.domain].includes(\n normalizePageTitle(message.page?.page_title)\n )\n ) {\n // Performer field is required in the schema.\n message.performer = {};\n delete message.revision.editor;\n delete message.prior_state?.revision?.editor;\n pageTitle = message.page?.page_title;\n } else if ( // case recentchange\n message.meta.stream === 'mediawiki.recentchange' &&\n domains.includes(message.meta.domain) &&\n redacted_pages[message.meta.domain].includes(normalizePageTitle(message.title))\n ) {\n delete message.user;\n pageTitle = message.title;\n } else if ( // case all other streams\n domains.includes(message.meta?.domain) &&\n (redacted_pages[message.meta.domain].includes(normalizePageTitle(message?.page_title)) ||\n redacted_pages[message.meta.domain].includes(normalizePageTitle(message?.page?.page_title)))\n ) {\n delete message.performer;\n pageTitle = message?.page_title ?? message?.page?.page_title;\n }\n\n if (options.logger && !!pageTitle) {\n options.logger.log('info',\n `Redacted Page. Wiki: ${ domain }, ` +\n `Stream: ${ message.meta.stream }, ` +\n `Page: ${ pageTitle }, ` +\n `ClientIp: ${ options.clientIp ?? '' }, ` +\n `UserAgent: ${ options.userAgent ?? '' }, ` +\n `Referrer: ${ options.referer ?? '' }`\n );\n }\n\n km.message = message;\n return km;\n };\n}\n\n/**\n * Filter function that will be passed as an option to the\n * event.stats cb function that node-rdkafka-statsd will create\n * to give each new node-rdkafka client instance.\n *\n * We implement a custom filter because we don't care to report\n * some of these rdkafka metrics. Specifically, we remove\n * metrics about committed offsets, since kafka-sse does not commit.\n */\nconst rdkafkaStatsWhitelist = [\n // Broker stats\n 'outbuf_cnt',\n 'outbuf_msg_cnt',\n 'waitresp_cnt',\n 'waitresp_msg_cnt',\n 'tx',\n 'txbytes',\n 'txerrs',\n 'txretries',\n 'req_timeouts',\n 'rx',\n 'rxbytes',\n 'rxerrs',\n 'rxcorriderrs',\n 'rxpartial',\n 'rtt',\n 'throttle',\n\n // Topic partition stats\n 'msgq_cnt',\n 'msgq_bytes',\n 'xmit_msgq_cnt',\n 'xmit_msgq_bytes',\n 'fetchq_cnt',\n 'fetchq_size',\n 'next_offset',\n 'eof_offset',\n 'lo_offset',\n 'hi_offset',\n 'consumer_lag',\n 'txmsgs',\n 'txbytes',\n 'msgs',\n 'rx_ver_drops'\n];\n\n\nfunction rdkafkaStatsFilter(key) {\n return _.includes(rdkafkaStatsWhitelist, key);\n}\n\n\nmodule.exports = {\n makeMediaWikiRedactorDeserializer,\n deserializer,\n rdkafkaStatsFilter\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":99,"column":1,"nodeType":"Block","endLine":99,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":143,"column":1,"nodeType":"Block","endLine":143,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Router' is undefined.","line":223,"column":1,"nodeType":"Block","endLine":223,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Application' is undefined.","line":243,"column":1,"nodeType":"Block","endLine":243,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst BBPromise = require('bluebird');\nconst preq = require('preq');\nconst express = require('express');\nconst uuidv1 = require('uuid/v1');\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 * 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 const metric = app.metrics.makeMetric({\n type: 'Gauge',\n name: 'router',\n prometheus: {\n name: 'express_router_request_duration_seconds',\n help: 'request duration handled by router in seconds',\n staticLabels: { service: app.metrics.getServiceName() }\n },\n labels: {\n names: ['path', 'method', 'status'],\n omitLabelNames: true\n }\n });\n layer.handle = (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 metric.set(Date.now() - startTime, [path || 'root', req.method, statusCode]);\n });\n };\n });\n });\n\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.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 errObj.status = errObj.status || 500;\n errObj.type = errObj.type || 'internal_error';\n // add the offending URI and method as well\n errObj.method = errObj.method || req.method;\n errObj.uri = errObj.uri || req.url;\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 // 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'] || uuidv1();\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.context = { reqId: req.headers['x-request-id'] };\n req.issueRequest = (request) => {\n if (!(request.constructor === Object)) {\n request = { uri: request };\n }\n if (request.url) {\n request.uri = request.url;\n delete request.url;\n }\n if (!request.uri) {\n return BBPromise.reject(new HTTPError({\n status: 500,\n type: 'internal_error',\n title: 'No request to issue',\n detail: 'No request has been specified'\n }));\n }\n request.method = request.method || 'get';\n request.headers = request.headers || {};\n Object.assign(request.headers, {\n 'user-agent': app.conf.user_agent,\n 'x-request-id': req.context.reqId\n });\n req.logger.log('trace/req', { msg: 'Outgoing request', out_request: request });\n return preq(request);\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":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/info.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/stream.js","messages":[{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":28,"column":1,"nodeType":"Block","endLine":64,"endColumn":4},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":249,"column":1,"nodeType":"Block","endLine":253,"endColumn":4},{"ruleId":"max-len","severity":1,"message":"This line has a length of 103. Maximum allowed is 100.","line":424,"column":1,"nodeType":"Program","messageId":"max","endLine":424,"endColumn":104}],"suppressedMessages":[{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_logger'.","line":444,"column":41,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":444,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst fs = require('fs');\nconst _ = require('lodash');\nconst kafkaSse = require('kafka-sse');\nconst express = require('express');\n\nconst {\n urlGetObject,\n uriGetFirstObject,\n} = require('@wikimedia/url-get');\n\nconst sUtil = require('../lib/util');\nconst eUtil = require('../lib/eventstreams-util');\nconst HTTPError = sUtil.HTTPError;\n\n/**\n * This file exports a function that configures the ExpressJS router to\n * serve /v2/stream/{stream} using information about streams to expose\n * obtained from stream_config_uri.\n */\n\n/**\n * The main router object\n */\nconst router = sUtil.router();\n\n/**\n * Loads stream configs from app.conf.stream_config_uri.\n * Regex stream names are not supported and will be removed.\n * Streams without a topics setting will be removed.\n * If a stream_aliases setting is found for a stream, that stream\n * config will be duplicated and also keyed by each stream alias.\n *\n * Some stream config settings will be used to augment the OpenAPI spec.\n * See updateSpec below.\n *\n * @param {Object} app\n * @param {Object} app.conf\n * @param {string} app.conf.stream_config_uri\n * A URI from which stream configs will be requested. This URI should resolve to a JSON\n * object keyed by stream name with stream config settings.\n * @param {string} app.conf.stream_config_object_path\n * If set, the stream configs are expected to live in a subobject\n * of the result object returned from stream_config_uri at this dotted path.\n * @param {Object} app.conf.stream_config_uri_options\n * @param {string} app.conf.stream_config_ttl\n * How long in seconds stream configs live in cache before being recached.\n * 0 or unset means no expiration.\n * @param {string} app.conf.stream_config_defaults\n * Defaults to use for stream configs. This will be applied to the object fetched\n * from stream_config_uri like _.defaultsDeep(streamConfigs, app.conf.stream_config_defaults)\n * @param {Array<string>} app.conf.allowed_streams\n * If provided, stream configs will be filtered for streams that are in this list.\n * @param {string} app.conf.schema_latest_version\n * If given, and no $schema is found in a stream config, a schema URI will\n * attempt to be constructed a /${schema_title}/${schema_latest_version}.\n * If this is not set, no schema URU will be inferred using schema_title.\n * @param {Array<string>} app.conf.schema_base_uris\n * If provided, relative $schema urls for a stream will attempt to be resolved from these.\n * @param {Object} app.conf.schema_uri_options\n * If provided, redacts user from these pages. Map of wiki to pages to redact.\n * @param {Object<string, Array<string>>} app.conf.mediawiki_redacted_pages\n */\nasync function loadStreamConfigs(app) {\n let streamConfigs;\n if (_.isUndefined(app.conf.stream_config_uri)) {\n throw new Error('Must set stream_config_uri with URI of streams to expose.');\n }\n\n app.logger.log('info', `Loading stream configs from ${ app.conf.stream_config_uri }`);\n streamConfigs = await urlGetObject(\n app.conf.stream_config_uri,\n app.conf.stream_config_uri_options || {}\n );\n\n // If stream_config_object_path was configured,\n // expect the config settings for stream to exist at that path.\n if (app.conf.stream_config_object_path) {\n streamConfigs = _.get(streamConfigs, app.conf.stream_config_object_path);\n }\n\n // Merge any stream_config_extra overides with fetched stream config\n _.defaultsDeep(streamConfigs, app.conf.stream_config_defaults);\n\n streamConfigs = _.pickBy(streamConfigs, (streamConfig, streamName) => {\n // eventstreams does not support regex stream keys, so remove those.\n if (streamName.startsWith('/')) {\n app.logger.log(\n 'trace',\n `Regex stream names are not supported, removing ${ streamName }.`\n );\n return false;\n }\n\n // We need defined topics in order to consume from a stream.\n if (_.isUndefined(streamConfig.topics)) {\n app.logger.log('trace', `${ streamName } does not have configured topics, removing.`);\n return false;\n }\n\n // Also if allowed_streams is an array, remove any non allowed streams.\n if (\n app.conf.allowed_streams &&\n !app.conf.allowed_streams.includes(streamName)\n ) {\n app.logger.log(\n 'trace',\n `${ streamName } is not in the list of allowed_streams, removing.`\n );\n return false;\n }\n\n // Else this stream is allowed.\n return true;\n });\n\n // Augment streamConfigs with extra info from defaults and schema.\n await Promise.all(_.keys(streamConfigs).map(async (streamName) => {\n const streamConfig = streamConfigs[streamName];\n\n // Add a nice default description.\n if (_.isUndefined(streamConfig.description)) {\n streamConfig.description = `${ streamName } events.`;\n }\n\n if (_.isUndefined(streamConfig.schema)) {\n if (\n _.isUndefined(streamConfig.$schema) &&\n streamConfig.schema_title &&\n app.conf.schema_base_uris &&\n app.conf.schema_latest_version\n ) {\n streamConfig.$schema =\n `/${ streamConfig.schema_title }/${ app.conf.schema_latest_version }`;\n }\n\n if (streamConfig.$schema) {\n try {\n app.logger.log(\n 'debug',\n `Fetching schema for ${ streamName } at ${ streamConfig.$schema }`\n );\n streamConfig.schema = await uriGetFirstObject(\n streamConfig.$schema,\n app.conf.schema_base_uris,\n undefined,\n app.conf.schema_uri_options\n );\n } catch (error) {\n app.logger.log('warn', {\n msg: `Failed fetching schema for ${ streamName } at ` +\n `${ streamConfig.$schema }, not augmenting OpenAPI spec with schema.`,\n error,\n });\n }\n }\n }\n\n // If this stream has stream_aliases defined, duplicate this stream config\n // keyed by this name. When updating the dynamic spec below, and when\n // figuring out what streams are subscribed to, this alias can be used\n // as a shortcut route for a stream name, but the original stream name is\n // still exposed as a route too.\n if (streamConfig.stream_aliases) {\n streamConfig.stream_aliases.forEach((stream_alias) => {\n streamConfigs[stream_alias] = _.cloneDeep(streamConfig);\n streamConfigs[stream_alias].description +=\n `\\n\\n(NOTE: This stream is an alias of ${ streamName })`;\n // Remove the stream_aliases from this copied stream config.\n // stream_aliases should only be set on the canonical stream config.\n delete streamConfigs[stream_alias].stream_aliases;\n });\n }\n }));\n\n // 'sort' streamConfigs by stream name and then return it.\n // streamConfigs are used to generated the dynamic OpenAPI spec,\n // and it is nice to see the routes in order in /?doc.\n return Object.keys(streamConfigs).sort().reduce((r, k) => {\n r[k] = streamConfigs[k];\n return r;\n }, {});\n}\n\n/**\n * Updates the OpenAPI spec at app.conf.spec with path routes exposing\n * the streams declared in streamConfigs.\n *\n * streamConfig.description will be used for the route path description.\n * If streamConfig.schema is given, it will be\n * used as the route path responses schema and example.\n *\n * @param {Object} app\n * @param {Object} streamConfigs\n */\nfunction updateSpec(app, streamConfigs) {\n // Update the /v2/stream/{stream} path spec with the list of available streams.\n app.conf.spec.paths['/v2/stream/{streams}'].get.parameters[0].schema.items.enum =\n _.keys(streamConfigs);\n\n // Make an OpenAPI spec paths entry for each stream declared in streamConfigs.\n _.forOwn(streamConfigs, (streamConfig, streamName) => {\n // The /v2/stream/{streams} route is the only 'real' route, the stream specific ones\n // are maintained for API clarity and documentation. Use the /v2/stream/{streams}\n // route as a base for each of the stream specific routes.\n const streamRouteSpec = _.cloneDeep(app.conf.spec.paths['/v2/stream/{streams}']);\n // The stream specific routes do not take a {streams} parameter. It is the first\n // listed, so shift the parameters array to remove it.\n streamRouteSpec.get.parameters.shift();\n streamRouteSpec.get.summary = `${ streamName } events`;\n\n if (streamConfig.description) {\n // Use the streamConfig description in the route description.\n streamRouteSpec.get.description = streamConfig.description;\n }\n if (streamConfig.schema_title) {\n streamRouteSpec.get.description += `\\n\\nSchema title: ${ streamConfig.schema_title }`;\n }\n\n if (streamConfig.schema) {\n const schema = _.cloneDeep(streamConfig.schema);\n const examples = schema.examples;\n // Remove OpenAPI unsupported JSONSchema keywords.\n // See: https://swagger.io/docs/specification/data-models/keywords/\n delete schema.$schema;\n delete schema.$id;\n delete schema.examples;\n\n // Use the schema in the route response\n streamRouteSpec.get.responses['200'].content['application/json'].schema = schema;\n streamRouteSpec.get.responses['200'].content['text/event-stream'].schema = schema;\n\n if (!_.isEmpty(examples)) {\n // Use the schema examples in the route response.\n streamRouteSpec.get.responses['200'].content['application/json'].example =\n examples[0];\n streamRouteSpec.get.responses['200'].content['text/event-stream'].example =\n examples[0];\n }\n }\n\n // Add the stream specific route to the OpenAPI spec.\n app.conf.spec.paths[`/v2/stream/${ streamName }`] = streamRouteSpec;\n });\n\n}\n\n/**\n * Set up the /v2/stream/{stream} route using stream config.\n *\n * @param {Object} app\n */\nmodule.exports = async (app) => {\n let streamConfigs = await loadStreamConfigs(app);\n updateSpec(app, streamConfigs);\n app.logger.log('trace', { msg: 'Loaded stream configs', streamConfigs });\n\n if (_.isEmpty(streamConfigs)) {\n throw new Error(\n `No stream configs were configuerd to be exposed in ${ app.conf.stream_config_uri }`\n );\n }\n\n // If we loaded stream_config_uri and it should have a TTL, referesh it every TTL seconds.\n if (!app.conf.streams && app.conf.stream_config_uri && app.conf.stream_config_ttl) {\n setInterval(async () => {\n try {\n streamConfigs = await loadStreamConfigs(app);\n updateSpec(app, streamConfigs);\n app.logger.log('trace', {\n msg: `Reloaded stream configs after ${ app.conf.stream_config_ttl } seconds`,\n streamConfigs\n });\n } catch (error) {\n app.logger.log('warn', {\n msg: 'Caught error while reloading stream configs. ' +\n 'Keeping previous stream configs and routes.',\n error\n });\n }\n }, app.conf.stream_config_ttl * 1000);\n }\n\n // Connected clients per stream and client IP service-runner metric.\n // This is a guage and indicates the current number of connected clients.\n const connectedClientsMetric = app.metrics.makeMetric({\n type: 'Gauge',\n name: 'connected-clients',\n prometheus: {\n name: 'eventstreams_connected_clients',\n help: 'Connected clients per stream guage',\n staticLabels: { service: app.metrics.getServiceName() },\n },\n labels: {\n names: ['stream'],\n omitLabelNames: true,\n }\n });\n\n // This is a counter of the total number of client connections ever made.\n // We don't care so much about the totals by client IP here, so it is not a label.\n // Also, since clients can connect to multiple streams at once, we keep track\n // of total connections per list of streams, not individual streams.\n // This helps us keep track of how folks connect and use streams together over time.\n // The streams label will be a sorted comma separated string list\n // of streams the client has subscribed to.\n // https://phabricator.wikimedia.org/T238658#5947574\n const clientConnectionsTotalMetric = app.metrics.makeMetric({\n type: 'Counter',\n name: 'client-connections-total',\n prometheus: {\n name: 'eventstreams_client_connections_total',\n help: 'Client connections total per combination of subscribed streams',\n staticLabels: { service: app.metrics.getServiceName() },\n },\n labels: {\n names: ['streams'],\n omitLabelNames: true,\n }\n });\n\n // Keep track of currently connected client IPs for poor-man's rate limiting.\n const connectionCountPerIp = {};\n\n router.get('/stream/:streams', (req, res) => {\n const clientIp = req.get('x-client-ip') || 'UNKNOWN';\n const userAgent = req.get('user-agent') || 'UNKNOWN';\n const referer = req.get('referer') || 'UNKNOWN';\n\n // ensure the requesting client hasn't gone over the concurrent connection limits.\n if (app.conf.client_ip_connection_limit) {\n if (!req.headers['x-client-ip']) {\n throw new HTTPError({\n status: 400,\n type: 'bad_request',\n title: 'Missing Required X-Client-IP Header',\n detail: 'X-Client-IP is a required request header'\n });\n }\n\n const clientIpConnectionCount = connectionCountPerIp[clientIp] || 0;\n if (clientIpConnectionCount >= app.conf.client_ip_connection_limit) {\n throw new HTTPError({\n status: 429,\n type: 'too_many_requests',\n title: 'Too Many Concurrent Connections From Your Client IP',\n detail: 'Your HTTP client is likely opening too many concurrent connections.'\n });\n }\n }\n\n const requestedStreams = req.params.streams.split(',');\n // Ensure all requested streams are available.\n const invalidStreams =\n requestedStreams.filter((s) => !_.includes(_.keys(streamConfigs), s));\n if (invalidStreams.length > 0) {\n throw new HTTPError({\n status: 400,\n type: 'not_found',\n title: 'Stream Not Found',\n detail: `Invalid streams: ${ invalidStreams.join(',') }`\n });\n }\n\n const topics = _.uniq(_.flatMap(\n _.pick(streamConfigs, requestedStreams),\n (streamConfig) => streamConfig.topics\n ));\n\n // If since param is provided, it will be used to consume from\n // a point in time in the past, if Last-Event-ID doesn't already\n // have assignments in it.\n let atTimestamp = req.query.since;\n // If not a milliseconds timestamp, attempt to parse it into one.\n if (atTimestamp && isNaN(atTimestamp)) {\n atTimestamp = Date.parse(atTimestamp);\n }\n // If atTimestamp is defined but is still not a number milliseconds timestamp,\n // throw HTTPError.\n if (atTimestamp !== undefined && isNaN(atTimestamp)) {\n throw new HTTPError({\n status: 400,\n type: 'invalid_timestamp',\n title: 'Invalid timestamp',\n detail: `since timestamp is not a UTC milliseconds unix epoch and was not parseable: '${ req.query.since }'`\n });\n }\n\n requestedStreams.forEach((stream) => {\n // Increment the number of current connections for this stream using this key.\n // NOTE: This is a guage so we have to decrement it when the client is disconnected too.\n connectedClientsMetric.increment(1, [stream]);\n app.logger.log('info', `Connected: IP ${ clientIp }, User-Agent: ${ userAgent }, Referer: ${ referer }, stream ${ stream }`);\n });\n // Increment the total counter of clients ever connected to this combination of streams.\n clientConnectionsTotalMetric.increment(1, [requestedStreams.sort().join(',')]);\n\n // Increment the number of connctions for this clientIp\n connectionCountPerIp[clientIp] = (connectionCountPerIp[clientIp] || 0) + 1;\n\n // After the connection is closed, decrement the number\n // of current connections for these streams.\n function decrementConnectionCount() {\n requestedStreams.forEach((stream) => {\n app.logger.log('info', `Disconnected: IP ${ clientIp }, User-Agent: ${ userAgent }, Referer: ${ referer }, stream ${ stream }`);\n connectedClientsMetric.decrement(1, [stream]);\n });\n // Decrement the number of concurrent connections for this client ip\n // (never going below 0).\n connectionCountPerIp[clientIp] = Math.max(connectionCountPerIp[clientIp] - 1, 0);\n }\n // I'm not sure why, but the 'close' event is fired when the client closes the connection,\n // and the 'finish' event is fired when KafkaSSE closes the connection (due to an error).\n // Register them both.\n res.on('close', decrementConnectionCount);\n res.on('finish', decrementConnectionCount);\n\n // See if redactor is enabled, and if so set it.\n // This is router.get so we have access to clientIp and userAgent.\n let deserializer;\n if (app.conf.mediawiki_redacted_pages) {\n deserializer = eUtil.makeMediaWikiRedactorDeserializer(\n app.conf.mediawiki_redacted_pages, { logger: app.logger, clientIp, userAgent, referer }\n );\n } else {\n deserializer = eUtil.deserializer;\n }\n\n // Start the SSE EventStream connection with topics.\n return kafkaSse(req, res, topics,\n {\n // Using topics for allowedTopics may seem redundant, but it\n // prevents requests for /stream/streamA from consuming from topics\n // that are not configured for streamA by setting other topics\n // in the Last-Event-ID header. Last-Event-ID topic, partition, offset\n // assignments will take precedence over topics parameter.\n allowedTopics: topics,\n // Support multi DC Kafka clusters by using message timestamps\n // in Last-Event-ID instead of offsets.\n useTimestampForId: true,\n // Give kafkaSse the request bunyan logger to use.\n // eslint-disable-next-line no-underscore-dangle\n logger: req.logger._logger,\n kafkaConfig: app.conf.kafka,\n // Use the eventstreams custom deserializer to include\n // kafka message meta data in the deserialized message.meta object\n // that will be sent to the client as an event.\n deserializer: deserializer\n },\n atTimestamp\n );\n });\n\n const uiEnabled = app.conf.stream_ui_enabled || true;\n if (uiEnabled) {\n if (!fs.existsSync(`${ __dirname }/../ui/dist`)) {\n throw new Error(\n 'Cannot enable HTML stream GUI at /v2/ui: The ui/dist directory does not exist.' +\n ' Run `npm run build-ui`.'\n );\n }\n\n app.logger.log('debug', 'Enabling HTML stream GUI at /v2/ui');\n app.use('/v2/ui', express.static(`${ __dirname }/../ui/dist`));\n app.use(express.static(`${ __dirname }/../ui/dist`));\n }\n\n return {\n path: '/v2',\n api_version: 2,\n skip_domain: true,\n router\n };\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":[{"ruleId":"es-x/no-hashbang","severity":2,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\n// Service entry point. Try node server --help for commandline options.\n\n// Start the service by running service-runner, which in turn loads the config\n// (config.yaml by default, specify other path with -c). It requires the\n// module(s) specified in the config 'services' section (app.js in this\n// example).\nconst ServiceRunner = require('service-runner');\nnew ServiceRunner().start();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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.template.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/spec.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/stream-config.yaml","messages":[{"ruleId":"max-len","severity":1,"message":"This line has a length of 169. Maximum allowed is 100.","line":5,"column":1,"nodeType":"Program","messageId":"max","endLine":5,"endColumn":170}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"mediawiki.page-create:\n topics: [ eqiad.mediawiki.page-create, codfw.mediawiki.page-delete ]\n stream_aliases: [ page-create ]\n description: |-\n mediawiki.page-create events. This page create stream is just the first revision create event for each page. As such, it reuses the mediawiki/revision/create schema.\n# $schema: https://schema.wikimedia.org/repositories/primary/jsonschema/mediawiki/revision/create/latest\n\nmediawiki.revision-create:\n topics: [ eqiad.mediawiki.revision-create, codfw.mediawiki.revision-create ]\n stream_aliases: [ revision-create ]\n description: |-\n mediawiki.revision-create events.\n # $schema: https://schema.wikimedia.org/repositories/primary/jsonschema/mediawiki/revision/create/latest\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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-shadow","severity":2,"message":"'spec' is already declared in the upper scope on line 11 column 5.","line":70,"column":25,"nodeType":"Identifier","messageId":"noShadow","endLine":70,"endColumn":29},{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":154,"column":14,"nodeType":"NewExpression","endLine":154,"endColumn":47},{"ruleId":"no-shadow","severity":2,"message":"'spec' is already declared in the upper scope on line 11 column 5.","line":265,"column":16,"nodeType":"Identifier","messageId":"noShadow","endLine":265,"endColumn":20}],"suppressedMessages":[{"ruleId":"brace-style","severity":2,"message":"Statement inside of curly braces should be on next line.","line":196,"column":19,"nodeType":"Punctuator","messageId":"blockSameLine","endLine":196,"endColumn":20,"fix":{"range":[6105,6105],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-statements-per-line","severity":2,"message":"This line has 2 statements. Maximum allowed is 1.","line":196,"column":21,"nodeType":"ReturnStatement","messageId":"exceed","endLine":196,"endColumn":33,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"brace-style","severity":2,"message":"Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.","line":196,"column":34,"nodeType":"Punctuator","messageId":"singleLineClose","endLine":196,"endColumn":35,"fix":{"range":[6119,6119],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"brace-style","severity":2,"message":"Statement inside of curly braces should be on next line.","line":197,"column":19,"nodeType":"Punctuator","messageId":"blockSameLine","endLine":197,"endColumn":20,"fix":{"range":[6163,6163],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-statements-per-line","severity":2,"message":"This line has 2 statements. Maximum allowed is 1.","line":197,"column":21,"nodeType":"ReturnStatement","messageId":"exceed","endLine":197,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"brace-style","severity":2,"message":"Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.","line":197,"column":35,"nodeType":"Punctuator","messageId":"singleLineClose","endLine":197,"endColumn":36,"fix":{"range":[6178,6178],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-prototype-builtins","severity":2,"message":"Do not access Object.prototype method 'hasOwnProperty' from target object.","line":213,"column":38,"nodeType":"CallExpression","messageId":"prototypeBuildIn","endLine":213,"endColumn":52,"suggestions":[{"messageId":"callObjectPrototype","data":{"prop":"hasOwnProperty"},"fix":{"range":[6705,6728],"text":"Object.prototype.hasOwnProperty.call(resBody, "},"desc":"Call Object.prototype.hasOwnProperty explicitly."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"template-curly-spacing","severity":2,"message":"Expected space(s) after '${'.","line":213,"column":77,"nodeType":null,"messageId":"expectedAfter","endLine":213,"endColumn":79,"fix":{"range":[6754,6754],"text":" "},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"template-curly-spacing","severity":2,"message":"Expected space(s) before '}'.","line":213,"column":82,"nodeType":null,"messageId":"expectedBefore","endLine":213,"endColumn":83,"fix":{"range":[6757,6757],"text":" "},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"block-spacing","severity":2,"message":"Requires a space after '{'.","line":235,"column":41,"nodeType":"BlockStatement","messageId":"missing","endLine":235,"endColumn":42,"fix":{"range":[7462,7462],"text":" "},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"brace-style","severity":2,"message":"Statement inside of curly braces should be on next line.","line":235,"column":41,"nodeType":"Punctuator","messageId":"blockSameLine","endLine":235,"endColumn":42,"fix":{"range":[7462,7462],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-statements-per-line","severity":2,"message":"This line has 2 statements. Maximum allowed is 1.","line":235,"column":42,"nodeType":"ReturnStatement","messageId":"exceed","endLine":235,"endColumn":55,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"brace-style","severity":2,"message":"Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.","line":235,"column":56,"nodeType":"Punctuator","messageId":"singleLineClose","endLine":235,"endColumn":57,"fix":{"range":[7476,7476],"text":"\n"},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-prototype-builtins","severity":2,"message":"Do not access Object.prototype method 'hasOwnProperty' from target object.","line":240,"column":38,"nodeType":"CallExpression","messageId":"prototypeBuildIn","endLine":240,"endColumn":52,"suggestions":[{"messageId":"callObjectPrototype","data":{"prop":"hasOwnProperty"},"fix":{"range":[7655,7682],"text":"Object.prototype.hasOwnProperty.call(res.headers, "},"desc":"Call Object.prototype.hasOwnProperty explicitly."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"template-curly-spacing","severity":2,"message":"Expected space(s) after '${'.","line":240,"column":73,"nodeType":null,"messageId":"expectedAfter","endLine":240,"endColumn":75,"fix":{"range":[7704,7704],"text":" "},"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"template-curly-spacing","severity":2,"message":"Expected space(s) before '}'.","line":240,"column":78,"nodeType":null,"messageId":"expectedBefore","endLine":240,"endColumn":79,"fix":{"range":[7707,7707],"text":" "},"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":2,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst parallel = require('mocha.parallel');\nconst preq = require('preq');\nconst assert = require('../../utils/assert.js');\nconst Server = require('../../utils/server.js');\nconst URI = require('swagger-router').URI;\nconst OpenAPISchemaValidator = require('openapi-schema-validator').default;\nconst validator = new OpenAPISchemaValidator({ version: 3 });\n\nlet spec = null;\nlet baseUrl = null;\nconst server = new Server();\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 return {\n title,\n request: {\n uri: (baseUrl || 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(spec) {\n const ret = [];\n const paths = spec.paths;\n const defParams = spec['x-default-params'] || {};\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 ret.push(constructTestCase(\n ex.title,\n uri.toString({\n params: Object.assign({},\n defParams,\n ex.request.params || {})\n }),\n method,\n ex.request,\n ex.response || {}\n ));\n });\n });\n });\n\n return ret;\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 validateArray(val, resVal, key) {\n assert.deepEqual(Array.isArray(resVal), true, `Body field ${ key } is not an array!`);\n let arrVal;\n if (val.length === 1) {\n // special case: we have specified only one item in the expected body,\n // but what we really want is to check all of the returned items so\n // fill the expected array with as many items as the returned one\n if (resVal.length < 1) {\n throw new assert.AssertionError({\n message: `Expected more then one element in the field: ${ key }`\n });\n }\n arrVal = [];\n while (arrVal.length < resVal.length) {\n arrVal.push(val[0]);\n }\n } else {\n arrVal = val;\n }\n assert.deepEqual(arrVal.length, resVal.length,\n `Different size of array for field ${ key }, expected ${ arrVal.length\n } actual ${ resVal.length }`);\n arrVal.forEach((item, index) => {\n validateBody(resVal[index], item);\n });\n}\n\nfunction validateBody(resBody, expBody) {\n if (!expBody) { return true; } // eslint-disable-line\n if (!resBody) { return false; } // eslint-disable-line\n\n if (Buffer.isBuffer(resBody)) {\n resBody = resBody.toString();\n }\n if (expBody.constructor !== resBody.constructor) {\n if (expBody.constructor === String) {\n resBody = JSON.stringify(resBody);\n } else {\n resBody = JSON.parse(resBody);\n }\n }\n if (expBody.constructor === Object) {\n Object.keys(expBody).forEach((key) => {\n const val = expBody[key];\n // eslint-disable-next-line\n assert.deepEqual(resBody.hasOwnProperty(key), true, `Body field ${key} not found in response!`);\n if (val.constructor === Object) {\n validateBody(resBody[key], val);\n } else if (val.constructor === Array) {\n validateArray(val, resBody[key], key);\n } else {\n cmp(resBody[key], val, `${ key } body field mismatch!`);\n }\n });\n } else if (Array.isArray(expBody)) {\n validateArray(expBody, resBody, 'body');\n } else {\n cmp(resBody, expBody, 'Body mismatch!');\n }\n return true;\n}\n\nfunction validateTestResponse(testCase, res) {\n const expRes = testCase.response;\n\n assert.deepEqual(res.status, expRes.status);\n\n if (expRes.headers && !res.headers) {return false; } // eslint-disable-line\n\n Object.keys(expRes.headers).forEach((key) => {\n const val = expRes.headers[key];\n // eslint-disable-next-line\n assert.deepEqual(res.headers.hasOwnProperty(key), true, `Header ${key} not found in response!`);\n cmp(res.headers[key], val, `${ key } header mismatch!`);\n });\n\n validateBody(res.body || '', expRes.body);\n}\n\ndescribe('Swagger spec', function () {\n\n this.timeout(20000);\n\n before(() => server.start());\n after(() => server.stop());\n\n it('get the spec', () => {\n baseUrl = server.config.uri;\n return preq.get(`${ baseUrl }?spec`)\n .then((res) => {\n assert.status(200);\n assert.contentType(res, 'application/json');\n assert.notDeepEqual(res.body, undefined, 'No body received!');\n // save a copy\n spec = res.body;\n return spec;\n })\n .then((spec) => {\n const routeTests = () => {\n before(() => server.start());\n after(() => server.stop());\n\n constructTests(spec).forEach((testCase) => {\n it(testCase.title, function (done) {\n return preq(testCase.request)\n .then((res) => {\n assert.status(res, testCase.response.status);\n validateTestResponse(testCase, res);\n }, (err) => {\n assert.status(err, testCase.response.status);\n validateTestResponse(testCase, err);\n });\n });\n });\n };\n parallel('Monitoring routes', routeTests);\n });\n });\n\n it('should expose valid OpenAPI spec', () => {\n return preq.get({ uri: `${ server.config.uri }?spec` })\n .then((res) => {\n assert.deepEqual({ errors: [] }, validator.validate(res.body), 'Spec must have no validation errors');\n });\n });\n\n it('spec validation', () => {\n // check the high-level attributes\n [ 'info', 'openapi', '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, spec['x-default-params'] || {}, mSpec['x-amples']);\n });\n });\n });\n});\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/info/info.js","messages":[{"ruleId":"no-redeclare","severity":2,"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":2,"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":2,"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":2,"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":4,"fatalErrorCount":0,"warningCount":0,"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\ndescribe('service information', function () {\n\n this.timeout(20000);\n\n let infoUri = null;\n\n const server = new Server();\n\n before(() => {\n return server.start()\n .then(() => {\n infoUri = `${ server.config.uri }_info/`;\n });\n });\n\n after(() => server.stop());\n\n // common function used for generating requests\n // and checking their return values\n function checkRet(fieldName) {\n return preq.get({\n uri: infoUri + fieldName\n }).then((res) => {\n // check the returned Content-Type header\n assert.contentType(res, 'application/json');\n // the status as well\n assert.status(res, 200);\n // finally, check the body has the specified field\n assert.notDeepEqual(res.body, undefined, 'No body returned!');\n assert.notDeepEqual(res.body[fieldName], undefined, `No ${ fieldName } field returned!`);\n });\n }\n\n it('should get the service name', () => {\n return checkRet('name');\n });\n\n it('should get the service version', () => {\n return checkRet('version');\n });\n\n it('should redirect to the service home page', () => {\n return preq.get({\n uri: `${ infoUri }home`,\n followRedirect: false\n }).then((res) => {\n // check the status\n assert.status(res, 301);\n });\n });\n\n it('should get the service info', () => {\n return preq.get({\n uri: infoUri\n }).then((res) => {\n // check the status\n assert.status(res, 200);\n // check the returned Content-Type header\n assert.contentType(res, 'application/json');\n // inspect the body\n assert.notDeepEqual(res.body, undefined, 'No body returned!');\n assert.notDeepEqual(res.body.name, undefined, 'No name field returned!');\n assert.notDeepEqual(res.body.version, undefined, 'No version field returned!');\n assert.notDeepEqual(res.body.description, undefined, 'No description field returned!');\n assert.notDeepEqual(res.body.home, undefined, 'No home field returned!');\n });\n });\n});\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/eventstreams-util.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'integer' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\n\n'use strict';\n\nconst assert = require('assert');\n\nfunction deepEqual(result, expected, message) {\n\n try {\n assert.deepEqual(result, expected, message);\n } catch (e) {\n console.log(`Expected:\\n${ JSON.stringify(expected, null, 2) }`);\n console.log(`Result:\\n${ JSON.stringify(result, null, 2) }`);\n throw e;\n }\n\n}\n\n/**\n * Asserts whether the return status was as expected\n *\n * @param {Object} res\n * @param {integer} expected\n */\nfunction status(res, expected) {\n\n deepEqual(res.status, expected,\n `Expected status to be ${ expected }, but was ${ res.status }`);\n\n}\n\n/**\n * Asserts whether content type was as expected\n *\n * @param {Object} res\n * @param {string} expectedRegexString\n */\nfunction contentType(res, expectedRegexString) {\n\n const actual = res.headers['content-type'];\n assert.ok(RegExp(expectedRegexString).test(actual),\n `Expected content-type to match ${ expectedRegexString }, but was ${ actual }`);\n\n}\n\nfunction isDeepEqual(result, expected, message) {\n\n try {\n assert.deepEqual(result, expected, message);\n return true;\n } catch (e) {\n return false;\n }\n\n}\n\nfunction notDeepEqual(result, expected, message) {\n\n try {\n assert.notDeepEqual(result, expected, message);\n } catch (e) {\n console.log(`Not expected:\\n${ JSON.stringify(expected, null, 2) }`);\n console.log(`Result:\\n${ JSON.stringify(result, null, 2) }`);\n throw e;\n }\n\n}\n\nfunction fails(promise, onRejected) {\n\n let failed = false;\n\n function trackFailure(e) {\n failed = true;\n return onRejected(e);\n }\n\n function check() {\n if (!failed) {\n throw new Error('expected error was not thrown');\n }\n }\n\n return promise.catch(trackFailure).then(check);\n\n}\n\nmodule.exports.ok = assert.ok;\nmodule.exports.fails = fails;\nmodule.exports.deepEqual = deepEqual;\nmodule.exports.isDeepEqual = isDeepEqual;\nmodule.exports.notDeepEqual = notDeepEqual;\nmodule.exports.contentType = contentType;\nmodule.exports.status = status;\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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":[{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":9,"column":9,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":9,"endColumn":19},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_running'.","line":13,"column":14,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":13,"endColumn":27},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_impl'.","line":19,"column":30,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":19,"endColumn":48},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_runner'.","line":19,"column":30,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":19,"endColumn":42},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_impl'.","line":20,"column":27,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":20,"endColumn":45},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_runner'.","line":20,"column":27,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":20,"endColumn":39},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":22,"column":14,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":22,"endColumn":24},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":30,"column":17,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":30,"endColumn":27},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":34,"column":17,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":34,"endColumn":27},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_impl'.","line":40,"column":27,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":40,"endColumn":45},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_runner'.","line":40,"column":27,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":40,"endColumn":39},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":41,"column":27,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":41,"endColumn":37},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_impl'.","line":49,"column":19,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":49,"endColumn":37},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_runner'.","line":49,"column":19,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":49,"endColumn":31},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_spec'.","line":50,"column":19,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":50,"endColumn":29}],"suppressedMessages":[],"errorCount":15,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst preq = require('preq');\nconst TestRunner = require('service-runner/test/TestServer');\n\nclass TestServiceTemplateNodeRunner extends TestRunner {\n constructor(configPath = `${ __dirname }/../../config.yaml`) {\n super(configPath);\n this._spec = null;\n }\n\n get config() {\n if (!this._running) {\n throw new Error('Accessing test service config before starting the service');\n }\n\n // build the API endpoint URI by supposing the actual service\n // is the last one in the 'services' list in the config file\n const myServiceIdx = this._runner._impl.config.services.length - 1;\n const myService = this._runner._impl.config.services[myServiceIdx];\n const uri = `http://localhost:${ myService.conf.port }/`;\n if (!this._spec) {\n // We only want to load this once.\n preq.get(`${ uri }?spec`)\n .then((res) => {\n if (!res.body) {\n throw new Error('Failed to get spec');\n }\n // save a copy\n this._spec = res.body;\n })\n .catch((err) => {\n // this error will be detected later, so ignore it\n this._spec = { paths: {}, 'x-default-params': {} };\n })\n .then(() => {\n return {\n uri,\n service: myService,\n conf: this._runner._impl.config,\n spec: this._spec\n };\n });\n }\n\n return {\n uri,\n service: myService,\n conf: this._runner._impl.config,\n spec: this._spec\n };\n\n }\n}\n\nmodule.exports = TestServiceTemplateNodeRunner;\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/ui/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","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/ui/src/apis/EventStreamsApi.js","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'","line":1,"column":1,"nodeType":null}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Config from '@/config';\n\n/**\n * Gets the list of stream names that can be consumed\n * from the configured EventStreams instance.\n * @return {promise} When resolved, will contain the list of stream names\n * that can be consumed from the configured EventStreams.\n */\nfunction getAvailableStreams () {\n return new Promise(function (resolve) {\n fetch(Config.eventStreamsUri + '?spec')\n .then(function (response) { return response.json(); })\n .then(function (data) { resolve(formatAvailableStreams(data)); });\n });\n}\n\n// Extracts the stream names from the EventStreams spec.\nfunction formatAvailableStreams (spec) {\n // /v2/stream/{streams} endpoint spec has a list of all available streams.\n return spec.paths['/v2/stream/{streams}'].get.parameters[0].schema.items.enum;\n}\n\n/**\n * Opens an SSE EventSource that consumes the passed stream names.\n * Subscribes the passed callback to be called whenever an event is consumed.\n * Finally, returns the EventSource object, so it can be closed.\n * @param streamList {array} List of stream names to be consumed.\n * @param onMessage {function} To be called whenever an event is consumed.\n * @returns {object} Corresponding EventSource object.\n */\nfunction consumeStreams (streamList, onMessage) {\n const streamCSV = streamList.join(',');\n const streamUrl = Config.eventStreamsUri + \"v2/stream/\" + streamCSV;\n const eventSource = new EventSource(streamUrl);\n eventSource.onmessage = onMessage;\n return eventSource;\n}\n\nexport default {\n getAvailableStreams,\n consumeStreams\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/ui/src/config/index.js","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'","line":1,"column":1,"nodeType":null}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export default {\n // Set eventStreamsUri to the base URI of the EventStreams server\n // you want to view streams of.\n // A value of '/' will work with the locally running EventStreams service.\n // If you use this for development, don't use 'npm run serve', but rather\n // 'npm run build' and browse the ui from 'http://locahost:8092/v2/ui'\n // (port might change, depending on how you ran EventStreams server.js).\n // Caveat: You won't be able to consume streams this way locally.\n eventStreamsUri: '/'\n // eventStreamsUri: 'https://stream-beta.wmflabs.org/'\n // eventStreamsUri: 'https://stream.wikimedia.org/'\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/ui/src/main.js","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'","line":1,"column":1,"nodeType":null}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Vue from 'vue';\nimport App from './App.vue';\nimport router from './router';\n\n// Import element-ui.\nimport ElementUI from 'element-ui';\nimport 'element-ui/lib/theme-chalk/index.css';\nimport locale from 'element-ui/lib/locale/lang/en';\nVue.use(ElementUI, {locale});\n\nVue.config.productionTip = false;\n\nnew Vue({\n router,\n render: h => h(App)\n}).$mount('#app');\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/ui/src/router/index.js","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'","line":1,"column":1,"nodeType":null}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport Home from '../views/Home.vue'\n\nVue.use(VueRouter)\n\nconst routes = [\n {\n path: '/',\n name: 'Home',\n component: Home\n }\n]\n\nconst router = new VueRouter({\n routes\n})\n\nexport default router\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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/ui/src/utils/index.js","messages":[{"ruleId":null,"fatal":true,"severity":2,"message":"Parsing error: 'import' and 'export' may appear only with 'sourceType: module'","line":57,"column":1,"nodeType":null}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Returns an HTML blob that represents the passed yaml text.\n * The values will be color-highlighted, depending on type.\n */\nfunction yamlToHtml (yamlText) {\n const lines = yamlText.trim().split('\\n');\n\n const formattedLines = lines.map(function (line) {\n if (/^\\s*-/.test(line)) {\n // Line is a list item, thus is entirely a value (no label).\n const indentation = line.indexOf('-');\n const value = line.substr(indentation + 1).trim();\n return ' '.repeat(indentation) + '- ' + valueToHtml(value);\n } else {\n // Line has a label and optionally a value.\n const indentation = /^(\\s*)/g.exec(line)[1].length;\n const colonIndex = line.indexOf(':');\n const label = line.substr(indentation, colonIndex - indentation);\n const value = line.substr(colonIndex + 1).trim();\n return ' '.repeat(indentation) + label + ': ' + valueToHtml(value);\n }\n });\n return formattedLines.join('<br>');\n}\n\n// Returns the passed value withing an HTML span,\n// color-highlighting it depending on type.\nfunction valueToHtml (value) {\n var color;\n\n if (value === 'true' || value === 'false') {\n color = 'blue';\n } else if (isNaN(Number(value))) {\n color = 'green';\n } else {\n color = 'red';\n }\n\n const htmlValue = value.replaceAll('<', '<').replaceAll('>', '>');\n return `<span style=\"color:${color}\">${htmlValue}</span>`;\n}\n\n/**\n * Triggers a download of the given text\n * within a file with the specified filename.\n */\nfunction downloadText (text, filename) {\n const element = document.createElement('a');\n element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));\n element.setAttribute('download', filename);\n element.style.display = 'none';\n document.body.appendChild(element);\n element.click();\n document.body.removeChild(element);\n}\n\nexport default {\n yamlToHtml,\n downloadText\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","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":"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-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 ---
Traceback (most recent call last):
File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1534, in main
libup.run(args.repo, args.output, args.branch)
File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1472, in run
self.npm_upgrade(plan)
File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1054, in npm_upgrade
hook(update)
File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1290, in _handle_eslint
prefix = self._add_eslint_disables(fname, to_disable)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1230, in _add_eslint_disables
raise ValueError(f"Did not find eslint config for {fname}!")
ValueError: Did not find eslint config for /src/repo/server.js!