• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1module.exports = unbuild
2module.exports.rmStuff = rmStuff
3unbuild.usage = 'npm unbuild <folder>\n(this is plumbing)'
4
5const readJson = require('read-package-json')
6const gentlyRm = require('./utils/gently-rm.js')
7const npm = require('./npm.js')
8const path = require('path')
9const isInside = require('path-is-inside')
10const lifecycle = require('./utils/lifecycle.js')
11const asyncMap = require('slide').asyncMap
12const chain = require('slide').chain
13const log = require('npmlog')
14const build = require('./build.js')
15const output = require('./utils/output.js')
16
17// args is a list of folders.
18// remove any bins/etc, and then delete the folder.
19function unbuild (args, silent, cb) {
20  if (typeof silent === 'function') {
21    cb = silent
22    silent = false
23  }
24  asyncMap(args, unbuild_(silent), cb)
25}
26
27function unbuild_ (silent) {
28  return function (folder, cb_) {
29    function cb (er) {
30      cb_(er, path.relative(npm.root, folder))
31    }
32    folder = path.resolve(folder)
33    const base = isInside(folder, npm.prefix) ? npm.prefix : folder
34    delete build._didBuild[folder]
35    log.verbose('unbuild', folder.substr(npm.prefix.length + 1))
36    readJson(path.resolve(folder, 'package.json'), function (er, pkg) {
37      // if no json, then just trash it, but no scripts or whatever.
38      if (er) return gentlyRm(folder, false, base, cb)
39      chain(
40        [
41          [lifecycle, pkg, 'preuninstall', folder, { failOk: true }],
42          [lifecycle, pkg, 'uninstall', folder, { failOk: true }],
43          !silent && function (cb) {
44            output('unbuild ' + pkg._id)
45            cb()
46          },
47          [rmStuff, pkg, folder],
48          [lifecycle, pkg, 'postuninstall', folder, { failOk: true }],
49          [gentlyRm, folder, false, base]
50        ],
51        cb
52      )
53    })
54  }
55}
56
57function rmStuff (pkg, folder, cb) {
58  // if it's global, and folder is in {prefix}/node_modules,
59  // then bins are in {prefix}/bin
60  // otherwise, then bins are in folder/../.bin
61  const dir = path.dirname(folder)
62  const scope = path.basename(dir)
63  const parent = scope.charAt(0) === '@' ? path.dirname(dir) : dir
64  const gnm = npm.dir
65  // gnm might be an absolute path, parent might be relative
66  // this checks they're the same directory regardless
67  const top = path.relative(gnm, parent) === ''
68
69  log.verbose('unbuild rmStuff', pkg._id, 'from', gnm)
70  if (!top) log.verbose('unbuild rmStuff', 'in', parent)
71  asyncMap([rmBins, rmMans], function (fn, cb) {
72    fn(pkg, folder, parent, top, cb)
73  }, cb)
74}
75
76function rmBins (pkg, folder, parent, top, cb) {
77  if (!pkg.bin) return cb()
78  const binRoot = top ? npm.bin : path.resolve(parent, '.bin')
79  asyncMap(Object.keys(pkg.bin), function (b, cb) {
80    if (process.platform === 'win32') {
81      chain([
82        [gentlyRm, path.resolve(binRoot, b) + '.ps1', true, folder],
83        [gentlyRm, path.resolve(binRoot, b) + '.cmd', true, folder],
84        [gentlyRm, path.resolve(binRoot, b), true, folder]
85      ], cb)
86    } else {
87      gentlyRm(path.resolve(binRoot, b), true, folder, cb)
88    }
89  }, gentlyRmBinRoot)
90
91  function gentlyRmBinRoot (err) {
92    if (err || top) return cb(err)
93    return gentlyRm(binRoot, true, parent, cb)
94  }
95}
96
97function rmMans (pkg, folder, parent, top, cb) {
98  if (!pkg.man ||
99      !top ||
100      process.platform === 'win32' ||
101      !npm.config.get('global')) {
102    return cb()
103  }
104  const manRoot = path.resolve(npm.config.get('prefix'), 'share', 'man')
105  log.verbose('rmMans', 'man files are', pkg.man, 'in', manRoot)
106  asyncMap(pkg.man, function (man, cb) {
107    if (Array.isArray(man)) {
108      man.forEach(rmMan)
109    } else {
110      rmMan(man)
111    }
112
113    function rmMan (man) {
114      log.silly('rmMan', 'preparing to remove', man)
115      const parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
116      if (!parseMan) {
117        log.error(
118          'rmMan', man, 'is not a valid name for a man file.',
119          'Man files must end with a number, ' +
120          'and optionally a .gz suffix if they are compressed.'
121        )
122        return cb()
123      }
124
125      const stem = parseMan[1]
126      const sxn = parseMan[2]
127      const gz = parseMan[3] || ''
128      const bn = path.basename(stem)
129      const manDest = path.join(
130        manRoot,
131        'man' + sxn,
132        (bn.indexOf(pkg.name) === 0 ? bn : pkg.name + '-' + bn) + '.' + sxn + gz
133      )
134      gentlyRm(manDest, true, cb)
135    }
136  }, cb)
137}
138