How to Save a Json Package and Upload to Github
I dearest working with GitHub Actions. They're piece of cake to use yet and so powerful. I'one thousand especially excited when I come across how creative people are when using them to automate unlike tasks.
I want you to have that same power. That's why I'm going to prove you lot how to create your kickoff custom JavaScript activeness in just few steps.
Let's dig in.
What is a GitHub Activity?
First, we need to establish the distinction between "GitHub Actions" and an "Action". The erstwhile is the product's proper name and the latter is custom code that you can include in a workflow chore as a step to accomplish a task.
For case, an action can publish your code to a packet director like npm or yarn. It can too integrate with an SMS service provider to warning you when an urgent event is created in your repo. Or it can plough on your coffee machine when you create a new pull request.
The possibilities are endless for what y'all tin can practise!
What are the components of GitHub Deportment?
Before we get started writing code, it'southward important for us to empathise the building blocks of GitHub Actions.
Let's pause down this diagram, starting from the left and going correct:
- Issue: This is the event that triggers the action. Information technology represents an activity in the repository that will trigger a workflow run.
- Workflow: This is the workflow that is run when the event occurs.
- Job: A set of steps that are run in sequence to accomplish a task. Each chore runs on its own runner.
- Step: A step is either a shell script, or an action that volition be run on the runner assigned for the job the step is role of.
- Runner: A runner is a virtual motorcar (or any computer with a supported operating system) that runs the steps in a job.
This is very well explained in GitHub's extensive docs, and you tin read more about the components hither.
When do I need to create an Activeness?
Since each footstep can be either a crush script or an action, how do nosotros decide which option to choose?
If you lot reply "yes" to any of the below questions, and so you're amend off creating an Action:
- Volition other people benefit from the action you're creating and actually reuse it?
- Do you need to build circuitous logic that cannot be written in a shell script?
- Are you going to be using whatever tertiary-political party libraries?
- Do you demand to brand API calls to a tertiary-party service?
- Practice you have the ability to maintain this code and release fixes or updates?
- Practise y'all need to exist able to run this action on different operating systems?
- Are y'all proficient in JavaScript merely non Bash or PowerShell?
- Do you want to learn how to make one?
Let's create our Action
We're going to build an Action that volition create a comment whenever a pull request is opened on our repository and add labels depending on the file types changed. The comment will contain a summary of the changes introduced in the pull request.
1. Create an empty public repository
Permit's showtime past creating an empty GitHub repository called: PR-metadata-action. This will be the repository that we will use to store our Action.
It has to exist public, otherwise nosotros won't exist able to employ it in our workflows.
2. Clone the repository locally and initialize a Node project
Go to the folder where yous wish to store the Activeness'south repository. Then let's clone the repository on our machine:
$ git clone git@github.com:Link-/PR-metadata-activity.git Cloning into 'PR-metadata-activity'... remote: Enumerating objects: 4, done. remote: Counting objects: 100% (4/iv), done. remote: Compressing objects: 100% (4/4), done. Receiving objects: 100% (iv/4), done. remote: Total iv (delta 0), reused 0 (delta 0), pack-reused 0 Inside our newly created repository'southward folder, let's initialize a new Node.js project:
$ cd PR-metadata-action/ $ npm init -y Wrote to /Users/link-/PR-metadata-action/package.json: { "proper name": "pr-metadata-action", "version": "1.0.0", "description": "Adds pull request file changes equally a comment to a newly opened PR", "main": "index.js", "scripts": { "test": "repeat \"Error: no exam specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/Link-/PR-metadata-action.git" }, "keywords": [], "author": "", "license": "ISC", "bugs": { "url": "https://github.com/Link-/PR-metadata-action/issues" }, "homepage": "https://github.com/Link-/PR-metadata-action#readme" } 3. Create an Action metadata file
Let's create action.yml. This file is very important, as information technology will define the interface of our Action:
- inputs: the parameters containing information that the activity expects to use during runtime
- outputs: data that an activity sets after it's done. Nosotros're not going to take an output for our action this time.
- runs: specifies the execution runtime of the activity, which will be node16 in this case
Read more than about the metadata file syntax.
proper noun: 'PR Metadata Activeness' description: 'Adds pull request file changes as a comment to a newly opened PR' inputs: owner: clarification: 'The owner of the repository' required: truthful repo: clarification: 'The proper name of the repository' required: truthful pr_number: description: 'The number of the pull asking' required: true token: description: 'The token to utilise to access the GitHub API' required: true runs: using: 'node16' main: 'index.js' 4. Add Deportment toolkit packages
GitHub has created an open source software evolution toolkit (SDK) that will brand your life much easier when creating actions.
The two master packages we will exist using today are:
-
@deportment/core: this package contains the core functionality of the Action, such as the
contextobject that contains the data most the current run, theinputsobject that contains the parameters of the action, and theoutputsobject that will contain the data that the action sets after it'southward done. -
@deportment/github: this package contains the GitHub API Balance client that nosotros will apply to interact with the GitHub API.
$ npm install @deportment/core added three packages, and audited 4 packages in 1s found 0 vulnerabilities $ npm install @actions/github added 21 packages, and audited 25 packages in 1s found 0 vulnerabilities Our folder structure should await like this at present:
/Users/link-/PR-metadata-activity ├── LICENSE ├── README.md ├── action.yml ├── node_modules ├── package-lock.json └── package.json 1 directory, vi files v. Write the Action
Creating a .gitignore file is important at this stage to avoid pushing unnecessary files to the repository.
A bully tool I ofttimes use is: https://www.toptal.com/developers/gitignore
My .gitignore file is:
https://www.toptal.com/developers/gitignore/api/visualstudiocode,macos,node Create one that is specific to your surround and project.
Nosotros're finally ready to create our index.js file. This is where all the logic of our action volition be. We can definitely have a more complex structure, just for now one file will exercise.
I've commented all the code below so that you know what'southward happening footstep past step.
const core = require('@actions/core'); const github = require('@deportment/github'); const master = async () => { endeavour { /** * Nosotros need to fetch all the inputs that were provided to our action * and shop them in variables for us to utilise. **/ const owner = cadre.getInput('owner', { required: true }); const repo = core.getInput('repo', { required: true }); const pr_number = core.getInput('pr_number', { required: true }); const token = cadre.getInput('token', { required: true }); /** * At present we need to create an instance of Octokit which volition use to call * GitHub's REST API endpoints. * We volition pass the token every bit an statement to the constructor. This token * will be used to authenticate our requests. * Y'all tin can find all the information about how to utilise Octokit here: * https://octokit.github.io/rest.js/v18 **/ const octokit = new github.getOctokit(token); /** * Nosotros need to fetch the list of files that were changes in the Pull Request * and store them in a variable. * We use octokit.paginate() to automatically loop over all the pages of the * results. * Reference: https://octokit.github.io/rest.js/v18#pulls-list-files */ const { data: changedFiles } = await octokit.residuum.pulls.listFiles({ possessor, repo, pull_number: pr_number, }); /** * Contains the sum of all the additions, deletions, and changes * in all the files in the Pull Request. **/ permit diffData = { additions: 0, deletions: 0, changes: 0 }; // Reference for how to employ Assortment.reduce(): // https://developer.mozilla.org/en-US/docs/Spider web/JavaScript/Reference/Global_Objects/Array/Reduce diffData = changedFiles.reduce((acc, file) => { acc.additions += file.additions; acc.deletions += file.deletions; acc.changes += file.changes; return acc; }, diffData); /** * Loop over all the files inverse in the PR and add labels according * to files types. **/ for (const file of changedFiles) { /** * Add labels according to file types. */ const fileExtension = file.filename.split('.').pop(); switch(fileExtension) { example 'physician': await octokit.residue.issues.addLabels({ owner, repo, issue_number: pr_number, labels: ['markdown'], }); case 'js': await octokit.balance.issues.addLabels({ possessor, repo, issue_number: pr_number, labels: ['javascript'], }); case 'yml': await octokit.rest.issues.addLabels({ possessor, repo, issue_number: pr_number, labels: ['yaml'], }); case 'yaml': wait octokit.rest.issues.addLabels({ owner, repo, issue_number: pr_number, labels: ['yaml'], }); } } /** * Create a comment on the PR with the information we compiled from the * list of changed files. */ await octokit.rest.issues.createComment({ possessor, repo, issue_number: pr_number, body: ` Pull Request #${pr_number} has been updated with: \north - ${diffData.changes} changes \n - ${diffData.additions} additions \n - ${diffData.deletions} deletions \n ` }); } catch (error) { core.setFailed(error.message); } } // Telephone call the main role to run the activity main(); half dozen. Push our Action files to GitHub
Allow'due south stage, commit, and button our files to the main branch upstream:
$ git status On co-operative principal Your branch is upward to date with 'origin/chief'. Untracked files: (use "git add <file>..." to include in what will exist committed) .gitignore action.yml index.js parcel-lock.json package.json nothing added to commit simply untracked files present (use "git add" to track) Let's add all the files to be staged:
$ git add . Now we can commit our changes:
$ git commit -m "Add main action structure" [principal 1fc5d18] Add chief action structure 5 files changed, 686 insertions(+) create mode 100644 .gitignore create mode 100644 action.yml create mode 100644 index.js create mode 100644 parcel-lock.json create way 100644 package.json And push our changes:
$ git push origin primary Enumerating objects: 8, done. Counting objects: 100% (eight/8), done. Delta compression using up to 16 threads Compressing objects: 100% (7/7), washed. Writing objects: 100% (seven/7), 5.82 KiB | five.82 MiB/southward, done. Full vii (delta 0), reused 0 (delta 0), pack-reused 0 To github.com:Link-/PR-metadata-action.git 457fee2..1fc5d18 master -> chief 7. How to exam our Activeness
In lodge for us to be able to test our action, we need to create a bundle. If you noticed in the previous step, we did not push our node_modules binder which contains the packages we used in building our index.js file.
Our action volition not run without those packages! To fix this we can use a nice tool called ncc. It will help us create one file that includes our code and all the packages nosotros demand to run our action.
Let's start past installing ncc:
$ npm install @vercel/ncc added one package, and audited 26 packages in 5s plant 0 vulnerabilities Compiling our JavaScript is as simple as running:
$ ncc build index.js -o dist ncc: Version 0.22.1 ncc: Compiling file index.js 530kB dist/index.js 530kB [845ms] - ncc 0.22.1 This will create a new directory called dist and create a file called alphabetize.js that contains our code and all the packages we demand to run our action.
Now nosotros need to make sure our action.yml file contains the correct runs section. Yous need to replace:
runs: using: 'node16' main: 'index.js' with:
runs: using: 'node16' chief: 'dist/index.js' Allow's push our changes one more time upstream (to our GitHub repository). Make sure our dist/ binder is not in the .gitignore file:
$ git status $ git add together . $ git commit -1000 "Add compiled activeness" [main adfc4f0] Add compiled activeness iv files inverse, 8505 insertions(+), 3 deletions(-) create mode 100644 dist/index.js $ git push origin main We're finally fix to create our workflow! Create a new workflow in the same or in any other repository (public or private doesn't thing) as follows:
mkdir -p .github/workflows touch .github/workflows/pr-metadata.yaml Copy the following workflow into our pr-metadata.yaml file:
proper name: PR metadata notation on: pull_request: types: [opened, reopened, synchronize] jobs: annotate-pr: runs-on: ubuntu-latest proper name: Annotates pull asking with metadata steps: - name: Annotate PR uses: link-/PR-metadata-action@primary with: owner: ${{ github.repository_owner }} repo: ${{ github.event.repository.proper name }} pr_number: ${{ github.outcome.number }} token: ${{ secrets.GITHUB_TOKEN }} When y'all're washed with all these steps, our repository should look like this:
In order for us to test this workflow, we need to make a change in our repository and create a Pull Request (PR). Nosotros tin can do this by editing the README.doc file directly on GitHub:
GitHub Actions Best Practices
Lastly I want to share with you lot some best practices when creating custom Deportment:
-
Adopt the single responsibility principle. Make sure your actions practice one affair merely. It'll make your code easier to maintain and easier to test.
-
Think well well-nigh your action'due south interface (inputs and outputs). Proceed your interfaces uncomplicated and clear past reducing the number of optional inputs.
-
We didn't practise information technology in this tutorial, but you lot need to validate your action'due south inputs! The majority of security projects could exist eliminated by validating inputs.
-
Make sure your activeness is idempotent, meaning, if yous run the action multiple times in a sequence the consequence should e'er be the same. In our case, the activeness should execute and post a comment and add the labels, or it should exit gracefully.
-
Read and follow the security hardening best practices documented in these GitHub Docs.
-
Practice not create a new activity if you're not able to maintain information technology. Search for similar actions in the market place and utilize them instead.
Conclusion
For this tutorial, we created a custom action that comments a summary of changes in a Pull Request and adds labels for the file types that were modified.
You should be able to reuse these steps to create more than circuitous actions that can practise and so much more than!
I'm in the process of creating a thorough DevOps course using GitHub Actions. If you're looking for more than in-depth data on how you can use Deportment for Continuous Integration, Continuous Delivery, or gitOps (among many other topics), keep an eye on these videos:
Happy Coding!
Larn to lawmaking for free. freeCodeCamp'southward open source curriculum has helped more twoscore,000 people go jobs equally developers. Get started
Source: https://www.freecodecamp.org/news/build-your-first-javascript-github-action/
Post a Comment for "How to Save a Json Package and Upload to Github"