# Automate GitHub: Build a CLI App with Node.js #2

Welcome back to the 2nd and final part of the tutorial on how to build a simple CLI tool that can automate the process of pushing a local git repository to your GitHub.

If you haven't read the first part, please do so [here](https://lo-victoria.com/automate-github-build-a-cli-app-with-nodejs-1) so you can follow along.

### Step 7: New_repo.js (newRepo)
Let's continue where we left off. After we authenticate the user with the `authenticate` function in `creds.js`, we can now create a remote repo using `octokit`'s `repos.createForAuthenticatedUser(data)`.

We can supply information about the repo such as its name, description and visibility in `data` as an argument. 

> For more information on the parameters, please see the documentation [here](https://octokit.github.io/rest.js/v18#repos-create-for-authenticated-user).

The `repos.createForAuthenticatedUser(data)` is a request that on success, will return data about the repo that was created. All we need from that response data is the `clone_url`. 

This url is in the format: 
```url
https://github.com/[username]/[repo-name].git
```

We need this url when we push files from our local repo to the remote repo with:
```
git.push(url, 'master');
```

So let's create the `newRepo` function in `new_repo.js` where we will do the following:
1. Ask the user for repo information (i.e. name, description and visibility)
2. Pass this information as `data` in `repos.createForAuthenticatedUser(data)`
3. Return the `clone_url` of the response.data object

As usual, we create a `questions` array with objects representing each question. Then use `inquirer.prompt(questions)` to ask the user in the terminal. 

So first, import `inquirer` at the top of `new_repo.js`. We also need to import a built-in Node.js module called `path` to get the base name of our current working directory. 

For example, my current working directory is: **C:\Users\victo\Desktop\Projects\cli-app**. We can get this value using `process.cwd()`.

To get the base name, we can use `path.basename(process.cwd())`. Thus, the base name is **cli-app**, which will be the default name of our repo (unless the user specifies something else). 

> Feel free to read more about the Node.js path module in their [documentation](https://nodejs.org/api/path.html).

```
const path = require('path');
const inquirer = require('inquirer');
```
Then we write our `newRepo` function below:
```javascript
async function newRepo(octokit){
    //create questions array, ask for name, description and visibility
    const questions = [
        {
            name: 'name',
            type: 'input',
            message: 'Enter new repo name.',
            default: path.basename(process.cwd()), //set default to basename
            validate: function(value) {
                if (value.length) {
                    return true;
                } else {
                    return 'Please enter a valid input.';
                }
            }
        },
        {
            name: 'description',
            type: 'input',
            message: 'Enter new repo description (optional).',
            default: null
        },
        {
            name: 'visibility',
            type: 'input',
            message: 'Set repo to public or private?',
            choices: ['public', 'private'],
            default: 'private'
        }
    ];
    //prompt the questions
    const answers = await inquirer.prompt(questions);

    //create the data argument object from user's answers
    const data = {
        name: answers.name,
        description: answers.description,
        private: (answers.visibility === 'private')
    };

    try {
        //create the remote repo and return the clone_url
        const response = await octokit.repos.createForAuthenticatedUser(data)
        return response.data.clone_url;  
    } catch (error) {
        console.log(error)
    }
}

//final step, export function to use in index.js
module.exports = {newRepo}
```
Our function is complete. Let's call it in `index.js`, after the `authenticate` function. Import it first at the top of `index.js`.

```
const repo = require('./new_repo');
```
Then add these lines below the `auth.authenticate()` line, inside the `init` command.

```javascript
app.command('init')
	.description('Run CLI tool')
	.action(async() => {
		//welcome message - see part1
        //prompt question - see part1

		if(answer.proceed == "Yes"){
			console.log(chalk.gray("Authenticating...")) //from part1
			const octokit = await auth.authenticate(); //from part1
            //add these 2 lines below
            console.log(chalk.gray("Initializing new remote repo..."));
		    const url = await repo.newRepo(octokit);
		}else{
			console.log(chalk.gray("Okay, bye."))
		}
	})
```

Let's test if it works! Run `npm start` on the terminal.

![repo.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1616810461472/q0bVDl4_z.gif)

As shown in the clip above, our remote repo has been successfully created with the parameters the user provided in the 3 questions. 

Notice it is now just an empty repo. The next step is to create a `.gitignore` file, ask the user which files they want to ignore, then commit and push the rest to the remote repo.

### Step 8: New_repo.js (ignoreFiles)
In our `new_repo.js` file, let's create a function called `ignoreFiles`. This function will do 2 things:
1. List all the files in the project directory
2. Prompt the user to select the file(s) they want to add to `.gitignore` from the list. By default, `node_modules` would be included.

If the user selects none, an empty `.gitignore` file will be created. Else, we use Node.js built-in module `fs` (File System) to write the selected ignored files to `.gitignore` using `fs.writeFileSync()`.

> Feel free to check out Node.js File System module documentation [here](https://nodejs.org/api/fs.html).

In order to create a list of all the files in our project directory, including nested ones, we use `glob`, a dependency we installed earlier in Part 1 of this tutorial. `glob` allows us to iterate through our directory and return all the files as an array of file names.

We will then pass this array of file names in the prompt, so the user can select the ones they want to ignore from the terminal.

So let's first import the dependencies we need: `fs` and `glob` at the top of `new_repo.js`.
```
const fs   = require("fs");
const glob = require("glob");
```
Then we write the `ignoreFiles` function below:

```javascript
async function ignoreFiles(){
    //get array of files in the project, ignore node_modules folder
    const files = glob.sync("**/*",{"ignore":'**/node_modules/**'});

    //add any node_modules to gitignore by default
    const filesToIgnore = glob.sync('{*/node_modules/,node_modules/}');
    if(filesToIgnore.length){
        fs.writeFileSync('.gitignore', filesToIgnore.join('\n')+'\n');
    }else {
        //if no files are chosen to be ignored, create an empty .gitignore
        fs.closeSync(fs.openSync('.gitignore', 'w'));
    }

    //create question and pass files as the choices
    const question = [
        {
            name: 'ignore',
            type: 'checkbox',
            message: 'Select the file and/or folders you wish to ignore:',
            choices: files
        }
    ];
    //prompt the question
    const answers = await inquirer.prompt(question);

    //if user selects some files/folders, write them into .gitignore
    if (answers.ignore.length) {
        fs.writeFileSync('.gitignore', answers.ignore.join('\n'));
    }
}

//export ignoreFiles to use in index.js
module.exports = {newRepo, ignoreFiles}
```
Now we can call `ignoreFiles` in `index.js` after calling `newRepo`.
```javascript
//Some code above
if(answer.proceed == "Yes"){
	console.log(chalk.gray("Authenticating...")) //from part1
	const octokit = await auth.authenticate(); //from part1
    
	console.log(chalk.gray("Initializing new remote repo...")); //step7
	const url = await repo.newRepo(octokit); //step7

    //add these 2 lines below
	console.log(chalk.gray("Remote repo created. Choose files to ignore."));
	await repo.ignoreFiles();

}
//Some code below
```
Here's what our app should be able to do at this point:
1. Display a welcome message
2. Ask user if they want to continue with this tool
3. If yes, ask for token. If token already saved, skip to next step.
4. Ask repo name, description and visibility. Then create it on GitHub.
5. Ask which files to ignore. Then create `.gitignore` file with the selected files.

![gitignore.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1616837406907/nFxAEiuPx.gif)

Let's finish up this app. All there's left to do is to commit and push the files that are not in `.gitignore` from local to the remote repo.

### Step 9: New_repo.js (initialCommit)
The `initialCommit` function uses the `simple-git` dependency we installed in Part 1 to easily make git commands in JavaScript. So first, import it at the top of `new_repo.js`.

```
const git = require('simple-git')();
```
The function will take the `clone_url` from Step 7 as an argument to do the following:
1. `git init`
2. `git add .gitignore`
3. `git add ./*`
4. `git commit -m "Initial commit"`
5. `git remote add origin <clone_url>`
6. `git push <clone_url> master`

If there's no error, the function returns true. Let's see its code below:
```
async function initialCommit(url) {
    try {
        await git
        .init()
        .add('.gitignore')
        .add('./*')
        .commit('Initial commit')
        .addRemote('origin', url)
        .push(url, 'master', ['--set-upstream']);

        return true;
    } catch (error) {
        console.log(error)
    }
}

//don't forget to export it
module.exports = {newRepo, ignoreFiles, initialCommit}
```
Finally, let's add the last lines of code in our `index.js` to complete the app.
```javascript
//Some code above
if(answer.proceed == "Yes"){
	console.log(chalk.gray("Authenticating...")) //from part1
	const octokit = await auth.authenticate(); //from part1
    
	console.log(chalk.gray("Initializing new remote repo...")); //step7
	const url = await repo.newRepo(octokit); //step7

	console.log(chalk.gray("Remote repo created. Choose files to ignore.")); //step8
	await repo.ignoreFiles(); //step8

    //add these lines
    console.log(chalk.gray("Committing files to GitHub at: " + chalk.yellow(url)));
	const commit = await repo.initialCommit(url);
    //if initialCommit returns true, show success message
	if(commit){
		console.log(chalk.green("Your project has been successfully 
               committed to Github!"));
	}	
}
//Some code below
```

### Final Product

![done.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1616838928826/dtUrquiFp.gif)

Congratulations! We have completed the app. There's a couple of optional polishing actions such as:
1. Create another command to allow user to delete their stored token
2. Publish the tool to npm for reusability

Since this tutorial has been quite long, I'll just quickly show you the 2nd optional action on how to publish the tool to npm.

### Optional Step: Publishing
In package.json, add the `bin` property and remove the `start` script under `scripts`.
```
"bin": "index.js"
```
Then add this line below at the top on `index.js`. This tells the terminal to use Node.js when executing the file.
```
#! /usr/bin/env node
```
Finally, to test locally, run `npm link` to install the `cli-app`. Then run:
```
cli-app init
```
The app should start and work the same as `npm start`. After you tested the app and it works correctly, run `npm unlink` then login to your npm account with:
```javascript
npm login
```
Enter your credentials and then run:
```javascript
npm publish
```
> Note: I ended up renaming the project to `repo-create` because the name `cli-app` was taken on npm.

### Installation
Now that it's published on npm, we can install the tool with the command:
```javascript
npm install --global @victoria-lo/repo-create
```
To use the tool, simply run the `repo-create init` command on any project you want to create and commit a remote repo for.

## Conclusion
We've reached the end of this 2-part tutorial series on how to build a simple CLI tool with Node.js. Thank you for reading! I hope this has been as enjoyable to read for you as much as I've enjoyed writing this.

Please refer to the links below for the tool on npm and the GitHub repo for the code.
- https://www.npmjs.com/package/@victoria-lo/repo-create
- https://github.com/victoria-lo/repo-creator

Please like and share this article, and ask any questions you may have in the comments below. Cheers!
