├── .dockerignore ├── .env.schema ├── .github ├── images │ ├── cover.png │ ├── name-new-file.png │ ├── create-new-file.png │ ├── token-permissions.png │ └── private-contributions.png ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── workflows │ ├── tests.yml │ ├── release.yml │ ├── audit.yml │ └── build.yml ├── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── markdown_link_check_config.json ├── action.yml ├── .env.defaults ├── Dockerfile ├── package.json ├── src ├── random │ ├── index.js │ └── index.spec.js └── index.js ├── LICENSE ├── .gitignore ├── .gitattributes └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | clone 3 | node_modules 4 | -------------------------------------------------------------------------------- /.env.schema: -------------------------------------------------------------------------------- 1 | GITHUB_ACTOR= 2 | GITHUB_REPOSITORY= 3 | 4 | GITHUB_TOKEN= 5 | GIT_EMAIL= 6 | -------------------------------------------------------------------------------- /.github/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcanseco/github-contribution-graph-action/HEAD/.github/images/cover.png -------------------------------------------------------------------------------- /.github/images/name-new-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcanseco/github-contribution-graph-action/HEAD/.github/images/name-new-file.png -------------------------------------------------------------------------------- /.github/images/create-new-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcanseco/github-contribution-graph-action/HEAD/.github/images/create-new-file.png -------------------------------------------------------------------------------- /.github/images/token-permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcanseco/github-contribution-graph-action/HEAD/.github/images/token-permissions.png -------------------------------------------------------------------------------- /.github/images/private-contributions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcanseco/github-contribution-graph-action/HEAD/.github/images/private-contributions.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help me improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | -------------------------------------------------------------------------------- /markdown_link_check_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpHeaders": [ 3 | { 4 | "urls": ["https://docs.github.com/"], 5 | "headers": { 6 | "Accept-Encoding": "zstd, br, gzip, deflate" 7 | } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Please email [borj@cans.eco](mailto:borj@cans.eco) if you find a security vulnerability on this project. If you find a *potential* vulnerability, feel free to leave an issue and I'll respond ASAP. 4 | 5 | Thanks for disclosing responsibly 🖤 6 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - run: npm ci 15 | - run: npm test 16 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Autopopulate your contribution graph' 2 | author: 'Borja Canseco ' 3 | description: 'This action will automatically push empty commits to one of your GitHub repos.' 4 | runs: 5 | using: 'docker' 6 | image: 'Dockerfile' 7 | branding: 8 | icon: 'grid' 9 | color: 'green' 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [published, edited] 6 | 7 | jobs: 8 | latest-major-version-tagger: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions-r-us/actions-tagger@latest 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.env.defaults: -------------------------------------------------------------------------------- 1 | GIT_HOST=github.com 2 | GIT_COMMIT_MESSAGE=chore(actions): empty commit for contribution graph 3 | GIT_SSH_COMMAND=ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no 4 | 5 | MAX_DAYS=1 6 | MIN_COMMITS_PER_DAY=1 7 | MAX_COMMITS_PER_DAY=1 8 | INCLUDE_WEEKDAYS=true 9 | INCLUDE_WEEKENDS=true 10 | FORCE_PUSH=false 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Brief overview:** 2 | 3 | 4 | **Changelog:** 5 | * 6 | * 7 | * 8 | 9 | **Steps to test/confirm my changes:** 10 | 1. 11 | 2. 12 | 3. 13 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: audit 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '0 12 * * *' # every day at noon 10 | 11 | jobs: 12 | audit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@master 16 | - run: npm audit --production 17 | codeql: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@master 21 | - uses: github/codeql-action/init@main 22 | - uses: github/codeql-action/analyze@main 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.11.1 2 | 3 | # When running as a GitHub Action, the WORKDIR is controlled by GitHub. 4 | # Furthermore, they recommend NOT setting it in the Dockerfile, which makes running locally difficult. 5 | # Thus we copy files to another directory and cd into it before running (regardless of the environment). 6 | COPY . /github-contribution-graph-action 7 | 8 | # Executing a shell is required for environment variable substitution when running as a GitHub Action. 9 | ENTRYPOINT ["sh", "-c", "cd /github-contribution-graph-action && npm install --production && npm start"] 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "start": "node --experimental-top-level-await --experimental-specifier-resolution=node src", 6 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest" 7 | }, 8 | "engines": { 9 | "node": ">=16.11.1" 10 | }, 11 | "dependencies": { 12 | "auto-parse": "^1.8.0", 13 | "date-fns": "^2.14.0", 14 | "dotenv-extended": "^2.8.0", 15 | "simple-git": "^3.16.0" 16 | }, 17 | "devDependencies": { 18 | "@types/jest": "^25.2.3", 19 | "jest": "^27.5.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/random/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a pseudorandom number between two inclusive numbers. 3 | * @param {Number} min The minimum rollable number 4 | * @param {Number} max The maximum rollable number 5 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive 6 | */ 7 | export const getRandomInt = (min, max) => { 8 | min = Math.ceil(min); 9 | max = Math.floor(max); 10 | 11 | if (min > max || Math.min(min, max) < 0) { 12 | throw new Error('min and max must be a positive integer range'); 13 | } 14 | 15 | return Math.floor(Math.random() * (max - min + 1)) + min; 16 | }; 17 | -------------------------------------------------------------------------------- /src/random/index.spec.js: -------------------------------------------------------------------------------- 1 | import {getRandomInt} from '.'; 2 | 3 | it('should disallow negative values', () => { 4 | expect(() => getRandomInt(-5, 1)).toThrow(); 5 | expect(() => getRandomInt(-5, -1)).toThrow(); 6 | expect(() => getRandomInt(1, -5)).toThrow(); 7 | }); 8 | 9 | it('should disallow min greater than max', () => { 10 | expect(() => getRandomInt(1, 2)).not.toThrow(); 11 | expect(() => getRandomInt(1, 1)).not.toThrow(); 12 | expect(() => getRandomInt(1, 0)).toThrow(); 13 | expect(() => getRandomInt(0, 0)).not.toThrow(); 14 | }); 15 | 16 | it('should work with numeric strings', () => { 17 | expect(() => getRandomInt(1, 5)).not.toThrow(); 18 | expect(() => getRandomInt('1', '5')).not.toThrow(); 19 | }); 20 | 21 | it('should be inclusive on min and max', () => { 22 | expect(getRandomInt(0, 0)).toEqual(0); 23 | expect(getRandomInt(1, 1)).toEqual(1); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | commitizen: 11 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - uses: aevea/commitsar@master 16 | markdown: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@master 20 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 21 | with: 22 | config-file: markdown_link_check_config.json 23 | dogfooding: 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: write 27 | steps: 28 | - uses: actions/checkout@master # only required for eating our own dog food 29 | - uses: ./ 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | GIT_EMAIL: borj@cans.eco 33 | GIT_BRANCH: gh-pages 34 | GIT_COMMIT_MESSAGE: 'chore(actions): dogfooding triggered by push' 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Borja Canseco 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import autoParse from 'auto-parse'; 2 | import dotenv from 'dotenv-extended' 3 | import getUnixTime from 'date-fns/fp/getUnixTime'; 4 | import fromUnixTime from 'date-fns/fp/fromUnixTime'; 5 | import subDays from 'date-fns/fp/subDays'; 6 | import isWeekend from 'date-fns/fp/isWeekend'; 7 | import fs from 'fs/promises'; 8 | import git from 'simple-git'; 9 | import {getRandomInt} from './random'; 10 | 11 | const env = autoParse({ 12 | GIT_BRANCH: process.env.GIT_BRANCH || process.env.GITHUB_REF?.replace(/^refs\/heads\//, ''), 13 | ORIGIN_TIMESTAMP: process.env.ORIGIN_TIMESTAMP || getUnixTime(new Date()), 14 | ...dotenv.load({errorOnMissing: true, includeProcessEnv: true}), 15 | }); 16 | const localPath = './clone'; 17 | const repoPath = `https://${env.GITHUB_ACTOR}:${env.GITHUB_TOKEN}@${env.GIT_HOST}/${env.GITHUB_REPOSITORY}`; 18 | const secondLine = 'Committed via https://github.com/marketplace/actions/autopopulate-your-contribution-graph'; 19 | const dayOffsets = [...Array(env.MAX_DAYS).keys()]; 20 | 21 | await fs.mkdir(localPath); 22 | 23 | if (env.FORCE_PUSH) { 24 | await git(localPath).init(); 25 | } else { 26 | await git().clone(repoPath, localPath, ['--single-branch', '-b', env.GIT_BRANCH]); 27 | } 28 | 29 | await git(localPath).env({GIT_SSH_COMMAND: env.GIT_SSH_COMMAND}); 30 | await git(localPath).addConfig('user.name', env.GITHUB_ACTOR); 31 | await git(localPath).addConfig('user.email', env.GIT_EMAIL); 32 | 33 | await dayOffsets 34 | .map((dayOffset) => subDays(dayOffset, fromUnixTime(env.ORIGIN_TIMESTAMP))) 35 | .filter((day) => !(!env.INCLUDE_WEEKENDS && isWeekend(day))) 36 | .filter((day) => !(!env.INCLUDE_WEEKDAYS && !isWeekend(day))) 37 | .map((/** @type {Date} */ day) => { 38 | const commitsToMake = getRandomInt(env.MIN_COMMITS_PER_DAY, env.MAX_COMMITS_PER_DAY); 39 | return [...Array(commitsToMake)].map((_, i) => async () => { 40 | const {commit: sha} = await git(localPath).commit([env.GIT_COMMIT_MESSAGE, secondLine], { 41 | '--allow-empty': null, 42 | '--date': `format:iso8601:${day.toISOString()}`, 43 | }); 44 | console.log(`Successfully committed ${sha} on ${day.toISOString()} (${i + 1} / ${commitsToMake})`); 45 | }); 46 | }) 47 | .flat() 48 | .reduce((commitPromises, nextPromise) => commitPromises.then(nextPromise), Promise.resolve()); 49 | 50 | await git(localPath).push(repoPath, `HEAD:${env.GIT_BRANCH}`, env.FORCE_PUSH && {'--force': null}); 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | clone 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | .yarn/cache 115 | .yarn/unplugged 116 | .yarn/build-state.yml 117 | .yarn/install-state.gz 118 | .pnp.* 119 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project lead at [borj@cans.eco](mailto:borj@cans.eco). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | Thanks for considering a contribution to this project! 🙏 4 | 5 | ## Local development with Docker 🐳 6 | 7 | ### Requirements 📝 8 | 9 | * [git](https://git-scm.com) 10 | * [Docker](https://docs.docker.com/get-docker/) 11 | 12 | ### Setup 🛠 13 | 14 | 1. Clone and navigate to the repo: 15 | ```console 16 | $ git clone https://github.com/bcanseco/github-contribution-graph-action.git 17 | $ cd github-contribution-graph-action 18 | ``` 19 | 1. Create an `.env` file: 20 | ```console 21 | $ cp .env.schema .env 22 | ``` 23 | 1. Fill in the values. 24 | * `GITHUB_ACTOR`: Set this to your GitHub username. 25 | * e.g. `bcanseco` 26 | * Note that when running as a GitHub Action, the user doesn't need to provide this. 27 | * `GITHUB_REPOSITORY`: Set this to your username followed by a slash and your repository name. 28 | * e.g. `bcanseco/github-contribution-graph-action` 29 | * Again, this is only necessary when running locally. 30 | * `GITHUB_TOKEN`: Set this to [your personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). 31 | * Make sure to check the **Repo** boxes. 32 | * Note that the personal access token has more permissions than the `GITHUB_TOKEN` provided by the Actions runner. Read more about this [here](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token). 33 | * `GIT_EMAIL`: Set this to [an email associated with your GitHub account](https://github.com/settings/emails). 34 | * `GIT_BRANCH`: Set this to `master`. 35 | 1. Make sure your Docker daemon is running. 36 | 37 | ### Running 👟 38 | 39 | Use the commands below to build and run a container: 40 | 41 | ```console 42 | $ docker build -t github-contribution-graph-action . 43 | $ docker run --rm github-contribution-graph-action 44 | ``` 45 | 46 | You can alternatively run `npm start` directly without Docker, but this isn't recommended. 47 | 48 | ## Q&A 🤔 49 | 50 | ### Why is this a Docker action and not a JavaScript action? 📦 51 | 52 | Two reasons: 53 | 54 | 1. For things like this that mess with the `git` CLI directly, it's easier to test with containers. 55 | 1. You cannot currently specify an [npm run-script](https://docs.npmjs.com/cli/run-script) (e.g. `npm start`) as an entrypoint with JavaScript actions. 56 | 57 | ### Why go with Unix rather than ISO for `ORIGIN_TIMESTAMP`? ⌚ 58 | 59 | For some reason, the GitHub Actions runner mutates ISO timestamp strings passed as environment variables into something unparseable by the `date-fns` library. 60 | 61 | ### Why use environment variables instead of [inputs][inputs]? 🔌 62 | 63 | [inputs]: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs 64 | 65 | Two reasons: 66 | 67 | 1. GitHub transforms input parameter casing and prepends `INPUT_`, so additional code would be necessary to revert this before processing them. 68 | 1. Input parameters are expected to be documented in the [action definition](../action.yml), which would be yet another source of truth to maintain. 69 | 70 | ### Why not add a cool `CUSTOM_TEXT` feature for writing on the grid? 🎨 71 | 72 | This is a great idea! In theory, this GitHub Action could re-draw your custom string of text every week or so. That way it stays in the center of your contribution graph. 73 | 74 | Unfortunately though, commits made on a GitHub repository will always show on the graph even if those commits are later removed through a force push. This means that every time we would try to re-draw, the old text would remain on your graph. 75 | 76 | You can already imagine the consequences: 77 | 78 | * Text overlapping 79 | * Seeing last year's strings cut-off 80 | * Repeated text 81 | 82 | A workaround involves deleting and recreating the GitHub repo for the user on each run, but that requires permissions that the `GITHUB_TOKEN` doesn't provide. For now I'm calling this out-of-scope, but feel free to open an issue if you have any ideas. 83 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## GITATTRIBUTES FOR WEB PROJECTS 2 | # 3 | # These settings are for any web project. 4 | # 5 | # Details per file setting: 6 | # text These files should be normalized (i.e. convert CRLF to LF). 7 | # binary These files are binary and should be left untouched. 8 | # 9 | # Note that binary is a macro for -text -diff. 10 | ###################################################################### 11 | 12 | # Auto detect 13 | ## Handle line endings automatically for files detected as 14 | ## text and leave all files detected as binary untouched. 15 | ## This will handle all files NOT defined below. 16 | * text=auto 17 | 18 | # Source code 19 | *.bash text eol=lf 20 | *.bat text eol=crlf 21 | *.cmd text eol=crlf 22 | *.coffee text 23 | *.css text 24 | *.htm text diff=html 25 | *.html text diff=html 26 | *.inc text 27 | *.ini text 28 | *.js text 29 | *.json text 30 | *.jsx text 31 | *.less text 32 | *.ls text 33 | *.map text -diff 34 | *.od text 35 | *.onlydata text 36 | *.php text diff=php 37 | *.pl text 38 | *.ps1 text eol=crlf 39 | *.py text diff=python 40 | *.rb text diff=ruby 41 | *.sass text 42 | *.scm text 43 | *.scss text diff=css 44 | *.sh text eol=lf 45 | *.sql text 46 | *.styl text 47 | *.tag text 48 | *.ts text 49 | *.tsx text 50 | *.xml text 51 | *.xhtml text diff=html 52 | 53 | # Docker 54 | Dockerfile text 55 | 56 | # Documentation 57 | *.ipynb text 58 | *.markdown text 59 | *.md text 60 | *.mdwn text 61 | *.mdown text 62 | *.mkd text 63 | *.mkdn text 64 | *.mdtxt text 65 | *.mdtext text 66 | *.txt text 67 | AUTHORS text 68 | CHANGELOG text 69 | CHANGES text 70 | CONTRIBUTING text 71 | COPYING text 72 | copyright text 73 | *COPYRIGHT* text 74 | INSTALL text 75 | license text 76 | LICENSE text 77 | NEWS text 78 | readme text 79 | *README* text 80 | TODO text 81 | 82 | # Templates 83 | *.dot text 84 | *.ejs text 85 | *.haml text 86 | *.handlebars text 87 | *.hbs text 88 | *.hbt text 89 | *.jade text 90 | *.latte text 91 | *.mustache text 92 | *.njk text 93 | *.phtml text 94 | *.tmpl text 95 | *.tpl text 96 | *.twig text 97 | *.vue text 98 | 99 | # Configs 100 | *.cnf text 101 | *.conf text 102 | *.config text 103 | .editorconfig text 104 | .env text 105 | .gitattributes text 106 | .gitconfig text 107 | .htaccess text 108 | *.lock text -diff 109 | package-lock.json text -diff 110 | *.toml text 111 | *.yaml text 112 | *.yml text 113 | browserslist text 114 | Makefile text 115 | makefile text 116 | 117 | # Heroku 118 | Procfile text 119 | 120 | # Graphics 121 | *.ai binary 122 | *.bmp binary 123 | *.eps binary 124 | *.gif binary 125 | *.gifv binary 126 | *.ico binary 127 | *.jng binary 128 | *.jp2 binary 129 | *.jpg binary 130 | *.jpeg binary 131 | *.jpx binary 132 | *.jxr binary 133 | *.pdf binary 134 | *.png binary 135 | *.psb binary 136 | *.psd binary 137 | # SVG treated as an asset (binary) by default. 138 | *.svg text 139 | # If you want to treat it as binary, 140 | # use the following line instead. 141 | # *.svg binary 142 | *.svgz binary 143 | *.tif binary 144 | *.tiff binary 145 | *.wbmp binary 146 | *.webp binary 147 | 148 | # Audio 149 | *.kar binary 150 | *.m4a binary 151 | *.mid binary 152 | *.midi binary 153 | *.mp3 binary 154 | *.ogg binary 155 | *.ra binary 156 | 157 | # Video 158 | *.3gpp binary 159 | *.3gp binary 160 | *.as binary 161 | *.asf binary 162 | *.asx binary 163 | *.fla binary 164 | *.flv binary 165 | *.m4v binary 166 | *.mng binary 167 | *.mov binary 168 | *.mp4 binary 169 | *.mpeg binary 170 | *.mpg binary 171 | *.ogv binary 172 | *.swc binary 173 | *.swf binary 174 | *.webm binary 175 | 176 | # Archives 177 | *.7z binary 178 | *.gz binary 179 | *.jar binary 180 | *.rar binary 181 | *.tar binary 182 | *.zip binary 183 | 184 | # Fonts 185 | *.ttf binary 186 | *.eot binary 187 | *.otf binary 188 | *.woff binary 189 | *.woff2 binary 190 | 191 | # Executables 192 | *.exe binary 193 | *.pyc binary 194 | 195 | # RC files (like .babelrc or .eslintrc) 196 | *.*rc text 197 | 198 | # Ignore files (like .npmignore or .gitignore) 199 | *.*ignore text 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Contribution Graph Action 7 |

8 | 9 |

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | 31 | > Maybe most of your coding happens on another version control host, like GitLab or Bitbucket. Maybe your company uses GitHub Enterprise but hasn't enabled [unified contributions](https://docs.github.com/en/enterprise-server@latest/admin/configuration/configuring-github-connect/enabling-unified-contributions-for-your-enterprise). Maybe you're looking for a new software development job and are worried that recruiters will prejudge you by your scarce contribution graph. 32 | > Or maybe you have [other reasons](https://twitter.com/jacobmparis/status/1265740598277025792). Whatever the case may be, you've come to the right place. 33 | 34 | ## Quick start without leaving your browser ⚡ 35 | 36 | 1. [Create a new repo](https://github.com/new) (preferably private unless you have no shame) 37 | 1. Click on **Create a new file** 38 | ![](./.github/images/create-new-file.png) 39 | 1. In the **Name your file...** field, type in `.github/workflows/main.yml` 40 | ![](./.github/images/name-new-file.png) 41 | 1. Paste in one of the YAML file contents below, depending on what you want to do. Be sure to update `GIT_EMAIL`. 42 | 1. Click the **Commit new file** button at the bottom of the page. You're all set! 43 | * Note that you must enable the option below in your contribution settings for private commits to count on the graph. 44 | ![](.github/images/private-contributions.png) 45 | 46 | If you change your mind about these commits later, you can delete the repository and they'll disappear from your contribution graph. 47 | 48 | ### Push a commit to GitHub once a day 🍺 49 | 50 | ```yml 51 | # .github/workflows/main.yml 52 | 53 | on: 54 | schedule: 55 | - cron: '0 12 * * *' # every day at noon 56 | 57 | jobs: 58 | single-commit: 59 | runs-on: ubuntu-latest 60 | permissions: 61 | contents: write 62 | steps: 63 | - uses: bcanseco/github-contribution-graph-action@v2 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | GIT_EMAIL: you@youremail.com # replace me 67 | ``` 68 | 69 | If you need help with cron job syntax, [crontab guru](https://crontab.guru/) is a great resource. 70 | 71 | ### Backfill a year of commits whenever you push to GitHub 🍻 72 | 73 | This rolls a pseudorandom number generator between 1 and 5 (inclusive) to determine how many commits to make per-day. 74 | 75 | ```yml 76 | # .github/workflows/main.yml 77 | 78 | on: push 79 | 80 | jobs: 81 | backfill-commits: 82 | runs-on: ubuntu-latest 83 | permissions: 84 | contents: write 85 | steps: 86 | - uses: bcanseco/github-contribution-graph-action@v2 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | GIT_EMAIL: you@youremail.com # replace me 90 | MAX_DAYS: 365 91 | MIN_COMMITS_PER_DAY: 1 92 | MAX_COMMITS_PER_DAY: 5 93 | ``` 94 | 95 | Keep reading for more cool stuff like: 96 | 97 | * skipping weekends/weekdays 98 | * backfilling specific time periods 99 | * custom commit messages 100 | * and more! 101 | 102 | ## Environment variables 🌳 103 | 104 | | Key | Description | Default value | Required? | 105 | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------|-----------| 106 | | `GITHUB_TOKEN` | Allows this GitHub Action to make commits for you. Simply pass in `${{ secrets.GITHUB_TOKEN }}`. [Read more](#github_token-). | | 🟩 | 107 | | `GIT_EMAIL` | An email address associated with your GitHub account. Without this, contributions won't show up. [Read more](#git_email-). | | 🟩 | 108 | | `GIT_BRANCH` | Must either be the default branch or `gh-pages` for contributions to show up. | The branch that triggered this Github Action | | 109 | | `GIT_COMMIT_MESSAGE` | The message to use for commits made by this GitHub Action. | `chore(actions): empty commit for contribution graph` | | 110 | | `ORIGIN_TIMESTAMP` | The unix timestamp to start commits on. If you set `MAX_DAYS` greater than 1, commits will be made on days prior to this time. | The current timestamp | | 111 | | `MAX_DAYS` | The maximum integer number of days to commit on. If you want to backfill a year of commits, set this to `365`. | `1` | | 112 | | `MIN_COMMITS_PER_DAY` | The minimum integer number of commits to make per day (inclusive). Used by a pseudo-RNG. | `1` | | 113 | | `MAX_COMMITS_PER_DAY` | The maximum integer number of commits to make per day (inclusive). Used by a pseudo-RNG. | `1` | | 114 | | `INCLUDE_WEEKDAYS` | A boolean indicating whether or not to make commits on weekdays. | `true` | | 115 | | `INCLUDE_WEEKENDS` | A boolean indicating whether or not to make commits on weekends. | `true` | | 116 | | `FORCE_PUSH` | A boolean indicating whether or not to force push. **WARNING:** Setting this to `true` will clear out your repo on each run! | `false` | | 117 | 118 | ### Advanced environment variables 🧙‍♂️ 119 | 120 |
121 | 122 | There's also some advanced environment variables you can provide. Click here! 123 | 124 | Only set these if you know what you're doing: 125 | 126 | | Key | Description | Default value | Required? | 127 | |---------------------|------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------| 128 | | `GIT_HOST` | You may be able to override this to support a GitHub Enterprise environment. | `github.com` | | 129 | | `GIT_SSH_COMMAND` | | `ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no` | | 130 | | `GITHUB_ACTOR` | | Set by the GitHub Actions runner | | 131 | | `GITHUB_REPOSITORY` | | Set by the GitHub Actions runner | | 132 | 133 |
134 | 135 | ## How do I know this is secure? 🔒 136 | 137 | Explore the [code](src/index.js)! It's tiny and there aren't many dependencies. 138 | 139 | Speaking of dependencies, all production npm dependencies used by this GitHub Action are [automatically audited](./.github/workflows/audit.yml) for vulnerabilities. If the badge at the top of this README is green, you're good to go. 140 | 141 | If you're still worried about malicious code in this repository, GitHub recommends always using a specific version of any GitHub Actions you add to your repositories. [Read more](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses). 142 | 143 | ```diff 144 | - uses: bcanseco/github-contribution-graph-action@v2 145 | + uses: bcanseco/github-contribution-graph-action@2.0.0 146 | ``` 147 | 148 | As far as data security, there's two sensitive pieces of data that this Action handles: 149 | 150 | * [`GITHUB_TOKEN`](#github_token-) 151 | * [`GIT_EMAIL`](#git_email-) 152 | 153 | ### `GITHUB_TOKEN` 🔑 154 | 155 | GitHub has [a great article](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) about the token. It's the standard way that all GitHub Actions interact with GitHub on your behalf. The permissions of this token are both short-lived and scoped to one repo only. 156 | 157 | You don't need to create this secret yourself; GitHub handles that for you. All you need to do is provide the token in your workflow, job, or step: 158 | 159 | ```yml 160 | env: 161 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 162 | ``` 163 | 164 | #### Getting 403 errors? ❌ 165 | 166 | Make sure your token has [write permission for the `contents` scope](https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs). If you're using the examples in this README, this is already done for you. 167 | 168 | You can alternatively set this as a repo-level default: 169 | 170 | ![](./.github/images/token-permissions.png) 171 | 172 | ### `GIT_EMAIL` 📧 173 | 174 | This GitHub Action requires an email associated with your GitHub account. If you provide a random or throwaway email, contributions won't show up on your GitHub profile. [Read more](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/why-are-my-contributions-not-showing-up-on-my-profile#you-havent-added-your-local-git-commit-email-to-your-profile). 175 | 176 | Chances are, your email is already public if you're making commits with it. But if you're concerned about privacy, you can do either of the following: 177 | 178 | * [Add your email as a secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) and pass it in the same way as the `GITHUB_TOKEN` 179 | * [Use your GitHub-provided `noreply` email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address#about-commit-email-addresses) 180 | 181 | ## Contribute 👪 182 | 183 | PRs are welcome! Please read the [contributing guide](.github/CONTRIBUTING.md). This project is [MIT](LICENSE) licensed. 184 | --------------------------------------------------------------------------------