Add linter and minor bug fixes (#107)

This commit is contained in:
Simon Lecoq
2021-02-05 23:45:48 +01:00
committed by GitHub
parent 61e2f6e1a1
commit 882a93dea5
74 changed files with 1544 additions and 712 deletions

View File

@@ -24,11 +24,27 @@ jobs:
- name: Run tests - name: Run tests
run: docker run --workdir=/metrics --entrypoint="" lowlighter/metrics:${{ github.head_ref || 'master' }} npm test run: docker run --workdir=/metrics --entrypoint="" lowlighter/metrics:${{ github.head_ref || 'master' }} npm test
# Run linter to ensure new code respect coding rules
lint:
name: Apply linter
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup NodeJS
uses: actions/setup-node@v2
with:
node-version: 15
- name: Setup metrics
run: npm ci
- name: Run linter
run: npm run linter
# Run CodeQL on branch # Run CodeQL on branch
analyze: analyze:
name: Analyze code name: Analyze code
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build ] needs: [ lint ]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -44,7 +60,7 @@ jobs:
docker-master: docker-master:
name: Publish master to GitHub registry name: Publish master to GitHub registry
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build ] needs: [ build, lint ]
if: github.event_name == 'push' && github.ref == 'refs/heads/master' if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps: steps:
- name: Checkout repository - name: Checkout repository

594
package-lock.json generated
View File

@@ -416,6 +416,66 @@
"minimist": "^1.2.0" "minimist": "^1.2.0"
} }
}, },
"@eslint/eslintrc": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
"integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.1.1",
"espree": "^7.3.0",
"globals": "^12.1.0",
"ignore": "^4.0.6",
"import-fresh": "^3.2.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.20",
"minimatch": "^3.0.4",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"dev": true,
"requires": {
"type-fest": "^0.8.1"
}
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
}
}
},
"@istanbuljs/load-nyc-config": { "@istanbuljs/load-nyc-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1227,6 +1287,12 @@
"acorn-walk": "^7.1.1" "acorn-walk": "^7.1.1"
} }
}, },
"acorn-jsx": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
"dev": true
},
"acorn-walk": { "acorn-walk": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
@@ -1250,6 +1316,12 @@
"uri-js": "^4.2.2" "uri-js": "^4.2.2"
} }
}, },
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true
},
"ansi-escapes": { "ansi-escapes": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
@@ -1365,6 +1437,12 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true "dev": true
}, },
"astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"dev": true
},
"async": { "async": {
"version": "0.9.2", "version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
@@ -2261,6 +2339,15 @@
"integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==",
"dev": true "dev": true
}, },
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"dom-serializer": { "dom-serializer": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@@ -2356,6 +2443,15 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"enquirer": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
"integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
"dev": true,
"requires": {
"ansi-colors": "^4.1.1"
}
},
"entities": { "entities": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
@@ -2424,11 +2520,322 @@
"source-map": "~0.6.1" "source-map": "~0.6.1"
} }
}, },
"eslint": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.19.0.tgz",
"integrity": "sha512-CGlMgJY56JZ9ZSYhJuhow61lMPPjUzWmChFya71Z/jilVos7mR/jPgaEfVGgMBY5DshbKdG8Ezb8FDCHcoMEMg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@eslint/eslintrc": "^0.3.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
"eslint-scope": "^5.1.1",
"eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.1",
"esquery": "^1.2.0",
"esutils": "^2.0.2",
"file-entry-cache": "^6.0.0",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
"globals": "^12.1.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash": "^4.17.20",
"minimatch": "^3.0.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"progress": "^2.0.0",
"regexpp": "^3.1.0",
"semver": "^7.2.1",
"strip-ansi": "^6.0.0",
"strip-json-comments": "^3.1.0",
"table": "^6.0.4",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
}
},
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"globals": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"dev": true,
"requires": {
"type-fest": "^0.8.1"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"dev": true,
"requires": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.3"
}
},
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": {
"shebang-regex": "^3.0.0"
}
},
"shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"requires": {
"prelude-ls": "^1.2.1"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
}
}
},
"eslint-visitor-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz",
"integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==",
"dev": true
},
"espree": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
"integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==",
"dev": true,
"requires": {
"acorn": "^7.4.0",
"acorn-jsx": "^5.3.1",
"eslint-visitor-keys": "^1.3.0"
},
"dependencies": {
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
}
}
},
"esprima": { "esprima": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
}, },
"esquery": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
"integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
"dev": true,
"requires": {
"estraverse": "^5.1.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
},
"estraverse": { "estraverse": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
@@ -2771,6 +3178,15 @@
"pend": "~1.2.0" "pend": "~1.2.0"
} }
}, },
"file-entry-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz",
"integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==",
"dev": true,
"requires": {
"flat-cache": "^3.0.4"
}
},
"filelist": { "filelist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz",
@@ -2811,6 +3227,22 @@
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
} }
}, },
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"dev": true,
"requires": {
"flatted": "^3.1.0",
"rimraf": "^3.0.2"
}
},
"flatted": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz",
"integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
"dev": true
},
"follow-redirects": { "follow-redirects": {
"version": "1.13.2", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
@@ -2889,6 +3321,12 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
@@ -3007,6 +3445,15 @@
"path-is-absolute": "^1.0.0" "path-is-absolute": "^1.0.0"
} }
}, },
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"globals": { "globals": {
"version": "11.12.0", "version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -3214,6 +3661,12 @@
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
}, },
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"ignore-walk": { "ignore-walk": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
@@ -3231,6 +3684,24 @@
"node-fetch": "^2.6.0" "node-fetch": "^2.6.0"
} }
}, },
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"dependencies": {
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
}
}
},
"import-local": { "import-local": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz",
@@ -3390,6 +3861,12 @@
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true "dev": true
}, },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -3402,6 +3879,15 @@
"integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
"dev": true "dev": true
}, },
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-negative-zero": { "is-negative-zero": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
@@ -4935,6 +5421,12 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true "dev": true
}, },
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"json-stringify-safe": { "json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -5680,6 +6172,15 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
}, },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"parse-json": { "parse-json": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -6025,6 +6526,12 @@
"safe-regex": "^1.1.0" "safe-regex": "^1.1.0"
} }
}, },
"regexpp": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
"dev": true
},
"remove-trailing-separator": { "remove-trailing-separator": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -6133,6 +6640,12 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true "dev": true
}, },
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"require-main-filename": { "require-main-filename": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@@ -6522,6 +7035,43 @@
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true "dev": true
}, },
"slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
"integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
}
}
},
"snapdragon": { "snapdragon": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
@@ -6938,6 +7488,38 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true "dev": true
}, },
"table": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz",
"integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==",
"dev": true,
"requires": {
"ajv": "^7.0.2",
"lodash": "^4.17.20",
"slice-ansi": "^4.0.0",
"string-width": "^4.2.0"
},
"dependencies": {
"ajv": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.4.tgz",
"integrity": "sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
}
}
},
"tar": { "tar": {
"version": "4.4.13", "version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
@@ -7017,6 +7599,12 @@
"minimatch": "^3.0.4" "minimatch": "^3.0.4"
} }
}, },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"throat": { "throat": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
@@ -7316,6 +7904,12 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"v8-compile-cache": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
"integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
"dev": true
},
"v8-to-istanbul": { "v8-to-istanbul": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz",

View File

@@ -8,7 +8,8 @@
"test": "npx jest", "test": "npx jest",
"index": "node .github/index.mjs", "index": "node .github/index.mjs",
"upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest compression@latest ejs@latest express@latest express-rate-limit@latest faker@latest image-to-base64@latest js-yaml@latest memory-cache@latest prismjs@latest puppeteer@latest svgo@latest vue@latest jest@latest libxmljs@latest", "upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest compression@latest ejs@latest express@latest express-rate-limit@latest faker@latest image-to-base64@latest js-yaml@latest memory-cache@latest prismjs@latest puppeteer@latest svgo@latest vue@latest jest@latest libxmljs@latest",
"quickstart": "node .github/quickstart/index.mjs" "quickstart": "node .github/quickstart/index.mjs",
"linter": "npx eslint source/**/*.mjs"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -43,6 +44,7 @@
"vue-prism-component": "^1.2.0" "vue-prism-component": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.19.0",
"jest": "^26.6.3", "jest": "^26.6.3",
"libxmljs": "^0.19.7" "libxmljs": "^0.19.7"
}, },

158
source/.eslintrc.yml Normal file
View File

@@ -0,0 +1,158 @@
# Use recommended rules
extends: eslint:recommended
# Environment
env:
node: yes
es2021: yes
parserOptions:
ecmaVersion: 2021
sourceType: module
# Globally defined variables
globals:
# Puppeteer variables
document: readonly
window: readonly
XMLSerializer: readonly
# Rules
rules:
# Avoid useless statements
no-unused-vars: [error, {argsIgnorePattern: "^_"}]
no-unused-expressions: error
no-return-await: error
no-empty-function: error
no-useless-call: error
no-useless-constructor: error
no-useless-concat: error
no-useless-computed-key: error
no-useless-backreference: error
no-self-compare: error
no-extra-label: error
no-undef-init: error
# Avoid visual pollution
semi: [error, never]
semi-spacing: error
semi-style: [error, first]
curly: [error, multi-or-nest]
dot-notation: error
# Avoid confusing code
no-label-var: error
no-bitwise: error
new-cap: error
new-parens: error
func-name-matching: error
no-extend-native: error
no-extra-bind: error
# Avoid deprecated or legacy JavaScript
no-var: error
no-caller: error
no-alert: error
no-script-url: error
no-eval: error
no-implied-eval: error
no-implicit-globals: error
no-proto: error
no-iterator: error
no-new-object: error
# Code integrity
no-unsafe-optional-chaining: error
no-duplicate-imports: error
no-promise-executor-return: error
eqeqeq: error
# Code simplicity
max-depth: [error, 10]
max-nested-callbacks: error
max-params: [error, 3]
max-statements-per-line: error
newline-per-chained-call: [error, {ignoreChainWithDepth: 6}]
object-shorthand: error
# Code readability
default-case-last: error
default-param-last: error
no-else-return: error
no-lonely-if: error
no-multiple-empty-lines: error
no-multi-str: error
no-multi-assign: error
no-inline-comments: error
yoda: error
max-classes-per-file: [error, 1]
grouped-accessor-pairs: error
# Prefer using new syntax and features
prefer-regex-literals: error
prefer-named-capture-group: error
prefer-arrow-callback: error
prefer-destructuring: error
prefer-numeric-literals: error
prefer-exponentiation-operator: error
prefer-spread: error
prefer-object-spread: error
prefer-template: error
# Allow additional features
no-ex-assign: off
no-unsafe-finally: off
# =========================================================================
# Rules below are really motivated by
# Code style (general rules)
no-tabs: error
no-trailing-spaces: error
brace-style: [error, stroustrup]
comma-dangle: [error, always-multiline]
comma-style: error
sort-vars: error
# Coding style (quoting rules)
quote-props: [error, as-needed, {numbers: true}]
quotes: [error, double, {avoidEscape: true}]
template-curly-spacing: error
# Coding style (comments rules)
line-comment-position: error
capitalized-comments: error
multiline-comment-style: error
# Coding style (spacing rules)
block-spacing: [error, always]
comma-spacing: error
func-call-spacing: error
arrow-spacing: error
generator-star-spacing: error
object-curly-spacing: [error, never]
rest-spread-spacing: error
key-spacing: [error, {afterColon: false}]
computed-property-spacing: error
switch-colon-spacing: [error, {after: false}]
array-bracket-spacing: [error, never]
no-whitespace-before-property: error
space-before-function-paren: [error, never]
space-in-parens: error
spaced-comment: [error, never]
lines-between-class-members: error
keyword-spacing: error
array-bracket-newline: [error, consistent]
array-element-newline: [error, consistent]
# Coding style (function rules)
no-new: error
no-new-func: error
no-new-wrappers: error
func-names: [error, never]
function-call-argument-newline: [error, never]
function-paren-newline: [error, never]
no-extra-parens: [error, functions]
arrow-body-style: [error, as-needed]
arrow-parens: [error, as-needed]
implicit-arrow-linebreak: error

View File

@@ -5,7 +5,7 @@
import setup from "../metrics/setup.mjs" import setup from "../metrics/setup.mjs"
import mocks from "../mocks/index.mjs" import mocks from "../mocks/index.mjs"
import metrics from "../metrics/index.mjs" import metrics from "../metrics/index.mjs"
process.on("unhandledRejection", error => { throw error }) process.on("unhandledRejection", error => { throw error }) //eslint-disable-line max-statements-per-line, brace-style
//Debug message buffer //Debug message buffer
let DEBUG = true let DEBUG = true
@@ -28,15 +28,16 @@
info.break = () => console.log("─".repeat(88)) info.break = () => console.log("─".repeat(88))
//Runner //Runner
;(async function() {
try { try {
//Initialization //Initialization
info.break() info.break()
info.section(`Metrics`) info.section("Metrics")
//Skip process if needed //Skip process if needed
if ((github.context.eventName === "push")&&(github.context.payload?.head_commit)) { if ((github.context.eventName === "push")&&(github.context.payload?.head_commit)) {
if (/\[Skip GitHub Action\]/.test(github.context.payload.head_commit.message)) { if (/\[Skip GitHub Action\]/.test(github.context.payload.head_commit.message)) {
console.log(`Skipped because [Skip GitHub Action] is in commit message`) console.log("Skipped because [Skip GitHub Action] is in commit message")
process.exit(0) process.exit(0)
} }
} }
@@ -78,7 +79,7 @@
throw new Error("You must provide a valid GitHub token to gather your metrics") throw new Error("You must provide a valid GitHub token to gather your metrics")
conf.settings.token = token conf.settings.token = token
const api = {} const api = {}
api.graphql = octokit.graphql.defaults({headers:{authorization: `token ${token}`}}) api.graphql = octokit.graphql.defaults({headers:{authorization:`token ${token}`}})
info("Github GraphQL API", "ok") info("Github GraphQL API", "ok")
api.rest = github.getOctokit(token) api.rest = github.getOctokit(token)
info("Github REST API", "ok") info("Github REST API", "ok")
@@ -133,11 +134,12 @@
object(expression: "${committer.branch}:${filename}") { ... on Blob { oid } } object(expression: "${committer.branch}:${filename}") { ... on Blob { oid } }
} }
} }
`, `, {headers:{authorization:`token ${committer.token}`}})
{headers:{authorization:`token ${committer.token}`}}
)
committer.sha = oid committer.sha = oid
} catch (error) { console.debug(error) } }
catch (error) {
console.debug(error)
}
info("Previous render sha", committer.sha ?? "(none)") info("Previous render sha", committer.sha ?? "(none)")
} }
else else
@@ -206,23 +208,25 @@
...github.context.repo, path:filename, message:`Update ${filename} - [Skip GitHub Action]`, ...github.context.repo, path:filename, message:`Update ${filename} - [Skip GitHub Action]`,
content:Buffer.from(rendered).toString("base64"), content:Buffer.from(rendered).toString("base64"),
branch:committer.branch, branch:committer.branch,
...(committer.sha ? {sha:committer.sha} : {}) ...(committer.sha ? {sha:committer.sha} : {}),
}) })
info("Commit to repository", "success") info("Commit to repository", "success")
} }
//Success //Success
info.break() info.break()
console.log(`Success, thanks for using metrics!`) console.log("Success, thanks for using metrics!")
process.exit(0) process.exit(0)
} }
//Errors //Errors
catch (error) { catch (error) {
console.error(error) console.error(error)
//Print debug buffer if debug was not enabled (if it is, it's already logged on the fly) //Print debug buffer if debug was not enabled (if it is, it's already logged on the fly)
if (!DEBUG) if (!DEBUG) {
for (const log of [info.break(), "An error occured, logging debug message :", ...debugged]) for (const log of [info.break(), "An error occured, logging debug message :", ...debugged])
console.log(log) console.log(log)
}
core.setFailed(error.message) core.setFailed(error.message)
process.exit(1) process.exit(1)
} }
})()

View File

@@ -1,8 +1,8 @@
//Imports //Imports
import util from "util"
import ejs from "ejs"
import SVGO from "svgo"
import * as utils from "./utils.mjs" import * as utils from "./utils.mjs"
import ejs from "ejs"
import util from "util"
import SVGO from "svgo"
//Setup //Setup
export default async function metrics({login, q, dflags = []}, {graphql, rest, plugins, conf, die = false, verify = false, convert = null}, {Plugins, Templates}) { export default async function metrics({login, q, dflags = []}, {graphql, rest, plugins, conf, die = false, verify = false, convert = null}, {Plugins, Templates}) {
@@ -23,7 +23,7 @@
//Initialization //Initialization
const pending = [] const pending = []
const queries = conf.queries const {queries} = conf
const data = {animated:true, base:{}, config:{}, errors:[], plugins:{}, computed:{}} const data = {animated:true, base:{}, config:{}, errors:[], plugins:{}, computed:{}}
const imports = {plugins:Plugins, templates:Templates, metadata:conf.metadata, ...utils} const imports = {plugins:Plugins, templates:Templates, metadata:conf.metadata, ...utils}
@@ -47,7 +47,7 @@
if (errors.length) { if (errors.length) {
console.warn(`metrics/compute/${login} > ${errors.length} errors !`) console.warn(`metrics/compute/${login} > ${errors.length} errors !`)
if (die) if (die)
throw new Error(`An error occured during rendering, dying`) throw new Error("An error occured during rendering, dying")
else else
console.warn(util.inspect(errors, {depth:Infinity, maxStringLength:256})) console.warn(util.inspect(errors, {depth:Infinity, maxStringLength:256}))
} }

View File

@@ -1,10 +1,10 @@
//Imports //Imports
import fs from "fs" import fs from "fs"
import path from "path" import path from "path"
import yaml from "js-yaml"
import url from "url" import url from "url"
import yaml from "js-yaml"
/** Metadata descriptor parser */ /**Metadata descriptor parser */
export default async function metadata({log = true} = {}) { export default async function metadata({log = true} = {}) {
//Paths //Paths
const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../../..") const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../../..")
@@ -16,7 +16,7 @@
//Load plugins metadata //Load plugins metadata
let Plugins = {} let Plugins = {}
logger(`metrics/metadata > loading plugins metadata`) logger("metrics/metadata > loading plugins metadata")
for (const name of await fs.promises.readdir(__plugins)) { for (const name of await fs.promises.readdir(__plugins)) {
if (!(await fs.promises.lstat(path.join(__plugins, name))).isDirectory()) if (!(await fs.promises.lstat(path.join(__plugins, name))).isDirectory())
continue continue
@@ -29,7 +29,7 @@
//Load templates metadata //Load templates metadata
let Templates = {} let Templates = {}
logger(`metrics/metadata > loading templates metadata`) logger("metrics/metadata > loading templates metadata")
for (const name of await fs.promises.readdir(__templates)) { for (const name of await fs.promises.readdir(__templates)) {
if (!(await fs.promises.lstat(path.join(__templates, name))).isDirectory()) if (!(await fs.promises.lstat(path.join(__templates, name))).isDirectory())
continue continue
@@ -46,8 +46,8 @@
return {plugins:Plugins, templates:Templates} return {plugins:Plugins, templates:Templates}
} }
/** Metadata extractor for templates */ /**Metadata extractor for templates */
metadata.plugin = async function ({__plugins, name, logger}) { metadata.plugin = async function({__plugins, name, logger}) {
try { try {
//Load meta descriptor //Load meta descriptor
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}` const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}`
@@ -55,7 +55,7 @@
//Inputs parser //Inputs parser
{ {
meta.inputs = function ({data:{user = null} = {}, q, account}, defaults = {}) { meta.inputs = function({data:{user = null} = {}, q, account}, defaults = {}) {
//Support check //Support check
if (!account) if (!account)
logger(`metrics/inputs > account type not set for plugin ${name}!`) logger(`metrics/inputs > account type not set for plugin ${name}!`)
@@ -142,7 +142,7 @@
return value return value
} }
} }
})(defaults[key] ?? defaulted) })(defaults[key] ?? defaulted),
])) ]))
logger(`metrics/inputs > ${name} > ${JSON.stringify(result)}`) logger(`metrics/inputs > ${name} > ${JSON.stringify(result)}`)
return result return result
@@ -154,7 +154,7 @@
{ {
//Extract comments //Extract comments
const comments = {} const comments = {}
raw.split(/(\r?\n){2,}/m) raw.split(/(?:\r?\n){2,}/m)
.map(x => x.trim()).filter(x => x) .map(x => x.trim()).filter(x => x)
.map(x => x.split("\n").map(y => y.trim()).join("\n")) .map(x => x.split("\n").map(y => y.trim()).join("\n"))
.map(x => { .map(x => {
@@ -168,12 +168,12 @@
key, key,
{ {
comment:comments[key] ?? "", comment:comments[key] ?? "",
descriptor:yaml.dump({[key]:Object.fromEntries(Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)))}, {quotingType:'"', noCompatMode:true}) descriptor:yaml.dump({[key]:Object.fromEntries(Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)))}, {quotingType:'"', noCompatMode:true}),
} },
])) ]))
//Action inputs //Action inputs
meta.inputs.action = function ({core}) { meta.inputs.action = function({core}) {
//Build query object from inputs //Build query object from inputs
const q = {} const q = {}
for (const key of Object.keys(inputs)) { for (const key of Object.keys(inputs)) {
@@ -207,7 +207,6 @@
case "string":{ case "string":{
if (Array.isArray(values)) if (Array.isArray(values))
return {text, type:"select", values} return {text, type:"select", values}
else
return {text, type:"text", placeholder:example ?? defaulted, defaulted} return {text, type:"text", placeholder:example ?? defaulted, defaulted}
} }
case "json": case "json":
@@ -215,7 +214,7 @@
default: default:
return null return null
} }
})() })(),
]).filter(([key, value]) => (value)&&(key !== name))) ]).filter(([key, value]) => (value)&&(key !== name)))
} }
@@ -241,8 +240,8 @@
} }
} }
/** Metadata extractor for templates */ /**Metadata extractor for templates */
metadata.template = async function ({__templates, name, plugins, logger}) { metadata.template = async function({__templates, name, plugins, logger}) {
try { try {
//Load meta descriptor //Load meta descriptor
const raw = `${await fs.promises.readFile(path.join(__templates, name, "README.md"), "utf-8")}` const raw = `${await fs.promises.readFile(path.join(__templates, name, "README.md"), "utf-8")}`
@@ -262,7 +261,7 @@
return { return {
name:raw.match(/^### (?<name>[\s\S]+?)\n/)?.groups?.name?.trim(), name:raw.match(/^### (?<name>[\s\S]+?)\n/)?.groups?.name?.trim(),
readme:{ readme:{
demo:raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? (name === "community" ? `<td align="center">See <a href="/source/templates/community/README.md">documentation</a> 🌍</td>` : "<td></td>"), demo:raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? (name === "community" ? "<td align=\"center\">See <a href=\"/source/templates/community/README.md\">documentation</a> 🌍</td>" : "<td></td>"),
compatibility:{...compatibility, base:true}, compatibility:{...compatibility, base:true},
}, },
} }
@@ -273,10 +272,10 @@
} }
} }
/** Metadata converters */ /**Metadata converters */
metadata.to = { metadata.to = {
query(key, {name = null} = {}) { query(key, {name = null} = {}) {
key = key.replace(/^plugin_/, "").replace(/_/g, ".") key = key.replace(/^plugin_/, "").replace(/_/g, ".")
return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key
} },
} }

View File

@@ -1,17 +1,17 @@
//Imports //Imports
import fs from "fs" import fs from "fs"
import metadata from "./metadata.mjs"
import path from "path" import path from "path"
import processes from "child_process"
import util from "util" import util from "util"
import url from "url" import url from "url"
import processes from "child_process"
import metadata from "./metadata.mjs"
//Templates and plugins //Templates and plugins
const Templates = {} const Templates = {}
const Plugins = {} const Plugins = {}
/** Setup */ /**Setup */
export default async function ({log = true, nosettings = false, community = {}} = {}) { export default async function({log = true, nosettings = false, community = {}} = {}) {
//Paths //Paths
const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../../..") const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../../..")
@@ -24,7 +24,7 @@
//Init //Init
const logger = log ? console.debug : () => null const logger = log ? console.debug : () => null
logger(`metrics/setup > setup`) logger("metrics/setup > setup")
const conf = { const conf = {
templates:{}, templates:{},
queries:{}, queries:{},
@@ -34,21 +34,21 @@
statics:__statics, statics:__statics,
templates:__templates, templates:__templates,
node_modules:__modules, node_modules:__modules,
} },
} }
//Load settings //Load settings
logger(`metrics/setup > load settings.json`) logger("metrics/setup > load settings.json")
if (fs.existsSync(__settings)) { if (fs.existsSync(__settings)) {
if (nosettings) if (nosettings)
logger(`metrics/setup > load settings.json > skipped because no settings is enabled`) logger("metrics/setup > load settings.json > skipped because no settings is enabled")
else { else {
conf.settings = JSON.parse(`${await fs.promises.readFile(__settings)}`) conf.settings = JSON.parse(`${await fs.promises.readFile(__settings)}`)
logger(`metrics/setup > load settings.json > success`) logger("metrics/setup > load settings.json > success")
} }
} }
else else
logger(`metrics/setup > load settings.json > (missing)`) logger("metrics/setup > load settings.json > (missing)")
if (!conf.settings.templates) if (!conf.settings.templates)
conf.settings.templates = {default:"classic", enabled:[]} conf.settings.templates = {default:"classic", enabled:[]}
if (!conf.settings.plugins) if (!conf.settings.plugins)
@@ -59,13 +59,13 @@
logger(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256})) logger(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256}))
//Load package settings //Load package settings
logger(`metrics/setup > load package.json`) logger("metrics/setup > load package.json")
conf.package = JSON.parse(`${await fs.promises.readFile(__package)}`) conf.package = JSON.parse(`${await fs.promises.readFile(__package)}`)
logger(`metrics/setup > load package.json > success`) logger("metrics/setup > load package.json > success")
//Load community templates //Load community templates
if ((typeof conf.settings.community.templates === "string")&&(conf.settings.community.templates.length)) { if ((typeof conf.settings.community.templates === "string")&&(conf.settings.community.templates.length)) {
logger(`metrics/setup > parsing community templates list`) logger("metrics/setup > parsing community templates list")
conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])] conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])]
} }
if ((Array.isArray(conf.settings.community.templates))&&(conf.settings.community.templates.length)) { if ((Array.isArray(conf.settings.community.templates))&&(conf.settings.community.templates.length)) {
@@ -77,7 +77,7 @@
try { try {
//Parse community template //Parse community template
logger(`metrics/setup > load community template ${template}`) logger(`metrics/setup > load community template ${template}`)
const {repo, branch, name, trust = false} = template.match(/^(?<repo>[\s\S]+?)@(?<branch>[\s\S]+?):(?<name>[\s\S]+?)(?<trust>[+]trust)?$/)?.groups const {repo, branch, name, trust = false} = template.match(/^(?<repo>[\s\S]+?)@(?<branch>[\s\S]+?):(?<name>[\s\S]+?)(?<trust>[+]trust)?$/)?.groups ?? null
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}` const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
logger(`metrics/setup > run ${command}`) logger(`metrics/setup > run ${command}`)
//Clone remote repository //Clone remote repository
@@ -99,14 +99,15 @@
logger(`metrics/setup > clean ${repo}@${branch}`) logger(`metrics/setup > clean ${repo}@${branch}`)
await fs.promises.rmdir(path.join(__templates, ".community"), {recursive:true}) await fs.promises.rmdir(path.join(__templates, ".community"), {recursive:true})
logger(`metrics/setup > loaded community template ${name}`) logger(`metrics/setup > loaded community template ${name}`)
} catch (error) { }
catch (error) {
logger(`metrics/setup > failed to load community template ${template}`) logger(`metrics/setup > failed to load community template ${template}`)
logger(error) logger(error)
} }
} }
} }
else else
logger(`metrics/setup > no community templates to install`) logger("metrics/setup > no community templates to install")
//Load templates //Load templates
for (const name of await fs.promises.readdir(__templates)) { for (const name of await fs.promises.readdir(__templates)) {
@@ -122,7 +123,7 @@
conf.templates[name] = {image, style, fonts, partials, views:[directory]} conf.templates[name] = {image, style, fonts, partials, views:[directory]}
//Cache templates scripts //Cache templates scripts
Templates[name] = await (async () => { Templates[name] = await (async() => {
const template = path.join(directory, "template.mjs") const template = path.join(directory, "template.mjs")
const fallback = path.join(__templates, "classic", "template.mjs") const fallback = path.join(__templates, "classic", "template.mjs")
return (await import(url.pathToFileURL(fs.existsSync(template) ? template : fallback).href)).default return (await import(url.pathToFileURL(fs.existsSync(template) ? template : fallback).href)).default
@@ -137,7 +138,7 @@
const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`) const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`)
logger(`metrics/setup > reload template [${name}] > success`) logger(`metrics/setup > reload template [${name}] > success`)
return {image, style, fonts, partials, views:[directory]} return {image, style, fonts, partials, views:[directory]}
} },
}) })
} }
} }
@@ -156,11 +157,12 @@
const __queries = path.join(directory, "queries") const __queries = path.join(directory, "queries")
if (fs.existsSync(__queries)) { if (fs.existsSync(__queries)) {
//Alias for default query //Alias for default query
const queries = conf.queries[name] = function () { const queries = function() {
if (!queries[name]) if (!queries[name])
throw new ReferenceError(`Default query for ${name} undefined`) throw new ReferenceError(`Default query for ${name} undefined`)
return queries[name](...arguments) return queries[name](...arguments)
} }
conf.queries[name] = queries
//Load queries //Load queries
for (const file of await fs.promises.readdir(__queries)) { for (const file of await fs.promises.readdir(__queries)) {
//Cache queries //Cache queries
@@ -176,7 +178,7 @@
const raw = `${fs.readFileSync(path.join(__queries, file))}` const raw = `${fs.readFileSync(path.join(__queries, file))}`
logger(`metrics/setup > reload query [${name}/${query}] > success`) logger(`metrics/setup > reload query [${name}/${query}] > success`)
return raw return raw
} },
}) })
} }
} }
@@ -194,10 +196,12 @@
conf.metadata = await metadata({log}) conf.metadata = await metadata({log})
//Set no token property //Set no token property
Object.defineProperty(conf.settings, "notoken", {get() { return conf.settings.token === "NOT_NEEDED" }}) Object.defineProperty(conf.settings, "notoken", {get() {
return conf.settings.token === "NOT_NEEDED"
}})
//Conf //Conf
logger(`metrics/setup > setup > success`) logger("metrics/setup > setup > success")
return {Templates, Plugins, conf} return {Templates, Plugins, conf}
} }

View File

@@ -8,48 +8,50 @@
import axios from "axios" import axios from "axios"
import puppeteer from "puppeteer" import puppeteer from "puppeteer"
import imgb64 from "image-to-base64" import imgb64 from "image-to-base64"
import dayjs from 'dayjs'; import dayjs from "dayjs"
import utc from 'dayjs/plugin/utc.js'; import utc from "dayjs/plugin/utc.js"
dayjs.extend(utc); dayjs.extend(utc)
export {fs, os, paths, url, util, processes, axios, puppeteer, imgb64, dayjs}; export {fs, os, paths, url, util, processes, axios, puppeteer, imgb64, dayjs}
/** Returns module __dirname */ /**Returns module __dirname */
export function __module(module) { export function __module(module) {
return paths.join(paths.dirname(url.fileURLToPath(module))) return paths.join(paths.dirname(url.fileURLToPath(module)))
} }
/** Plural formatter */ /**Plural formatter */
export function s(value, end = "") { export function s(value, end = "") {
return value !== 1 ? {y:"ies", "":"s"}[end] : end return value !== 1 ? {y:"ies", "":"s"}[end] : end
} }
/** Formatter */ /**Formatter */
export function format(n, {sign = false} = {}) { export function format(n, {sign = false} = {}) {
for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) for (const {u, v} of [{u:"b", v:10**9}, {u:"m", v:10**6}, {u:"k", v:10**3}]) {
if (n/v >= 1) if (n/v >= 1)
return `${(sign)&&(n > 0) ? "+" : ""}${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}` return `${(sign)&&(n > 0) ? "+" : ""}${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
}
return `${(sign)&&(n > 0) ? "+" : ""}${n}` return `${(sign)&&(n > 0) ? "+" : ""}${n}`
} }
/** Bytes formatter */ /**Bytes formatter */
export function bytes(n) { export function bytes(n) {
for (const {u, v} of [{u:"E", v:10**18}, {u:"P", v:10**15}, {u:"T", v:10**12}, {u:"G", v:10**9}, {u:"M", v:10**6}, {u:"k", v:10**3}]) for (const {u, v} of [{u:"E", v:10**18}, {u:"P", v:10**15}, {u:"T", v:10**12}, {u:"G", v:10**9}, {u:"M", v:10**6}, {u:"k", v:10**3}]) {
if (n/v >= 1) if (n/v >= 1)
return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B` return `${(n/v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B`
}
return `${n} byte${n > 1 ? "s" : ""}` return `${n} byte${n > 1 ? "s" : ""}`
} }
format.bytes = bytes format.bytes = bytes
/** Percentage formatter */ /**Percentage formatter */
export function percentage(n, {rescale = true} = {}) { export function percentage(n, {rescale = true} = {}) {
return `${(n*(rescale ? 100 : 1)).toFixed(2) return `${(n*(rescale ? 100 : 1)).toFixed(2)
.replace(/(?<=[.])([1-9]*)(0+)$/, (m, a, b) => a) .replace(/(?<=[.])(?<decimal>[1-9]*)0+$/, "$<decimal>")
.replace(/[.]$/, "")}%` .replace(/[.]$/, "")}%`
} }
format.percentage = percentage format.percentage = percentage
/** Text ellipsis formatter */ /**Text ellipsis formatter */
export function ellipsis(text, {length = 20} = {}) { export function ellipsis(text, {length = 20} = {}) {
text = `${text}` text = `${text}`
if (text.length < length) if (text.length < length)
@@ -58,7 +60,7 @@
} }
format.ellipsis = ellipsis format.ellipsis = ellipsis
/** Array shuffler */ /**Array shuffler */
export function shuffle(array) { export function shuffle(array) {
for (let i = array.length-1; i > 0; i--) { for (let i = array.length-1; i > 0; i--) {
const j = Math.floor(Math.random()*(i+1)) const j = Math.floor(Math.random()*(i+1))
@@ -67,7 +69,7 @@
return array return array
} }
/** Escape html */ /**Escape html */
export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) { export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) {
return string return string
.replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&amp;" : "&") .replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&amp;" : "&")
@@ -77,18 +79,19 @@
.replace(/'/g, u["'"] ? "&apos;" : "'") .replace(/'/g, u["'"] ? "&apos;" : "'")
} }
/** Expand url */ /**Expand url */
export async function urlexpand(url) { export async function urlexpand(url) {
try { try {
return (await axios.get(url)).request.res.responseUrl return (await axios.get(url)).request.res.responseUrl
} catch { }
catch {
return url return url
} }
} }
/** Run command */ /**Run command */
export async function run(command, options) { export async function run(command, options) {
return await new Promise((solve, reject) => { return new Promise((solve, reject) => {
console.debug(`metrics/command > ${command}`) console.debug(`metrics/command > ${command}`)
const child = processes.exec(command, options) const child = processes.exec(command, options)
let [stdout, stderr] = ["", ""] let [stdout, stderr] = ["", ""]
@@ -101,7 +104,7 @@
}) })
} }
/** Render svg */ /**Render svg */
export async function svgresize(svg, {paddings = ["6%"], convert} = {}) { export async function svgresize(svg, {paddings = ["6%"], convert} = {}) {
//Instantiate browser if needed //Instantiate browser if needed
if (!svgresize.browser) { if (!svgresize.browser) {
@@ -144,7 +147,7 @@
return {resized, mime} return {resized, mime}
} }
/** Wait */ /**Wait */
export async function wait(seconds) { export async function wait(seconds) {
await new Promise(solve => setTimeout(solve, seconds*1000)) await new Promise(solve => setTimeout(solve, seconds*1000)) //eslint-disable-line no-promise-executor-return
} }

View File

@@ -0,0 +1,5 @@
# Overrides enforced rules for mocks
rules:
max-params: off
no-unused-vars: off
prefer-named-capture-group: off

View File

@@ -1,5 +1,5 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, options, login = faker.internet.userName()}) { export default function({faker, url, options, login = faker.internet.userName()}) {
//Last.fm api //Last.fm api
if (/^https:..ws.audioscrobbler.com.*$/.test(url)) { if (/^https:..ws.audioscrobbler.com.*$/.test(url)) {
//Get recently played tracks //Get recently played tracks

View File

@@ -1,5 +1,5 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, options, login = faker.internet.userName()}) { export default function({faker, url, options, login = faker.internet.userName()}) {
//Tested url //Tested url
const tested = url.match(/&url=(?<tested>.*?)(?:&|$)/)?.groups?.tested ?? faker.internet.url() const tested = url.match(/&url=(?<tested>.*?)(?:&|$)/)?.groups?.tested ?? faker.internet.url()
//Pagespeed api //Pagespeed api
@@ -20,17 +20,17 @@
"final-screenshot":{ "final-screenshot":{
id:"final-screenshot", id:"final-screenshot",
title:"Final Screenshot", title:"Final Screenshot",
score: null, score:null,
details:{ details:{
data:"data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==", data:"data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
type:"screenshot", type:"screenshot",
timestamp:Date.now() timestamp:Date.now(),
} },
}, },
metrics:{ metrics:{
id:"metrics", id:"metrics",
title:"Metrics", title:"Metrics",
score: null, score:null,
details:{ details:{
items:[ items:[
{ {
@@ -68,9 +68,9 @@
interactive:faker.random.number(1000), interactive:faker.random.number(1000),
observedNavigationStartTs:faker.time.recent(), observedNavigationStartTs:faker.time.recent(),
observedNavigationStart:faker.random.number(10), observedNavigationStart:faker.random.number(10),
observedFirstMeaningfulPaintTs:faker.time.recent() observedFirstMeaningfulPaintTs:faker.time.recent(),
}, },
] ],
}, },
}, },
}, },
@@ -90,15 +90,15 @@
title:"Accessibility", title:"Accessibility",
score:faker.random.float({max:1}), score:faker.random.float({max:1}),
}, },
performance: { performance:{
id:"performance", id:"performance",
title:"Performance", title:"Performance",
score:faker.random.float({max:1}), score:faker.random.float({max:1}),
} },
}, },
}, },
analysisUTCTimestamp:`${faker.date.recent()}`, analysisUTCTimestamp:`${faker.date.recent()}`,
} },
}) })
} }
} }

View File

@@ -1,5 +1,5 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, options, login = faker.internet.userName()}) { export default function({faker, url, options, login = faker.internet.userName()}) {
//Spotify api //Spotify api
if (/^https:..api.spotify.com.*$/.test(url)) { if (/^https:..api.spotify.com.*$/.test(url)) {
//Get recently played tracks //Get recently played tracks
@@ -19,24 +19,24 @@
{ {
name:artist, name:artist,
type:"artist", type:"artist",
} },
], ],
images:[ images:[
{ {
height:640, height:640,
url:faker.image.abstract(), url:faker.image.abstract(),
width:640 width:640,
}, },
{ {
height:300, height:300,
url:faker.image.abstract(), url:faker.image.abstract(),
width:300 width:300,
}, },
{ {
height:64, height:64,
url:faker.image.abstract(), url:faker.image.abstract(),
width:64 width:64,
} },
], ],
name:track, name:track,
release_date:`${faker.date.past()}`.substring(0, 10), release_date:`${faker.date.past()}`.substring(0, 10),
@@ -46,7 +46,7 @@
{ {
name:artist, name:artist,
type:"artist", type:"artist",
} },
], ],
name:track, name:track,
preview_url:faker.internet.url(), preview_url:faker.internet.url(),
@@ -55,10 +55,10 @@
played_at:`${faker.date.recent()}`, played_at:`${faker.date.recent()}`,
context:{ context:{
type:"album", type:"album",
} },
}, },
], ],
} },
}) })
} }
} }

View File

@@ -1,5 +1,5 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, options, login = faker.internet.userName()}) { export default function({faker, url, options, login = faker.internet.userName()}) {
//Twitter api //Twitter api
if (/^https:..api.twitter.com.*$/.test(url)) { if (/^https:..api.twitter.com.*$/.test(url)) {
//Get user profile //Get user profile
@@ -16,7 +16,7 @@
id:faker.random.number(1000000).toString(), id:faker.random.number(1000000).toString(),
username, username,
}, },
} },
}) })
} }
//Get recent tweets //Get recent tweets
@@ -40,7 +40,7 @@
id:faker.random.number(100000000000000).toString(), id:faker.random.number(100000000000000).toString(),
created_at:`${faker.date.recent()}`, created_at:`${faker.date.recent()}`,
text:faker.lorem.paragraph(), text:faker.lorem.paragraph(),
} },
], ],
includes:{ includes:{
users:[ users:[
@@ -49,7 +49,7 @@
name:"lowlighter", name:"lowlighter",
username:"lowlighter", username:"lowlighter",
}, },
] ],
}, },
meta:{ meta:{
newest_id:faker.random.number(100000000000000).toString(), newest_id:faker.random.number(100000000000000).toString(),
@@ -57,7 +57,7 @@
result_count:2, result_count:2,
next_token:"MOCKED_CURSOR", next_token:"MOCKED_CURSOR",
}, },
} },
}) })
} }
} }

View File

@@ -1,14 +1,16 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, options, login = faker.internet.userName()}) { export default function({faker, url, options, login = faker.internet.userName()}) {
//Wakatime api //Wakatime api
if (/^https:..wakatime.com.api.v1.users.current.stats.*$/.test(url)) { if (/^https:..wakatime.com.api.v1.users.current.stats.*$/.test(url)) {
//Get user profile //Get user profile
if (/api_key=MOCKED_TOKEN/.test(url)) { if (/api_key=MOCKED_TOKEN/.test(url)) {
console.debug(`metrics/compute/mocks > mocking wakatime api result > ${url}`) console.debug(`metrics/compute/mocks > mocking wakatime api result > ${url}`)
const stats = (array) => { const stats = array => {
const elements = [] const elements = []
let results = new Array(4+faker.random.number(2)).fill(null).map(_ => ({ let results = new Array(4+faker.random.number(2)).fill(null).map(_ => ({
get digital() { return `${this.hours}:${this.minutes}` }, get digital() {
return `${this.hours}:${this.minutes}`
},
hours:faker.random.number(1000), minutes:faker.random.number(1000), hours:faker.random.number(1000), minutes:faker.random.number(1000),
name:array ? faker.random.arrayElement(array) : faker.random.words(2).replace(/ /g, "-").toLocaleLowerCase(), name:array ? faker.random.arrayElement(array) : faker.random.words(2).replace(/ /g, "-").toLocaleLowerCase(),
percent:0, total_seconds:faker.random.number(1000000), percent:0, total_seconds:faker.random.number(1000000),
@@ -43,7 +45,7 @@
total_seconds:faker.random.number(1000000000), total_seconds:faker.random.number(1000000000),
total_seconds_including_other_language:faker.random.number(1000000000), total_seconds_including_other_language:faker.random.number(1000000000),
}, },
} },
}) })
} }
} }

View File

@@ -1,8 +1,8 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, url, body, login = faker.internet.userName()}) { export default function({faker, url, body, login = faker.internet.userName()}) {
if (/^https:..graphql.anilist.co.*$/.test(url)) { if (/^https:..graphql.anilist.co.*$/.test(url)) {
//Initialization and media generator //Initialization and media generator
const query = body.query const {query} = body
const media = ({type}) => ({ const media = ({type}) => ({
title:{romaji:faker.lorem.words(), english:faker.lorem.words(), native:faker.lorem.words()}, title:{romaji:faker.lorem.words(), english:faker.lorem.words(), native:faker.lorem.words()},
description:faker.lorem.paragraphs(), description:faker.lorem.paragraphs(),
@@ -15,11 +15,11 @@
countryOfOrigin:"JP", countryOfOrigin:"JP",
genres:new Array(6).fill(null).map(_ => faker.lorem.word()), genres:new Array(6).fill(null).map(_ => faker.lorem.word()),
coverImage:{medium:null}, coverImage:{medium:null},
startDate:{year:faker.date.past(20).getFullYear()} startDate:{year:faker.date.past(20).getFullYear()},
}) })
//User statistics query //User statistics query
if (/^query Statistics /.test(query)) { if (/^query Statistics /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Statistics`) console.debug("metrics/compute/mocks > mocking anilist api result > Statistics")
return ({ return ({
status:200, status:200,
data:{ data:{
@@ -41,15 +41,15 @@
volumesRead:faker.random.number(10000), volumesRead:faker.random.number(10000),
genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})), genres:new Array(4).fill(null).map(_ => ({genre:faker.lorem.word()})),
}, },
} },
} },
} },
} },
}) })
} }
//Favorites characters //Favorites characters
if (/^query FavoritesCharacters /.test(query)) { if (/^query FavoritesCharacters /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Favorites characters`) console.debug("metrics/compute/mocks > mocking anilist api result > Favorites characters")
return ({ return ({
status:200, status:200,
data:{ data:{
@@ -59,20 +59,19 @@
characters:{ characters:{
nodes:new Array(2+faker.random.number(16)).fill(null).map(_ => ({ nodes:new Array(2+faker.random.number(16)).fill(null).map(_ => ({
name:{full:faker.name.findName(), native:faker.name.findName()}, name:{full:faker.name.findName(), native:faker.name.findName()},
image:{medium:null} image:{medium:null},
}), })),
), pageInfo:{currentPage:1, hasNextPage:false},
pageInfo:{currentPage:1, hasNextPage:false} },
} },
} },
} },
} },
}
}) })
} }
//Favorites anime/manga query //Favorites anime/manga query
if (/^query Favorites /.test(query)) { if (/^query Favorites /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Favorites`) console.debug("metrics/compute/mocks > mocking anilist api result > Favorites")
const type = /anime[(]/.test(query) ? "ANIME" : /manga[(]/.test(query) ? "MANGA" : "OTHER" const type = /anime[(]/.test(query) ? "ANIME" : /manga[(]/.test(query) ? "MANGA" : "OTHER"
return ({ return ({
status:200, status:200,
@@ -83,17 +82,17 @@
[type.toLocaleLowerCase()]:{ [type.toLocaleLowerCase()]:{
nodes:new Array(16).fill(null).map(_ => media({type})), nodes:new Array(16).fill(null).map(_ => media({type})),
pageInfo:{currentPage:1, hasNextPage:false}, pageInfo:{currentPage:1, hasNextPage:false},
} },
} },
} },
} },
} },
}) })
} }
//Medias query //Medias query
if (/^query Medias /.test(query)) { if (/^query Medias /.test(query)) {
console.debug(`metrics/compute/mocks > mocking anilist api result > Medias`) console.debug("metrics/compute/mocks > mocking anilist api result > Medias")
const type = body.variables.type const {type} = body.variables
return ({ return ({
status:200, status:200,
data:{ data:{
@@ -106,17 +105,17 @@
entries:new Array(16).fill(null).map(_ => ({ entries:new Array(16).fill(null).map(_ => ({
status:faker.random.arrayElement(["CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"]), status:faker.random.arrayElement(["CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"]),
progress:faker.random.number(100), progress:faker.random.number(100),
progressVolumes: null, progressVolumes:null,
score:0, score:0,
startedAt:{year:null, month:null, day:null}, startedAt:{year:null, month:null, day:null},
completedAt:{year:null, month:null, day:null}, completedAt:{year:null, month:null, day:null},
media:media({type}) media:media({type}),
})), })),
} },
] ],
} },
} },
} },
}) })
} }

View File

@@ -1,8 +1,8 @@
//Imports //Imports
import urls from "url" import urls from "url"
/** Mocked data */ /**Mocked data */
export default function ({faker, url, body, login = faker.internet.userName()}) { export default function({faker, url, body, login = faker.internet.userName()}) {
if (/^https:..accounts.spotify.com.api.token.*$/.test(url)) { if (/^https:..accounts.spotify.com.api.token.*$/.test(url)) {
//Access token generator //Access token generator
const params = new urls.URLSearchParams(body) const params = new urls.URLSearchParams(body)
@@ -15,7 +15,7 @@
token_type:"Bearer", token_type:"Bearer",
expires_in:3600, expires_in:3600,
scope:"user-read-recently-played user-read-private", scope:"user-read-recently-played user-read-private",
} },
}) })
} }
} }

View File

@@ -1,19 +1,19 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > base/repositories`) console.debug("metrics/compute/mocks > mocking graphql api result > base/repositories")
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{ user:{
repositories:{ repositories:{
edges:[], edges:[],
nodes:[], nodes:[],
} },
} },
}) : ({ }) : ({
user:{ user:{
repositories:{ repositories:{
edges:[ edges:[
{ {
cursor:"MOCKED_CURSOR" cursor:"MOCKED_CURSOR",
}, },
], ],
nodes:[ nodes:[
@@ -31,7 +31,7 @@
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
] ],
}, },
issues_open:{totalCount:faker.random.number(100)}, issues_open:{totalCount:faker.random.number(100)},
issues_closed:{totalCount:faker.random.number(100)}, issues_closed:{totalCount:faker.random.number(100)},
@@ -39,10 +39,10 @@
pr_merged:{totalCount:faker.random.number(100)}, pr_merged:{totalCount:faker.random.number(100)},
releases:{totalCount:faker.random.number(100)}, releases:{totalCount:faker.random.number(100)},
forkCount:faker.random.number(100), forkCount:faker.random.number(100),
licenseInfo:{spdxId:"MIT"} licenseInfo:{spdxId:"MIT"},
},
],
},
}, },
]
}
}
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > base/repository`) console.debug("metrics/compute/mocks > mocking graphql api result > base/repository")
return ({ return ({
user:{ user:{
repository:{ repository:{
@@ -20,7 +20,7 @@
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
{size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}}, {size:faker.random.number(100000), node:{color:faker.internet.color(), name:faker.lorem.word()}},
] ],
}, },
issues_open:{totalCount:faker.random.number(100)}, issues_open:{totalCount:faker.random.number(100)},
issues_closed:{totalCount:faker.random.number(100)}, issues_closed:{totalCount:faker.random.number(100)},
@@ -28,8 +28,8 @@
pr_merged:{totalCount:faker.random.number(100)}, pr_merged:{totalCount:faker.random.number(100)},
releases:{totalCount:faker.random.number(100)}, releases:{totalCount:faker.random.number(100)},
forkCount:faker.random.number(100), forkCount:faker.random.number(100),
licenseInfo:{spdxId:"MIT"} licenseInfo:{spdxId:"MIT"},
},
}, },
}
}) })
} }

View File

@@ -1,8 +1,8 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > base/user`) console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
return ({ return ({
user: { user:{
databaseId:faker.random.number(10000000), databaseId:faker.random.number(10000000),
name:faker.name.findName(), name:faker.name.findName(),
login, login,
@@ -35,7 +35,7 @@
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
] ],
}, },
{ {
contributionDays:[ contributionDays:[
@@ -46,23 +46,23 @@
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
] ],
}, },
{ {
contributionDays:[ contributionDays:[
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
{color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])}, {color:faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
] ],
} },
] ],
} },
}, },
repositoriesContributedTo:{totalCount:faker.random.number(100)}, repositoriesContributedTo:{totalCount:faker.random.number(100)},
followers:{totalCount:faker.random.number(1000)}, followers:{totalCount:faker.random.number(1000)},
following:{totalCount:faker.random.number(1000)}, following:{totalCount:faker.random.number(1000)},
issueComments:{totalCount:faker.random.number(1000)}, issueComments:{totalCount:faker.random.number(1000)},
organizations:{totalCount:faker.random.number(10)} organizations:{totalCount:faker.random.number(10)},
} },
}) })
} }

View File

@@ -1,19 +1,19 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > gists/default`) console.debug("metrics/compute/mocks > mocking graphql api result > gists/default")
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{ user:{
gists:{ gists:{
edges:[], edges:[],
nodes:[], nodes:[],
} },
} },
}) : ({ }) : ({
user:{ user:{
gists:{ gists:{
edges:[ edges:[
{ {
cursor:"MOCKED_CURSOR" cursor:"MOCKED_CURSOR",
}, },
], ],
totalCount:faker.random.number(100), totalCount:faker.random.number(100),
@@ -23,17 +23,17 @@
isFork:false, isFork:false,
forks:{totalCount:faker.random.number(10)}, forks:{totalCount:faker.random.number(10)},
files:[{name:faker.system.fileName()}], files:[{name:faker.system.fileName()}],
comments:{totalCount:faker.random.number(10)} comments:{totalCount:faker.random.number(10)},
}, },
{ {
stargazerCount:faker.random.number(10), stargazerCount:faker.random.number(10),
isFork:false, isFork:false,
forks:{totalCount:faker.random.number(10)}, forks:{totalCount:faker.random.number(10)},
files:[{name:faker.system.fileName()}], files:[{name:faker.system.fileName()}],
comments:{totalCount:faker.random.number(10)} comments:{totalCount:faker.random.number(10)},
} },
] ],
} },
} },
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > isocalendar/calendar`) console.debug("metrics/compute/mocks > mocking graphql api result > isocalendar/calendar")
//Generate calendar //Generate calendar
const date = new Date(query.match(/from: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date) const date = new Date(query.match(/from: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date)
const to = new Date(query.match(/to: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date) const to = new Date(query.match(/to: "(?<date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)"/)?.groups?.date)
@@ -17,16 +17,16 @@
contributionDays.push({ contributionDays.push({
contributionCount, contributionCount,
color:["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"][Math.ceil(contributionCount/10/0.25)], color:["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"][Math.ceil(contributionCount/10/0.25)],
date:date.toISOString().substring(0, 10) date:date.toISOString().substring(0, 10),
}) })
} }
return ({ return ({
user: { user:{
calendar:{ calendar:{
contributionCalendar:{ contributionCalendar:{
weeks weeks,
} },
} },
} },
}) })
} }

View File

@@ -1,13 +1,13 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > people/default`) console.debug("metrics/compute/mocks > mocking graphql api result > people/default")
const type = query.match(/(?<type>followers|following)[(]/)?.groups?.type ?? "(unknown type)" const type = query.match(/(?<type>followers|following)[(]/)?.groups?.type ?? "(unknown type)"
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{ user:{
[type]:{ [type]:{
edges:[], edges:[],
} },
} },
}) : ({ }) : ({
user:{ user:{
[type]:{ [type]:{
@@ -16,9 +16,9 @@
node:{ node:{
login, login,
avatarUrl:null, avatarUrl:null,
} },
})) })),
} },
} },
}) })
} }

View File

@@ -1,15 +1,15 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > People`) console.debug("metrics/compute/mocks > mocking graphql api result > People")
const type = query.match(/(?<type>stargazers|watchers)[(]/)?.groups?.type ?? "(unknown type)" const type = query.match(/(?<type>stargazers|watchers)[(]/)?.groups?.type ?? "(unknown type)"
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{ user:{
repository:{ repository:{
[type]:{ [type]:{
edges:[], edges:[],
} },
} },
} },
}) : ({ }) : ({
user:{ user:{
repository:{ repository:{
@@ -19,10 +19,10 @@
node:{ node:{
login, login,
avatarUrl:null, avatarUrl:null,
} },
})) })),
} },
} },
} },
}) })
} }

View File

@@ -1,14 +1,14 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > People`) console.debug("metrics/compute/mocks > mocking graphql api result > People")
const type = query.match(/(?<type>sponsorshipsAsSponsor|sponsorshipsAsMaintainer)[(]/)?.groups?.type ?? "(unknown type)" const type = query.match(/(?<type>sponsorshipsAsSponsor|sponsorshipsAsMaintainer)[(]/)?.groups?.type ?? "(unknown type)"
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
user:{ user:{
login, login,
[type]:{ [type]:{
edges:[] edges:[],
} },
} },
}) : ({ }) : ({
user:{ user:{
login, login,
@@ -23,10 +23,10 @@
sponsorable:{ sponsorable:{
login:faker.internet.userName(), login:faker.internet.userName(),
avatarUrl:null, avatarUrl:null,
} },
} },
})) })),
} },
} },
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > projects/repository`) console.debug("metrics/compute/mocks > mocking graphql api result > projects/repository")
return ({ return ({
user:{ user:{
repository:{ repository:{
@@ -12,10 +12,10 @@
doneCount:faker.random.number(10), doneCount:faker.random.number(10),
inProgressCount:faker.random.number(10), inProgressCount:faker.random.number(10),
todoCount:faker.random.number(10), todoCount:faker.random.number(10),
enabled:true enabled:true,
} },
} },
} },
} },
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > projects/user`) console.debug("metrics/compute/mocks > mocking graphql api result > projects/user")
return ({ return ({
user:{ user:{
projects:{ projects:{
@@ -14,11 +14,11 @@
doneCount:faker.random.number(10), doneCount:faker.random.number(10),
inProgressCount:faker.random.number(10), inProgressCount:faker.random.number(10),
todoCount:faker.random.number(10), todoCount:faker.random.number(10),
enabled:true enabled:true,
} },
} },
] ],
} },
} },
}) })
} }

View File

@@ -1,20 +1,20 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > stargazers/default`) console.debug("metrics/compute/mocks > mocking graphql api result > stargazers/default")
return /after: "MOCKED_CURSOR"/m.test(query) ? ({ return /after: "MOCKED_CURSOR"/m.test(query) ? ({
repository:{ repository:{
stargazers:{ stargazers:{
edges:[], edges:[],
} },
} },
}) : ({ }) : ({
repository:{ repository:{
stargazers:{ stargazers:{
edges:new Array(faker.random.number({min:50, max:100})).fill(null).map(() => ({ edges:new Array(faker.random.number({min:50, max:100})).fill(null).map(() => ({
starredAt:`${faker.date.recent(30)}`, starredAt:`${faker.date.recent(30)}`,
cursor:"MOCKED_CURSOR" cursor:"MOCKED_CURSOR",
})) })),
} },
} },
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker, query, login = faker.internet.userName()}) { export default function({faker, query, login = faker.internet.userName()}) {
console.debug(`metrics/compute/mocks > mocking graphql api result > stars/default`) console.debug("metrics/compute/mocks > mocking graphql api result > stars/default")
return ({ return ({
user:{ user:{
starredRepositories:{ starredRepositories:{
@@ -22,16 +22,16 @@
stargazerCount:faker.random.number(10000), stargazerCount:faker.random.number(10000),
licenseInfo:{ licenseInfo:{
nickname:null, nickname:null,
name:"MIT License" name:"MIT License",
}, },
primaryLanguage:{ primaryLanguage:{
color:"#f1e05a", color:"#f1e05a",
name:"JavaScript" name:"JavaScript",
} },
} },
},
],
},
}, },
]
}
}
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function ({faker}, target, that, [{username:login, page, per_page}]) { export default function({faker}, target, that, [{username:login, page, per_page}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.activity.listEventsForAuthenticatedUser`) console.debug("metrics/compute/mocks > mocking rest api result > rest.activity.listEventsForAuthenticatedUser")
return ({ return ({
status:200, status:200,
url:`https://api.github.com/users/${login}/events?per_page=${per_page}&page=${page}`, url:`https://api.github.com/users/${login}/events?per_page=${per_page}&page=${page}`,
@@ -27,7 +27,7 @@
path:faker.system.fileName(), path:faker.system.fileName(),
commit_id:"MOCKED_SHA", commit_id:"MOCKED_SHA",
body:faker.lorem.sentence(), body:faker.lorem.sentence(),
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -55,7 +55,7 @@
login:faker.internet.userName(), login:faker.internet.userName(),
}, },
body:"", body:"",
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -77,8 +77,8 @@
login, login,
}, },
body:faker.lorem.paragraph(), body:faker.lorem.paragraph(),
performed_via_github_app:null performed_via_github_app:null,
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -99,8 +99,8 @@
summary:null, summary:null,
action:"created", action:"created",
sha:"MOCKED_SHA", sha:"MOCKED_SHA",
} },
] ],
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -125,14 +125,14 @@
{ {
name:"lorem ipsum", name:"lorem ipsum",
color:"d876e3", color:"d876e3",
} },
], ],
state:"open", state:"open",
}, },
comment:{ comment:{
body:faker.lorem.paragraph(), body:faker.lorem.paragraph(),
performed_via_github_app:null performed_via_github_app:null,
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -149,7 +149,7 @@
forkee:{ forkee:{
name:faker.random.word(), name:faker.random.word(),
full_name:`${faker.random.word()}/${faker.random.word()}`, full_name:`${faker.random.word()}/${faker.random.word()}`,
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -178,7 +178,7 @@
user:{ user:{
login:faker.internet.userName(), login:faker.internet.userName(),
}, },
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -198,7 +198,7 @@
name:faker.random.words(4), name:faker.random.words(4),
draft:faker.random.boolean(), draft:faker.random.boolean(),
prerelease:faker.random.boolean(), prerelease:faker.random.boolean(),
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -262,8 +262,8 @@
sha:"MOCKED_SHA", sha:"MOCKED_SHA",
message:faker.lorem.sentence(), message:faker.lorem.sentence(),
url:"https://api.github.com/repos/lowlighter/metrics/commits/MOCKED_SHA", url:"https://api.github.com/repos/lowlighter/metrics/commits/MOCKED_SHA",
} },
] ],
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -288,7 +288,7 @@
additions:faker.random.number(1000), additions:faker.random.number(1000),
deletions:faker.random.number(1000), deletions:faker.random.number(1000),
changed_files:faker.random.number(10), changed_files:faker.random.number(10),
} },
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -305,7 +305,7 @@
member:{ member:{
login:faker.internet.userName(), login:faker.internet.userName(),
}, },
action:"added" action:"added",
}, },
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
}, },
@@ -320,7 +320,7 @@
}, },
payload:{}, payload:{},
created_at:faker.date.recent(7), created_at:faker.date.recent(7),
} },
] ],
}) })
} }

View File

@@ -1,4 +1,4 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, args) { export default function({faker}, target, that, args) {
return ({ return ({
status:200, status:200,
@@ -17,7 +17,7 @@
source_import:{limit:100, used:0, remaining:100, reset:0}, source_import:{limit:100, used:0, remaining:100, reset:0},
code_scanning_upload:{limit:500, used:0, remaining:500, reset:0}, code_scanning_upload:{limit:500, used:0, remaining:500, reset:0},
}, },
rate:{limit:5000, used:0, remaining:"MOCKED", reset:0} rate:{limit:5000, used:0, remaining:"MOCKED", reset:0},
} },
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, [{owner, repo}]) { export default function({faker}, target, that, [{owner, repo}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.getContributorsStats`) console.debug("metrics/compute/mocks > mocking rest api result > rest.repos.getContributorsStats")
return ({ return ({
status:200, status:200,
url:`https://api.github.com/repos/${owner}/${repo}/stats/contributors`, url:`https://api.github.com/repos/${owner}/${repo}/stats/contributors`,
@@ -18,10 +18,10 @@
{w:3, a:faker.random.number(10000), d:faker.random.number(10000), c:faker.random.number(10000)}, {w:3, a:faker.random.number(10000), d:faker.random.number(10000), c:faker.random.number(10000)},
{w:4, a:faker.random.number(10000), d:faker.random.number(10000), c:faker.random.number(10000)}, {w:4, a:faker.random.number(10000), d:faker.random.number(10000), c:faker.random.number(10000)},
], ],
author: { author:{
login:owner, login:owner,
} },
} },
] ],
}) })
} }

View File

@@ -1,6 +1,6 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, [{owner, repo}]) { export default function({faker}, target, that, [{owner, repo}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.getViews`) console.debug("metrics/compute/mocks > mocking rest api result > rest.repos.getViews")
const count = faker.random.number(10000)*2 const count = faker.random.number(10000)*2
const uniques = faker.random.number(count)*2 const uniques = faker.random.number(count)*2
return ({ return ({
@@ -17,7 +17,7 @@
views:[ views:[
{timestamp:`${faker.date.recent()}`, count:count/2, uniques:uniques/2}, {timestamp:`${faker.date.recent()}`, count:count/2, uniques:uniques/2},
{timestamp:`${faker.date.recent()}`, count:count/2, uniques:uniques/2}, {timestamp:`${faker.date.recent()}`, count:count/2, uniques:uniques/2},
] ],
} },
}) })
} }

View File

@@ -1,28 +1,26 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, [{page, per_page, owner, repo}]) { export default function({faker}, target, that, [{page, per_page, owner, repo}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.listCommits`) console.debug("metrics/compute/mocks > mocking rest api result > rest.repos.listCommits")
return ({ return ({
status:200, status:200,
url:`https://api.github.com/repos/${owner}/${repo}/commits?per_page=${per_page}&page=${page}`, url:`https://api.github.com/repos/${owner}/${repo}/commits?per_page=${per_page}&page=${page}`,
headers: { headers:{
server:"GitHub.com", server:"GitHub.com",
status:"200 OK", status:"200 OK",
"x-oauth-scopes":"repo", "x-oauth-scopes":"repo",
}, },
data:page < 2 ? new Array(per_page).fill(null).map(() => data:page < 2 ? new Array(per_page).fill(null).map(() => ({
({
sha:"MOCKED_SHA", sha:"MOCKED_SHA",
commit:{ commit:{
author:{ author:{
name:owner, name:owner,
date:`${faker.date.recent(14)}` date:`${faker.date.recent(14)}`,
}, },
committer:{ committer:{
name:owner, name:owner,
date:`${faker.date.recent(14)}` date:`${faker.date.recent(14)}`,
}, },
} },
}) })) : [],
) : []
}) })
} }

View File

@@ -1,10 +1,10 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, [{owner, repo}]) { export default function({faker}, target, that, [{owner, repo}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.listContributors`) console.debug("metrics/compute/mocks > mocking rest api result > rest.repos.listContributors")
return ({ return ({
status:200, status:200,
url:`https://api.github.com/repos/${owner}/${repo}/contributors`, url:`https://api.github.com/repos/${owner}/${repo}/contributors`,
headers: { headers:{
server:"GitHub.com", server:"GitHub.com",
status:"200 OK", status:"200 OK",
"x-oauth-scopes":"repo", "x-oauth-scopes":"repo",
@@ -13,6 +13,6 @@
login:faker.internet.userName(), login:faker.internet.userName(),
avatar_url:null, avatar_url:null,
contributions:faker.random.number(1000), contributions:faker.random.number(1000),
})) })),
}) })
} }

View File

@@ -1,10 +1,10 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, args) { export default function({faker}, target, that, args) {
//Arguments //Arguments
const [url] = args const [url] = args
//Head request //Head request
if (/^HEAD .$/.test(url)) { if (/^HEAD .$/.test(url)) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.request HEAD`) console.debug("metrics/compute/mocks > mocking rest api result > rest.request HEAD")
return ({ return ({
status:200, status:200,
url:"https://api.github.com/", url:"https://api.github.com/",
@@ -13,7 +13,7 @@
status:"200 OK", status:"200 OK",
"x-oauth-scopes":"repo", "x-oauth-scopes":"repo",
}, },
data:undefined data:undefined,
}) })
} }
//Commit content //Commit content
@@ -44,14 +44,14 @@
login:faker.internet.userName(), login:faker.internet.userName(),
id:faker.random.number(100000000), id:faker.random.number(100000000),
}, },
files: [ files:[
{ {
sha:"MOCKED_SHA", sha:"MOCKED_SHA",
filename:faker.system.fileName(), filename:faker.system.fileName(),
patch:"@@ -0,0 +1,5 @@\n+//Imports\n+ import app from \"./src/app.mjs\"\n+\n+//Start app\n+ await app()\n\\ No newline at end of file" patch:"@@ -0,0 +1,5 @@\n+//Imports\n+ import app from \"./src/app.mjs\"\n+\n+//Start app\n+ await app()\n\\ No newline at end of file",
},
],
}, },
]
}
}) })
} }

View File

@@ -1,10 +1,10 @@
/** Mocked data */ /**Mocked data */
export default function({faker}, target, that, [{username}]) { export default function({faker}, target, that, [{username}]) {
console.debug(`metrics/compute/mocks > mocking rest api result > rest.repos.getByUsername`) console.debug("metrics/compute/mocks > mocking rest api result > rest.repos.getByUsername")
return ({ return ({
status:200, status:200,
url:`'https://api.github.com/users/${username}/`, url:`'https://api.github.com/users/${username}/`,
headers: { headers:{
server:"GitHub.com", server:"GitHub.com",
status:"200 OK", status:"200 OK",
"x-oauth-scopes":"repo", "x-oauth-scopes":"repo",
@@ -13,6 +13,6 @@
login:faker.internet.userName(), login:faker.internet.userName(),
avatar_url:null, avatar_url:null,
contributions:faker.random.number(1000), contributions:faker.random.number(1000),
} },
}) })
} }

View File

@@ -9,17 +9,17 @@
let mocked = false let mocked = false
//Mocking //Mocking
export default async function ({graphql, rest}) { export default async function({graphql, rest}) {
//Check if already mocked //Check if already mocked
if (mocked) if (mocked)
return {graphql, rest} return {graphql, rest}
mocked = true mocked = true
console.debug(`metrics/compute/mocks > mocking`) console.debug("metrics/compute/mocks > mocking")
//Load mocks //Load mocks
const __mocks = paths.join(paths.dirname(urls.fileURLToPath(import.meta.url))) const __mocks = paths.join(paths.dirname(urls.fileURLToPath(import.meta.url)))
const mock = async ({directory, mocks}) => { const mock = async({directory, mocks}) => {
for (const entry of await fs.readdir(directory)) { for (const entry of await fs.readdir(directory)) {
if ((await fs.lstat(paths.join(directory, entry))).isDirectory()) { if ((await fs.lstat(paths.join(directory, entry))).isDirectory()) {
if (!mocks[entry]) if (!mocks[entry])
@@ -36,7 +36,7 @@
//GraphQL API mocking //GraphQL API mocking
{ {
//Unmocked //Unmocked
console.debug(`metrics/compute/mocks > mocking graphql api`) console.debug("metrics/compute/mocks > mocking graphql api")
const unmocked = graphql const unmocked = graphql
//Mocked //Mocked
graphql = new Proxy(unmocked, { graphql = new Proxy(unmocked, {
@@ -46,20 +46,21 @@
const login = query.match(/login: "(?<login>.*?)"/)?.groups?.login ?? faker.internet.userName() const login = query.match(/login: "(?<login>.*?)"/)?.groups?.login ?? faker.internet.userName()
//Search for mocked query //Search for mocked query
for (const mocked of Object.keys(mocks.github.graphql)) for (const mocked of Object.keys(mocks.github.graphql)) {
if (new RegExp(`^query ${mocked.replace(/([.]\w)/g, (_, g) => g.toLocaleUpperCase().substring(1)).replace(/^(\w)/g, (_, g) => g.toLocaleUpperCase())} `).test(query)) if (new RegExp(`^query ${mocked.replace(/([.]\w)/g, (_, g) => g.toLocaleUpperCase().substring(1)).replace(/^(\w)/g, (_, g) => g.toLocaleUpperCase())} `).test(query))
return mocks.github.graphql[mocked]({faker, query, login}) return mocks.github.graphql[mocked]({faker, query, login})
}
//Unmocked call //Unmocked call
return target(...args) return target(...args)
} },
}) })
} }
//Rest API mocking //Rest API mocking
{ {
//Unmocked //Unmocked
console.debug(`metrics/compute/mocks > mocking rest api`) console.debug("metrics/compute/mocks > mocking rest api")
const unmocked = {} const unmocked = {}
//Mocked //Mocked
const mocker = ({path = "rest", mocks, mocked}) => { const mocker = ({path = "rest", mocks, mocked}) => {
@@ -79,12 +80,12 @@
//Axios mocking //Axios mocking
{ {
//Unmocked //Unmocked
console.debug(`metrics/compute/mocks > mocking axios`) console.debug("metrics/compute/mocks > mocking axios")
const unmocked = {get:axios.get, post:axios.post} const unmocked = {get:axios.get, post:axios.post}
//Mocked post requests //Mocked post requests
axios.post = new Proxy(unmocked.post, { axios.post = new Proxy(unmocked.post, {
apply:function(target, that, args) { apply(target, that, args) {
//Arguments //Arguments
const [url, body] = args const [url, body] = args
@@ -97,12 +98,12 @@
//Unmocked call //Unmocked call
return target(...args) return target(...args)
} },
}) })
//Mocked get requests //Mocked get requests
axios.get = new Proxy(unmocked.get, { axios.get = new Proxy(unmocked.get, {
apply:function(target, that, args) { apply(target, that, args) {
//Arguments //Arguments
const [url, options] = args const [url, options] = args
@@ -115,7 +116,7 @@
//Unmocked call //Unmocked call
return target(...args) return target(...args)
} },
}) })
} }

View File

@@ -2,4 +2,6 @@
import app from "./instance.mjs" import app from "./instance.mjs"
//Start app //Start app
(async function() {
await app({mock:process.env.USE_MOCKED_DATA, nosettings:process.env.NO_SETTINGS}) await app({mock:process.env.USE_MOCKED_DATA, nosettings:process.env.NO_SETTINGS})
})()

View File

@@ -10,8 +10,8 @@
import mocks from "../mocks/index.mjs" import mocks from "../mocks/index.mjs"
import metrics from "../metrics/index.mjs" import metrics from "../metrics/index.mjs"
/** App */ /**App */
export default async function ({mock, nosettings} = {}) { export default async function({mock, nosettings} = {}) {
//Load configuration settings //Load configuration settings
const {conf, Plugins, Templates} = await setup({nosettings}) const {conf, Plugins, Templates} = await setup({nosettings})
@@ -39,14 +39,14 @@
} }
} }
if (((mock)&&(!conf.settings.token))||(mock === "force")) { if (((mock)&&(!conf.settings.token))||(mock === "force")) {
console.debug(`metrics/app > using mocked token`) console.debug("metrics/app > using mocked token")
conf.settings.token = "MOCKED_TOKEN" conf.settings.token = "MOCKED_TOKEN"
} }
if (debug) if (debug)
console.debug(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256})) console.debug(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256}))
//Load octokits //Load octokits
const api = {graphql:octokit.graphql.defaults({headers:{authorization: `token ${token}`}}), rest:new OctokitRest.Octokit({auth:token})} const api = {graphql:octokit.graphql.defaults({headers:{authorization:`token ${token}`}}), rest:new OctokitRest.Octokit({auth:token})}
//Apply mocking if needed //Apply mocking if needed
if (mock) if (mock)
Object.assign(api, await mocks(api)) Object.assign(api, await mocks(api))
@@ -60,9 +60,11 @@
if (ratelimiter) { if (ratelimiter) {
app.set("trust proxy", 1) app.set("trust proxy", 1)
middlewares.push(ratelimit({ middlewares.push(ratelimit({
skip(req, res) { return !!cache.get(req.params.login) }, skip(req, _res) {
return !!cache.get(req.params.login)
},
message:"Too many requests", message:"Too many requests",
...ratelimiter ...ratelimiter,
})) }))
} }
//Cache headers middleware //Cache headers middleware
@@ -82,7 +84,7 @@
let requests = {limit:0, used:0, remaining:0, reset:NaN} let requests = {limit:0, used:0, remaining:0, reset:NaN}
if (!conf.settings.notoken) { if (!conf.settings.notoken) {
requests = (await rest.rateLimit.get()).data.rate requests = (await rest.rateLimit.get()).data.rate
setInterval(async () => requests = (await rest.rateLimit.get()).data.rate, 30*1000) setInterval(async() => requests = (await rest.rateLimit.get()).data.rate, 30*1000)
} }
//Web //Web
app.get("/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/index.html`)) app.get("/", limiter, (req, res) => res.sendFile(`${conf.paths.statics}/index.html`))
@@ -117,9 +119,9 @@
app.get("/.js/prism.markdown.min.js", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/prismjs/components/prism-markdown.min.js`)) app.get("/.js/prism.markdown.min.js", limiter, (req, res) => res.sendFile(`${conf.paths.node_modules}/prismjs/components/prism-markdown.min.js`))
//Meta //Meta
app.get("/.version", limiter, (req, res) => res.status(200).send(conf.package.version)) app.get("/.version", limiter, (req, res) => res.status(200).send(conf.package.version))
app.get("/.requests", limiter, async (req, res) => res.status(200).json(requests)) app.get("/.requests", limiter, async(req, res) => res.status(200).json(requests))
//Cache //Cache
app.get("/.uncache", limiter, async (req, res) => { app.get("/.uncache", limiter, async(req, res) => {
const {token, user} = req.query const {token, user} = req.query
if (token) { if (token) {
if (actions.flush.has(token)) { if (actions.flush.has(token)) {
@@ -127,10 +129,9 @@
cache.del(actions.flush.get(token)) cache.del(actions.flush.get(token))
return res.sendStatus(200) return res.sendStatus(200)
} }
else
return res.sendStatus(404) return res.sendStatus(404)
} }
else { {
const token = `${Math.random().toString(16).replace("0.", "")}${Math.random().toString(16).replace("0.", "")}` const token = `${Math.random().toString(16).replace("0.", "")}${Math.random().toString(16).replace("0.", "")}`
actions.flush.set(token, user) actions.flush.set(token, user)
return res.json({token}) return res.json({token})
@@ -138,7 +139,7 @@
}) })
//Metrics //Metrics
app.get("/:login", ...middlewares, async (req, res) => { app.get("/:login", ...middlewares, async(req, res) => {
//Request params //Request params
const login = req.params.login?.replace(/[\n\r]/g, "") const login = req.params.login?.replace(/[\n\r]/g, "")
if ((restricted.length)&&(!restricted.includes(login))) { if ((restricted.length)&&(!restricted.includes(login))) {
@@ -146,7 +147,6 @@
return res.sendStatus(403) return res.sendStatus(403)
} }
//Read cached data if possible //Read cached data if possible
//User cached
if ((!debug)&&(cached)&&(cache.get(login))) { if ((!debug)&&(cached)&&(cache.get(login))) {
const {rendered, mime} = cache.get(login) const {rendered, mime} = cache.get(login)
res.header("Content-Type", mime) res.header("Content-Type", mime)
@@ -167,8 +167,8 @@
const {rendered, mime} = await metrics({login, q}, { const {rendered, mime} = await metrics({login, q}, {
graphql, rest, plugins, conf, graphql, rest, plugins, conf,
die:q["plugins.errors.fatal"] ?? false, die:q["plugins.errors.fatal"] ?? false,
verify:q["verify"] ?? false, verify:q.verify ?? false,
convert:["jpeg", "png"].includes(q["config.output"]) ? q["config.output"] : null convert:["jpeg", "png"].includes(q["config.output"]) ? q["config.output"] : null,
}, {Plugins, Templates}) }, {Plugins, Templates})
//Cache //Cache
if ((!debug)&&(cached)) if ((!debug)&&(cached))
@@ -206,6 +206,6 @@
`Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`, `Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
`Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`, `Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`,
`SVG optimization │ ${conf.settings.optimize ?? false}`, `SVG optimization │ ${conf.settings.optimize ?? false}`,
`Server ready !` "Server ready !",
].join("\n"))) ].join("\n")))
} }

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, rest, q, account, imports}, {enabled = false} = {}) { export default async function({login, data, rest, q, account, imports}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met

View File

@@ -34,6 +34,7 @@ inputs:
type: array type: array
format: comma-separated format: comma-separated
default: all default: all
example: issue, pr, review, wiki, star
values: values:
- all # Display all types of events - all # Display all types of events
- comment # Display commits, issues and pull requests comments - comment # Display commits, issues and pull requests comments

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, queries, imports, q, account}, {enabled = false} = {}) { export default async function({login, data, queries, imports, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -31,7 +31,7 @@
//Format and save results //Format and save results
for (const {name, entries} of lists) { for (const {name, entries} of lists) {
//Format results //Format results
const list = await Promise.all(entries.map(async media => await format({media, imports}))) const list = await Promise.all(entries.map(media => format({media, imports})))
result.lists[type][name.toLocaleLowerCase()] = shuffle ? imports.shuffle(list) : list result.lists[type][name.toLocaleLowerCase()] = shuffle ? imports.shuffle(list) : list
//Limit results //Limit results
if (limit > 0) { if (limit > 0) {
@@ -131,7 +131,7 @@
} }
} }
/** Media formatter */ /**Media formatter */
async function format({media, imports}) { async function format({media, imports}) {
const {progress, score:userScore, media:{title, description, status, startDate:{year:release}, genres, averageScore, episodes, chapters, type, coverImage:{medium:artwork}}} = media const {progress, score:userScore, media:{title, description, status, startDate:{year:release}, genres, averageScore, episodes, chapters, type, coverImage:{medium:artwork}}} = media
return { return {
@@ -140,6 +140,6 @@
description:description.replace(/<br\s*\\?>/g, " "), description:description.replace(/<br\s*\\?>/g, " "),
scores:{user:userScore, community:averageScore}, scores:{user:userScore, community:averageScore},
released:type === "ANIME" ? episodes : chapters, released:type === "ANIME" ? episodes : chapters,
artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==" artwork:artwork ? await imports.imgb64(artwork) : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
} }
} }

View File

@@ -28,6 +28,7 @@ inputs:
type: array type: array
format: comma-separated format: comma-separated
default: favorites default: favorites
example: favorites, watching, characters
values: values:
- favorites # Favorites animes and mangas (depending on plugin_anilist_medias values) - favorites # Favorites animes and mangas (depending on plugin_anilist_medias values)
- watching # Animes in your watching list - watching # Animes in your watching list

View File

@@ -4,7 +4,7 @@
*/ */
//Setup //Setup
export default async function ({login, graphql, data, q, queries, imports}, conf) { export default async function({login, graphql, data, q, queries, imports}, conf) {
//Load inputs //Load inputs
console.debug(`metrics/compute/${login}/base > started`) console.debug(`metrics/compute/${login}/base > started`)
let {repositories, repositories_forks:forks} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"}, {repositories:conf.settings.repositories ?? 100}) let {repositories, repositories_forks:forks} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"}, {repositories:conf.settings.repositories ?? 100})
@@ -16,7 +16,7 @@
//Base parts (legacy handling for web instance) //Base parts (legacy handling for web instance)
const defaulted = ("base" in q) ? legacy.converter(q.base) ?? true : true const defaulted = ("base" in q) ? legacy.converter(q.base) ?? true : true
for (const part of conf.settings.plugins.base.parts) for (const part of conf.settings.plugins.base.parts)
data.base[part] = `base.${part}` in q ? legacy.converter(q[ `base.${part}`]) : defaulted data.base[part] = `base.${part}` in q ? legacy.converter(q[`base.${part}`]) : defaulted
//Iterate through account types //Iterate through account types
for (const account of ["user", "organization"]) { for (const account of ["user", "organization"]) {
@@ -46,7 +46,8 @@
//Success //Success
console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`) console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`)
return {} return {}
} catch (error) { }
catch (error) {
console.debug(`metrics/compute/${login}/base > account ${account} > failed : ${error}`) console.debug(`metrics/compute/${login}/base > account ${account} > failed : ${error}`)
console.debug(`metrics/compute/${login}/base > checking next account`) console.debug(`metrics/compute/${login}/base > checking next account`)
} }
@@ -69,7 +70,7 @@
//Organization //Organization
organization({login, data}) { organization({login, data}) {
console.debug(`metrics/compute/${login}/base > applying postprocessing`) console.debug(`metrics/compute/${login}/base > applying postprocessing`)
data.account = "organization", data.account = "organization"
Object.assign(data.user, { Object.assign(data.user, {
isHireable:false, isHireable:false,
starredRepositories:{totalCount:0}, starredRepositories:{totalCount:0},
@@ -107,7 +108,7 @@
repositories:{totalCount:0, totalDiskUsage:0, nodes:[]}, repositories:{totalCount:0, totalDiskUsage:0, nodes:[]},
packages:{totalCount:0}, packages:{totalCount:0},
}) })
} },
} }
//Legacy functions //Legacy functions
@@ -119,5 +120,5 @@
return false return false
if (Number.isFinite(Number(value))) if (Number.isFinite(Number(value)))
return !!(Number(value)) return !!(Number(value))
} },
} }

View File

@@ -4,22 +4,25 @@
*/ */
//Setup //Setup
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {pending, imports}) { export default async function({login, q, dflags}, {conf, data, rest, graphql, plugins, queries, account}, {pending, imports}) {
//Load inputs //Load inputs
imports.metadata.plugins.core.inputs({data, account, q}) imports.metadata.plugins.core.inputs({data, account, q})
//Init //Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}} const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
const avatar = imports.imgb64(data.user.avatarUrl) const avatar = imports.imgb64(data.user.avatarUrl)
data.computed = computed
console.debug(`metrics/compute/${login} > formatting common metrics`) console.debug(`metrics/compute/${login} > formatting common metrics`)
//Timezone config //Timezone config
if (q["config.timezone"]) { if (q["config.timezone"]) {
const timezone = data.config.timezone = {name:q["config.timezone"], offset:0} const timezone = {name:q["config.timezone"], offset:0}
data.config.timezone = timezone
try { try {
timezone.offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset*60*60*1000) || 0 timezone.offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset*60*60*1000) || 0
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset/(60*60*1000))} hours)`) console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset/(60*60*1000))} hours)`)
} catch { }
catch {
timezone.error = `Failed to use timezone "${timezone.name}"` timezone.error = `Failed to use timezone "${timezone.name}"`
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`) console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
} }
@@ -35,7 +38,7 @@
for (const name of Object.keys(imports.plugins)) { for (const name of Object.keys(imports.plugins)) {
if (!plugins[name]?.enabled) if (!plugins[name]?.enabled)
continue continue
pending.push((async () => { pending.push((async() => {
try { try {
console.debug(`metrics/compute/${login}/plugins > ${name} > started`) console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, plugins[name]) data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, plugins[name])
@@ -71,7 +74,7 @@
computed.diskUsage = `${imports.bytes(data.user.repositories.totalDiskUsage*1000)}` computed.diskUsage = `${imports.bytes(data.user.repositories.totalDiskUsage*1000)}`
//Compute licenses stats //Compute licenses stats
computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) ?? "" computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([_an, a], [_bn, b]) => b - a).slice(0, 1).map(([name, _value]) => name) ?? ""
//Compute total commits //Compute total commits
computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
@@ -117,7 +120,7 @@
computed.calendar.map(day => day.color = halloween(day.color)) computed.calendar.map(day => day.color = halloween(day.color))
//Update isocalendar colors //Update isocalendar colors
const waiting = [...pending] const waiting = [...pending]
pending.push((async () => { pending.push((async() => {
await Promise.all(waiting) await Promise.all(waiting)
if (data.plugins.isocalendar?.svg) if (data.plugins.isocalendar?.svg)
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg) data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({data, computed, imports, q, account}, {enabled = false} = {}) { export default async function({data, computed, imports, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -12,15 +12,27 @@
//Define getters //Define getters
const followup = { const followup = {
issues:{ issues:{
get count() { return this.open + this.closed }, get count() {
get open() { return computed.repositories.issues_open }, return this.open + this.closed
get closed() { return computed.repositories.issues_closed }, },
get open() {
return computed.repositories.issues_open
},
get closed() {
return computed.repositories.issues_closed
},
}, },
pr:{ pr:{
get count() { return this.open + this.merged }, get count() {
get open() { return computed.repositories.pr_open }, return this.open + this.merged
get merged() { return computed.repositories.pr_merged } },
} get open() {
return computed.repositories.pr_open
},
get merged() {
return computed.repositories.pr_merged
},
},
} }
//Results //Results

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) { export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -28,7 +28,7 @@
//Iterate through gists //Iterate through gists
console.debug(`metrics/compute/${login}/plugins > gists > processing ${gists.length} gists`) console.debug(`metrics/compute/${login}/plugins > gists > processing ${gists.length} gists`)
let stargazers = 0, forks = 0, comments = 0, files = 0 let comments = 0, files = 0, forks = 0, stargazers = 0
for (const gist of gists) { for (const gist of gists) {
//Skip forks //Skip forks
if (gist.isFork) if (gist.isFork)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, rest, imports, q, account}, {enabled = false, ...defaults} = {}) { export default async function({login, data, rest, imports, q, account}, {enabled = false, ...defaults} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -22,7 +22,10 @@
console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`) console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`)
events.push(...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data) events.push(...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data)
} }
} catch { console.debug(`metrics/compute/${login}/plugins > habits > no more page to load`) } }
catch {
console.debug(`metrics/compute/${login}/plugins > habits > no more page to load`)
}
console.debug(`metrics/compute/${login}/plugins > habits > ${events.length} events loaded`) console.debug(`metrics/compute/${login}/plugins > habits > ${events.length} events loaded`)
//Get user recent commits //Get user recent commits
@@ -36,8 +39,7 @@
console.debug(`metrics/compute/${login}/plugins > habits > loading patches`) console.debug(`metrics/compute/${login}/plugins > habits > loading patches`)
const patches = [...await Promise.allSettled(commits const patches = [...await Promise.allSettled(commits
.flatMap(({payload}) => payload.commits).map(commit => commit.url) .flatMap(({payload}) => payload.commits).map(commit => commit.url)
.map(async commit => (await rest.request(commit)).data.files) .map(async commit => (await rest.request(commit)).data.files))]
)]
.filter(({status}) => status === "fulfilled") .filter(({status}) => status === "fulfilled")
.map(({value}) => value) .map(({value}) => value)
.flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), patch:file.patch ?? ""}))) .flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), patch:file.patch ?? ""})))
@@ -52,7 +54,7 @@
habits.commits.days[day] = (habits.commits.days[day] ?? 0) + 1 habits.commits.days[day] = (habits.commits.days[day] ?? 0) + 1
habits.commits.days.max = Math.max(...Object.values(habits.commits.days)) habits.commits.days.max = Math.max(...Object.values(habits.commits.days))
//Compute day with most commits //Compute day with most commits
habits.commits.day = days.length ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][Object.entries(habits.commits.days).sort(([an, a], [bn, b]) => b - a).map(([day, occurence]) => day)[0]] ?? NaN : NaN habits.commits.day = days.length ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][Object.entries(habits.commits.days).sort(([_an, a], [_bn, b]) => b - a).map(([day, _occurence]) => day)[0]] ?? NaN : NaN
} }
//Commit hour //Commit hour
@@ -64,7 +66,7 @@
habits.commits.hours[hour] = (habits.commits.hours[hour] ?? 0) + 1 habits.commits.hours[hour] = (habits.commits.hours[hour] ?? 0) + 1
habits.commits.hours.max = Math.max(...Object.values(habits.commits.hours)) habits.commits.hours.max = Math.max(...Object.values(habits.commits.hours))
//Compute hour with most commits //Compute hour with most commits
habits.commits.hour = hours.length ? `${Object.entries(habits.commits.hours).sort(([an, a], [bn, b]) => b - a).map(([hour, occurence]) => hour)[0]}`.padStart(2, "0") : NaN habits.commits.hour = hours.length ? `${Object.entries(habits.commits.hours).sort(([_an, a], [_bn, b]) => b - a).map(([hour, _occurence]) => hour)[0]}`.padStart(2, "0") : NaN
} }
//Indent style //Indent style
@@ -72,7 +74,7 @@
//Attempt to guess whether tabs or spaces are used in patches //Attempt to guess whether tabs or spaces are used in patches
console.debug(`metrics/compute/${login}/plugins > habits > searching indent style`) console.debug(`metrics/compute/${login}/plugins > habits > searching indent style`)
patches patches
.map(({patch}) => patch.match(/((?:\t)|(?: )) /gm) ?? []) .map(({patch}) => patch.match(/((?:\t)|(?:[ ]{2})) /gm) ?? []) //eslint-disable-line prefer-named-capture-group
.forEach(indent => habits.indents[/^\t/.test(indent) ? "tabs" : "spaces"]++) .forEach(indent => habits.indents[/^\t/.test(indent) ? "tabs" : "spaces"]++)
habits.indents.style = habits.indents.spaces > habits.indents.tabs ? "spaces" : habits.indents.tabs > habits.indents.spaces ? "tabs" : "" habits.indents.style = habits.indents.spaces > habits.indents.tabs ? "spaces" : habits.indents.tabs > habits.indents.spaces ? "tabs" : ""
} }
@@ -89,18 +91,18 @@
//Create temporary directory and save patches //Create temporary directory and save patches
console.debug(`metrics/compute/${login}/plugins > habits > creating temp dir ${path} with ${patches.length} files`) console.debug(`metrics/compute/${login}/plugins > habits > creating temp dir ${path} with ${patches.length} files`)
await imports.fs.mkdir(path, {recursive:true}) await imports.fs.mkdir(path, {recursive:true})
await Promise.all(patches.map(async ({name, patch}, i) => await imports.fs.writeFile(imports.paths.join(path, `${i}${imports.paths.extname(name)}`), patch))) await Promise.all(patches.map(({name, patch}, i) => imports.fs.writeFile(imports.paths.join(path, `${i}${imports.paths.extname(name)}`), patch)))
//Create temporary git repository //Create temporary git repository
console.debug(`metrics/compute/${login}/plugins > habits > creating temp git repository`) console.debug(`metrics/compute/${login}/plugins > habits > creating temp git repository`)
await imports.run(`git init && git add . && git config user.name "linguist" && git config user.email "<>" && git commit -m "linguist"`, {cwd:path}).catch(console.debug) await imports.run('git init && git add . && git config user.name "linguist" && git config user.email "<>" && git commit -m "linguist"', {cwd:path}).catch(console.debug)
await imports.run(`git status`, {cwd:path}) await imports.run("git status", {cwd:path})
//Spawn linguist process //Spawn linguist process
console.debug(`metrics/compute/${login}/plugins > habits > running linguist`) console.debug(`metrics/compute/${login}/plugins > habits > running linguist`)
;(await imports.run(`${prefix} github-linguist --breakdown`, {cwd:path})) ;(await imports.run(`${prefix} github-linguist --breakdown`, {cwd:path}))
//Parse linguist result //Parse linguist result
.split("\n").map(line => line.match(/(?<value>[\d.]+)%\s+(?<language>\w+)/)?.groups).filter(line => line) .split("\n").map(line => line.match(/(?<value>[\d.]+)%\s+(?<language>\w+)/)?.groups).filter(line => line)
.map(({value, language}) => habits.linguist.languages[language] = (habits.linguist.languages[language] ?? 0) + value/100) .map(({value, language}) => habits.linguist.languages[language] = (habits.linguist.languages[language] ?? 0) + value/100)
habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([an, a], [bn, b]) => b - a) habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([_an, a], [_bn, b]) => b - a)
} }
else else
console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`) console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) { export default async function({login, data, graphql, q, imports, queries, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -39,7 +39,7 @@
//Compute the highest contributions in a day, streaks and average commits per day //Compute the highest contributions in a day, streaks and average commits per day
console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`) console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`)
let max = 0, streak = {max:0, current:0}, values = [], average = 0 let average = 0, max = 0, streak = {max:0, current:0}, values = []
for (const week of calendar.weeks) { for (const week of calendar.weeks) {
for (const day of week.contributionDays) { for (const day of week.contributionDays) {
values.push(day.contributionCount) values.push(day.contributionCount)
@@ -61,8 +61,8 @@
<feComponentTransfer> <feComponentTransfer>
${[..."RGB"].map(channel => `<feFunc${channel} type="linear" slope="${1-k*0.4}" />`).join("")} ${[..."RGB"].map(channel => `<feFunc${channel} type="linear" slope="${1-k*0.4}" />`).join("")}
</feComponentTransfer> </feComponentTransfer>
</filter>` </filter>`)
).join("")} .join("")}
<g transform="scale(4) translate(12, 0)">` <g transform="scale(4) translate(12, 0)">`
//Iterate through weeks //Iterate through weeks
for (const week of calendar.weeks) { for (const week of calendar.weeks) {
@@ -79,10 +79,10 @@
</g>` </g>`
j++ j++
} }
svg += `</g>` svg += "</g>"
i++ i++
} }
svg += `</g></svg>` svg += "</g></svg>"
//Results //Results
return {streak, max, average, svg, duration} return {streak, max, average, svg, duration}

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) { export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -42,7 +42,7 @@
//Compute languages stats //Compute languages stats
console.debug(`metrics/compute/${login}/plugins > languages > computing stats`) console.debug(`metrics/compute/${login}/plugins > languages > computing stats`)
languages.favorites = Object.entries(languages.stats).sort(([an, a], [bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value/languages.total > threshold) languages.favorites = Object.entries(languages.stats).sort(([_an, a], [_bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value/languages.total > threshold)
const visible = {total:Object.values(languages.favorites).map(({size}) => size).reduce((a, b) => a + b, 0)} const visible = {total:Object.values(languages.favorites).map(({size}) => size).reduce((a, b) => a + b, 0)}
for (let i = 0; i < languages.favorites.length; i++) { for (let i = 0; i < languages.favorites.length; i++) {
languages.favorites[i].value /= visible.total languages.favorites[i].value /= visible.total

View File

@@ -18,6 +18,7 @@ inputs:
type: array type: array
format: comma-separated format: comma-separated
default: "" default: ""
example: html, css, ...
# List of repositories that will be skipped # List of repositories that will be skipped
plugin_languages_skipped: plugin_languages_skipped:
@@ -25,6 +26,7 @@ inputs:
type: array type: array
format: comma-separated format: comma-separated
default: "" default: ""
example: my-repo-1, my-repo-2, ...
# Overrides default languages colors # Overrides default languages colors
# Use `${n}:${color}` to change the color of the n-th most used language (e.g. "0:red" to make your most used language red) # Use `${n}:${color}` to change the color of the n-th most used language (e.g. "0:red" to make your most used language red)
@@ -38,6 +40,7 @@ inputs:
- comma-separated - comma-separated
- /((?<index>[0-9])|(?<language>[-+a-z0-9#])):(?<color>#?[-a-z0-9]+)/ - /((?<index>[0-9])|(?<language>[-+a-z0-9#])):(?<color>#?[-a-z0-9]+)/
default: github default: github
example: javascript:red, 0:blue, 1:#ff00aa
# Languages additional details # Languages additional details
plugin_languages_details: plugin_languages_details:
@@ -48,6 +51,7 @@ inputs:
- bytes-size # Languages total size written in bytes - bytes-size # Languages total size written in bytes
- percentage # Languages proportions in % - percentage # Languages proportions in %
default: "" default: ""
example: bytes-size, percentage
# Minimum threshold (in percentage) to reach for languages to be displayed # Minimum threshold (in percentage) to reach for languages to be displayed
plugin_languages_threshold: plugin_languages_threshold:

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, imports, rest, q, account}, {enabled = false} = {}) { export default async function({login, data, imports, rest, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -22,7 +22,7 @@
//Get contributors stats from repositories //Get contributors stats from repositories
console.debug(`metrics/compute/${login}/plugins > lines > querying api`) console.debug(`metrics/compute/${login}/plugins > lines > querying api`)
const lines = {added:0, deleted:0} const lines = {added:0, deleted:0}
const response = await Promise.all(repositories.map(async ({repo, owner}) => await rest.repos.getContributorsStats({owner, repo}))) const response = await Promise.all(repositories.map(({repo, owner}) => rest.repos.getContributorsStats({owner, repo})))
//Compute changed lines //Compute changed lines
console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`) console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`)

View File

@@ -180,8 +180,8 @@ Register your API key to finish setup.
plugin_music_provider: spotify # Use Spotify as provider plugin_music_provider: spotify # Use Spotify as provider
plugin_music_mode: recent # Set plugin mode plugin_music_mode: recent # Set plugin mode
plugin_music_limit: 4 # Limit to 4 entries plugin_music_limit: 4 # Limit to 4 entries
plugin_music_played_at: yes # Show timestamp (only works with spotify, 🚧 @master feature)
plugin_music_token: "${{ secrets.SPOTIFY_CLIENT_ID }}, ${{ secrets.SPOTIFY_CLIENT_SECRET }}, ${{ secrets.SPOTIFY_REFRESH_TOKEN }}" plugin_music_token: "${{ secrets.SPOTIFY_CLIENT_ID }}, ${{ secrets.SPOTIFY_CLIENT_SECRET }}, ${{ secrets.SPOTIFY_REFRESH_TOKEN }}"
plugin_music_played_at: yes # Only works with spotify.
``` ```
```yaml ```yaml

View File

@@ -20,7 +20,7 @@
} }
//Setup //Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = ""} = {}) { export default async function({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -29,34 +29,40 @@
//Initialization //Initialization
const raw = { const raw = {
get provider() { return providers[provider]?.name ?? "" }, get provider() {
get mode() { return modes[mode] ?? "Unconfigured music plugin"}, return providers[provider]?.name ?? ""
},
get mode() {
return modes[mode] ?? "Unconfigured music plugin"
},
} }
let tracks = null let tracks = null
//Load inputs //Load inputs
let {provider, mode, playlist, limit, user, played_at} = imports.metadata.plugins.music.inputs({data, account, q}) let {provider, mode, playlist, limit, user, "played.at":played_at} = imports.metadata.plugins.music.inputs({data, account, q})
//Auto-guess parameters //Auto-guess parameters
if ((playlist)&&(!mode)) if ((playlist)&&(!mode))
mode = "playlist" mode = "playlist"
if ((playlist)&&(!provider)) if ((playlist)&&(!provider)) {
for (const [name, {embed}] of Object.entries(providers)) for (const [name, {embed}] of Object.entries(providers)) {
if (embed.test(playlist)) if (embed.test(playlist))
provider = name provider = name
}
}
if (!mode) if (!mode)
mode = "recent" mode = "recent"
//Provider //Provider
if (!(provider in providers)) if (!(provider in providers))
throw {error:{message:provider ? `Unsupported provider "${provider}"` : `Missing provider`}, ...raw} throw {error:{message:provider ? `Unsupported provider "${provider}"` : "Missing provider"}, ...raw}
//Mode //Mode
if (!(mode in modes)) if (!(mode in modes))
throw {error:{message:`Unsupported mode "${mode}"`}, ...raw} throw {error:{message:`Unsupported mode "${mode}"`}, ...raw}
//Playlist mode //Playlist mode
if (mode === "playlist") { if (mode === "playlist") {
if (!playlist) if (!playlist)
throw {error:{message:`Missing playlist url`}, ...raw} throw {error:{message:"Missing playlist url"}, ...raw}
if (!providers[provider].embed.test(playlist)) if (!providers[provider].embed.test(playlist))
throw {error:{message:`Unsupported playlist url format`}, ...raw} throw {error:{message:"Unsupported playlist url format"}, ...raw}
} }
//Limit //Limit
limit = Math.max(1, Math.min(100, Number(limit))) limit = Math.max(1, Math.min(100, Number(limit)))
@@ -83,7 +89,7 @@
tracks = [...await frame.evaluate(() => [...document.querySelectorAll(".tracklist li")].map(li => ({ tracks = [...await frame.evaluate(() => [...document.querySelectorAll(".tracklist li")].map(li => ({
name:li.querySelector(".tracklist__track__name").innerText, name:li.querySelector(".tracklist__track__name").innerText,
artist:li.querySelector(".tracklist__track__sub").innerText, artist:li.querySelector(".tracklist__track__sub").innerText,
artwork:li.querySelector(".tracklist__track__artwork img").src artwork:li.querySelector(".tracklist__track__artwork img").src,
})))] })))]
break break
} }
@@ -95,7 +101,7 @@
name:tr.querySelector("td:nth-child(2) div:nth-child(1)").innerText, name:tr.querySelector("td:nth-child(2) div:nth-child(1)").innerText,
artist:tr.querySelector("td:nth-child(2) div:nth-child(2)").innerText, artist:tr.querySelector("td:nth-child(2) div:nth-child(2)").innerText,
//Spotify doesn't provide artworks so we fallback on playlist artwork instead //Spotify doesn't provide artworks so we fallback on playlist artwork instead
artwork:window.getComputedStyle(document.querySelector("button[title=Play]").parentNode, null).backgroundImage.match(/^url\("(https:...+)"\)$/)[1] artwork:window.getComputedStyle(document.querySelector("button[title=Play]").parentNode, null).backgroundImage.match(/^url\("(?<url>https:...+)"\)$/)?.groups?.url ?? null,
})))] })))]
break break
} }
@@ -118,8 +124,6 @@
} }
//Recently played //Recently played
case "recent":{ case "recent":{
//Initialisation
const timestamp = Date.now()-24*60*60*1000
//Handle provider //Handle provider
switch (provider) { switch (provider) {
//Spotify //Spotify
@@ -127,28 +131,40 @@
//Prepare credentials //Prepare credentials
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim()) const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
if ((!client_id)||(!client_secret)||(!refresh_token)) if ((!client_id)||(!client_secret)||(!refresh_token))
throw {error:{message:`Spotify token must contain client id/secret and refresh token`}} throw {error:{message:"Spotify token must contain client id/secret and refresh token"}}
//API call and parse tracklist //API call and parse tracklist
try { try {
//Request access token //Request access token
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`) console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token", const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, {headers:{
`${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, "Content-Type":"application/x-www-form-urlencoded",
{headers:{"Content-Type":"application/x-www-form-urlencoded"}}, }})
)
console.debug(`metrics/compute/${login}/plugins > music > got access token`) console.debug(`metrics/compute/${login}/plugins > music > got access token`)
//Retrieve tracks //Retrieve tracks
console.debug(`metrics/compute/${login}/plugins > music > querying spotify api`) console.debug(`metrics/compute/${login}/plugins > music > querying spotify api`)
tracks = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?limit=${limit}&after=${timestamp}`, {headers:{ tracks = []
"Accept":"application/json", for (let hours = .5; hours <= 24; hours++) {
//Load track half-hour by half-hour
const timestamp = Date.now()-hours*60*60*1000
const loaded = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?after=${timestamp}`, {headers:{
"Content-Type":"application/json", "Content-Type":"application/json",
"Authorization":`Bearer ${access}`} Accept:"application/json",
})).data.items.map(({track, played_at}) => ({ Authorization:`Bearer ${access}`,
}})).data.items.map(({track, played_at}) => ({
name:track.name, name:track.name,
artist:track.artists[0].name, artist:track.artists[0].name,
artwork:track.album.images[0].url, artwork:track.album.images[0].url,
played_at: played_at ? imports.dayjs(played_at).format('[played at] HH:MM on DD/MM/YYYY') : '' played_at:played_at ? imports.dayjs(played_at).format("[played at] HH:MM on DD/MM/YYYY") : null,
})) }))
//Ensure no duplicate are added
for (const track of loaded) {
if (!tracks.map(({name}) => name).includes(track.name))
tracks.push(track)
}
//Early break
if (tracks.length >= limit)
break
}
} }
//Handle errors //Handle errors
catch (error) { catch (error) {
@@ -169,9 +185,9 @@
try { try {
console.debug(`metrics/compute/${login}/plugins > music > querying lastfm api`) console.debug(`metrics/compute/${login}/plugins > music > querying lastfm api`)
tracks = (await imports.axios.get(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${user}&api_key=${token}&limit=${limit}&format=json`, {headers:{ tracks = (await imports.axios.get(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${user}&api_key=${token}&limit=${limit}&format=json`, {headers:{
"Accept":"application/json", "User-Agent":"lowlighter/metrics",
"User-Agent":"lowlighter/metrics"} Accept:"application/json",
})).data.recenttracks.track.map((track) => ({ }})).data.recenttracks.track.map(track => ({
name:track.name, name:track.name,
artist:track.artist["#text"], artist:track.artist["#text"],
artwork:track.image.reverse()[0]["#text"], artwork:track.image.reverse()[0]["#text"],
@@ -215,11 +231,11 @@
track.artwork = await imports.imgb64(track.artwork) track.artwork = await imports.imgb64(track.artwork)
} }
//Save results //Save results
return {...raw, tracks} return {...raw, tracks, played_at}
} }
//Unhandled error //Unhandled error
throw {error:{message:`An error occured (could not retrieve tracks)`}} throw {error:{message:"An error occured (could not retrieve tracks)"}}
} }
//Handle errors //Handle errors
catch (error) { catch (error) {

View File

@@ -47,6 +47,7 @@ inputs:
description: Embed playlist url description: Embed playlist url
type: string type: string
default: "" default: ""
example: https://embed.music.apple.com/--/playlist/--------/--------
# Number of music tracks to display # Number of music tracks to display
plugin_music_limit: plugin_music_limit:
@@ -55,6 +56,8 @@ inputs:
default: 4 default: 4
min: 1 min: 1
max: 100 max: 100
# Display when track was last played
plugin_music_played_at: plugin_music_played_at:
description: Display when the track was played description: Display when the track was played
type: boolean type: boolean

View File

@@ -18,6 +18,7 @@
token: NOT_NEEDED token: NOT_NEEDED
plugin_music_token: MOCKED_CLIENT_ID, MOCKED_CLIENT_SECRET, MOCKED_REFRESH_TOKEN plugin_music_token: MOCKED_CLIENT_ID, MOCKED_CLIENT_SECRET, MOCKED_REFRESH_TOKEN
plugin_music: yes plugin_music: yes
plugin_music_played_at: yes
plugin_music_provider: spotify plugin_music_provider: spotify
- name: Music plugin (recent - lastfm) - name: Music plugin (recent - lastfm)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = null} = {}) { export default async function({login, imports, data, q, account}, {enabled = false, token = null} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, graphql, rest, q, queries, imports, account}, {enabled = false} = {}) { export default async function({login, data, graphql, rest, q, queries, imports, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -12,7 +12,7 @@
types:account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"], types:account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"],
default:"followers, following", default:"followers, following",
alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor"}, alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor"},
sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"} sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"},
} }
if (q.repo) { if (q.repo) {
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`) console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)

View File

@@ -34,6 +34,7 @@ inputs:
type: array type: array
format: comma-separated format: comma-separated
default: followers, following default: followers, following
example: follower, following, sponsors, sponsoring
values: values:
- followers # For user metrics - followers # For user metrics
- following # For user metrics - following # For user metrics

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) { export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -36,7 +36,7 @@
} }
//Unhandled error //Unhandled error
throw {error:{message:`An error occured (could not retrieve posts)`}} throw {error:{message:"An error occured (could not retrieve posts)"}}
} }
//Handle errors //Handle errors
catch (error) { catch (error) {

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, imports, graphql, q, queries, account}, {enabled = false} = {}) { export default async function({login, data, imports, graphql, q, queries, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -21,7 +21,7 @@
for (const identifier of repositories) { for (const identifier of repositories) {
//Querying repository project //Querying repository project
console.debug(`metrics/compute/${login}/plugins > projects > querying api for ${identifier}`) console.debug(`metrics/compute/${login}/plugins > projects > querying api for ${identifier}`)
const {user, repository, id} = identifier.match(/(?<user>[-\w]+)[/](?<repository>[-\w]+)[/]projects[/](?<id>\d+)/)?.groups const {user, repository, id} = identifier.match(/(?<user>[-\w]+)[/](?<repository>[-\w]+)[/]projects[/](?<id>\d+)/)?.groups ?? {}
const {[account]:{repository:{project}}} = await graphql(queries.projects.repository({user, repository, id, account})) const {[account]:{repository:{project}}} = await graphql(queries.projects.repository({user, repository, id, account}))
//Adding it to projects list //Adding it to projects list
console.debug(`metrics/compute/${login}/plugins > projects > registering ${identifier}`) console.debug(`metrics/compute/${login}/plugins > projects > registering ${identifier}`)

View File

@@ -27,6 +27,7 @@ inputs:
plugin_projects_repositories: plugin_projects_repositories:
description: List of repository project identifiers to disaplay description: List of repository project identifiers to disaplay
type: array type: array
example: username/repo/projects/1, username/repo/projects/2, ...
format: format:
- comma-separated - comma-separated
- /(?<user>[-a-z0-9]+)[/](?<repo>[-a-z0-9]+)[/]projects[/](?<id>[0-9]+)/ - /(?<user>[-a-z0-9]+)[/](?<repo>[-a-z0-9]+)[/]projects[/](?<id>[0-9]+)/

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, graphql, data, imports, q, queries, account}, {enabled = false} = {}) { export default async function({login, graphql, data, imports, q, queries, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -41,7 +41,7 @@
increments.max = Math.max(...Object.values(increments.dates)) increments.max = Math.max(...Object.values(increments.dates))
//Compute total stargazers //Compute total stargazers
let stargazers = data.computed.repositories.stargazers let {stargazers} = data.computed.repositories
const total = {dates:{...increments.dates}, max:NaN, min:NaN} const total = {dates:{...increments.dates}, max:NaN, min:NaN}
{ {
const dates = Object.keys(total.dates) const dates = Object.keys(total.dates)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, graphql, q, queries, imports, account}, {enabled = false} = {}) { export default async function({login, data, graphql, q, queries, imports, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, data, imports, q, account}, {enabled = false} = {}) { export default async function({login, data, imports, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, imports, data, rest, q, account}, {enabled = false} = {}) { export default async function({login, imports, data, rest, q, account}, {enabled = false} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -15,7 +15,7 @@
//Get views stats from repositories //Get views stats from repositories
console.debug(`metrics/compute/${login}/plugins > traffic > querying api`) console.debug(`metrics/compute/${login}/plugins > traffic > querying api`)
const views = {count:0, uniques:0} const views = {count:0, uniques:0}
const response = await Promise.all(repositories.map(async ({repo, owner}) => await rest.repos.getViews({owner, repo}))) const response = await Promise.all(repositories.map(({repo, owner}) => rest.repos.getViews({owner, repo})))
//Compute views //Compute views
console.debug(`metrics/compute/${login}/plugins > traffic > computing stats`) console.debug(`metrics/compute/${login}/plugins > traffic > computing stats`)

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, imports, data, q, account}, {enabled = false, token = ""} = {}) { export default async function({login, imports, data, q, account}, {enabled = false, token = ""} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -35,18 +35,17 @@
tweet.mentions = tweet.entities?.mentions.map(({username}) => username) ?? [] tweet.mentions = tweet.entities?.mentions.map(({username}) => username) ?? []
//Format text //Format text
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`) console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
tweet.text = imports.htmlescape( tweet.text = imports.htmlescape( //eslint-disable-line function-paren-newline
//Escape tags //Escape tags
imports.htmlescape(tweet.text, {"<":true, ">":true}) imports.htmlescape(tweet.text, {"<":true, ">":true})
//Mentions //Mentions
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ` <span class="mention">@$1</span> `) .replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), ' <span class="mention">@$1</span> ')
//Hashtags (this regex comes from the twitter source code) //Hashtags (this regex comes from the twitter source code)
.replace(/(?<!&)[#|]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ` <span class="hashtag">#$1</span> `) .replace(/(?<!&)[#|]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*[a-z_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f][a-z0-9_\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u024f\u0253-\u0254\u0256-\u0257\u0300-\u036f\u1e00-\u1eff\u0400-\u04ff\u0500-\u0527\u2de0-\u2dff\ua640-\ua69f\u0591-\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05d0-\u05ea\u05f0-\u05f4\ufb12-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufb4f\u0610-\u061a\u0620-\u065f\u066e-\u06d3\u06d5-\u06dc\u06de-\u06e8\u06ea-\u06ef\u06fa-\u06fc\u0750-\u077f\u08a2-\u08ac\u08e4-\u08fe\ufb50-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\u200c-\u200c\u0e01-\u0e3a\u0e40-\u0e4e\u1100-\u11ff\u3130-\u3185\ua960-\ua97f\uac00-\ud7af\ud7b0-\ud7ff\uffa1-\uffdc\u30a1-\u30fa\u30fc-\u30fe\uff66-\uff9f\uff10-\uff19\uff21-\uff3a\uff41-\uff5a\u3041-\u3096\u3099-\u309e\u3400-\u4dbf\u4e00-\u9fff\u20000-\u2a6df\u2a700-\u2b73f\u2b740-\u2b81f\u2f800-\u2fa1f]*)/gi, ' <span class="hashtag">#$1</span> ') //eslint-disable-line no-misleading-character-class, prefer-named-capture-group
//Line breaks //Line breaks
.replace(/\n/g, "<br/>") .replace(/\n/g, "<br/>")
//Links //Links
.replace(/https?:[/][/](t.co[/]\w+)/g, ` <span class="link">$1</span> `) .replace(/https?:[/][/](?<link>t.co[/]\w+)/g, ' <span class="link">$<link></span> '), {"&":true})
, {"&":true})
})) }))
//Result //Result

View File

@@ -1,5 +1,5 @@
//Setup //Setup
export default async function ({login, q, imports, data, account}, {enabled = false, token} = {}) { export default async function({login, q, imports, data, account}, {enabled = false, token} = {}) {
//Plugin execution //Plugin execution
try { try {
//Check if plugin is enabled and requirements are met //Check if plugin is enabled and requirements are met
@@ -12,8 +12,7 @@
limit = void(limit) limit = void(limit)
const range = {"7":"last_7_days", "30":"last_30_days", "180":"last_6_months", "365":"last_year"}[days] ?? "last_7_days" const range = {"7":"last_7_days", "30":"last_30_days", "180":"last_6_months", "365":"last_year"}[days] ?? "last_7_days"
//Querying api and format result //Querying api and format result (https://wakatime.com/developers#stats)
//https://wakatime.com/developers#stats
console.debug(`metrics/compute/${login}/plugins > wakatime > querying api`) console.debug(`metrics/compute/${login}/plugins > wakatime > querying api`)
const {data:{data:stats}} = await imports.axios.get(`https://wakatime.com/api/v1/users/current/stats/${range}?api_key=${token}`) const {data:{data:stats}} = await imports.axios.get(`https://wakatime.com/api/v1/users/current/stats/${range}?api_key=${token}`)
const result = { const result = {

View File

@@ -22,12 +22,12 @@
<div class="tracklist"> <div class="tracklist">
<% for (const {name = "", artist = "", artwork = "", played_at = ""} of plugins.music.tracks) { %> <% for (const {name = "", artist = "", artwork = "", played_at = ""} of plugins.music.tracks) { %>
<div class="track"> <div class="track">
<img src="data:image/png;base64,<%= artwork %>" width="<%= !!played_at.length ? 48 : 32 %>" height="<%= !!played_at.length ? 48 : 32 %>" alt=""/> <img src="data:image/png;base64,<%= artwork %>" width="<%= plugins.music.played_at ? 48 : 32 %>" height="<%= plugins.music.played_at ? 48 : 32 %>" alt=""/>
<div class="infos"> <div class="infos">
<div class="name"><b><%= name %><b></div> <div class="name"><%= name %></div>
<div class="artist"><%= artist %></div> <div class="artist"><%= artist %></div>
<% if (played_at.length) { %> <% if (plugins.music.played_at) { %>
<div class="played_at"><%= played_at %></div> <div class="played-at"><%= played_at %></div>
<% } %> <% } %>
</div> </div>
</div> </div>

View File

@@ -272,8 +272,9 @@
.track .name { .track .name {
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 14px;
font-weight: 600;
} }
.track .artist { .track .artist, .track .played-at {
font-size: 12px; font-size: 12px;
color: #666666; color: #666666;
} }

View File

@@ -1,5 +1,5 @@
/** Template processor */ /**Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) { export default async function(_, __, {imports}) {
//Core //Core
await imports.plugins.core(...arguments) await imports.plugins.core(...arguments)
} }

View File

@@ -1,11 +1,11 @@
/** Template processor */ /**Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries, account}, {s, pending, imports}) { export default async function({login, q}, {data, rest, graphql, queries, account}, {pending, imports}) {
//Check arguments //Check arguments
const {repo} = q const {repo} = q
if (!repo) { if (!repo) {
console.debug(`metrics/compute/${login}/${repo} > error, repo was undefined`) console.debug(`metrics/compute/${login}/${repo} > error, repo was undefined`)
data.errors.push({error:{message:`You must pass a "repo" argument to use this template`}}) data.errors.push({error:{message:"You must pass a \"repo\" argument to use this template"}})
return await imports.plugins.core(...arguments) return imports.plugins.core(...arguments)
} }
console.debug(`metrics/compute/${login}/${repo} > switching to mode ${account}`) console.debug(`metrics/compute/${login}/${repo} > switching to mode ${account}`)

View File

@@ -1,5 +1,5 @@
/** Template processor */ /**Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) { export default async function({q}, _, {imports}) {
//Core //Core
await imports.plugins.core(...arguments) await imports.plugins.core(...arguments)
//Disable optimization to keep white-spaces //Disable optimization to keep white-spaces