├── .gitignore ├── start.sh ├── character.js ├── package.json ├── notify.sh ├── README.md ├── run.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | pm2 start run.js --name github_run --cron '0 */2 * * *' -------------------------------------------------------------------------------- /character.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Creeper from Minecraft 4 | * 5 | * 000002222222200000000000000000000000000000000 6 | * 000002442244200000000000000000000000000000000 7 | * 000002442244200000000000000000000000000000000 8 | * 000002224422200000000000000000000000000000000 9 | * 000002244442200000000000000000000000000000000 10 | * 000002244442200000000000000000000000000000000 11 | * 000002242242200000000000000000000000000000000 12 | */ 13 | exports.creeper = [ 14 | [2, 2, 2, 2, 2, 2, 2, 2], 15 | [2, 4, 4, 2, 2, 4, 4, 2], 16 | [2, 4, 4, 2, 2, 4, 4, 2], 17 | [2, 2, 2, 4, 4, 2, 2, 2], 18 | [2, 2, 4, 4, 4, 4, 2, 2], 19 | [2, 2, 4, 4, 4, 4, 2, 2], 20 | [2, 2, 4, 2, 2, 4, 2, 2], 21 | ]; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-run", 3 | "version": "1.0.0", 4 | "description": "Running commits graph, powered by travis-ci", 5 | "main": "run.js", 6 | "scripts": { 7 | "retag": "git tag time-machine --force", 8 | "gen": "node ./run.js", 9 | "travel": "git reset time-machine --hard" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/liangzr/github-run.git" 14 | }, 15 | "keywords": [ 16 | "commits", 17 | "graph", 18 | "draw", 19 | "github" 20 | ], 21 | "author": "liangzr ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/liangzr/github-run/issues" 25 | }, 26 | "homepage": "https://github.com/liangzr/github-run#readme", 27 | "dependencies": { 28 | "date-fns": "^1.30.1", 29 | "shelljs": "^0.8.3", 30 | "single-line-log": "^1.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /notify.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | # GitHub didn't update the contributions graph immediately, but 4 | # only the commits from default branch can be display on the 5 | # contributions graph. So we can change the default branch to 6 | # force it refresh 7 | 8 | # Wait a moment 9 | sleep 60 10 | 11 | curl https://api.github.com/repos/${GITHUB_USER}/github-run \ 12 | -u ${GITHUB_USER}:${GITHUB_DEV_TOKEN} \ 13 | -X PATCH \ 14 | -H 'Content-Type: application/json;charset=UTF-8' \ 15 | -d '{"name":"github-run","default_branch":"master"}' \ 16 | > /dev/null -s 17 | 18 | echo 'Change default branch to master' 19 | # curl https://github.com/${GITHUB_USER} > /dev/null -s 20 | 21 | curl https://api.github.com/repos/${GITHUB_USER}/github-run \ 22 | -u ${GITHUB_USER}:${GITHUB_DEV_TOKEN} \ 23 | -X PATCH \ 24 | -H 'Content-Type: application/json;charset=UTF-8' \ 25 | -d '{"name":"github-run","default_branch":"graph"}' \ 26 | > /dev/null -s 27 | 28 | echo 'Default branch change back to graph' 29 | echo 'Notified the GitHub' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Run 🏃 2 | 3 | Let your contributions graph running! 4 | 5 | ![](https://ws3.sinaimg.cn/large/006tKfTcgy1g198k0szxej31ks0dwwhy.jpg) 6 | 7 | ## Setup 8 | 9 | **Step 1** 10 | 11 | Fork this repository, and clone YOUR OWN `github-run` 12 | 13 | **Step 2** 14 | 15 | Initialize dependecies by using `yarn`,and you need to install `pm2` in advance. 16 | 17 | ```sh 18 | yarn 19 | sh start.sh 20 | ``` 21 | 22 | ## Usage 23 | 24 | You can create your own characters by [this site](https://codepen.io/sebdeckers/pen/vOXeKV), and put it in `character.js` file. 25 | 26 | ### Environment Variables 27 | 28 | There are three global environment variables used in the project, you can configure them in your `.bashrc` or `.zshrc` 29 | 30 | ```sh 31 | export GITHUB_USER= 32 | export GITHUB_EMAIL= 33 | export GITHUB_DEV_TOKEN= 34 | ``` 35 | 36 | You can apply a personal access token in [here](https://github.com/settings/tokens) 37 | 38 | > Generate contributions graph didn't need one token, but the `notify.sh` script use it to force GitHub update contributions graph immediately 39 | 40 | ## Problems 41 | 42 | - [x] ~~GitHub did not update the contributions graph immediately, so you might see two graph overlay~~ 43 | - [ ] You have to run scripts locally to reuse private ssh keys of GitHub 44 | 45 | ## License 46 | 47 | MIT 48 | 49 | just for fun 🔥 50 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const { 4 | subYears, 5 | subWeeks, 6 | addDays, 7 | lastDayOfWeek, 8 | addHours, 9 | } = require('date-fns'); 10 | const logLine = require('single-line-log').stdout; 11 | const { exec } = require('shelljs'); 12 | 13 | const character = require('./character').creeper; 14 | 15 | /* ---------------- User Config ---------------------*/ 16 | 17 | // Set up a default branch to commit 18 | const graphBranch = 'graph'; 19 | 20 | 21 | const COLOR = { 22 | NONE: 0, 23 | LIGHT: 1, 24 | MEDIUM: 2, 25 | DEEP: 3, 26 | HEAVY: 4, 27 | }; 28 | 29 | const level2commits = { 30 | 0: 0, 31 | 1: 1, 32 | 2: 4, 33 | 3: 7, 34 | 4: 10, 35 | }; 36 | 37 | // The background color that besides the character graph 38 | const BG_COLOR = COLOR.LIGHT; 39 | 40 | // Start date (first block) of the contributions graph 41 | const oneYearAgo = subYears(new Date(), 1); 42 | const prevWeek = subWeeks(oneYearAgo, 1); 43 | const START_DATE = addHours(lastDayOfWeek(prevWeek, { weekStartsOn: 1 }), 12); 44 | 45 | const globalConfig = { 46 | async: false, 47 | silent: true, 48 | env: process.env, 49 | }; 50 | 51 | /** 52 | * Prepare the repositoy before commit character graph 53 | */ 54 | const initRepository = () => { 55 | // Configure git user inside the node shell 56 | exec(`git config --global user.name ${process.env.GITHUB_USER}`); 57 | exec(`git config --global user.email ${process.env.GITHUB_EMAIL}`); 58 | 59 | // Clean up & reset the graph branch 60 | exec('git checkout master'); 61 | exec(`git branch -D ${graphBranch}`); 62 | exec(`git checkout -b ${graphBranch}`); 63 | }; 64 | 65 | /** 66 | * Check that the matrix is validated 67 | * @param {Array} matrix pixel matrix 68 | */ 69 | const checkMatrix = (matrix) => { 70 | console.log(matrix); 71 | if ( 72 | !Array.isArray(matrix) || 73 | matrix.some(subarray => !Array.isArray(subarray)) 74 | ) { 75 | throw new Error('Character matrix need to be a two-dimensional array'); 76 | } 77 | }; 78 | 79 | /** 80 | * Complete the matrix with default color 81 | * @param {Array} matrix pixel matrix 82 | */ 83 | const completeMatrix = (matrix) => { 84 | const movableTimes = 52 - matrix[0].length; 85 | const pos = ~~(movableTimes * (new Date().getHours() / 24)); 86 | const paddings = [new Array(pos), new Array(movableTimes - pos)].map(arr => arr.fill(BG_COLOR)); 87 | return matrix.map(row => paddings[0].concat(row, paddings[1])); 88 | }; 89 | 90 | /** 91 | * Drawing commits graph by git commit 92 | * @param {} matrix pixel matrix 93 | */ 94 | const draw = (matrix) => { 95 | for (let col = 0; col <= 52; col += 1) { 96 | for (let row = 0; row <= 6; row += 1) { 97 | const level = matrix[row][col]; 98 | const commits = level2commits[level]; 99 | for (let i = 0; i < commits; i += 1) { 100 | const someday = addDays(START_DATE, (col * 7) + row); 101 | exec( 102 | `GIT_AUTHOR_DATE="${someday}" GIT_COMMITTER_DATE="${someday}" git commit --allow-empty -m ${col}-${row}-${i}`, 103 | globalConfig, 104 | ); 105 | logLine(`Committed: ${col}-${row}-${i}`); 106 | } 107 | } 108 | } 109 | }; 110 | 111 | // Prepare the pixel matrix 112 | checkMatrix(character); 113 | const matrix = completeMatrix(character); 114 | 115 | // Drawing graph 116 | initRepository(); 117 | draw(matrix); 118 | 119 | console.log('\nAll committed, wait to push...'); 120 | 121 | exec(`git push -u origin HEAD:${graphBranch} --force`); 122 | exec('sh notify.sh', globalConfig); 123 | 124 | console.log('\nAll done :P'); 125 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ansi-regex@^2.0.0: 6 | version "2.1.1" 7 | resolved "http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 8 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 9 | 10 | balanced-match@^1.0.0: 11 | version "1.0.0" 12 | resolved "http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 13 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 14 | 15 | brace-expansion@^1.1.7: 16 | version "1.1.11" 17 | resolved "http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 18 | integrity sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= 19 | dependencies: 20 | balanced-match "^1.0.0" 21 | concat-map "0.0.1" 22 | 23 | code-point-at@^1.0.0: 24 | version "1.1.0" 25 | resolved "http://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 26 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 27 | 28 | concat-map@0.0.1: 29 | version "0.0.1" 30 | resolved "http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 31 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 32 | 33 | date-fns@^1.30.1: 34 | version "1.30.1" 35 | resolved "http://registry.npm.taobao.org/date-fns/download/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" 36 | integrity sha1-LnG/CxGRU9u0zE6I2epaz7UNwFw= 37 | 38 | fs.realpath@^1.0.0: 39 | version "1.0.0" 40 | resolved "http://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 41 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 42 | 43 | glob@^7.0.0: 44 | version "7.1.3" 45 | resolved "http://registry.npm.taobao.org/glob/download/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 46 | integrity sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE= 47 | dependencies: 48 | fs.realpath "^1.0.0" 49 | inflight "^1.0.4" 50 | inherits "2" 51 | minimatch "^3.0.4" 52 | once "^1.3.0" 53 | path-is-absolute "^1.0.0" 54 | 55 | inflight@^1.0.4: 56 | version "1.0.6" 57 | resolved "http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 58 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 59 | dependencies: 60 | once "^1.3.0" 61 | wrappy "1" 62 | 63 | inherits@2: 64 | version "2.0.3" 65 | resolved "http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 66 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 67 | 68 | interpret@^1.0.0: 69 | version "1.2.0" 70 | resolved "http://registry.npm.taobao.org/interpret/download/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" 71 | integrity sha1-1QYaYiS+WOgIOYX1AU2EQ1lXYpY= 72 | 73 | is-fullwidth-code-point@^1.0.0: 74 | version "1.0.0" 75 | resolved "http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 76 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 77 | dependencies: 78 | number-is-nan "^1.0.0" 79 | 80 | minimatch@^3.0.4: 81 | version "3.0.4" 82 | resolved "http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 83 | integrity sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM= 84 | dependencies: 85 | brace-expansion "^1.1.7" 86 | 87 | number-is-nan@^1.0.0: 88 | version "1.0.1" 89 | resolved "http://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 90 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 91 | 92 | once@^1.3.0: 93 | version "1.4.0" 94 | resolved "http://registry.npm.taobao.org/once/download/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 95 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 96 | dependencies: 97 | wrappy "1" 98 | 99 | path-is-absolute@^1.0.0: 100 | version "1.0.1" 101 | resolved "http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 102 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 103 | 104 | path-parse@^1.0.6: 105 | version "1.0.6" 106 | resolved "http://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 107 | integrity sha1-1i27VnlAXXLEc37FhgDp3c8G0kw= 108 | 109 | rechoir@^0.6.2: 110 | version "0.6.2" 111 | resolved "http://registry.npm.taobao.org/rechoir/download/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" 112 | integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= 113 | dependencies: 114 | resolve "^1.1.6" 115 | 116 | resolve@^1.1.6: 117 | version "1.10.0" 118 | resolved "http://registry.npm.taobao.org/resolve/download/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" 119 | integrity sha1-O9qur0XMB/N1ZW39LlTtCBCxAbo= 120 | dependencies: 121 | path-parse "^1.0.6" 122 | 123 | shelljs@^0.8.3: 124 | version "0.8.3" 125 | resolved "http://registry.npm.taobao.org/shelljs/download/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" 126 | integrity sha1-p/MxlSDr8J7oEnWyNorbKGZZsJc= 127 | dependencies: 128 | glob "^7.0.0" 129 | interpret "^1.0.0" 130 | rechoir "^0.6.2" 131 | 132 | single-line-log@^1.1.2: 133 | version "1.1.2" 134 | resolved "http://registry.npm.taobao.org/single-line-log/download/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" 135 | integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= 136 | dependencies: 137 | string-width "^1.0.1" 138 | 139 | string-width@^1.0.1: 140 | version "1.0.2" 141 | resolved "http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 142 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 143 | dependencies: 144 | code-point-at "^1.0.0" 145 | is-fullwidth-code-point "^1.0.0" 146 | strip-ansi "^3.0.0" 147 | 148 | strip-ansi@^3.0.0: 149 | version "3.0.1" 150 | resolved "http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 151 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 152 | dependencies: 153 | ansi-regex "^2.0.0" 154 | 155 | wrappy@1: 156 | version "1.0.2" 157 | resolved "http://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 158 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 159 | --------------------------------------------------------------------------------