summaryrefslogtreecommitdiff
path: root/deps/npm/lib/publish.js
blob: c0e910e85f3a3b7b39c0baa45b28fe0258526765 (plain)
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

module.exports = publish

var npm = require('./npm.js')
var log = require('npmlog')
var path = require('path')
var readJson = require('read-package-json')
var lifecycle = require('./utils/lifecycle.js')
var chain = require('slide').chain
var mapToRegistry = require('./utils/map-to-registry.js')
var cachedPackageRoot = require('./cache/cached-package-root.js')
var createReadStream = require('graceful-fs').createReadStream
var npa = require('npm-package-arg')
var semver = require('semver')
var getPublishConfig = require('./utils/get-publish-config.js')
var output = require('./utils/output.js')

publish.usage = 'npm publish [<tarball>|<folder>] [--tag <tag>] [--access <public|restricted>]' +
                "\n\nPublishes '.' if no argument supplied" +
                '\n\nSets tag `latest` if no --tag specified'

publish.completion = function (opts, cb) {
  // publish can complete to a folder with a package.json
  // or a tarball, or a tarball url.
  // for now, not yet implemented.
  return cb()
}

function publish (args, isRetry, cb) {
  if (typeof cb !== 'function') {
    cb = isRetry
    isRetry = false
  }
  if (args.length === 0) args = ['.']
  if (args.length !== 1) return cb(publish.usage)

  log.verbose('publish', args)

  var t = npm.config.get('tag').trim()
  if (semver.validRange(t)) {
    var er = new Error('Tag name must not be a valid SemVer range: ' + t)
    return cb(er)
  }

  var arg = args[0]
  // if it's a local folder, then run the prepublish there, first.
  readJson(path.resolve(arg, 'package.json'), function (er, data) {
    if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)

    if (data) {
      if (!data.name) return cb(new Error('No name provided'))
      if (!data.version) return cb(new Error('No version provided'))
    }

    // if readJson errors, the argument might be a tarball or package URL
    if (er) {
      npm.commands.cache.add(arg, null, null, false, function (er, data) {
        if (er) return cb(er)
        log.silly('publish', data)
        var cached = path.resolve(cachedPackageRoot(data), 'package') + '.tgz'
        // *publish* lifecycle scripts aren't run when publishing a built artifact
        // go to the next step directly
        publish_(arg, data, isRetry, cached, cb)
      })
    } else {
      var dir = arg
      npm.commands.cache.add(dir, null, null, false, function (er, data) {
        if (er) return cb(er)
        log.silly('publish', data)
        var cached = path.resolve(cachedPackageRoot(data), 'package') + '.tgz'
        // `prepublish` and `prepare` are run by cache.add
        chain(
          [
            [lifecycle, data, 'prepublishOnly', dir],
            [publish_, dir, data, isRetry, cached],
            [lifecycle, data, 'publish', dir],
            [lifecycle, data, 'postpublish', dir]
          ],
          cb
        )
      })
    }
  })
}

function publish_ (arg, data, isRetry, cached, cb) {
  if (!data) return cb(new Error('no package.json file found'))

  var mappedConfig = getPublishConfig(
    data.publishConfig,
    npm.config,
    npm.registry
  )
  var config = mappedConfig.config
  var registry = mappedConfig.client

  data._npmVersion = npm.version
  data._nodeVersion = process.versions.node

  delete data.modules
  if (data.private) {
    return cb(new Error(
      'This package has been marked as private\n' +
      "Remove the 'private' field from the package.json to publish it."
    ))
  }

  mapToRegistry(data.name, config, function (er, registryURI, auth, registryBase) {
    if (er) return cb(er)

    // we just want the base registry URL in this case
    log.verbose('publish', 'registryBase', registryBase)
    log.silly('publish', 'uploading', cached)

    data._npmUser = {
      name: auth.username,
      email: auth.email
    }

    var params = {
      metadata: data,
      body: createReadStream(cached),
      auth: auth
    }

    // registry-frontdoor cares about the access level, which is only
    // configurable for scoped packages
    if (config.get('access')) {
      if (!npa(data.name).scope && config.get('access') === 'restricted') {
        return cb(new Error("Can't restrict access to unscoped packages."))
      }

      params.access = config.get('access')
    }

    log.showProgress('publish:' + data._id)
    registry.publish(registryBase, params, function (er) {
      if (er && er.code === 'EPUBLISHCONFLICT' &&
          npm.config.get('force') && !isRetry) {
        log.warn('publish', 'Forced publish over ' + data._id)
        return npm.commands.unpublish([data._id], function (er) {
          // ignore errors.  Use the force.  Reach out with your feelings.
          // but if it fails again, then report the first error.
          publish([arg], er || true, cb)
        })
      }
      // report the unpublish error if this was a retry and unpublish failed
      if (er && isRetry && isRetry !== true) return cb(isRetry)
      if (er) return cb(er)
      output('+ ' + data._id)
      cb()
    })
  })
}