All files / check-installed/lib checkModules.js

100% Statements 35/35
100% Branches 28/28
100% Functions 5/5
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 902x 2x 2x 2x         2x                   50x 50x 9x   9x 9x       50x 15x     35x     35x 8x 2x   6x       27x 27x   27x 6x     21x                   44x     44x   30x   11x   9x     32x 1x     32x 17x 22x       32x 18x       2x      
const { resolve } = require('path')
const fs = require('fs')
const semver = require('semver')
const { OPTIONS } = require('./constants')
 
/**
 * Regex used to extract the real name and version from an npm alias
 */
const NPM_ALIAS_REGEX = /npm:(.+)@(.+)/
 
/**
 * Checks that the given node module is installed and satisfies version (throws if not)
 * @param {string} name - The name of the node module to check
 * @param {string} version - The version to check against
 * @returns {[string, string, string, string]} - The [name, version, aliased, installed] installed and checked successfully
 */
function checkModuleVersion (name, version, optional) {
  // Update with npm alias if needed ("my-react": "npm:react@^18.2.0")
  let aliased = ''
  if (NPM_ALIAS_REGEX.test(version)) {
    const [, realName, realVersion] = version.match(NPM_ALIAS_REGEX)
 
    aliased = `${realName} `
    version = realVersion
  }
 
  // Ignore git URLs version
  if (/^(file:|https?:\/\/|git.*:\/\/|github:|gitlab:|bitbucket:|gist:)/.test(version)) {
    return [name, version, aliased, 'skipped']
  }
 
  const jsonPath = resolve('node_modules', name, 'package.json')
 
  // Check if module is installed and contains a `package.json` (throw if not optional)
  if (!fs.existsSync(jsonPath)) {
    if (optional) {
      return [name, version, aliased, 'none']
    } else {
      throw new Error(`check-installed module failed for ${name}: installed none, expected ${aliased}${version}`)
    }
  }
 
  const json = require(jsonPath)
  const installed = json.version
 
  if (version !== 'latest' && !semver.satisfies(json.version, version)) {
    throw new Error(`check-installed module failed for ${name}: installed ${installed}, expected ${aliased}${version}`)
  }
 
  return [name, version, aliased, installed]
}
 
/**
 * Checks node modules in `package.json` are installed
 * @param {object} json - The `package.json` JSON object
 * @param {Partial<typeof import('./constants').OPTIONS>} options - Options for the CLI
 * @returns {Promise<void>} - Resolves when all modules are checked (throws on check fail)
 */
async function checkModules (json = {}, options = {}) {
  const { showSuccess, showModules } = { ...OPTIONS, ...options }
 
  // Check modules
  const modules = [
    ...Object.entries(json.dependencies || {})
      .map(([name, version]) => checkModuleVersion(name, version, false)),
    ...Object.entries(json.devDependencies || {})
      .map(([name, version]) => checkModuleVersion(name, version, false)),
    ...Object.entries(json.optionalDependencies || {})
      .map(([name, version]) => checkModuleVersion(name, version, true))
  ]
 
  if (showSuccess) {
    console.log('check-installed modules successful')
  }
 
  if (showModules) {
    for (const [name, version, aliased, installed] of modules) {
      console.log(`${name}: ${installed} (${aliased}${version})`)
    }
  }
 
  if (showSuccess || showModules) {
    console.log('')
  }
}
 
module.exports = {
  checkModules
}