oclif/plugin-update

`update` command errors when attempting to write channel (i.e. "stable") to dataDir

joegoggins opened this issue · 1 comments

I'm working on a CLI and running into a problem with the update command. When I run mycli update, the update succeeds, however, a mis-leading and confusing error like this is shown to the user:

(node:99765) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '/Users/me/.local/share/@particle/cli/channel'
(node:99765) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:99765) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
@particle/cli: Updating CLI from 1.5.11 to 1.7.0... done
@particle/cli: Updating CLI... done

This issue appears to be caused by setChannel() not checking to see if the directory already exists, see code. Since the dir /Users/me/.local/share/@particle/cli/ doesn't exist, it fails in this way.

I was able to eliminate this error locally by manually editing the update.js file's setChannel() method like so:

    async setChannel() {
        const channelPath = path.join(this.config.dataDir, 'channel');
        try {
            await fs.writeFile(channelPath, this.channel, 'utf8');
        } catch (e) {
            if (e.code !== 'ENOENT') {
                throw e;
            }
        }
    }

aka, swallow the error instead of show it to users.

Another, better solution is to confirm that the dir exists and then create it if it doesn't already. In other words, a method analogous to ensureClientDir like ensureDataDir(), I implemented this approach via an init hook as a workaround until this bug is fixed. For reference, here is the init hook:

const fs = require('fs-extra');
module.exports = async function initializeDefaultDirsAndFiles(){
	if (!fs.existsSync(this.config.dataDir)) {
		fs.mkdirpSync(this.config.dataDir);
	}
};

Technical notes

The error does not happen for CLIs that happen to initialize the this.config.dataDir such as the Heroku CLI.
This CLI (and many others), initialize their XDG dirs and therefore don't run into this bug.
For example, heroku update doesn't exercise this error.

I looked to see if there was some other part of the oclif eco-system responsible for initializing default directories, but couldn't find it. Thus, I assume oclif plugins should ensure that any file system pre-reqs are met before it writes to disk.

This error and fixes were validated on OS X 10.14

Closing this due to inactivity. Please open a new issue if needed. Thanks!