0

I am learning nodejs and yargs and tried to implement it in my code using command function.

I am trying to make a CLI based note taking app.

I have two files app.js and utils.js, I run app.js and utils.js is imported in app.js to use functions in it.

There is a problem that I cannot debug, when calling app.js with remove option it is automatically also calling add command even when it is not being called explicitly by remove command.

Input:

node app.js remove --title="hello"

Output:

{ _: [ 'remove' ], title: 'hello', '$0': 'app.js' }
Already exists!
Operation successful!

This is my app.js:

// import modules
const validator = require('validator');
const yargs = require('yargs');
// const chalk = require('chalk');
const utils = require('./utils.js');

// version

yargs.version('1.0.0');

const argv = yargs.argv;
console.log(argv);
const command = argv._[0];

// commands
yargs.command({
    command: 'add',
    describe: 'Add a new note',
    builder: {
        overwrite: {
            describe: 'Overwrite the existing file',
            demandOption: true,
            type: 'boolean'
        },
        title: {
            describe: 'Title of the note',
            demandOption: true,
            type: 'string'
        },
        body: {
            body: 'Body of the note',
            demandOption: true,
            type: 'string'
        }
    },
    handler: utils.addNote(argv.overwrite, argv.title, argv.body)
});

yargs.command({
    command: 'remove',
    describe: 'Remove a note by its title',
    builder: {
        title: {
            describe: 'Title to search for',
            demandOption: true,
            type: 'string'
        }
    },
    handler: utils.removeNote(argv.title)
});

// eof
yargs.parse()

This is my utils.js:

// import
const fs = require('fs');

// load notes
function loadNote() {
    try {
        const dataBuffer = fs.readFileSync('notes.json');
        const stringData = dataBuffer.toString();
        const dataJson = JSON.parse(stringData);
        return dataJson;
    } catch (e) {
        return [];
    }
}

// add note
function addNote(overwrite, title, body) {
    const newNote = {
        "title": title,
        "body": body
    };

    const dataJson = loadNote();
    if (overwrite) {
        fs.writeFileSync('notes.json', JSON.stringify([newNote]));
        console.log("Operation successful!");
    } else {
        let flag = true;
        dataJson.forEach(function (object) {
            if (object.title === title) {
                flag = false;
            }
        });
        if (flag) {
            dataJson.push(newNote);
            fs.writeFileSync('notes.json', JSON.stringify(dataJson));
            console.log("Operation successful!");
        } else {
            console.log("Already exists!");
        }
    }
}

// remove notes
function removeNote(title) {
    const dataJson = loadNote();
    dataJson.filter((object) => object.title !== title);
    fs.writeFileSync('notes.json', JSON.stringify(dataJson));
    console.log("Operation successful!");
}

// export
module.exports = {
    addNote: addNote,
    removeNote: removeNote,
};
Piyush Keshari
  • 170
  • 2
  • 10

1 Answers1

0

I encountered the same problem in my code and it appears that the handler: property of yargs.command needs an Anonymous Function as its value. I believe that this is because your command basically serves as the "name" of your function. Otherwise, it is treated as a regular function call. So, you need to have either handler: function(){/*yourFunction()*/} or handler: ()=>{/*yourFunction()*/}. I'll show a more detailed example with my code...

My app.js

const yargs = require('yargs')
const notes = require('./notes')


//set the app.js version
yargs.version('0.5.0')

//set the notes directory
const dir = './MyNotes'

/**
 *  Commands
 */
// Add a note
yargs.command({
    command: 'add',
    describe: 'Adds a new note',
    builder: {
        'title':{
            describe: 'Note Title',
            demandOption: true,
            type: 'string'
        },
        'body':{
            describe: 'The contents of the note',
            demandOption: true,
            type: 'string'
        }
    },
    handler: (argv)=>{notes.addNote(argv.title,argv.body,dir)}
})


// Remove a note
yargs.command({
    command: 'remove',
    describe: 'Removes the specified note.',
    builder:{
        title:{
            describe: 'Title of the note you want to remove.',
            type: 'string',
            demandOption: true
        }
    },
    handler:(argv)=>{notes.removeNote(argv.title,dir)}
})

// Read a note
yargs.command({
    command: 'read',
    describe: 'Reads the specified note.',
    builder: {
        title:{
            describe: 'The title of the note you want to read.',
            type: 'string',
            demandOption: true
        }
    },
    handler: (argv)=>{notes.readNote(argv.title,dir)}
})

// List the notes in the directory
yargs.command({
    command: 'list',
    describe: 'Lists all notes in the directory.',
    handler: ()=>{notes.listNotes(dir)}
})
//parse the arguments to execute the commands
yargs.parse()

Let's look at the last command "list"; if you remove the arrow function around notes.listNotes(dir)...

yargs.command({
    command: 'list',
    describe: 'Lists all notes in the directory.',
    handler: notes.listNotes(dir)
})

I will get a list of all the notes in my ./MyNotes directory every time I run a command (even if it is a different command).

As you are refactoring your code, please keep in mind that argv is created and parsed during runtime. After argv is created, it is parsed and passed to handler:. This means that you have to pass argv to your anonymous function before you access any of its members within the function. As seen with my "read" command:

// Read a note
yargs.command({
    command: 'read',
    describe: 'Reads the specified note.',
    builder: {
        title:{
            describe: 'The title of the note you want to read.',
            type: 'string',
            demandOption: true
        }
    },
    handler: (argv)=>{notes.readNote(argv.title,dir)}
})

If I did not pass argv to the function, ()=>{notes.readNote(argv.title,dir)} I would get the error message ReferenceError: argv is not defined. I did not need to pass a variable to the anonymous function of my list command because dir is defined during compile time const dir = './MyNotes'.

I hope this helps! :D