1// check to see if a bin is allowed to be overwritten
2// either rejects or resolves to nothing.  return value not relevant.
3const isWindows = require('./is-windows.js')
4const binTarget = require('./bin-target.js')
5const { resolve, dirname } = require('path')
6const readCmdShim = require('read-cmd-shim')
7const { readlink } = require('fs/promises')
8
9const checkBin = async ({ bin, path, top, global, force }) => {
10  // always ok to clobber when forced
11  // always ok to clobber local bins, or when forced
12  if (force || !global || !top) {
13    return
14  }
15
16  // ok, need to make sure, then
17  const target = resolve(binTarget({ path, top }), bin)
18  path = resolve(path)
19  return isWindows ? checkShim({ target, path }) : checkLink({ target, path })
20}
21
22// only enoent is allowed.  anything else is a problem.
23const handleReadLinkError = async ({ er, target }) =>
24  er.code === 'ENOENT' ? null
25  : failEEXIST({ target })
26
27const checkLink = async ({ target, path }) => {
28  const current = await readlink(target)
29    .catch(er => handleReadLinkError({ er, target }))
30
31  if (!current) {
32    return
33  }
34
35  const resolved = resolve(dirname(target), current)
36
37  if (resolved.toLowerCase().indexOf(path.toLowerCase()) !== 0) {
38    return failEEXIST({ target })
39  }
40}
41
42const handleReadCmdShimError = ({ er, target }) =>
43  er.code === 'ENOENT' ? null
44  : failEEXIST({ target })
45
46const failEEXIST = ({ target }) =>
47  Promise.reject(Object.assign(new Error('EEXIST: file already exists'), {
48    path: target,
49    code: 'EEXIST',
50  }))
51
52const checkShim = async ({ target, path }) => {
53  const shims = [
54    target,
55    target + '.cmd',
56    target + '.ps1',
57  ]
58  await Promise.all(shims.map(async shim => {
59    const current = await readCmdShim(shim)
60      .catch(er => handleReadCmdShimError({ er, target: shim }))
61
62    if (!current) {
63      return
64    }
65
66    const resolved = resolve(dirname(shim), current.replace(/\\/g, '/'))
67
68    if (resolved.toLowerCase().indexOf(path.toLowerCase()) !== 0) {
69      return failEEXIST({ target: shim })
70    }
71  }))
72}
73
74module.exports = checkBin
75