1 var mkdir = require("mkdirp")
2 , assert = require("assert")
3 , fs = require("graceful-fs")
4 , writeFileAtomic = require("write-file-atomic")
5 , path = require("path")
7 , npm = require("../npm.js")
8 , log = require("npmlog")
9 , tar = require("../utils/tar.js")
10 , pathIsInside = require("path-is-inside")
11 , getCacheStat = require("./get-stat.js")
12 , cachedPackageRoot = require("./cached-package-root.js")
13 , chownr = require("chownr")
14 , inflight = require("inflight")
15 , once = require("once")
16 , writeStreamAtomic = require("fs-write-stream-atomic")
17 , randomBytes = require("crypto").pseudoRandomBytes // only need uniqueness
19 module.exports = addLocalTarball
21 function addLocalTarball (p, pkgData, shasum, cb) {
22 assert(typeof p === "string", "must have path")
23 assert(typeof cb === "function", "must have callback")
25 if (!pkgData) pkgData = {}
27 // If we don't have a shasum yet, compute it.
29 return sha.get(p, function (er, shasum) {
31 log.silly("addLocalTarball", "shasum (computed)", shasum)
32 addLocalTarball(p, pkgData, shasum, cb)
36 if (pathIsInside(p, npm.cache)) {
37 if (path.basename(p) !== "package.tgz") {
38 return cb(new Error("Not a valid cache tarball name: "+p))
40 log.verbose("addLocalTarball", "adding from inside cache", p)
41 return addPlacedTarball(p, pkgData, shasum, cb)
44 addTmpTarball(p, pkgData, shasum, function (er, data) {
47 data._shasum = data._shasum || shasum
53 function addPlacedTarball (p, pkgData, shasum, cb) {
54 assert(pkgData, "should have package data by now")
55 assert(typeof cb === "function", "cb function required")
57 getCacheStat(function (er, cs) {
59 return addPlacedTarball_(p, pkgData, cs.uid, cs.gid, shasum, cb)
63 function addPlacedTarball_ (p, pkgData, uid, gid, resolvedSum, cb) {
64 var folder = path.join(cachedPackageRoot(pkgData), "package")
66 // First, make sure we have the shasum, if we don't already.
68 sha.get(p, function (er, shasum) {
70 addPlacedTarball_(p, pkgData, uid, gid, shasum, cb)
75 mkdir(folder, function (er) {
77 var pj = path.join(folder, "package.json")
78 var json = JSON.stringify(pkgData, null, 2)
79 writeFileAtomic(pj, json, function (er) {
85 function addTmpTarball (tgz, pkgData, shasum, cb) {
86 assert(typeof cb === "function", "must have callback function")
87 assert(shasum, "must have shasum by now")
89 cb = inflight("addTmpTarball:" + tgz, cb)
90 if (!cb) return log.verbose("addTmpTarball", tgz, "already in flight; not adding")
91 log.verbose("addTmpTarball", tgz, "not in flight; adding")
93 // we already have the package info, so just move into place
94 if (pkgData && pkgData.name && pkgData.version) {
97 "already have metadata; skipping unpack for",
98 pkgData.name + "@" + pkgData.version
100 return addTmpTarball_(tgz, pkgData, shasum, cb)
103 // This is a tarball we probably downloaded from the internet. The shasum's
104 // already been checked, but we haven't ever had a peek inside, so we unpack
105 // it here just to make sure it is what it says it is.
107 // NOTE: we might not have any clue what we think it is, for example if the
108 // user just did `npm install ./foo.tgz`
110 // generate a unique filename
111 randomBytes(6, function (er, random) {
112 if (er) return cb(er)
114 var target = path.join(npm.tmp, "unpack-" + random.toString("hex"))
115 getCacheStat(function (er, cs) {
116 if (er) return cb(er)
118 log.verbose("addTmpTarball", "validating metadata from", tgz)
119 tar.unpack(tgz, target, null, null, cs.uid, cs.gid, function (er, data) {
120 if (er) return cb(er)
122 // check that this is what we expected.
124 return cb(new Error("No name provided"))
126 else if (pkgData.name && data.name !== pkgData.name) {
127 return cb(new Error("Invalid Package: expected " + pkgData.name +
128 " but found " + data.name))
132 return cb(new Error("No version provided"))
134 else if (pkgData.version && data.version !== pkgData.version) {
135 return cb(new Error("Invalid Package: expected " +
136 pkgData.name + "@" + pkgData.version +
137 " but found " + data.name + "@" + data.version))
140 addTmpTarball_(tgz, data, shasum, cb)
146 function addTmpTarball_ (tgz, data, shasum, cb) {
147 assert(typeof cb === "function", "must have callback function")
150 assert(data.name, "should have package name by now")
151 assert(data.version, "should have package version by now")
153 var root = cachedPackageRoot(data)
154 var pkg = path.resolve(root, "package")
155 var target = path.resolve(root, "package.tgz")
156 getCacheStat(function (er, cs) {
157 if (er) return cb(er)
158 mkdir(pkg, function (er, created) {
160 // chown starting from the first dir created by mkdirp,
161 // or the root dir, if none had to be created, so that
162 // we know that we get all the children.
164 chownr(created || root, cs.uid, cs.gid, done)
167 if (er) return cb(er)
168 var read = fs.createReadStream(tgz)
169 var write = writeStreamAtomic(target, { mode: npm.modes.file })
170 var fin = cs.uid && cs.gid ? chown : done
171 read.on("error", cb).pipe(write).on("error", cb).on("close", fin)
177 data._shasum = data._shasum || shasum