In this article, I will guide you through setting up a LaunchAgent that will automatically run a JavaScript project to update & commit to GitHub. This is a fun way to keep that Github "grass" green.
LaunchAgents contain .plist
files that your mac will look for and run on login.
Important notes:
- LaunchAgents are only on macos
- Node.js & Git are required
- Any code editor of your choice
Table of Contents
- Setup Node project
- Setup Github repo
- Write some JavaScript pt.1
- Write some JavaScript pt.2
- Setup LaunchAgent
Setup new Node.js project with git
Let's first start with a fresh node project in a new directory(using auto-commit in this example) in your desired location on your mac. With your terminal open, run the following:
mkdir auto-commit
cd auto-commit
npm init -y
The above command will initialize the project and skip all the required details required by the package.json
file. You can set these configuration details later if/when you’re ready to add them.
After running this, you should see package.json
contents printed in your terminal.
Now we can open this new project and we're going to install one package, shelljs
Shelljs
is a great Command Line Utility for interacting with the command line in JavaScript.
npm i shelljs
After this, you will see a package-lock.json
file and node_modules
appear in your project. Your package.json
should look like this:
{
"name": "auto-commit",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"shelljs": "^0.8.5"
}
}
Let's now create our index.js
file:
touch index.js
Now we need git
in our project, so we can push to a remote repo. We're also going to create a .gitignore
file so we don't push up our node_modules
:
git init
touch .gitignore
Inside your new .gitignore
file, type in node_modules
.
You should have the following files in your project:
node_modules
.gitignore
index.js
package-lock.json
package.json
Now it's time to move onto the next step and create an empty repository in Github.
Setup new Github repository
We're going to create a new repository on Github. For this example, I am going to name it the same name as my project, auto-commit
. No other info is needed and you can make it public or private:
Once it is created, you will now need to copy the remote origin line:
& paste it in the terminal at the root of your auto-commit
directory.
Once you do this, you can run the following to see your Github remote link that you just pasted in:
git remote -v
Now within our project, let's push everything we currently have up to Github.
git add .
git commit -m"first commit with initial file creation"
git push -u origin master
Now if you go back to your repo on Github, you will see your files.
Time to write some JavaScript
Table of Contents
For this tutorial, we're going to write some code that renames & increments a file name every time the project is ran.
Let's create a new empty file called renameMe-0.js
at the root of your project:
touch renameMe-0.js
Let's push this new file up to Github:
git add renameMe-0.js
git commit -m"pushes file up at number 0"
git push
Now let's create another file called nameChange.js
where we will write the logic to find renameMe-0.js
and update it.
touch nameChange.js
At the top of nameChange.js
, let's bring in shelljs
const shell = require('shelljs');
Let's create our first function and look for the files in our project:
If you run node nameChange.js
you will see this function log our projects JavaScript files in addition to the output that this shelljs method gives out within an array in the terminal.
Now that we have our array of files & output, all we need is the file named renameMe-0.js
, so we can use the filter
array method to return only that file in an array.
Now that we have isolated our file, let's create a new function that gets the number from the file name and adds to it so that it can be renamed:
If you run node nameChange.js
, you will see renameMe-1.js
printed to the terminal.
We can now use shell
to update our previous file name with the new one & if you run node nameChange.js
again in the terminal, you will see the file be renamed:
You will see the file get renamed as well as 3 git changes.
- we haven't committed
nameChange.js
yet -
renameMe-0.js
was technically deleted -
renameMe-1.js
was created
We should currently have these files in our project:
You should have the following files in your project:
node_modules
.gitignore
index.js
nameChange.js. <-------- added during this section
package-lock.json
package.json
renameMe-1.js <-------- added during this section
Before we move on, let's remove the setNewFileName()
function call from the end of the file & export setNewFileName
. Our final nameChange.js
file should look like this:
const shell = require('shelljs');
const getFileNames = () => {
const fileNames = shell.ls('*.js');
return fileNames.filter(fileName => fileName.includes('renameMe'));
}
exports.setNewFileName = () => {
const [fileToRename] = getFileNames();
const fileNumber = fileToRename.split('-')[1];
const newFileName = `renameMe-${parseInt(fileNumber) + 1}.js`;
shell.mv(fileToRename, newFileName);
return { fileToRename, newFileName };
}
At this point, we are ready to move one & get git
involved.
Let's write some JavaScript pt. 2
Table of Contents
Let's start by creating a new file at the root of the project called gitCommands.js
. We need to bring in shelljs
and our setNewFileName
function:
const shell = require('shelljs');
const { setNewFileName } = require('./nameChange');
We're going to create a new function expression called commitNewFile
and destructure the object we returned in setNewFileName
:
const commitNewFile = () => {
const { fileToRename, newFileName } = setNewFileName();
}
Now let's setup "state-like" variables to help debug in the future if necessary & setup three command string variables to git add
,git commit
& git push
the files.
const commitNewFile = () => {
const { fileToRename, newFileName } = setNewFileName();
let isFileAdded = false;
let error = { message: "", code: "" };
const gitAddFiles = `git add ${fileToRename} ${newFileName}`;
const gitCommitFiles = `git commit -m "renamed ${fileToRename} to ${newFileName}"`;
const gitPushFiles = `git push`;
};
Now let's use shell
to execute these commands(add, commit, push)
:
const shell = require("shelljs");
const { setNewFileName } = require("./nameChange");
const commitNewFile = () => {
const { fileToRename, newFileName } = setNewFileName();
let isFileAdded = false;
let error = { message: "", code: "" };
const gitAddFiles = `git add ${fileToRename} ${newFileName}`;
const gitCommitFiles = `git commit -m "renamed ${fileToRename} to ${newFileName}"`;
const gitPushFiles = `git push`;
shell.exec(gitAddFiles, (code, stdout, stderr) => {
// code is 0 for success, 1 for error
code === 0 ? (isFileAdded = true) : (error = { message: stderr, code });
console.log(`File added to git: ${isFileAdded}`);
if (isFileAdded) {
shell.exec(gitCommitFiles, (code) => {
code === 0 ? shell.exec(gitPushFiles) : console.log(error);
});
}
});
};
Before we try this code out, let's manually commit & push up our renameMe-1.js
file.
git add renameMe-1.js
git commit -m"adds manual push of renameMe file"
git push
Now that file should have a clean git slate.
Lets export our commitNewFile
, import it & invoke it within index.js
exports.commitNewFile = () => ....
Inside index.js
:
const { commitNewFile } = require("./gitCommands");
commitNewFile();
Now if we run node index.js
, you should see everything in action. The file should be renamed to renameMe-2.js
, committed and pushed to Github.
Let's add the rest of our files and move on to the next step.
git add .
git commit -m"adds nameChange and gitCommands functionality"
git push
Setup LaunchAgent
Table of Contents
Now within the root of our project let's create a plist
file named com.autocommit.plist
that we will later move to our LaunchAgents directory.
With this file open, let's paste this boilerplate in:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.autocommit</string>
<key>ProgramArguments</key>
<array>
<string>__{NODE LOCATION WILL GO HERE}__</string>
<string>__{PROJECT FILE ENTRY WILL GO HERE}__</string>
</array>
<key>WorkingDirectory</key>
<string>__{PROJECT ROOT LOCATION WILL GO HERE}__</string>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>__{OUTPUT LOCATION}__</string>
<key>StandardErrorPath</key>
<string>__{ERROR OUTPUT LOCATION}__</string>
</dict>
</plist>
There are five areas of this plist
file that we need to update:
- NODE LOCATION
- PROJECT FILE ENTRY
- PROJECT ROOT LOCATION
- OUTPUT LOCATION
ERROR OUTPUT LOCATION
Updating NODE LOCATION:
If we open up a terminal window and run the following, you will get the file path to your node version:
which node
The output should look something like this:
/Users/jared.long/.nvm/versions/node/v16.16.0/bin/node
I have nvm
(node version manager) installed so my output may look slightly different
Lets take that output and replace the node location line:
REPLACE:
<string>__{NODE LOCATION WILL GO HERE}__</string>
WITH:
<string>/Users/jared.long/.nvm/versions/node/v16.16.0/bin/node</string>
- Updating PROJECT FILE ENTRY
This is the main file that we are running our project from, which in this case is
index.js
REPLACE:
<string>__{PROJECT FILE ENTRY WILL GO HERE}__</string>
WITH:
<string>index.js</string>
- Updating PROJECT ROOT LOCATION
This is referring to the root file location of the app that you are creating now. Running the
pwd
command in the terminal is all you'll need for this. Grab that path and replace the following string with it:
REPLACE:
<string>__{PROJECT ROOT LOCATION WILL GO HERE}__</string>
***** WITH YOUR PATH TO YOUR PROJECT :
<string>/Users/jared.long/article-auto-commit</string>
4 & 5. Updating OUTPUT & ERROR OUTPUT LOCATION
These are files that your app will print output & errors from this LaunchAgent action into, which we can put directly into the project. We can grab your project path from above and add log files to the end of the path:
REPLACE
<key>StandardOutPath</key>
<string>__{OUTPUT LOCATION}__</string>
<key>StandardErrorPath</key>
<string>__{ERROR OUTPUT LOCATION}__</string>
WITH
<key>StandardOutPath</key>
<string>/Users/jaredlong/article-auto-commit/logs/autocommit.log</string>
<key>StandardErrorPath</key>
<string>/Users/jaredlong/article-auto-commit/logs/autocommit_err.log</string>
With your own file paths, our final plist
file should look something like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.autocommit.la</string>
<key>ProgramArguments</key>
<array>
<string>/Users/jaredlong/.nvm/versions/node/v16.14.2/bin/node</string>
<string>index.js</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/jaredlong/auto-commit</string>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/jaredlong/auto-commit/logs/autocommit.log</string>
<key>StandardErrorPath</key>
<string>/Users/jaredlong/auto-commit/logs/autocommiterr.log</string>
</dict>
</plist>
Our com.autocommit.plist
file is complete and needs to be moved into our LaunchAgents directory, so that it will RunAtLoad
.
LaunchAgents are found at the this file path:
~/Library/LaunchAgents
Using the terminal, lets move our plist
file to the LaunchAgents directory. Your first file path may look different than mine, as this is where your com.autocommit.plist
is found on your machine:
mv ~/article-auto-commit/com.autocommit.plist ~/Library/LaunchAgents/com.autocommit.plist
Now we can double check that it has been moved to the LaunchAgents by running the following:
cd ~/Library/LaunchAgents
ls
You should see your com.autocommit.plist
file here.
Now for the final test, reboot your mac, login and check your Github repo. You should see it get updated (may take a minute).
Notes to end on:
- if you use nvm and you uninstall the version of node that this is pointing to, your node file path will need to be updated in your
plist
file
Top comments (0)