3
\$\begingroup\$

Improved version of the original question

I've created a simple npm module and CLI program to get the latest GitHub and Bitbucket release from their APIs using Node.js. Please tell me if there's anything to improve!

GitHub
npm
RunKit

Improved module code

#!/usr/bin/env node
const fetch = require("node-fetch");

/**
 * @param {string} string
 * @return {string}
 */
const normalize = (string) => {
    try {
        return string.toUpperCase().trim()
    } catch (e) {
        return string
    }
}

/**
 * @param {string} url
 * @return {string}
 */
const getJSON = async (url) => {
    return (await (await fetch(url)).json())
}

module.exports.providerMethods = {
    GITHUB: async ({user, repo, part = ""}) => {
        let json = await getJSON(`https://api.github.com/repos/${user}/${repo}/releases/latest`)
        if (json.message === "Not Found") throw "Invalid repository"
        if (!("assets" in json)) throw "Rate limit exceeded"
        let browser_download_urls = json.assets.map(asset => asset.browser_download_url)
        return browser_download_urls.filter(url => url.includes(part))
    },
    BITBUCKET: async ({user, repo, part = ""}) => {
        let json = await getJSON(`https://api.bitbucket.org/2.0/repositories/${user}/${repo}/downloads/`)
        if (json.type === "error") throw "Invalid repository"
        let links = json.values.map(value => value.links.self.href)
        return links.filter(url => url.includes(part))
    }
}

/**
 * @param {string} provider
 * @param {string} user
 * @param {string} repo
 * @param {string} part
 */
module.exports.getRelease = async ({provider, user, repo, part = ""}) => {
    if (!(module.exports.providerMethods[normalize(provider)])) {
        throw "Invalid provider"
    }
    return module.exports.providerMethods[normalize(provider)]({user, repo, part})
}

const usage = _ => {
    console.log(
        `Usage: get-release (github|bitbucket) user repo [partofreleasefile]
   Ex: get-release github phhusson treble_experimentations
       get-release github phhusson treble_experimentations arm64-ab-gapps
       get-release bitbucket JesusFreke smali
       get-release bitbucket JesusFreke smali baksmali`
    )
    process.exit(1)
}

// If via CLI
if (require.main === module) {
    let args = process.argv.slice(2)
    if (args.length !== 3 && args.length !== 4) {
        usage()
    }
    module.exports.getRelease({
        provider: args[0],
        user: args[1],
        repo: args[2],
        part: args[3]
    }).then(result => {
        if (result.length !== 1) {
            console.log(result)
        } else {
            console.log(result[0])
        }
    }).catch(error => {
        console.log(error)
        usage()
        process.exit(1)
    })
}

To be called using

/**
 * Options:
 * {
 *     provider: (github | bitbucket),
 *     user: username,
 *     repo: repository,
 *     part: part of release file name - optional
 * }
 *
 */

const { getRelease, providerMethods } = require("get-release")
;(async _ => {
  let url = await getRelease(
      {
          provider: "github",
          user: "phhusson",
          repo: "treble_experimentations",
          part: "arm64-ab-gapps"
      }
  )
  console.log(url[0])
})()

Custom providers can be defined using

const { getRelease, providerMethods } = require("get-release")
providerMethods.GITLAB = async ({user, repo, part = ""}) => {
  // Custom code, should return a string
}
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Now that you've allowed the users to add custom providers, how about hiding the providerMethods object itself, and only providing a function which allows them to add to it. This would allow you to also validate if the users' want to override behaviour for predefined providers.

You have a process.exit(1) inside the call to usage(). Butm when used from cli, you catch all exceptions, then call the usage function, and against have another process.exit(1).

The call to usage should not be exiting from the program entirely. It is already handled in case of errors. You can then have a --help commandline parameter, which spits out the usage string (without raising an error for the shell).


Most of your if- conditions use the negated equality check, which is somewhat harder to follow for a developer (at least in my experience). When checking for args.length, you have double negation checks. It took me almost a minute to figure out what it is supposed to do. How about applying de Morgan's laws:

if (!(args.length === 3 || args.length === 4))

Or, maybe simply adding a comment?

Also consider:

console.log((result.length === 1) ? result[0] : result)
\$\endgroup\$
2
  • \$\begingroup\$ Hi, thanks for answering (again). I don't understand why hiding providerMethods is required. I want users to be able to override the existing providers anyway, and they can add new providers. Is there any advantage to this? \$\endgroup\$
    – user191336
    Jul 24, 2020 at 3:53
  • \$\begingroup\$ @KhushrajRathod Since you're releasing this as a package, someone is free to extend/use in their application. Now, if you're providing them access to the providerMethods object itself, they may override and cause (un)intentional harm to the end user. Which is why I closed the statement with "This would allow you to also validate if the users' want to override behaviour for predefined providers". \$\endgroup\$
    – hjpotter92
    Jul 29, 2020 at 7:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.