mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-23 09:48:14 +00:00
Remove old CLI (#1883)
This commit removes the old CLI, which is being replaced by the new instance-system based approach (currently called cli2). This also removes sharness tests on the old CLI, as well as the associated snapshots. This is a little unfortunate since the GitHub snapshot did provide some validation against changes to the plugin; we'd do well to re-integrate such a system when adding testing to cli2. Test plan: `yarn test --full` passes. Since this is just a removal, there's not much that can go wrong.
This commit is contained in:
parent
222f4a0738
commit
609d07e04c
@ -29,7 +29,6 @@ module.exports = {
|
||||
// source file, and the key will be the filename of the bundled entry
|
||||
// point within the build directory.
|
||||
backendEntryPoints: {
|
||||
sourcecred: resolveApp("src/cli/main.js"),
|
||||
sc2: resolveApp("src/cli2/main.js"),
|
||||
//
|
||||
generateGithubGraphqlFlowTypes: resolveApp(
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
[{"type":"sourcecred/pluginDeclarations","version":"0.1.0"},[{"edgePrefix":"E\u0000sourcecred\u0000github\u0000","edgeTypes":[{"backwardName":"is authored by","defaultWeight":{"backwards":1,"forwards":0.5},"description":"Connects a GitHub account to a post that they authored.\n\nExamples of posts include issues, pull requests, and comments.\n","forwardName":"authors","prefix":"E\u0000sourcecred\u0000github\u0000AUTHORS\u0000"},{"backwardName":"has child","defaultWeight":{"backwards":0.25,"forwards":1},"description":"Connects a GitHub entity to its child entities.\n\nFor example, a Repository has Issues and Pull Requests as children, and a\nPull Request has comments and reviews as children.\n","forwardName":"has parent","prefix":"E\u0000sourcecred\u0000github\u0000HAS_PARENT\u0000"},{"backwardName":"is merged by","defaultWeight":{"backwards":1,"forwards":0.5},"description":"Connects a GitHub pull request to the Git commit that it merges.\n","forwardName":"merges","prefix":"E\u0000sourcecred\u0000github\u0000MERGED_AS\u0000"},{"backwardName":"is referenced by","defaultWeight":{"backwards":0,"forwards":1},"description":"Connects a GitHub post to an entity that it references.\n\nFor example, if you write a GitHub issue comment that says \"thanks\n@username for pull #1337\", it will create references edges to both the user\n@username, and to pull #1337 in the same repository.\n","forwardName":"references","prefix":"E\u0000sourcecred\u0000github\u0000REFERENCES\u0000"},{"backwardName":"got 👍 from","defaultWeight":{"backwards":0,"forwards":1},"description":"Connects users to posts to which they gave a 👍 reaction.\n","forwardName":"reacted 👍 to","prefix":"E\u0000sourcecred\u0000github\u0000REACTS\u0000THUMBS_UP\u0000"},{"backwardName":"got ❤️ from","defaultWeight":{"backwards":0,"forwards":2},"description":"Connects users to posts to which they gave a ❤️ reaction.\n","forwardName":"reacted ❤️ to","prefix":"E\u0000sourcecred\u0000github\u0000REACTS\u0000HEART\u0000"},{"backwardName":"got 🎉 from","defaultWeight":{"backwards":0,"forwards":4},"description":"Connects users to posts to which they gave a 🎉 reaction.\n","forwardName":"reacted 🎉 to","prefix":"E\u0000sourcecred\u0000github\u0000REACTS\u0000HOORAY\u0000"},{"backwardName":"got 🚀 from","defaultWeight":{"backwards":0,"forwards":1},"description":"Connects users to posts to which they gave a 🚀 reaction.\n","forwardName":"reacted 🚀 to","prefix":"E\u0000sourcecred\u0000github\u0000REACTS\u0000ROCKET\u0000"},{"backwardName":"merged on GitHub as","defaultWeight":{"backwards":1,"forwards":1},"description":"Connects a commit on GitHub to the corresponding raw Git commit.\n","forwardName":"corresponds to Git commit","prefix":"E\u0000sourcecred\u0000github\u0000CORRESPONDS_TO_COMMIT_TYPE\u0000"}],"name":"GitHub","nodePrefix":"N\u0000sourcecred\u0000github\u0000","nodeTypes":[{"defaultWeight":4,"description":"NodeType for a GitHub repository","name":"Repository","pluralName":"Repositories","prefix":"N\u0000sourcecred\u0000github\u0000REPO\u0000"},{"defaultWeight":2,"description":"NodeType for a GitHub issue","name":"Issue","pluralName":"Issues","prefix":"N\u0000sourcecred\u0000github\u0000ISSUE\u0000"},{"defaultWeight":4,"description":"NodeType for a GitHub pull request","name":"Pull request","pluralName":"Pull requests","prefix":"N\u0000sourcecred\u0000github\u0000PULL\u0000"},{"defaultWeight":1,"description":"NodeType for a GitHub code review","name":"Pull request review","pluralName":"Pull request reviews","prefix":"N\u0000sourcecred\u0000github\u0000REVIEW\u0000"},{"defaultWeight":1,"description":"NodeType for a GitHub comment","name":"Comment","pluralName":"Comments","prefix":"N\u0000sourcecred\u0000github\u0000COMMENT\u0000"},{"defaultWeight":1,"description":"Represents a particular Git commit on GitHub, i.e. scoped to a particular repository","name":"Commit","pluralName":"Commits","prefix":"N\u0000sourcecred\u0000github\u0000COMMIT\u0000"},{"defaultWeight":0,"description":"NodeType for a GitHub user","name":"User","pluralName":"Users","prefix":"N\u0000sourcecred\u0000github\u0000USERLIKE\u0000USER\u0000"},{"defaultWeight":0,"description":"NodeType for a GitHub bot account","name":"Bot","pluralName":"Bots","prefix":"N\u0000sourcecred\u0000github\u0000USERLIKE\u0000BOT\u0000"}],"userTypes":[{"defaultWeight":0,"description":"NodeType for a GitHub user","name":"User","pluralName":"Users","prefix":"N\u0000sourcecred\u0000github\u0000USERLIKE\u0000USER\u0000"}]}]]
|
@ -1 +0,0 @@
|
||||
[{"type":"sourcecred/project","version":"0.5.2"},{"discord":null,"discourseServer":null,"id":"sourcecred-test/example-github","identities":[],"initiatives":null,"repoIds":[{"name":"example-github","owner":"sourcecred-test"}],"timelineCredParams":null}]
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -1,324 +0,0 @@
|
||||
[
|
||||
{
|
||||
"type": "sourcecred/cli/scores",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"intervals": [
|
||||
{
|
||||
"endTimeMs": 1520121600000,
|
||||
"startTimeMs": 1519516800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1520726400000,
|
||||
"startTimeMs": 1520121600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1521331200000,
|
||||
"startTimeMs": 1520726400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1521936000000,
|
||||
"startTimeMs": 1521331200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1522540800000,
|
||||
"startTimeMs": 1521936000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1523145600000,
|
||||
"startTimeMs": 1522540800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1523750400000,
|
||||
"startTimeMs": 1523145600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1524355200000,
|
||||
"startTimeMs": 1523750400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1524960000000,
|
||||
"startTimeMs": 1524355200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1525564800000,
|
||||
"startTimeMs": 1524960000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1526169600000,
|
||||
"startTimeMs": 1525564800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1526774400000,
|
||||
"startTimeMs": 1526169600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1527379200000,
|
||||
"startTimeMs": 1526774400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1527984000000,
|
||||
"startTimeMs": 1527379200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1528588800000,
|
||||
"startTimeMs": 1527984000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1529193600000,
|
||||
"startTimeMs": 1528588800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1529798400000,
|
||||
"startTimeMs": 1529193600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1530403200000,
|
||||
"startTimeMs": 1529798400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1531008000000,
|
||||
"startTimeMs": 1530403200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1531612800000,
|
||||
"startTimeMs": 1531008000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1532217600000,
|
||||
"startTimeMs": 1531612800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1532822400000,
|
||||
"startTimeMs": 1532217600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1533427200000,
|
||||
"startTimeMs": 1532822400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1534032000000,
|
||||
"startTimeMs": 1533427200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1534636800000,
|
||||
"startTimeMs": 1534032000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1535241600000,
|
||||
"startTimeMs": 1534636800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1535846400000,
|
||||
"startTimeMs": 1535241600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1536451200000,
|
||||
"startTimeMs": 1535846400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1537056000000,
|
||||
"startTimeMs": 1536451200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1537660800000,
|
||||
"startTimeMs": 1537056000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1538265600000,
|
||||
"startTimeMs": 1537660800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1538870400000,
|
||||
"startTimeMs": 1538265600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1539475200000,
|
||||
"startTimeMs": 1538870400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1540080000000,
|
||||
"startTimeMs": 1539475200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1540684800000,
|
||||
"startTimeMs": 1540080000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1541289600000,
|
||||
"startTimeMs": 1540684800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1541894400000,
|
||||
"startTimeMs": 1541289600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1542499200000,
|
||||
"startTimeMs": 1541894400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1543104000000,
|
||||
"startTimeMs": 1542499200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1543708800000,
|
||||
"startTimeMs": 1543104000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1544313600000,
|
||||
"startTimeMs": 1543708800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1544918400000,
|
||||
"startTimeMs": 1544313600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1545523200000,
|
||||
"startTimeMs": 1544918400000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1546128000000,
|
||||
"startTimeMs": 1545523200000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1546732800000,
|
||||
"startTimeMs": 1546128000000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1547337600000,
|
||||
"startTimeMs": 1546732800000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1547942400000,
|
||||
"startTimeMs": 1547337600000
|
||||
},
|
||||
{
|
||||
"endTimeMs": 1548547200000,
|
||||
"startTimeMs": 1547942400000
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"address": [
|
||||
"sourcecred",
|
||||
"github",
|
||||
"USERLIKE",
|
||||
"USER",
|
||||
"decentralion"
|
||||
],
|
||||
"intervalCred": [
|
||||
9.734371505131229,
|
||||
4.866769476502015,
|
||||
5.45967678251199,
|
||||
4.7373704438615425,
|
||||
2.3682738955453986,
|
||||
1.183726718845694,
|
||||
0.5914613180048877,
|
||||
0.2953441802319966,
|
||||
0.1473144899165441,
|
||||
3.4978536212952984,
|
||||
1.7559491655988428,
|
||||
0.8839962777883124,
|
||||
0.44653137513507696,
|
||||
0.22600614078858708,
|
||||
0.11412708331142937,
|
||||
0.04241671830125515,
|
||||
0.02095600332095804,
|
||||
0.5492132258029613,
|
||||
0.27337032661021154,
|
||||
0.13551003711598433,
|
||||
0.06682765923330519,
|
||||
0.03282560139637494,
|
||||
0.01614582900556915,
|
||||
0.008011848698948467,
|
||||
0.0040263763117773495,
|
||||
0.002045146244647576,
|
||||
0.234125107916456,
|
||||
0.11935025856951863,
|
||||
2.066716441640751,
|
||||
1.225987646747624,
|
||||
0.6136508223713587,
|
||||
0.3070637150934105,
|
||||
0.15361287410473679,
|
||||
0.07683553891284155,
|
||||
0.03842818491166919,
|
||||
0.019214582306587714,
|
||||
0.009601140978431138,
|
||||
0.004791390002030775,
|
||||
0.002387368453447827,
|
||||
0.0011885801142664197,
|
||||
0.0005920728445612202,
|
||||
0.00029490583043628775,
|
||||
0.00014677639527190518,
|
||||
0.000073579877320932,
|
||||
0.00003767874911054204,
|
||||
0.000019748431956321648,
|
||||
0.000010429797539164292,
|
||||
0.0000021162129948814497
|
||||
],
|
||||
"totalCred": 42.33425220677316
|
||||
},
|
||||
{
|
||||
"address": [
|
||||
"sourcecred",
|
||||
"github",
|
||||
"USERLIKE",
|
||||
"USER",
|
||||
"wchargin"
|
||||
],
|
||||
"intervalCred": [
|
||||
3.2656284948687713,
|
||||
1.6332305234979851,
|
||||
0.7903232174880094,
|
||||
0.387629556138458,
|
||||
0.19422610445460123,
|
||||
0.0975232811543062,
|
||||
0.049163681995112264,
|
||||
0.024968319768003445,
|
||||
0.012841760083455873,
|
||||
2.0822245037047016,
|
||||
1.034089896901157,
|
||||
0.5110232534616876,
|
||||
0.2509783904899231,
|
||||
0.12274874202391291,
|
||||
0.06025035809482062,
|
||||
0.5447720024018698,
|
||||
0.27263835703060446,
|
||||
0.59758395437282,
|
||||
0.3000282634776791,
|
||||
0.15118925792796095,
|
||||
0.07652198828866748,
|
||||
0.03884922236461138,
|
||||
0.01969158287492401,
|
||||
0.009906857241298115,
|
||||
0.004932976658345941,
|
||||
0.002434530240414069,
|
||||
0.2681147303260748,
|
||||
0.13176966055174677,
|
||||
4.058843517919881,
|
||||
1.8367923330326925,
|
||||
0.9177391675187996,
|
||||
0.4586312798516685,
|
||||
0.22923462336780273,
|
||||
0.11458820982342821,
|
||||
0.05728368945646571,
|
||||
0.02864135487747973,
|
||||
0.014326827613602582,
|
||||
0.007172594293986085,
|
||||
0.003594623694560603,
|
||||
0.0018024159597377954,
|
||||
0.0009034251924408875,
|
||||
0.00045284318806476605,
|
||||
0.00022709811397862172,
|
||||
0.00011335737730433143,
|
||||
0.00005578987820208967,
|
||||
0.000026985881699994214,
|
||||
0.00001293735928899364,
|
||||
0.000009567365419197517
|
||||
],
|
||||
"totalCred": 20.665736109648424
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,96 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Disable these lint rules globally:
|
||||
# 2034 = unused variable (used by sharness)
|
||||
# 2016 = parameter expansion in single quotes
|
||||
# 1004 = backslash-newline in single quotes
|
||||
# shellcheck disable=SC2034,SC2016,SC1004
|
||||
:
|
||||
|
||||
test_description='tests for cli/output.js'
|
||||
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
export GIT_ATTR_NOSYSTEM=1
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. ./sharness.sh
|
||||
|
||||
test_expect_success "environment and Node linking setup" '
|
||||
toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" &&
|
||||
snapshot_directory="${toplevel}/sharness/__snapshots__/" &&
|
||||
SOURCECRED_DIRECTORY="${snapshot_directory}/example-github-load" &&
|
||||
export SOURCECRED_DIRECTORY &&
|
||||
snapshot_file="${snapshot_directory}/example-github-output.json" &&
|
||||
if [ -z "${SOURCECRED_BIN}" ]; then
|
||||
printf >&2 "warn: missing environment variable SOURCECRED_BIN\n" &&
|
||||
printf >&2 "warn: using repository bin directory as fallback\n" &&
|
||||
export SOURCECRED_BIN="${toplevel}/bin"
|
||||
fi &&
|
||||
export NODE_PATH="${toplevel}/node_modules${NODE_PATH:+:${NODE_PATH}}" &&
|
||||
test_set_prereq SETUP
|
||||
'
|
||||
|
||||
run() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
code=0
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err || code=$?
|
||||
if [ "${code}" -ne 0 ]; then
|
||||
printf '%s failed with %d\n' "sourcecred $*"
|
||||
printf 'stdout:\n'
|
||||
cat out
|
||||
printf 'stderr:\n'
|
||||
cat err
|
||||
fi
|
||||
)
|
||||
|
||||
# Use this instead of `run` when we are expecting sourcecred to return a
|
||||
# non-zero exit code
|
||||
run_without_validation() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err
|
||||
)
|
||||
|
||||
test_expect_success SETUP "should print help message when called without args" '
|
||||
test_must_fail run_without_validation output &&
|
||||
grep -q "no project ID provided" err &&
|
||||
grep -q "sourcecred help output" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "help should print usage info" '
|
||||
run help output &&
|
||||
grep -q "usage: sourcecred output PROJECT_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "--help should print usage info" '
|
||||
run output --help &&
|
||||
grep -q "usage: sourcecred output PROJECT_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for multiple projects" '
|
||||
test_must_fail run_without_validation output sourcecred/sourcecred torvalds/linux &&
|
||||
grep -q "fatal: multiple project IDs provided" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for unloaded project" '
|
||||
test_must_fail run_without_validation output torvalds/linux &&
|
||||
grep -q "fatal: project torvalds/linux not loaded" err
|
||||
'
|
||||
|
||||
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
||||
test_set_prereq UPDATE_SNAPSHOT
|
||||
fi
|
||||
|
||||
test_expect_success SETUP,UPDATE_SNAPSHOT "should update the snapshot" '
|
||||
run output sourcecred-test/example-github &&
|
||||
mv out "${snapshot_file}"
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should be identical to the snapshot" '
|
||||
run output sourcecred-test/example-github &&
|
||||
diff -u out ${snapshot_file}
|
||||
'
|
||||
|
||||
test_done
|
||||
# vim: ft=sh
|
@ -1,96 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Disable these lint rules globally:
|
||||
# 2034 = unused variable (used by sharness)
|
||||
# 2016 = parameter expansion in single quotes
|
||||
# 1004 = backslash-newline in single quotes
|
||||
# shellcheck disable=SC2034,SC2016,SC1004
|
||||
:
|
||||
|
||||
test_description='tests for cli/scores.js'
|
||||
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
export GIT_ATTR_NOSYSTEM=1
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. ./sharness.sh
|
||||
|
||||
test_expect_success "environment and Node linking setup" '
|
||||
toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" &&
|
||||
snapshot_directory="${toplevel}/sharness/__snapshots__/" &&
|
||||
SOURCECRED_DIRECTORY="${snapshot_directory}/example-github-load" &&
|
||||
export SOURCECRED_DIRECTORY &&
|
||||
snapshot_file="${snapshot_directory}/example-github-scores.json" &&
|
||||
if [ -z "${SOURCECRED_BIN}" ]; then
|
||||
printf >&2 "warn: missing environment variable SOURCECRED_BIN\n" &&
|
||||
printf >&2 "warn: using repository bin directory as fallback\n" &&
|
||||
export SOURCECRED_BIN="${toplevel}/bin"
|
||||
fi &&
|
||||
export NODE_PATH="${toplevel}/node_modules${NODE_PATH:+:${NODE_PATH}}" &&
|
||||
test_set_prereq SETUP
|
||||
'
|
||||
|
||||
run() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
code=0
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err || code=$?
|
||||
if [ "${code}" -ne 0 ]; then
|
||||
printf '%s failed with %d\n' "sourcecred $*"
|
||||
printf 'stdout:\n'
|
||||
cat out
|
||||
printf 'stderr:\n'
|
||||
cat err
|
||||
fi
|
||||
)
|
||||
|
||||
# Use this instead of `run` when we are expecting sourcecred to return a
|
||||
# non-zero exit code
|
||||
run_without_validation() (
|
||||
set -eu
|
||||
rm -f out err
|
||||
node "${SOURCECRED_BIN}"/sourcecred.js "$@" >out 2>err
|
||||
)
|
||||
|
||||
test_expect_success SETUP "should print help message when called without args" '
|
||||
test_must_fail run_without_validation scores &&
|
||||
grep -q "no project ID provided" err &&
|
||||
grep -q "sourcecred help scores" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "help should print usage info" '
|
||||
run help scores &&
|
||||
grep -q "usage: sourcecred scores PROJECT_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "--help should print usage info" '
|
||||
run scores --help &&
|
||||
grep -q "usage: sourcecred scores PROJECT_ID" out
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for multiple projects" '
|
||||
test_must_fail run_without_validation scores sourcecred/sourcecred torvalds/linux &&
|
||||
grep -q "fatal: multiple project IDs provided" err
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should fail for unloaded project" '
|
||||
test_must_fail run_without_validation scores torvalds/linux &&
|
||||
grep -q "fatal: project torvalds/linux not loaded" err
|
||||
'
|
||||
|
||||
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
||||
test_set_prereq UPDATE_SNAPSHOT
|
||||
fi
|
||||
|
||||
test_expect_success SETUP,UPDATE_SNAPSHOT "should update the snapshot" '
|
||||
run scores sourcecred-test/example-github &&
|
||||
mv out "${snapshot_file}"
|
||||
'
|
||||
|
||||
test_expect_success SETUP "should be identical to the snapshot" '
|
||||
run scores sourcecred-test/example-github &&
|
||||
diff -u out ${snapshot_file}
|
||||
'
|
||||
|
||||
test_done
|
||||
# vim: ft=sh
|
@ -1,62 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Disable these lint rules globally:
|
||||
# 2034 = unused variable (used by sharness)
|
||||
# 2016 = parameter expansion in single quotes
|
||||
# 1004 = backslash-newline in single quotes
|
||||
# shellcheck disable=SC2034,SC2016,SC1004
|
||||
:
|
||||
|
||||
# If this test is failing, it probably means you need to update snapshots.
|
||||
# You can do so by setting your SOURCECRED_GITHUB_TOKEN (see README) and then
|
||||
# running scripts/update_snapshots.sh.
|
||||
|
||||
test_description='test snapshot integrity for sourcecred load'
|
||||
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
export GIT_ATTR_NOSYSTEM=1
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. ./sharness.sh
|
||||
|
||||
test_expect_success "environment and Node linking setup" '
|
||||
toplevel="$(git -C "$(dirname "$0")" rev-parse --show-toplevel)" &&
|
||||
snapshot_directory="${toplevel}/sharness/__snapshots__/example-github-load" &&
|
||||
if [ -z "${SOURCECRED_BIN}" ]; then
|
||||
printf >&2 "warn: missing environment variable SOURCECRED_BIN\n" &&
|
||||
printf >&2 "warn: using repository bin directory as fallback\n" &&
|
||||
export SOURCECRED_BIN="${toplevel}/bin"
|
||||
fi &&
|
||||
export NODE_PATH="${toplevel}/node_modules${NODE_PATH:+:${NODE_PATH}}" &&
|
||||
test_set_prereq SETUP
|
||||
'
|
||||
|
||||
if [ -n "${SOURCECRED_GITHUB_TOKEN:-}" ]; then
|
||||
test_set_prereq HAVE_GITHUB_TOKEN
|
||||
fi
|
||||
|
||||
test_expect_success EXPENSIVE,SETUP,HAVE_GITHUB_TOKEN \
|
||||
"should load sourcecred-test/example-github" '
|
||||
SOURCECRED_DIRECTORY=. node "${SOURCECRED_BIN}/sourcecred.js" \
|
||||
load sourcecred-test/example-github &&
|
||||
rm -rf cache &&
|
||||
test_set_prereq LOADED_GITHUB
|
||||
'
|
||||
|
||||
if [ -n "${UPDATE_SNAPSHOT}" ]; then
|
||||
test_set_prereq UPDATE_SNAPSHOT
|
||||
fi
|
||||
|
||||
test_expect_success LOADED_GITHUB,UPDATE_SNAPSHOT \
|
||||
"should update the snapshot" '
|
||||
rm -rf "$snapshot_directory" &&
|
||||
cp -r . "$snapshot_directory"
|
||||
'
|
||||
|
||||
test_expect_success LOADED_GITHUB "should be identical to the snapshot" '
|
||||
diff -qr . "$snapshot_directory"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
# vim: ft=sh
|
111
src/cli/clear.js
111
src/cli/clear.js
@ -1,111 +0,0 @@
|
||||
// @flow
|
||||
// implementation of `sourcecred clear`
|
||||
|
||||
import path from "path";
|
||||
import rimraf from "rimraf";
|
||||
|
||||
import dedent from "../util/dedent";
|
||||
import {type Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred clear --all
|
||||
sourcecred clear --cache
|
||||
sourcecred clear --help
|
||||
|
||||
Remove the SOURCECRED_DIRECTORY, i.e. the directory where data, caches,
|
||||
registries, etc. owned by SourceCred are stored.
|
||||
|
||||
Arguments:
|
||||
--all
|
||||
remove entire SOURCECRED_DIRECTORY
|
||||
|
||||
--cache
|
||||
remove only the SourcCred cache directory
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help clear'.
|
||||
|
||||
Environment Variables:
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help clear' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export function makeClear(removeDir: (string) => Promise<void>): Command {
|
||||
return async function clear(args, std) {
|
||||
async function remove(dir) {
|
||||
try {
|
||||
await removeDir(dir);
|
||||
return 0;
|
||||
} catch (error) {
|
||||
return die(std, `${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
return die(std, "no arguments provided");
|
||||
case 1:
|
||||
switch (args[0]) {
|
||||
case "--help":
|
||||
usage(std.out);
|
||||
return 0;
|
||||
|
||||
case "--all":
|
||||
return remove(Common.sourcecredDirectory());
|
||||
|
||||
case "--cache":
|
||||
return remove(path.join(Common.sourcecredDirectory(), "cache"));
|
||||
|
||||
default:
|
||||
return die(std, `unrecognized argument: '${args[0]}'`);
|
||||
}
|
||||
default:
|
||||
return die(
|
||||
std,
|
||||
`expected 1 argument but recieved: ${args.length} arguments`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function removeDir(p: string): Promise<void> {
|
||||
return new Promise((resolve, reject) =>
|
||||
rimraf(p, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
export const clear = makeClear(removeDir);
|
||||
|
||||
export default clear;
|
@ -1,134 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import path from "path";
|
||||
import tmp from "tmp";
|
||||
import fs from "fs";
|
||||
|
||||
import {makeClear, removeDir, help} from "./clear";
|
||||
import {run} from "./testUtil";
|
||||
import * as Common from "./common";
|
||||
|
||||
describe("cli/clear", () => {
|
||||
describe("'help' command", () => {
|
||||
it("prints usage when given no arguments", async () => {
|
||||
expect(await run(help, [])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred clear/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails when given arguments", async () => {
|
||||
expect(await run(help, ["foo/bar"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred clear/),
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("'makeClear' command", () => {
|
||||
it("prints usage with '--help'", async () => {
|
||||
const clear = makeClear(jest.fn());
|
||||
expect(await run(clear, ["--help"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred clear/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails when no arguments specified", async () => {
|
||||
const clear = makeClear(jest.fn());
|
||||
expect(await run(clear, [])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
"fatal: no arguments provided",
|
||||
"fatal: run 'sourcecred help clear' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails when an invalid argument is specified", async () => {
|
||||
const clear = makeClear(jest.fn());
|
||||
expect(await run(clear, ["invalid"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
"fatal: unrecognized argument: 'invalid'",
|
||||
"fatal: run 'sourcecred help clear' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails when more than one argument specified", async () => {
|
||||
const clear = makeClear(jest.fn());
|
||||
expect(await run(clear, ["1", "2"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
"fatal: expected 1 argument but recieved: 2 arguments",
|
||||
"fatal: run 'sourcecred help clear' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("passes correct param to removeDir with `--all`", async () => {
|
||||
const rmDir = jest.fn();
|
||||
const clear = makeClear(rmDir);
|
||||
await run(clear, ["--all"]);
|
||||
expect(rmDir).toHaveBeenCalledWith(Common.sourcecredDirectory());
|
||||
});
|
||||
|
||||
it("passes correct param to removeDir with `--cache`", async () => {
|
||||
const rmDir = jest.fn();
|
||||
const clear = makeClear(rmDir);
|
||||
await run(clear, ["--cache"]);
|
||||
const cacheDir = path.join(Common.sourcecredDirectory(), "cache");
|
||||
expect(rmDir).toHaveBeenCalledWith(cacheDir);
|
||||
});
|
||||
|
||||
function throwError() {
|
||||
return Promise.reject(new Error("test error"));
|
||||
}
|
||||
|
||||
it("--all returns error if removeDir errors", async () => {
|
||||
const clear = makeClear(throwError);
|
||||
expect(await run(clear, ["--all"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
"fatal: Error: test error",
|
||||
"fatal: run 'sourcecred help clear' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("--cache returns error if removeDir errors", async () => {
|
||||
const clear = makeClear(throwError);
|
||||
expect(await run(clear, ["--cache"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
"fatal: Error: test error",
|
||||
"fatal: run 'sourcecred help clear' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("rimraf", () => {
|
||||
it("removes the correct directory", async () => {
|
||||
const dir = tmp.dirSync();
|
||||
expect(fs.existsSync(dir.name)).toBe(true);
|
||||
await removeDir(dir.name);
|
||||
expect(fs.existsSync(dir.name)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
// @flow
|
||||
// Configuration and environment variables used by the CLI.
|
||||
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import deepFreeze from "deep-freeze";
|
||||
import fs from "fs-extra";
|
||||
import {type Weights, fromJSON as weightsFromJSON} from "../core/weights";
|
||||
import {validateToken, type GithubToken} from "../plugins/github/token";
|
||||
import {type DiscordToken} from "../plugins/experimental-discord/config";
|
||||
|
||||
export type PluginName = "git" | "github";
|
||||
|
||||
export const defaultPlugins: PluginName[] = deepFreeze(["github"]);
|
||||
|
||||
export function defaultSourcecredDirectory() {
|
||||
return path.join(os.tmpdir(), "sourcecred");
|
||||
}
|
||||
|
||||
export function sourcecredDirectory(): string {
|
||||
const env = process.env.SOURCECRED_DIRECTORY;
|
||||
return env != null ? env : defaultSourcecredDirectory();
|
||||
}
|
||||
|
||||
export function initiativesDirectory(): string | null {
|
||||
return process.env.SOURCECRED_INITIATIVES_DIRECTORY || null;
|
||||
}
|
||||
|
||||
export function githubToken(): ?GithubToken {
|
||||
const envToken = process.env.SOURCECRED_GITHUB_TOKEN;
|
||||
if (envToken == null || !envToken.length) {
|
||||
return null;
|
||||
}
|
||||
return validateToken(envToken);
|
||||
}
|
||||
|
||||
export function discordToken(): ?DiscordToken {
|
||||
return process.env.SOURCECRED_DISCORD_TOKEN || null;
|
||||
}
|
||||
|
||||
export async function loadWeights(path: string): Promise<Weights> {
|
||||
if (!(await fs.exists(path))) {
|
||||
throw new Error("Could not find the weights file");
|
||||
}
|
||||
const raw = await fs.readFile(path, "utf-8");
|
||||
const weightsJSON = JSON.parse(raw);
|
||||
try {
|
||||
return weightsFromJSON(weightsJSON);
|
||||
} catch (e) {
|
||||
throw new Error(`provided weights file is invalid:\n${e}`);
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import path from "path";
|
||||
import tmp from "tmp";
|
||||
import fs from "fs-extra";
|
||||
import * as Weights from "../core/weights";
|
||||
import {NodeAddress} from "../core/graph";
|
||||
import {validateToken} from "../plugins/github/token";
|
||||
|
||||
import {
|
||||
defaultPlugins,
|
||||
defaultSourcecredDirectory,
|
||||
sourcecredDirectory,
|
||||
githubToken,
|
||||
loadWeights,
|
||||
initiativesDirectory,
|
||||
} from "./common";
|
||||
|
||||
describe("cli/common", () => {
|
||||
const exampleGithubToken = validateToken("0".repeat(40));
|
||||
const exampleInitiativesDirectory = path.join(__dirname, "initiatives");
|
||||
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(require("os"), "tmpdir")
|
||||
.mockReturnValue(path.join("/", "your", "tmpdir"));
|
||||
});
|
||||
|
||||
describe("defaultPlugins", () => {
|
||||
it("is an array including the GitHub plugin name", () => {
|
||||
expect(defaultPlugins).toEqual(expect.arrayContaining(["github"]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultSourcecredDirectory", () => {
|
||||
it("gives a file under the OS's temporary directory", () => {
|
||||
expect(defaultSourcecredDirectory()).toEqual(
|
||||
path.join("/", "your", "tmpdir", "sourcecred")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sourcecredDirectory", () => {
|
||||
it("uses the environment variable when available", () => {
|
||||
const dir = path.join("/", "my", "sourcecred");
|
||||
process.env.SOURCECRED_DIRECTORY = dir;
|
||||
expect(sourcecredDirectory()).toEqual(dir);
|
||||
});
|
||||
it("uses the default directory if no environment variable is set", () => {
|
||||
delete process.env.SOURCECRED_DIRECTORY;
|
||||
expect(sourcecredDirectory()).toEqual(
|
||||
path.join("/", "your", "tmpdir", "sourcecred")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("githubToken", () => {
|
||||
it("uses the environment variable when available", () => {
|
||||
process.env.SOURCECRED_GITHUB_TOKEN = exampleGithubToken;
|
||||
expect(githubToken()).toEqual(exampleGithubToken);
|
||||
});
|
||||
it("returns `null` if the environment variable is not set", () => {
|
||||
delete process.env.SOURCECRED_GITHUB_TOKEN;
|
||||
expect(githubToken()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initiativesDirectory", () => {
|
||||
it("uses the environment variable when available", () => {
|
||||
process.env.SOURCECRED_INITIATIVES_DIRECTORY = exampleInitiativesDirectory;
|
||||
expect(initiativesDirectory()).toEqual(exampleInitiativesDirectory);
|
||||
});
|
||||
it("returns `null` if the environment variable is not set", () => {
|
||||
delete process.env.SOURCECRED_INITIATIVES_DIRECTORY;
|
||||
expect(initiativesDirectory()).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadWeights", () => {
|
||||
function tmpWithContents(contents: mixed) {
|
||||
const name = tmp.tmpNameSync();
|
||||
fs.writeFileSync(name, JSON.stringify(contents));
|
||||
return name;
|
||||
}
|
||||
it("works in a simple success case", async () => {
|
||||
const weights = Weights.empty();
|
||||
// Make a modification, just to be sure we aren't always loading the
|
||||
// default weights.
|
||||
weights.nodeWeights.set(NodeAddress.empty, 3);
|
||||
const weightsJSON = Weights.toJSON(weights);
|
||||
const file = tmpWithContents(weightsJSON);
|
||||
const weights_ = await loadWeights(file);
|
||||
expect(weights).toEqual(weights_);
|
||||
});
|
||||
it("rejects if the file is not a valid weights file", () => {
|
||||
const file = tmpWithContents(1234);
|
||||
expect.assertions(1);
|
||||
return loadWeights(file).catch((e) =>
|
||||
expect(e.message).toMatch("provided weights file is invalid:")
|
||||
);
|
||||
});
|
||||
it("rejects if the file does not exist", () => {
|
||||
const file = tmp.tmpNameSync();
|
||||
expect.assertions(1);
|
||||
return loadWeights(file).catch((e) =>
|
||||
expect(e.message).toMatch("Could not find the weights file")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import * as Common from "./common";
|
||||
import {type Command} from "./command";
|
||||
import {LoggingTaskReporter} from "../util/taskReporter";
|
||||
import {DataDirectory} from "../backend/dataDirectory";
|
||||
import Loader from "../plugins/experimental-discord/loader";
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help discord' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO: hack
|
||||
const reactionWeights = {"sourcecred:678399364418502669": 4};
|
||||
|
||||
const discord: Command = async (args, std) => {
|
||||
if (args.length !== 1) {
|
||||
return die(std, "Expected one positional argument (or --help).");
|
||||
}
|
||||
const [guildId] = args;
|
||||
|
||||
const taskReporter = new LoggingTaskReporter();
|
||||
const dir = new DataDirectory(Common.sourcecredDirectory());
|
||||
const token = process.env.SOURCECRED_DISCORD_TOKEN || null;
|
||||
if (!token) {
|
||||
throw new Error("Expecting a SOURCECRED_DISCORD_TOKEN");
|
||||
}
|
||||
|
||||
const opts = {
|
||||
guildId,
|
||||
reactionWeights,
|
||||
};
|
||||
|
||||
await Loader.updateMirror(opts, token, dir, taskReporter);
|
||||
const wg = await Loader.createGraph(opts, dir);
|
||||
console.log(wg.graph, wg.weights);
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default discord;
|
@ -1,123 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of `sourcecred discourse`
|
||||
// This is a (likely temporary command) to facilitate loading a single
|
||||
// discourse server.
|
||||
|
||||
import dedent from "../util/dedent";
|
||||
import {LoggingTaskReporter} from "../util/taskReporter";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import * as Weights from "../core/weights";
|
||||
import {load} from "../api/load";
|
||||
import {declaration as discourseDeclaration} from "../plugins/discourse/declaration";
|
||||
import {type Project, createProject} from "../core/project";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred discourse DISCOURSE_URL
|
||||
[--weights WEIGHTS_FILE]
|
||||
sourcecred discourse --help
|
||||
|
||||
Loads a target Discourse server, generating cred scores for it.
|
||||
|
||||
Arguments:
|
||||
DISCOURSE_URL
|
||||
The url to the Discourse server in question, for example
|
||||
https://discourse.sourcecred.io
|
||||
|
||||
--weights WEIGHTS_FILE
|
||||
Path to a json file which contains a weights configuration.
|
||||
This will be used instead of the default weights and persisted.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help discourse'.
|
||||
|
||||
Environment variables:
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help discourse' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const command: Command = async (args, std) => {
|
||||
const positionalArgs = [];
|
||||
let weightsPath: string | null = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
case "--weights": {
|
||||
if (weightsPath != null)
|
||||
return die(std, "'--weights' given multiple times");
|
||||
if (++i >= args.length)
|
||||
return die(std, "'--weights' given without value");
|
||||
weightsPath = args[i];
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
positionalArgs.push(args[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (positionalArgs.length !== 1) {
|
||||
return die(std, "Expected one positional arguments (or --help).");
|
||||
}
|
||||
const [serverUrl] = positionalArgs;
|
||||
const httpRE = new RegExp(/^https?:\/\//);
|
||||
if (!httpRE.test(serverUrl)) {
|
||||
die(std, "expected server url to start with 'https://' or 'http://'");
|
||||
}
|
||||
const projectId = serverUrl.trim().replace(httpRE, "");
|
||||
const project: Project = createProject({
|
||||
id: projectId,
|
||||
discourseServer: {serverUrl},
|
||||
});
|
||||
const taskReporter = new LoggingTaskReporter();
|
||||
let weights = Weights.empty();
|
||||
if (weightsPath) {
|
||||
weights = await Common.loadWeights(weightsPath);
|
||||
}
|
||||
const plugins = [discourseDeclaration];
|
||||
|
||||
await load(
|
||||
{
|
||||
project,
|
||||
params: null,
|
||||
weightsOverrides: weights,
|
||||
plugins,
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: null,
|
||||
discordToken: null,
|
||||
initiativesDirectory: null,
|
||||
},
|
||||
taskReporter
|
||||
);
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
export default command;
|
@ -1,163 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of `sourcecred gen-project`.
|
||||
// This method is intended as a placeholder for generating a project definition,
|
||||
// before we build a more intentional declarative json config approach, as discussed
|
||||
// here: https://github.com/sourcecred/sourcecred/issues/1232#issuecomment-519538494
|
||||
// This method is untested; please take care when modifying it!
|
||||
|
||||
import dedent from "../util/dedent";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import stringify from "json-stable-stringify";
|
||||
import {
|
||||
type Project,
|
||||
projectToJSON,
|
||||
createProject as defaultProject,
|
||||
} from "../core/project";
|
||||
import {type RepoId} from "../plugins/github/repoId";
|
||||
import {specToProject} from "../plugins/github/specToProject";
|
||||
import {type GithubToken} from "../plugins/github/token";
|
||||
import * as NullUtil from "../util/null";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred gen-project PROJECT_ID
|
||||
[--github GITHUB_SPEC [...]]
|
||||
[--discourse-url DISCOURSE_URL]
|
||||
sourcecred gen-project --help
|
||||
|
||||
Generates a SourceCred project configuration based on the provided specs.
|
||||
|
||||
A PROJECT_ID must be provided, and will be the name of the project.
|
||||
|
||||
Zero or more github specs may be provided; each GitHub spec can be of the
|
||||
form OWNER/NAME (as in 'torvalds/linux') for loading a single repository,
|
||||
or @owner (as in '@torvalds') for loading all repositories owned by a given
|
||||
account.
|
||||
|
||||
A discourse url and discourse username may be provided. If one is provided,
|
||||
then both must be. The discourse url is a url to a valid Discourse server,
|
||||
as in 'https://discourse.sourcecred.io', and the username must be a valid
|
||||
user on that server, as in 'credbot'. The user in question should not have
|
||||
any special or admin permissions, so that it won't encounter hidden
|
||||
messages.
|
||||
|
||||
All of the GitHub specs, and the Discourse specification (if it exists)
|
||||
will be combined into a single project. The serialized project
|
||||
configuration will be printed to stdout.
|
||||
|
||||
Arguments:
|
||||
PROJECT_ID
|
||||
Locally unique identifier for the project.
|
||||
|
||||
--github GITHUB_SPEC
|
||||
A specification (in form 'OWNER/NAME' or '@OWNER') of GitHub
|
||||
repositories to load.
|
||||
|
||||
--discourse-url DISCOURSE_URL
|
||||
The url of a Discourse server to load.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help scores'.
|
||||
|
||||
Environment Variables:
|
||||
SOURCECRED_GITHUB_TOKEN
|
||||
API token for GitHub. This should be a 40-character hex
|
||||
string. Required if using GitHub specs.
|
||||
|
||||
To generate a token, create a "Personal access token" at
|
||||
<https://github.com/settings/tokens>. When loading data for
|
||||
public repositories, no special permissions are required.
|
||||
For private repositories, the 'repo' scope is required.
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help gen-project' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export const genProject: Command = async (args, std) => {
|
||||
let projectId: string | null = null;
|
||||
let discourseUrl: string | null = null;
|
||||
const githubSpecs: string[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
case "--github": {
|
||||
if (++i >= args.length)
|
||||
return die(std, "'--github' given without value");
|
||||
githubSpecs.push(args[i]);
|
||||
break;
|
||||
}
|
||||
case "--discourse-url": {
|
||||
if (discourseUrl != null)
|
||||
return die(std, "'--discourse-url' given multiple times");
|
||||
if (++i >= args.length)
|
||||
return die(std, "'--discourse-url' given without value");
|
||||
discourseUrl = args[i];
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (projectId != null) return die(std, "multiple project IDs provided");
|
||||
projectId = args[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectId == null) {
|
||||
return die(std, "no project ID provided");
|
||||
}
|
||||
|
||||
const githubToken = Common.githubToken();
|
||||
const project: Project = await createProject({
|
||||
projectId,
|
||||
githubSpecs,
|
||||
discourseUrl,
|
||||
githubToken,
|
||||
});
|
||||
const projectJSON = projectToJSON(project);
|
||||
console.log(stringify(projectJSON));
|
||||
return 0;
|
||||
};
|
||||
|
||||
export async function createProject(opts: {|
|
||||
+projectId: string,
|
||||
+githubSpecs: $ReadOnlyArray<string>,
|
||||
+discourseUrl: string | null,
|
||||
+githubToken: ?GithubToken,
|
||||
|}): Promise<Project> {
|
||||
const {projectId, githubSpecs, discourseUrl, githubToken} = opts;
|
||||
let repoIds: RepoId[] = [];
|
||||
let discourseServer = null;
|
||||
if (discourseUrl) {
|
||||
discourseServer = {serverUrl: discourseUrl};
|
||||
}
|
||||
if (githubSpecs.length && githubToken == null) {
|
||||
throw new Error("Provided GitHub specs without GitHub token.");
|
||||
}
|
||||
for (const spec of githubSpecs) {
|
||||
const subproject = await specToProject(spec, NullUtil.get(githubToken));
|
||||
repoIds = repoIds.concat(subproject.repoIds);
|
||||
}
|
||||
return defaultProject({id: projectId, repoIds, discourseServer});
|
||||
}
|
||||
|
||||
export default genProject;
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
@ -4,13 +4,6 @@
|
||||
import type {Command} from "./command";
|
||||
import dedent from "../util/dedent";
|
||||
|
||||
import {help as loadHelp} from "./load";
|
||||
import {help as scoresHelp} from "./scores";
|
||||
import {help as outputHelp} from "./output";
|
||||
import {help as clearHelp} from "./clear";
|
||||
import {help as genProjectHelp} from "./genProject";
|
||||
import {help as discourseHelp} from "./discourse";
|
||||
|
||||
const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
@ -19,12 +12,6 @@ const help: Command = async (args, std) => {
|
||||
const command = args[0];
|
||||
const subHelps: {[string]: Command} = {
|
||||
help: metaHelp,
|
||||
load: loadHelp,
|
||||
scores: scoresHelp,
|
||||
output: outputHelp,
|
||||
clear: clearHelp,
|
||||
"gen-project": genProjectHelp,
|
||||
discourse: discourseHelp,
|
||||
};
|
||||
if (subHelps[command] !== undefined) {
|
||||
return subHelps[command](args.slice(1), std);
|
||||
@ -43,12 +30,6 @@ function usage(print: (string) => void): void {
|
||||
sourcecred [--version] [--help]
|
||||
|
||||
Commands:
|
||||
load load repository data into SourceCred
|
||||
clear clear SoucrceCred data
|
||||
scores print SourceCred scores to stdout
|
||||
output print SourceCred data output to stdout
|
||||
gen-project print a SourceCred project config to stdout
|
||||
discourse load a Discourse server into SourceCred
|
||||
help show this help message
|
||||
|
||||
Use 'sourcecred help COMMAND' for help about an individual command.
|
||||
|
@ -25,26 +25,6 @@ describe("cli/help", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("prints help about 'sourcecred load'", async () => {
|
||||
expect(await run(help, ["load"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred load/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("prints help about 'sourcecred clear'", async () => {
|
||||
expect(await run(help, ["clear"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred clear/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails when given an unknown command", async () => {
|
||||
expect(await run(help, ["wat"])).toEqual({
|
||||
exitCode: 1,
|
||||
|
204
src/cli/load.js
204
src/cli/load.js
@ -1,204 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of `sourcecred load`
|
||||
|
||||
import dedent from "../util/dedent";
|
||||
import {LoggingTaskReporter} from "../util/taskReporter";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import * as Weights from "../core/weights";
|
||||
import {projectFromJSON, type Project} from "../core/project";
|
||||
import {load} from "../api/load";
|
||||
import {specToProject} from "../plugins/github/specToProject";
|
||||
import fs from "fs-extra";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import {declaration as discourseDeclaration} from "../plugins/discourse/declaration";
|
||||
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||
import {declaration as identityDeclaration} from "../plugins/identity/declaration";
|
||||
import {partialParams} from "../analysis/timeline/params";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred load [PROJECT_SPEC...]
|
||||
[--weights WEIGHTS_FILE]
|
||||
[--project PROJECT_FILE]
|
||||
sourcecred load --help
|
||||
|
||||
Load a target project, generating a cred attribution for it.
|
||||
|
||||
PROJET_SPEC is a string that describes a project.
|
||||
Currently, it must be a GitHub repository in the form OWNER/NAME: for
|
||||
example, torvalds/linux. Support for more PROJECT_SPECS will be added
|
||||
shortly.
|
||||
|
||||
Arguments:
|
||||
PROJECT_SPEC:
|
||||
Identifier of a project to load.
|
||||
|
||||
--project PROJECT_FILE
|
||||
Path to a json file which contains a project configuration.
|
||||
That project will be loaded.
|
||||
|
||||
--weights WEIGHTS_FILE
|
||||
Path to a json file which contains a weights configuration.
|
||||
This will be used instead of the default weights and persisted.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help load'.
|
||||
|
||||
Environment variables:
|
||||
SOURCECRED_GITHUB_TOKEN
|
||||
API token for GitHub. This should be a 40-character hex
|
||||
string. Required if using the GitHub plugin; ignored
|
||||
otherwise.
|
||||
|
||||
To generate a token, create a "Personal access token" at
|
||||
<https://github.com/settings/tokens>. When loading data for
|
||||
public repositories, no special permissions are required.
|
||||
For private repositories, the 'repo' scope is required.
|
||||
|
||||
SOURCECRED_INITIATIVES_DIRECTORY
|
||||
Local path to a directory containing json files with
|
||||
initiative declarations. Required when using the Initiatives
|
||||
plugin; ignored otherwise.
|
||||
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help load' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const loadCommand: Command = async (args, std) => {
|
||||
const projectSpecs: string[] = [];
|
||||
const projectPaths: string[] = [];
|
||||
let weightsPath: ?string;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
case "--weights": {
|
||||
if (weightsPath != null)
|
||||
return die(std, "'--weights' given multiple times");
|
||||
if (++i >= args.length)
|
||||
return die(std, "'--weights' given without value");
|
||||
weightsPath = args[i];
|
||||
break;
|
||||
}
|
||||
case "--project": {
|
||||
if (++i >= args.length)
|
||||
return die(std, "'--project' given without value");
|
||||
projectPaths.push(args[i]);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
projectSpecs.push(args[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (projectSpecs.length === 0 && projectPaths.length === 0) {
|
||||
return die(std, "projects not specified");
|
||||
}
|
||||
|
||||
let weights = Weights.empty();
|
||||
if (weightsPath) {
|
||||
weights = await loadWeightOverrides(weightsPath);
|
||||
}
|
||||
|
||||
const initiativesDirectory = Common.initiativesDirectory();
|
||||
const githubToken = Common.githubToken();
|
||||
if (githubToken == null) {
|
||||
return die(std, "SOURCECRED_GITHUB_TOKEN not set");
|
||||
}
|
||||
|
||||
const taskReporter = new LoggingTaskReporter();
|
||||
|
||||
const specProjects: $ReadOnlyArray<Project> = await Promise.all(
|
||||
projectSpecs.map((s) => specToProject(s, githubToken))
|
||||
);
|
||||
const manualProjects = await Promise.all(projectPaths.map(loadProject));
|
||||
const projects = specProjects.concat(manualProjects);
|
||||
const optionses = projects.map((project) => {
|
||||
const plugins: PluginDeclaration[] = [];
|
||||
if (project.discourseServer != null) {
|
||||
plugins.push(discourseDeclaration);
|
||||
}
|
||||
if (project.repoIds.length) {
|
||||
plugins.push(githubDeclaration);
|
||||
}
|
||||
if (project.identities.length) {
|
||||
plugins.push(identityDeclaration);
|
||||
}
|
||||
const params = partialParams(project.timelineCredParams);
|
||||
|
||||
return {
|
||||
project,
|
||||
params,
|
||||
weightsOverrides: weights,
|
||||
plugins,
|
||||
discordToken: Common.discordToken(),
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken,
|
||||
initiativesDirectory,
|
||||
};
|
||||
});
|
||||
// Deliberately load in serial because GitHub requests that their API not
|
||||
// be called concurrently
|
||||
for (const options of optionses) {
|
||||
await load(options, taskReporter);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const loadWeightOverrides = async (path: string) => {
|
||||
if (!(await fs.exists(path))) {
|
||||
throw new Error("Could not find the weights file");
|
||||
}
|
||||
|
||||
const raw = await fs.readFile(path, "utf-8");
|
||||
const weightsJSON = JSON.parse(raw);
|
||||
try {
|
||||
return Weights.fromJSON(weightsJSON);
|
||||
} catch (e) {
|
||||
throw new Error(`provided weights file is invalid:\n${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const loadProject = async (path: string) => {
|
||||
if (!(await fs.exists(path))) {
|
||||
throw new Error(`Project path ${path} does not exist`);
|
||||
}
|
||||
|
||||
const raw = await fs.readFile(path, "utf-8");
|
||||
const json = JSON.parse(raw);
|
||||
try {
|
||||
return projectFromJSON(json);
|
||||
} catch (e) {
|
||||
throw new Error(`project at path ${path} is invalid:\n${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
export default loadCommand;
|
@ -1,228 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import tmp from "tmp";
|
||||
import fs from "fs-extra";
|
||||
|
||||
import {LoggingTaskReporter} from "../util/taskReporter";
|
||||
import {NodeAddress} from "../core/graph";
|
||||
import {run} from "./testUtil";
|
||||
import loadCommand, {help} from "./load";
|
||||
import type {LoadOptions} from "../api/load";
|
||||
import * as Weights from "../core/weights";
|
||||
import * as Common from "./common";
|
||||
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||
import {createProject} from "../core/project";
|
||||
import {makeRepoId, stringToRepoId} from "../plugins/github/repoId";
|
||||
import {validateToken} from "../plugins/github/token";
|
||||
import {defaultParams} from "../analysis/timeline/params";
|
||||
|
||||
jest.mock("../api/load", () => ({load: jest.fn()}));
|
||||
type JestMockFn = $Call<typeof jest.fn>;
|
||||
const load: JestMockFn = (require("../api/load").load: any);
|
||||
|
||||
describe("cli/load", () => {
|
||||
const exampleGithubToken = validateToken("0".repeat(40));
|
||||
const exampleDiscordToken = "fakeBotToken";
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Tests should call `newSourcecredDirectory` directly when they
|
||||
// need the value. We call it here in case a test needs it to be set
|
||||
// but does not care about the particular value.
|
||||
newSourcecredDirectory();
|
||||
});
|
||||
|
||||
function newSourcecredDirectory() {
|
||||
const dirname = tmp.dirSync().name;
|
||||
process.env.SOURCECRED_DIRECTORY = dirname;
|
||||
process.env.SOURCECRED_GITHUB_TOKEN = exampleGithubToken;
|
||||
process.env.SOURCECRED_DISCORD_TOKEN = exampleDiscordToken;
|
||||
process.env.SOURCECRED_INITIATIVES_DIRECTORY = tmp.dirSync().name;
|
||||
return dirname;
|
||||
}
|
||||
|
||||
describe("'help' command", () => {
|
||||
it("prints usage when given no arguments", async () => {
|
||||
expect(await run(help, [])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred load/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
it("fails when given arguments", async () => {
|
||||
expect(await run(help, ["foo/bar"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred load/),
|
||||
]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("'load' command wrapper", () => {
|
||||
it("prints usage with '--help'", async () => {
|
||||
expect(await run(loadCommand, ["--help"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: expect.arrayContaining([
|
||||
expect.stringMatching(/^usage: sourcecred load/),
|
||||
]),
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("calls load with a single repo", async () => {
|
||||
const invocation = run(loadCommand, ["foo/bar"]);
|
||||
const expectedOptions: LoadOptions = {
|
||||
project: createProject({
|
||||
id: "foo/bar",
|
||||
repoIds: [makeRepoId("foo", "bar")],
|
||||
}),
|
||||
params: defaultParams(),
|
||||
weightsOverrides: Weights.empty(),
|
||||
plugins: [githubDeclaration],
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: exampleGithubToken,
|
||||
discordToken: exampleDiscordToken,
|
||||
initiativesDirectory: Common.initiativesDirectory(),
|
||||
};
|
||||
expect(await invocation).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: [],
|
||||
stderr: [],
|
||||
});
|
||||
expect(load).toHaveBeenCalledWith(
|
||||
expectedOptions,
|
||||
expect.any(LoggingTaskReporter)
|
||||
);
|
||||
});
|
||||
|
||||
it("calls load with multiple repos", async () => {
|
||||
const invocation = run(loadCommand, ["foo/bar", "zoink/zod"]);
|
||||
const expectedOptions: (string) => LoadOptions = (projectId: string) => ({
|
||||
project: createProject({
|
||||
id: projectId,
|
||||
repoIds: [stringToRepoId(projectId)],
|
||||
}),
|
||||
params: defaultParams(),
|
||||
weightsOverrides: Weights.empty(),
|
||||
plugins: [githubDeclaration],
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: exampleGithubToken,
|
||||
discordToken: exampleDiscordToken,
|
||||
initiativesDirectory: Common.initiativesDirectory(),
|
||||
});
|
||||
expect(await invocation).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: [],
|
||||
stderr: [],
|
||||
});
|
||||
expect(load).toHaveBeenCalledWith(
|
||||
expectedOptions("foo/bar"),
|
||||
expect.any(LoggingTaskReporter)
|
||||
);
|
||||
expect(load).toHaveBeenCalledWith(
|
||||
expectedOptions("zoink/zod"),
|
||||
expect.any(LoggingTaskReporter)
|
||||
);
|
||||
});
|
||||
|
||||
it("loads the weights, if provided", async () => {
|
||||
const weights = Weights.empty();
|
||||
weights.nodeWeights.set(NodeAddress.empty, 33);
|
||||
const weightsJSON = Weights.toJSON(weights);
|
||||
const weightsFile = tmp.tmpNameSync();
|
||||
fs.writeFileSync(weightsFile, JSON.stringify(weightsJSON));
|
||||
const invocation = run(loadCommand, [
|
||||
"foo/bar",
|
||||
"--weights",
|
||||
weightsFile,
|
||||
]);
|
||||
const expectedOptions: LoadOptions = {
|
||||
project: createProject({
|
||||
id: "foo/bar",
|
||||
repoIds: [makeRepoId("foo", "bar")],
|
||||
}),
|
||||
params: defaultParams(),
|
||||
weightsOverrides: weights,
|
||||
plugins: [githubDeclaration],
|
||||
sourcecredDirectory: Common.sourcecredDirectory(),
|
||||
githubToken: exampleGithubToken,
|
||||
discordToken: exampleDiscordToken,
|
||||
initiativesDirectory: Common.initiativesDirectory(),
|
||||
};
|
||||
expect(await invocation).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: [],
|
||||
stderr: [],
|
||||
});
|
||||
expect(load).toHaveBeenCalledWith(
|
||||
expectedOptions,
|
||||
expect.any(LoggingTaskReporter)
|
||||
);
|
||||
});
|
||||
|
||||
describe("errors if", () => {
|
||||
async function expectFailure({args, message}) {
|
||||
expect(await run(loadCommand, args)).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: message,
|
||||
});
|
||||
expect(load).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
it("no projects specified", async () => {
|
||||
await expectFailure({
|
||||
args: [],
|
||||
message: [
|
||||
"fatal: projects not specified",
|
||||
"fatal: run 'sourcecred help load' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("the weights file does not exist", async () => {
|
||||
const weightsFile = tmp.tmpNameSync();
|
||||
await expectFailure({
|
||||
args: ["foo/bar", "--weights", weightsFile],
|
||||
message: [
|
||||
expect.stringMatching("^Error: Could not find the weights file"),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("the weights file is invalid", async () => {
|
||||
const weightsFile = tmp.tmpNameSync();
|
||||
fs.writeFileSync(weightsFile, JSON.stringify({weights: 3}));
|
||||
await expectFailure({
|
||||
args: ["foo/bar", "--weights", weightsFile],
|
||||
message: [
|
||||
expect.stringMatching("^Error: provided weights file is invalid"),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("the repo identifier is invalid", async () => {
|
||||
await expectFailure({
|
||||
args: ["missing_delimiter"],
|
||||
message: [
|
||||
expect.stringMatching("^Error: invalid spec: missing_delimiter"),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("the SOURCECRED_GITHUB_TOKEN is unset", async () => {
|
||||
delete process.env.SOURCECRED_GITHUB_TOKEN;
|
||||
await expectFailure({
|
||||
args: ["missing_delimiter"],
|
||||
message: [
|
||||
"fatal: SOURCECRED_GITHUB_TOKEN not set",
|
||||
"fatal: run 'sourcecred help load' for help",
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,23 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import {handlingErrors} from "./command";
|
||||
import sourcecred from "./sourcecred";
|
||||
|
||||
require("../tools/entry");
|
||||
|
||||
export default function main(): Promise<void> {
|
||||
return handlingErrors(sourcecred)(process.argv.slice(2), {
|
||||
out: (x) => console.log(x),
|
||||
err: (x) => console.error(x),
|
||||
}).then((exitCode) => {
|
||||
process.exitCode = exitCode;
|
||||
});
|
||||
}
|
||||
|
||||
// Only run in the Webpack bundle, not as a Node module (during tests).
|
||||
/* istanbul ignore next */
|
||||
/*:: declare var __webpack_require__: mixed; */
|
||||
// eslint-disable-next-line camelcase
|
||||
if (typeof __webpack_require__ !== "undefined") {
|
||||
main();
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of `sourcecred output`.
|
||||
|
||||
import {toCompat} from "../util/compat";
|
||||
import {fromJSON as pluginsFromJSON} from "../analysis/pluginDeclaration";
|
||||
import {
|
||||
fromTimelineCredAndPlugins,
|
||||
COMPAT_INFO as OUTPUT_COMPAT_INFO,
|
||||
} from "../analysis/output";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import dedent from "../util/dedent";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import stringify from "json-stable-stringify";
|
||||
import {TimelineCred} from "../analysis/timeline/timelineCred";
|
||||
import {directoryForProjectId} from "../core/project_io";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred output PROJECT_ID [--help]
|
||||
|
||||
Print the SourceCred data output for a given PROJECT_ID.
|
||||
Data must already be loaded for the given PROJECT_ID, using
|
||||
'sourcecred load PROJECT_ID'
|
||||
|
||||
PROJECT_ID refers to a project, as loaded by the \`load\` command.
|
||||
Run \`sourcecred load --help\` for details.
|
||||
|
||||
Arguments:
|
||||
PROJECT_ID
|
||||
Already-loaded project for which to load data.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help output'.
|
||||
|
||||
Environment Variables:
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help output' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export const output: Command = async (args, std) => {
|
||||
let projectId: string | null = null;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
default: {
|
||||
if (projectId != null) return die(std, "multiple project IDs provided");
|
||||
projectId = args[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectId == null) {
|
||||
return die(std, "no project ID provided");
|
||||
}
|
||||
|
||||
const projectDirectory = directoryForProjectId(
|
||||
projectId,
|
||||
Common.sourcecredDirectory()
|
||||
);
|
||||
const credFile = path.join(projectDirectory, "cred.json");
|
||||
const pluginsFile = path.join(projectDirectory, "pluginDeclarations.json");
|
||||
if (!fs.existsSync(credFile) || !fs.existsSync(pluginsFile)) {
|
||||
std.err(`fatal: project ${projectId} not loaded`);
|
||||
std.err(`Try running \`sourcecred load ${projectId}\` first.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const credBlob = await fs.readFile(credFile);
|
||||
const credJSON = JSON.parse(credBlob.toString());
|
||||
const timelineCred = TimelineCred.fromJSON(credJSON);
|
||||
|
||||
const pluginsBlob = await fs.readFile(pluginsFile);
|
||||
const pluginsJSON = JSON.parse(pluginsBlob.toString());
|
||||
const plugins = pluginsFromJSON(pluginsJSON);
|
||||
const output = fromTimelineCredAndPlugins(timelineCred, plugins);
|
||||
const compatOutput = toCompat(OUTPUT_COMPAT_INFO, output);
|
||||
std.out(stringify(compatOutput, {space: 2}));
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default output;
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
@ -1,134 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of `sourcecred scores`.
|
||||
|
||||
import {toCompat, type Compatible} from "../util/compat";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import dedent from "../util/dedent";
|
||||
import type {Command} from "./command";
|
||||
import * as Common from "./common";
|
||||
import stringify from "json-stable-stringify";
|
||||
import {
|
||||
TimelineCred,
|
||||
type Interval,
|
||||
type CredNode,
|
||||
} from "../analysis/timeline/timelineCred";
|
||||
import {directoryForProjectId} from "../core/project_io";
|
||||
import {NodeAddress} from "../core/graph";
|
||||
|
||||
const COMPAT_INFO = {type: "sourcecred/cli/scores", version: "0.2.0"};
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
dedent`\
|
||||
usage: sourcecred scores PROJECT_ID [--help]
|
||||
|
||||
Print the SourceCred user scores for a given PROJECT_ID.
|
||||
Data must already be loaded for the given PROJECT_ID, using
|
||||
'sourcecred load PROJECT_ID'
|
||||
|
||||
PROJECT_ID refers to a project, as loaded by the \`load\` command.
|
||||
Run \`sourcecred load --help\` for details.
|
||||
|
||||
Arguments:
|
||||
PROJECT_ID
|
||||
Already-loaded project for which to load data.
|
||||
|
||||
--help
|
||||
Show this help message and exit, as 'sourcecred help scores'.
|
||||
|
||||
Environment Variables:
|
||||
SOURCECRED_DIRECTORY
|
||||
Directory owned by SourceCred, in which data, caches,
|
||||
registries, etc. are stored. Optional: defaults to a
|
||||
directory 'sourcecred' under your OS's temporary directory;
|
||||
namely:
|
||||
${Common.defaultSourcecredDirectory()}
|
||||
`.trimRight()
|
||||
);
|
||||
}
|
||||
|
||||
function die(std, message) {
|
||||
std.err("fatal: " + message);
|
||||
std.err("fatal: run 'sourcecred help scores' for help");
|
||||
return 1;
|
||||
}
|
||||
|
||||
export type NodeOutput = {|
|
||||
// The components of the SourceCred address for the node
|
||||
// Conventionally, the first two components designate what plugin
|
||||
// generated the node, as in [PLUGIN_AUTHOR, PLUGIN_NAME, ...]
|
||||
// Subsequent components are created according to plugin-specific logic.
|
||||
+address: $ReadOnlyArray<string>,
|
||||
+totalCred: number,
|
||||
+intervalCred: $ReadOnlyArray<number>,
|
||||
|};
|
||||
|
||||
export type ScoreOutput = Compatible<{|
|
||||
+users: $ReadOnlyArray<NodeOutput>,
|
||||
+intervals: $ReadOnlyArray<Interval>,
|
||||
|}>;
|
||||
|
||||
export const scores: Command = async (args, std) => {
|
||||
let projectId: string | null = null;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
switch (args[i]) {
|
||||
case "--help": {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
}
|
||||
default: {
|
||||
if (projectId != null) return die(std, "multiple project IDs provided");
|
||||
projectId = args[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectId == null) {
|
||||
return die(std, "no project ID provided");
|
||||
}
|
||||
|
||||
const projectDirectory = directoryForProjectId(
|
||||
projectId,
|
||||
Common.sourcecredDirectory()
|
||||
);
|
||||
const credFile = path.join(projectDirectory, "cred.json");
|
||||
if (!fs.existsSync(credFile)) {
|
||||
std.err(`fatal: project ${projectId} not loaded`);
|
||||
std.err(`Try running \`sourcecred load ${projectId}\` first.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const credBlob = await fs.readFile(credFile);
|
||||
const credJSON = JSON.parse(credBlob.toString());
|
||||
const timelineCred = TimelineCred.fromJSON(credJSON);
|
||||
const userOutput: NodeOutput[] = timelineCred
|
||||
.userNodes()
|
||||
.map((n: CredNode) => {
|
||||
const address = NodeAddress.toParts(n.node.address);
|
||||
return {
|
||||
address,
|
||||
intervalCred: n.cred,
|
||||
totalCred: n.total,
|
||||
};
|
||||
});
|
||||
const output: ScoreOutput = toCompat(COMPAT_INFO, {
|
||||
users: userOutput,
|
||||
intervals: timelineCred.intervals(),
|
||||
});
|
||||
std.out(stringify(output, {space: 2}));
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default scores;
|
||||
|
||||
export const help: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
usage(std.out);
|
||||
return 0;
|
||||
} else {
|
||||
usage(std.err);
|
||||
return 1;
|
||||
}
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
// @flow
|
||||
// Implementation of the root `sourcecred` command.
|
||||
|
||||
import type {Command} from "./command";
|
||||
|
||||
import {VERSION_SHORT} from "../core/version";
|
||||
|
||||
import help from "./help";
|
||||
import load from "./load";
|
||||
import scores from "./scores";
|
||||
import output from "./output";
|
||||
import clear from "./clear";
|
||||
import genProject from "./genProject";
|
||||
import discourse from "./discourse";
|
||||
import discord from "./discord";
|
||||
|
||||
const sourcecred: Command = async (args, std) => {
|
||||
if (args.length === 0) {
|
||||
help([], {out: std.err, err: std.err});
|
||||
return 1;
|
||||
}
|
||||
switch (args[0]) {
|
||||
case "--version":
|
||||
std.out("sourcecred " + VERSION_SHORT);
|
||||
return 0;
|
||||
case "--help":
|
||||
case "help":
|
||||
return help(args.slice(1), std);
|
||||
case "load":
|
||||
return load(args.slice(1), std);
|
||||
case "clear":
|
||||
return clear(args.slice(1), std);
|
||||
case "scores":
|
||||
return scores(args.slice(1), std);
|
||||
case "output":
|
||||
return output(args.slice(1), std);
|
||||
case "gen-project":
|
||||
return genProject(args.slice(1), std);
|
||||
case "discord":
|
||||
return discord(args.slice(1), std);
|
||||
case "discourse":
|
||||
return discourse(args.slice(1), std);
|
||||
default:
|
||||
std.err("fatal: unknown command: " + JSON.stringify(args[0]));
|
||||
std.err("fatal: run 'sourcecred help' for commands and usage");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
export default sourcecred;
|
@ -1,77 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import {run} from "./testUtil";
|
||||
import sourcecred from "./sourcecred";
|
||||
|
||||
function mockCommand(name) {
|
||||
return jest.fn().mockImplementation(async (args, std) => {
|
||||
std.out(`out(${name}): ${JSON.stringify(args)}`);
|
||||
std.err(`err(${name})`);
|
||||
return args.length;
|
||||
});
|
||||
}
|
||||
|
||||
jest.mock("./help", () => mockCommand("help"));
|
||||
jest.mock("./load", () => mockCommand("load"));
|
||||
jest.mock("./clear", () => mockCommand("clear"));
|
||||
|
||||
describe("cli/sourcecred", () => {
|
||||
it("fails with usage when invoked with no arguments", async () => {
|
||||
expect(await run(sourcecred, [])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: ["out(help): []", "err(help)"],
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to '--version'", async () => {
|
||||
expect(await run(sourcecred, ["--version"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: [expect.stringMatching(/^sourcecred v\d+\.\d+\.\d+$/)],
|
||||
stderr: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to '--help'", async () => {
|
||||
expect(await run(sourcecred, ["--help"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: ["out(help): []"],
|
||||
stderr: ["err(help)"],
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to 'help'", async () => {
|
||||
expect(await run(sourcecred, ["help"])).toEqual({
|
||||
exitCode: 0,
|
||||
stdout: ["out(help): []"],
|
||||
stderr: ["err(help)"],
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to 'load'", async () => {
|
||||
expect(await run(sourcecred, ["load", "foo/bar", "foo/baz"])).toEqual({
|
||||
exitCode: 2,
|
||||
stdout: ['out(load): ["foo/bar","foo/baz"]'],
|
||||
stderr: ["err(load)"],
|
||||
});
|
||||
});
|
||||
|
||||
it("responds to 'clear --all'", async () => {
|
||||
expect(await run(sourcecred, ["clear", "--all"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: ['out(clear): ["--all"]'],
|
||||
stderr: ["err(clear)"],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails given an unknown command", async () => {
|
||||
expect(await run(sourcecred, ["wat"])).toEqual({
|
||||
exitCode: 1,
|
||||
stdout: [],
|
||||
stderr: [
|
||||
'fatal: unknown command: "wat"',
|
||||
"fatal: run 'sourcecred help' for commands and usage",
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
@ -11,7 +11,7 @@ jest.spyOn(console, "error").mockImplementation(() => {});
|
||||
const logMock: JestMockFn<any, void> = console.log;
|
||||
const errorMock: JestMockFn<any, void> = console.error;
|
||||
|
||||
describe("cli/main", () => {
|
||||
describe("cli2/main", () => {
|
||||
beforeEach(() => {
|
||||
sourcecredMock.mockReset();
|
||||
logMock.mockClear();
|
Loading…
x
Reference in New Issue
Block a user