All files / check-installed/lib checkModules.js

100% Statements 37/37
96.66% Branches 29/30
100% Functions 5/5
100% Lines 37/37

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 90 91 92 932x 2x 2x 2x         2x                     50x 50x 9x 9x 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
 * @param {boolean} optional - Whether the module is optional
 * @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 match = version.match(NPM_ALIAS_REGEX)
    Eif (match != null) {
      const [, realName, realVersion] = match
      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 {Record<string, any>} 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
}