update doctor command · npm/cli@f6d468a (original) (raw)

1

``

`-

'use strict'

`

2

``

-

3

``

`-

const ansiTrim = require('./utils/ansi-trim')

`

4

``

`-

const chain = require('slide').chain

`

5

``

`-

const color = require('ansicolors')

`

6

``

`-

const defaultRegistry = require('./config/defaults').defaults.registry

`

7

``

`-

const log = require('npmlog')

`

8

``

`-

const npm = require('./npm')

`

9

``

`-

const output = require('./utils/output')

`

10

``

`-

const path = require('path')

`

11

``

`-

const semver = require('semver')

`

12

``

`-

const styles = require('ansistyles')

`

``

1

`+

const npm = require('./npm.js')

`

``

2

+

``

3

`+

const chalk = require('chalk')

`

``

4

`+

const ansiTrim = require('./utils/ansi-trim.js')

`

13

5

`const table = require('text-table')

`

``

6

`+

const output = require('./utils/output.js')

`

``

7

`+

const completion = require('./utils/completion/none.js')

`

``

8

`+

const usageUtil = require('./utils/usage.js')

`

``

9

`+

const usage = usageUtil('doctor', 'npm doctor')

`

``

10

`+

const { resolve } = require('path')

`

14

11

``

15

``

`-

// steps

`

16

``

`-

const checkFilesPermission = require('./doctor/check-files-permission')

`

17

``

`-

const checkPing = require('./doctor/check-ping')

`

18

``

`-

const getGitPath = require('./doctor/get-git-path')

`

19

``

`-

const getLatestNodejsVersion = require('./doctor/get-latest-nodejs-version')

`

20

``

`-

const getLatestNpmVersion = require('./doctor/get-latest-npm-version')

`

21

``

`-

const verifyCachedFiles = require('./doctor/verify-cached-files')

`

``

12

`+

const ping = require('./utils/ping.js')

`

``

13

`+

const checkPing = async () => {

`

``

14

`+

const tracker = npm.log.newItem('checkPing', 1)

`

``

15

`+

tracker.info('checkPing', 'Pinging registry')

`

``

16

`+

try {

`

``

17

`+

await ping(npm.flatOptions)

`

``

18

`+

return ''

`

``

19

`+

} catch (er) {

`

``

20

`+

if (/^E\d{3}$/.test(er.code || '')) {

`

``

21

`+

throw er.code.substr(1) + ' ' + er.message

`

``

22

`+

} else {

`

``

23

`+

throw er

`

``

24

`+

}

`

``

25

`+

} finally {

`

``

26

`+

tracker.finish()

`

``

27

`+

}

`

``

28

`+

}

`

22

29

``

23

``

`-

const globalNodeModules = path.join(npm.config.globalPrefix, 'lib', 'node_modules')

`

24

``

`-

const localNodeModules = path.join(npm.config.localPrefix, 'node_modules')

`

``

30

`+

const pacote = require('pacote')

`

``

31

`+

const getLatestNpmVersion = async () => {

`

``

32

`+

const tracker = npm.log.newItem('getLatestNpmVersion', 1)

`

``

33

`+

tracker.info('getLatestNpmVersion', 'Getting npm package information')

`

``

34

`+

try {

`

``

35

`+

const latest = (await pacote.manifest('npm@latest', npm.flatOptions)).version

`

``

36

`+

if (semver.gte(npm.version, latest)) {

`

``

37

`` +

return current: v${npm.version}, latest: v${latest}

``

``

38

`+

} else {

`

``

39

`` +

throw Use npm v${latest}

``

``

40

`+

}

`

``

41

`+

} finally {

`

``

42

`+

tracker.finish()

`

``

43

`+

}

`

``

44

`+

}

`

25

45

``

26

``

`-

const usageUtil = require('./utils/usage.js')

`

27

``

`-

const usage = usageUtil('doctor', 'npm doctor')

`

28

``

`-

const completion = require('./utils/completion/none.js')

`

``

46

`+

const semver = require('semver')

`

``

47

`+

const fetch = require('make-fetch-happen')

`

``

48

`+

const getLatestNodejsVersion = async () => {

`

``

49

`+

// XXX get the latest in the current major as well

`

``

50

`+

const current = process.version

`

``

51

`` +

const currentRange = ^${current}

``

``

52

`+

const url = 'https://nodejs.org/dist/index.json'

`

``

53

`+

const tracker = npm.log.newItem('getLatestNodejsVersion', 1)

`

``

54

`+

tracker.info('getLatestNodejsVersion', 'Getting Node.js release information')

`

``

55

`+

try {

`

``

56

`+

const res = await fetch(url, { method: 'GET', ...npm.flatOptions })

`

``

57

`+

const data = await res.json()

`

``

58

`+

let maxCurrent = '0.0.0'

`

``

59

`+

let maxLTS = '0.0.0'

`

``

60

`+

for (const { lts, version } of data) {

`

``

61

`+

if (lts && semver.gt(version, maxLTS)) {

`

``

62

`+

maxLTS = version

`

``

63

`+

}

`

``

64

`+

if (semver.satisfies(version, currentRange) && semver.gt(version, maxCurrent)) {

`

``

65

`+

maxCurrent = version

`

``

66

`+

}

`

``

67

`+

}

`

``

68

`+

const recommended = semver.gt(maxCurrent, maxLTS) ? maxCurrent : maxLTS

`

``

69

`+

if (semver.gte(process.version, recommended)) {

`

``

70

`` +

return current: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>c</mi><mi>u</mi><mi>r</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>t</mi></mrow><mo separator="true">,</mo><mi>r</mi><mi>e</mi><mi>c</mi><mi>o</mi><mi>m</mi><mi>m</mi><mi>e</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>d</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{current}, recommended: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">c</span><span class="mord mathnormal">u</span><span class="mord mathnormal">rre</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">reco</span><span class="mord mathnormal">mm</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal">d</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{recommended}

``

``

71

`+

} else {

`

``

72

`` +

throw Use node <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>c</mi><mi>o</mi><mi>m</mi><mi>m</mi><mi>e</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>d</mi></mrow><mo stretchy="false">(</mo><mi>c</mi><mi>u</mi><mi>r</mi><mi>r</mi><mi>e</mi><mi>n</mi><mi>t</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{recommended} (current: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">reco</span><span class="mord mathnormal">mm</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal">d</span></span><span class="mopen">(</span><span class="mord mathnormal">c</span><span class="mord mathnormal">u</span><span class="mord mathnormal">rre</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{current})

``

``

73

`+

}

`

``

74

`+

} finally {

`

``

75

`+

tracker.finish()

`

``

76

`+

}

`

``

77

`+

}

`

29

78

``

30

``

`-

function doctor (args, silent, cb) {

`

31

``

`-

args = args || {}

`

32

``

`-

if (typeof cb !== 'function') {

`

33

``

`-

cb = silent

`

34

``

`-

silent = false

`

``

79

`+

const { promisify } = require('util')

`

``

80

`+

const fs = require('fs')

`

``

81

`+

const { R_OK, W_OK, X_OK } = fs.constants

`

``

82

`+

const maskLabel = mask => {

`

``

83

`+

const label = []

`

``

84

`+

if (mask & R_OK) {

`

``

85

`+

label.push('readable')

`

``

86

`+

}

`

``

87

`+

if (mask & W_OK) {

`

``

88

`+

label.push('writable')

`

``

89

`+

}

`

``

90

`+

if (mask & X_OK) {

`

``

91

`+

label.push('executable')

`

``

92

`+

}

`

``

93

`+

return label.join(', ')

`

``

94

`+

}

`

``

95

`+

const lstat = promisify(fs.lstat)

`

``

96

`+

const readdir = promisify(fs.readdir)

`

``

97

`+

const access = promisify(fs.access)

`

``

98

`+

const isWindows = require('./utils/is-windows.js')

`

``

99

`+

const checkFilesPermission = async (root, shouldOwn = true, mask = null) => {

`

``

100

`+

if (mask === null) {

`

``

101

`+

mask = shouldOwn ? R_OK | W_OK : R_OK

`

35

102

`}

`

36

103

``

37

``

`-

const actionsToRun = [

`

38

``

`-

[checkPing],

`

39

``

`-

[getLatestNpmVersion],

`

40

``

`-

[getLatestNodejsVersion, args['node-url']],

`

41

``

`-

[getGitPath],

`

42

``

`-

[checkFilesPermission, npm.cache, 4, 6],

`

43

``

`-

[checkFilesPermission, globalNodeModules, 4, 4],

`

44

``

`-

[checkFilesPermission, localNodeModules, 6, 6],

`

45

``

`-

[verifyCachedFiles, path.join(npm.cache, '_cacache')]

`

46

``

`-

]

`

``

104

`+

let ok = true

`

``

105

+

``

106

`+

const tracker = npm.log.newItem(root, 1)

`

``

107

+

``

108

`+

try {

`

``

109

`+

const uid = process.getuid()

`

``

110

`+

const gid = process.getgid()

`

``

111

`+

const files = new Set([root])

`

``

112

`+

for (const f of files) {

`

``

113

`+

tracker.silly('checkFilesPermission', f.substr(root.length + 1))

`

``

114

`+

const st = await lstat(f)

`

``

115

`+

.catch(er => {

`

``

116

`+

ok = false

`

``

117

`+

tracker.warn('checkFilesPermission', 'error getting info for ' + f)

`

``

118

`+

})

`

``

119

+

``

120

`+

tracker.completeWork(1)

`

47

121

``

48

``

`-

log.info('doctor', 'Running checkup')

`

49

``

`-

chain(actionsToRun, function (stderr, stdout) {

`

50

``

`-

if (stderr && stderr.message !== 'not found: git') return cb(stderr)

`

51

``

`-

const list = makePretty(stdout)

`

52

``

`-

let outHead = ['Check', 'Value', 'Recommendation']

`

53

``

`-

let outBody = list

`

54

``

-

55

``

`-

if (npm.color) {

`

56

``

`-

outHead = outHead.map(function (item) {

`

57

``

`-

return styles.underline(item)

`

58

``

`-

})

`

59

``

`-

outBody = outBody.map(function (item) {

`

60

``

`-

if (item[2]) {

`

61

``

`-

item[0] = color.red(item[0])

`

62

``

`-

item[2] = color.magenta(item[2])

`

``

122

`+

if (!st) {

`

``

123

`+

continue

`

``

124

`+

}

`

``

125

+

``

126

`+

if (shouldOwn && (uid !== st.uid || gid !== st.gid)) {

`

``

127

`+

tracker.warn('checkFilesPermission', 'should be owner of ' + f)

`

``

128

`+

ok = false

`

``

129

`+

}

`

``

130

+

``

131

`+

if (!st.isDirectory() && !st.isFile()) {

`

``

132

`+

continue

`

``

133

`+

}

`

``

134

+

``

135

`+

try {

`

``

136

`+

await access(f, mask)

`

``

137

`+

} catch (er) {

`

``

138

`+

ok = false

`

``

139

`` +

const msg = Missing permissions on <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>e</mi><mi>x</mi><mi>p</mi><mi>e</mi><mi>c</mi><mi>t</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">{f} (expect: </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span><span class="mopen">(</span><span class="mord mathnormal">e</span><span class="mord mathnormal">x</span><span class="mord mathnormal">p</span><span class="mord mathnormal">ec</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{maskLabel(mask)})

``

``

140

`+

tracker.error('checkFilesPermission', msg)

`

``

141

`+

continue

`

``

142

`+

}

`

``

143

+

``

144

`+

if (st.isDirectory()) {

`

``

145

`+

const entries = await readdir(f)

`

``

146

`+

.catch(er => {

`

``

147

`+

ok = false

`

``

148

`+

tracker.warn('checkFilesPermission', 'error reading directory ' + f)

`

``

149

`+

return []

`

``

150

`+

})

`

``

151

`+

for (const entry of entries) {

`

``

152

`+

files.add(resolve(f, entry))

`

63

153

`}

`

64

``

`-

return item

`

65

``

`-

})

`

``

154

`+

}

`

66

155

`}

`

67

``

-

68

``

`-

const outTable = [outHead].concat(outBody)

`

69

``

`-

const tableOpts = {

`

70

``

`-

stringLength: function (s) { return ansiTrim(s).length }

`

``

156

`+

} finally {

`

``

157

`+

tracker.finish()

`

``

158

`+

if (!ok) {

`

``

159

`` +

throw Check the permissions of files in ${root} +

``

``

160

`+

(shouldOwn ? ' (should be owned by current user)' : '')

`

``

161

`+

} else {

`

``

162

`+

return ''

`

71

163

`}

`

``

164

`+

}

`

``

165

`+

}

`

72

166

``

73

``

`-

if (!silent) output(table(outTable, tableOpts))

`

``

167

`+

const which = require('which')

`

``

168

`+

const getGitPath = async () => {

`

``

169

`+

const tracker = npm.log.newItem('getGitPath', 1)

`

``

170

`+

tracker.info('getGitPath', 'Finding git in your PATH')

`

``

171

`+

try {

`

``

172

`+

return await which('git').catch(er => {

`

``

173

`+

tracker.warn(er)

`

``

174

`+

throw "Install git and ensure it's in your PATH."

`

``

175

`+

})

`

``

176

`+

} finally {

`

``

177

`+

tracker.finish()

`

``

178

`+

}

`

``

179

`+

}

`

``

180

+

``

181

`+

const cacache = require('cacache')

`

``

182

`+

const verifyCachedFiles = async () => {

`

``

183

`+

const tracker = npm.log.newItem('verifyCachedFiles', 1)

`

``

184

`+

tracker.info('verifyCachedFiles', 'Verifying the npm cache')

`

``

185

`+

try {

`

``

186

`+

const stats = await cacache.verify(npm.flatOptions.cache)

`

``

187

`+

const { badContentCount, reclaimedCount, missingContent, reclaimedSize } = stats

`

``

188

`+

if (badContentCount || reclaimedCount || missingContent) {

`

``

189

`+

if (badContentCount) {

`

``

190

`` +

tracker.warn('verifyCachedFiles', Corrupted content removed: ${badContentCount})

``

``

191

`+

}

`

``

192

`+

if (reclaimedCount) {

`

``

193

`` +

tracker.warn('verifyCachedFiles', Content garbage-collected: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>c</mi><mi>l</mi><mi>a</mi><mi>i</mi><mi>m</mi><mi>e</mi><mi>d</mi><mi>C</mi><mi>o</mi><mi>u</mi><mi>n</mi><mi>t</mi></mrow><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">{reclaimedCount} (</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">rec</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">aim</span><span class="mord mathnormal">e</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal">o</span><span class="mord mathnormal">u</span><span class="mord mathnormal">n</span><span class="mord mathnormal">t</span></span><span class="mopen">(</span></span></span></span>{reclaimedSize} bytes))

``

``

194

`+

}

`

``

195

`+

if (missingContent) {

`

``

196

`` +

tracker.warn('verifyCachedFiles', Missing content: ${missingContent})

``

``

197

`+

}

`

``

198

`+

tracker.warn('verifyCachedFiles', 'Cache issues have been fixed')

`

``

199

`+

}

`

``

200

`` +

tracker.info('verifyCachedFiles', `Verification complete. Stats: ${

``

``

201

`+

JSON.stringify(stats, null, 2)

`

``

202

`` +

}`)

``

``

203

`` +

return verified ${stats.verifiedContent} tarballs

``

``

204

`+

} finally {

`

``

205

`+

tracker.finish()

`

``

206

`+

}

`

``

207

`+

}

`

74

208

``

75

``

`-

cb(null, list)

`

76

``

`-

})

`

``

209

`+

const { defaults: { registry: defaultRegistry } } = require('./config/defaults.js')

`

``

210

`+

const checkNpmRegistry = async () => {

`

``

211

`+

if (npm.flatOptions.registry !== defaultRegistry) {

`

``

212


throw `Try \`npm config set registry=${defaultRegistry}\``

``

213

`+

} else {

`

``

214

`` +

return using default registry (${defaultRegistry})

``

``

215

`+

}

`

77

216

`}

`

78

217

``

79

``

`-

function makePretty (p) {

`

80

``

`-

const ping = p[1]

`

81

``

`-

const npmLTS = p[2]

`

82

``

`-

const nodeLTS = p[3].replace('v', '')

`

83

``

`-

const whichGit = p[4] || 'not installed'

`

84

``

`-

const readbleCaches = p[5] ? 'ok' : 'notOk'

`

85

``

`-

const executableGlobalModules = p[6] ? 'ok' : 'notOk'

`

86

``

`-

const executableLocalModules = p[7] ? 'ok' : 'notOk'

`

87

``

`` -

const cacheStatus = p[8] ? verified ${p[8].verifiedContent} tarballs : 'notOk'

``

88

``

`-

const npmV = npm.version

`

89

``

`-

const nodeV = process.version.replace('v', '')

`

90

``

`-

const registry = npm.config.get('registry') || ''

`

91

``

`-

const list = [

`

92

``

`-

['npm ping', ping],

`

93

``

`-

['npm -v', 'v' + npmV],

`

94

``

`-

['node -v', 'v' + nodeV],

`

95

``

`-

['npm config get registry', registry],

`

96

``

`-

['which git', whichGit],

`

97

``

`-

['Perms check on cached files', readbleCaches],

`

98

``

`-

['Perms check on global node_modules', executableGlobalModules],

`

99

``

`-

['Perms check on local node_modules', executableLocalModules],

`

100

``

`-

['Verify cache contents', cacheStatus]

`

``

218

`+

const cmd = (args, cb) => doctor(args).then(() => cb()).catch(cb)

`

``

219

+

``

220

`+

const doctor = async args => {

`

``

221

`+

npm.log.info('Running checkup')

`

``

222

+

``

223

`+

// each message is [title, ok, message]

`

``

224

`+

const messages = []

`

``

225

+

``

226

`+

const actions = [

`

``

227

`+

['npm ping', checkPing, []],

`

``

228

`+

['npm -v', getLatestNpmVersion, []],

`

``

229

`+

['node -v', getLatestNodejsVersion, []],

`

``

230

`+

['npm config get registry', checkNpmRegistry, []],

`

``

231

`+

['which git', getGitPath, []],

`

``

232

`+

...(isWindows ? [] : [

`

``

233

`+

['Perms check on cached files', checkFilesPermission, [npm.cache, true, R_OK]],

`

``

234

`+

['Perms check on local node_modules', checkFilesPermission, [npm.localDir, true]],

`

``

235

`+

['Perms check on global node_modules', checkFilesPermission, [npm.globalDir, false]],

`

``

236

`+

['Perms check on local bin folder', checkFilesPermission, [npm.localBin, false, R_OK | W_OK | X_OK]],

`

``

237

`+

['Perms check on global bin folder', checkFilesPermission, [npm.globalBin, false, X_OK]]

`

``

238

`+

]),

`

``

239

`+

['Verify cache contents', verifyCachedFiles, [npm.flatOptions.cache]]

`

``

240

`+

// TODO:

`

``

241

`+

// - ensure arborist.loadActual() runs without errors and no invalid edges

`

``

242

`+

// - ensure package-lock.json matches loadActual()

`

``

243

`+

// - verify loadActual without hidden lock file matches hidden lockfile

`

``

244

`+

// - verify all local packages have bins linked

`

101

245

`]

`

102

246

``

103

``

`-

if (p[0] !== 200) list[0][2] = 'Check your internet connection'

`

104

``

`-

if (!semver.satisfies(npmV, '>=' + npmLTS)) list[1][2] = 'Use npm v' + npmLTS

`

105

``

`-

if (!semver.satisfies(nodeV, '>=' + nodeLTS)) list[2][2] = 'Use node v' + nodeLTS

`

106

``

`` -

if (registry !== defaultRegistry) list[3][2] = 'Try npm config set registry ' + defaultRegistry + ''

``

107

``

`-

if (whichGit === 'not installed') list[4][2] = 'Install git and ensure it's in your PATH.'

`

108

``

`-

if (readbleCaches !== 'ok') list[5][2] = 'Check the permissions of your files in ' + npm.config.get('cache')

`

109

``

`-

if (executableGlobalModules !== 'ok') list[6][2] = globalNodeModules + ' must be readable and writable by the current user.'

`

110

``

`-

if (executableLocalModules !== 'ok') list[7][2] = localNodeModules + ' must be readable and writable by the current user.'

`

``

247

`+

for (const [msg, fn, args] of actions) {

`

``

248

`+

const line = [msg]

`

``

249

`+

try {

`

``

250

`+

line.push(true, await fn(...args))

`

``

251

`+

} catch (er) {

`

``

252

`+

line.push(false, er)

`

``

253

`+

}

`

``

254

`+

messages.push(line)

`

``

255

`+

}

`

``

256

+

``

257

`+

const silent = npm.log.levels[npm.log.level] > npm.log.levels.error

`

111

258

``

112

``

`-

return list

`

``

259

`+

const outHead = ['Check', 'Value', 'Recommendation/Notes']

`

``

260

`+

.map(!npm.color ? h => h : h => chalk.underline(h))

`

``

261

`+

let allOk = true

`

``

262

`+

const outBody = messages.map(!npm.color

`

``

263

`+

? item => {

`

``

264

`+

allOk = allOk && item[1]

`

``

265

`+

item[1] = item[1] ? 'ok' : 'not ok'

`

``

266

`+

item[2] = String(item[2])

`

``

267

`+

return item

`

``

268

`+

}

`

``

269

`+

: item => {

`

``

270

`+

allOk = allOk && item[1]

`

``

271

`+

if (!item[1]) {

`

``

272

`+

item[0] = chalk.red(item[0])

`

``

273

`+

item[2] = chalk.magenta(String(item[2]))

`

``

274

`+

}

`

``

275

`+

item[1] = item[1] ? chalk.green('ok') : chalk.red('not ok')

`

``

276

`+

return item

`

``

277

`+

})

`

``

278

`+

const outTable = [outHead, ...outBody]

`

``

279

`+

const tableOpts = {

`

``

280

`+

stringLength: s => ansiTrim(s).length

`

``

281

`+

}

`

``

282

+

``

283

`+

if (!silent) {

`

``

284

`+

output(table(outTable, tableOpts))

`

``

285

`+

if (!allOk) {

`

``

286

`+

console.error('')

`

``

287

`+

}

`

``

288

`+

}

`

``

289

`+

if (!allOk) {

`

``

290

`+

throw 'Some problems found. See above for recommendations.'

`

``

291

`+

}

`

113

292

`}

`

114

293

``

115

``

`-

module.exports = Object.assign(doctor, { completion, usage })

`

``

294

`+

module.exports = Object.assign(cmd, { completion, usage })

`