Comparing version 0.2.0-alpha to 0.2.0-alpha.1
@@ -1,7 +0,7 @@ | ||
var path = require("path"); | ||
var path = require('path'); | ||
module.exports = function(grunt) { | ||
module.exports = function (grunt) { | ||
grunt.initConfig({ | ||
jshint : ['tasks/**/*.js'], | ||
jshint: ['tasks/**/*.js'], | ||
nodeunit: { | ||
@@ -30,4 +30,4 @@ all: ['test/upload.js', 'test/download.js', 'test/s3Task.js'] | ||
upload: [{ | ||
src: path.join(process.cwd(), "test", "files", "**", "*.txt"), | ||
rel: path.join(process.cwd(), "test", "files") | ||
src: path.join(process.cwd(), 'test', 'files', '**', '*.txt'), | ||
rel: path.join(process.cwd(), 'test', 'files') | ||
}] | ||
@@ -34,0 +34,0 @@ } |
@@ -1,5 +0,5 @@ | ||
{ | ||
{ | ||
"name": "grunt-s3", | ||
"description": "A grunt task to automate moving files to/from Amazon S3.", | ||
"version": "0.2.0-alpha", | ||
"version": "0.2.0-alpha.1", | ||
"author": "Aaron Forsander (https://github.com/pifantastic)", | ||
@@ -37,3 +37,4 @@ "homepage": "https://github.com/pifantastic/grunt-s3", | ||
"knox": "0.6.x", | ||
"mime": "~1.2.5" | ||
"mime": "~1.2.5", | ||
"temporary": "0.0.5" | ||
}, | ||
@@ -40,0 +41,0 @@ "devDependencies": { |
@@ -154,3 +154,3 @@ [![Build Status](https://secure.travis-ci.org/pifantastic/grunt-s3.png?branch=master)](https://travis-ci.org/pifantastic/grunt-s3) | ||
grunt.initConfig({ | ||
aws: '<json:grunt-aws.json>', | ||
aws: grunt.file.readJSON('grunt-aws.json'), | ||
s3: { | ||
@@ -177,4 +177,10 @@ key: '<%= aws.key %>', | ||
### grunt.helper('s3.put', src, dest, options) | ||
Helpers have been removed from Grunt 0.4 to access these methods directly. You can now require the s3 library files directly like so: | ||
`var s3 = require('grunt-s3').helpers;` | ||
Make sure you explicitly pass the options into the method. If you've used `grunt.initConfig()` you can use `grunt.config.get('s3')` to access them. | ||
### s3.upload(src, dest, options) | ||
Upload a file to s3. Returns a Promises/J-style Deferred object. | ||
@@ -198,3 +204,3 @@ | ||
### grunt.helper('s3.pull', src, dest, options) | ||
### s3.download(src, dest, options) | ||
Download a file from s3. Returns a Promises/J-style Deferred object. | ||
@@ -214,3 +220,3 @@ | ||
### grunt.helper('s3.delete', src, options) | ||
### s3.delete(src, options) | ||
@@ -232,3 +238,3 @@ Delete a file from s3. Returns a Promises/J-style Deferred object. | ||
```javascript | ||
var upload = grunt.helper('s3.put', 'dist/my-app-1.0.0.tar.gz', 'archive/my-app-1.0.0.tar.gz'); | ||
var upload = s3.upload('dist/my-app-1.0.0.tar.gz', 'archive/my-app-1.0.0.tar.gz'); | ||
@@ -246,6 +252,6 @@ upload | ||
var download = grunt.helper('s3.pull', 'dist/my-app-0.9.9.tar.gz', 'local/my-app-0.9.9.tar.gz'); | ||
var download = s3.download('dist/my-app-0.9.9.tar.gz', 'local/my-app-0.9.9.tar.gz'); | ||
download.done(function() { | ||
grunt.helper('s3.delete', 'dist/my-app-0.9.9.tar.gz'); | ||
s3.delete('dist/my-app-0.9.9.tar.gz'); | ||
}); | ||
@@ -252,0 +258,0 @@ |
@@ -20,2 +20,3 @@ /*jshint esnext:true*/ | ||
const deferred = require('underscore.deferred'); | ||
var Tempfile = require('temporary/lib/file'); | ||
@@ -49,4 +50,4 @@ // Local | ||
exports.init = function (grunt) { | ||
var async = grunt.util.async, | ||
_ = grunt.util._; | ||
var async = grunt.util.async; | ||
var _ = grunt.util._; | ||
@@ -155,5 +156,4 @@ _.mixin(deferred); | ||
if (options.gzip && gzipExclude.indexOf(path.extname(src)) === -1) { | ||
headers['Content-Encoding'] = 'gzip'; | ||
headers['Content-Type'] = mime.lookup(src); | ||
headers['Content-Type'] = headers['Content-Type'] || mime.lookup(src); | ||
@@ -165,11 +165,5 @@ var charset = mime.charsets.lookup(headers['Content-Type'], null); | ||
// Determine a unique temp file name. | ||
var tmp = src + '.gz'; | ||
var incr = 0; | ||
while (existsSync(tmp)) { | ||
tmp = src + '.' + (incr++) + '.gz'; | ||
} | ||
var tmp = new Tempfile(); | ||
var input = fs.createReadStream(src); | ||
var output = fs.createWriteStream(tmp); | ||
var output = fs.createWriteStream(tmp.path); | ||
@@ -183,6 +177,6 @@ // Gzip the file and upload when done. | ||
// Update the src to point to the newly created .gz file. | ||
src = tmp; | ||
src = tmp.path; | ||
upload(function (err, msg) { | ||
// Clean up the temp file. | ||
fs.unlinkSync(tmp); | ||
tmp.unlinkSync(); | ||
@@ -314,3 +308,3 @@ if (err) { | ||
'Content-Length': 0, | ||
'x-amz-copy-source' : src | ||
'x-amz-copy-source': src | ||
}; | ||
@@ -317,0 +311,0 @@ |
@@ -1,147 +0,148 @@ | ||
var path = require("path"); | ||
var path = require('path'); | ||
var grunt = require("grunt"), | ||
_ = grunt.util._, | ||
async = grunt.util.async; | ||
var grunt = require('grunt'); | ||
var _ = grunt.util._; | ||
var async = grunt.util.async; | ||
var S3Task = function(origTask, s3) { | ||
this._origTask = origTask; | ||
this.s3 = s3; | ||
var S3Task = function (origTask, s3) { | ||
this._origTask = origTask; | ||
this.s3 = s3; | ||
}; | ||
S3Task.prototype = { | ||
run: function() { | ||
var self = this, | ||
s3 = this.s3; | ||
run: function () { | ||
var self = this; | ||
var s3 = this.s3; | ||
var done = this._origTask.async(); | ||
var config = this.getConfig(); | ||
var transfers = []; | ||
var done = this._origTask.async(); | ||
if (config.debug) { | ||
grunt.log.writeln('Running in debug mode, no transfers will be made'.yellow); | ||
grunt.log.writeln(); | ||
} | ||
var config = this.getConfig(); | ||
config.upload.forEach(function (upload) { | ||
var uploadFiles = self._parseUploadFiles(upload, config); | ||
var transfers = []; | ||
uploadFiles.forEach(function (uploadFile) { | ||
transfers.push(s3.upload.bind(s3, uploadFile.file, uploadFile.dest, uploadFile.upload)); | ||
}); | ||
}); | ||
if (config.debug) { | ||
grunt.log.writeln("Running in debug mode, no transfers will be made".yellow); | ||
grunt.log.writeln(); | ||
} | ||
config.download.forEach(function (download) { | ||
transfers.push(s3.download.bind(s3,download.src, download.dest, download)); | ||
}); | ||
config.upload.forEach(function(upload) { | ||
var uploadFiles = self._parseUploadFiles(upload, config); | ||
config.del.forEach(function (del) { | ||
transfers.push(s3.del.bind(s3,del.src, del)); | ||
}); | ||
uploadFiles.forEach(function(uploadFile) { | ||
transfers.push(s3.upload.bind(s3, uploadFile.file, uploadFile.dest, uploadFile.upload)); | ||
}); | ||
}); | ||
config.copy.forEach(function (copy) { | ||
transfers.push(s3.copy.bind(s3,copy.src, copy.dest, copy)); | ||
}); | ||
config.download.forEach(function(download) { | ||
transfers.push(s3.download.bind(s3,download.src, download.dest, download)); | ||
}); | ||
var total = transfers.length; | ||
var errors = 0; | ||
config.del.forEach(function(del) { | ||
transfers.push(s3.del.bind(s3,del.src, del)); | ||
}); | ||
var eachTransfer = config.maxOperations > 0 ? | ||
async.forEachLimit.bind(async,transfers,config.maxOperations) : | ||
async.forEach.bind(async,transfers); | ||
config.copy.forEach(function(copy) { | ||
transfers.push(s3.copy.bind(s3,copy.src, copy.dest, copy)); | ||
}); | ||
eachTransfer(function (transferFn, completed){ | ||
var transfer = transferFn(); | ||
var total = transfers.length; | ||
var errors = 0; | ||
transfer.done(function (msg) { | ||
grunt.log.ok(msg); | ||
completed(); | ||
}); | ||
var eachTransfer = config.maxOperations > 0 ? | ||
async.forEachLimit.bind(async,transfers,config.maxOperations) | ||
: async.forEach.bind(async,transfers); | ||
transfer.fail(function (msg) { | ||
grunt.log.error(msg); | ||
++errors; | ||
completed(); | ||
}); | ||
eachTransfer(function(transferFn, completed){ | ||
var transfer = transferFn(); | ||
}, function () { | ||
done(!errors); | ||
}); | ||
}, | ||
transfer.done(function(msg) { | ||
grunt.log.ok(msg); | ||
completed(); | ||
}); | ||
_parseUploadFiles: function (upload, config) { | ||
// Expand list of files to upload. | ||
var files = grunt.file.expand({ filter: 'isFile' }, upload.src); | ||
var destPath = grunt.template.process(upload.dest || ''); | ||
transfer.fail(function(msg) { | ||
grunt.log.error(msg); | ||
++errors; | ||
completed(); | ||
}); | ||
return _.map(files, function (file) { | ||
file = path.resolve(file); | ||
upload.src = path.resolve(grunt.template.process(upload.src)); | ||
},function(){ | ||
// we're all done. | ||
done(!errors); | ||
}); | ||
}, | ||
// Put the key, secret and bucket information into the upload for knox | ||
_.extend(upload, config); | ||
_parseUploadFiles: function(upload, config) { | ||
// Expand list of files to upload. | ||
var files = grunt.file.expand({ filter: "isFile" }, upload.src), | ||
destPath = grunt.template.process(upload.dest || ""); | ||
// If there is only 1 file and it matches the original file wildcard, | ||
// we know this is a single file transfer. Otherwise, we need to build | ||
// the destination. | ||
var dest; | ||
if (files.length === 1 && file === upload.src) { | ||
dest = destPath; | ||
} | ||
else { | ||
if (upload.rel) { | ||
dest = path.join(destPath, path.relative(grunt.file.expand({ filter: "isDirectory" }, upload.rel)[0], file)); | ||
} | ||
else { | ||
dest = path.join(destPath, path.basename(file)); | ||
} | ||
} | ||
return _.map(files, function(file) { | ||
file = path.resolve(file); | ||
upload.src = path.resolve(grunt.template.process(upload.src)); | ||
if (config.encodePaths === true) { | ||
dest = encodeURIComponent(dest); | ||
} | ||
// Put the key, secret and bucket information into the upload for knox | ||
_.extend(upload, config); | ||
dest = dest.replace(/\\/g, '/'); | ||
// If there is only 1 file and it matches the original file wildcard, | ||
// we know this is a single file transfer. Otherwise, we need to build | ||
// the destination. | ||
var dest; | ||
if (files.length === 1 && file === upload.src) { | ||
dest = destPath; | ||
} | ||
else { | ||
if (upload.rel) { | ||
dest = path.join(destPath, path.relative(grunt.file.expand({ filter: "isDirectory" }, upload.rel)[0], file)); | ||
} | ||
else { | ||
dest = path.join(destPath, path.basename(file)); | ||
} | ||
} | ||
return { | ||
file: file, | ||
dest: dest, | ||
upload: upload | ||
}; | ||
}); | ||
}, | ||
if(config.encodePaths === true) { | ||
dest = encodeURIComponent(dest); | ||
} | ||
/** | ||
* Return the config, allow for arbitrary options that don't originate | ||
* from grunt. | ||
* | ||
* @param {Object=} optOptions optionally define an options object. | ||
* @param {Object=} optData optionally define file actions, | ||
* will ignore defaults. | ||
* @return {Object} A normalized configuration. | ||
*/ | ||
getConfig: function (optOptions, optData) { | ||
// Grab the options for this task. | ||
var opts = optOptions || this._origTask.options(); | ||
return {file: file, dest: dest, upload: upload}; | ||
}); | ||
}, | ||
var defaultOpts = { | ||
key: process.env.AWS_ACCESS_KEY_ID, | ||
secret: process.env.AWS_SECRET_ACCESS_KEY, | ||
debug: false, | ||
maxOperations: 0, | ||
encodePaths: false | ||
}; | ||
/** | ||
* Return the config, allow for arbitrary options that don't originate | ||
* from grunt. | ||
* | ||
* @param {Object=} optOptions optionally define an options object. | ||
* @param {Object=} optData optionally define file actions, | ||
* will ignore defaults. | ||
* @return {Object} A normalized configuration. | ||
*/ | ||
getConfig: function(optOptions, optData) { | ||
// Grab the options for this task | ||
// Grab the actions to perform from the task data, default to empty arrays | ||
var fileActions = optData || this._origTask.data; | ||
var defaultFileActions = { | ||
upload: [], | ||
download: [], | ||
del: [], | ||
copy: [] | ||
}; | ||
var opts = optOptions || this._origTask.options(); | ||
var defaultOpts = { | ||
key : process.env.AWS_ACCESS_KEY_ID, | ||
secret : process.env.AWS_SECRET_ACCESS_KEY, | ||
debug: false, | ||
maxOperations: 0, | ||
encodePaths: false | ||
}; | ||
// Grab the actions to perform from the task data, default to empty arrays | ||
var fileActions = optData || this._origTask.data; | ||
var defaultFileActions = { | ||
upload: [], | ||
download: [], | ||
del: [], | ||
copy: [] | ||
}; | ||
// Combine the options and fileActions as the config | ||
return _.extend({}, defaultOpts, defaultFileActions, opts, fileActions); | ||
} | ||
// Combine the options and fileActions as the config | ||
return _.extend({}, defaultOpts, defaultFileActions, opts, fileActions); | ||
} | ||
}; | ||
module.exports = S3Task; |
@@ -14,9 +14,9 @@ /*jshint esnext:true*/ | ||
module.exports = function (grunt) { | ||
var s3 = require('./lib/s3'); | ||
var s3 = require('./lib/s3'); | ||
const S3Task = require('./lib/S3Task'); | ||
var exportFn = function (grunt) { | ||
var S3Task = require('./lib/S3Task'); | ||
// if grunt is not provided, then expose internal API | ||
if ('object' !== typeof(grunt)) { | ||
// If grunt is not provided, then expose internal API. | ||
if (typeof grunt !== 'object') { | ||
return { | ||
@@ -41,1 +41,7 @@ s3: s3, | ||
}; | ||
exportFn.init = function (grunt) { | ||
return s3.init(grunt); | ||
}; | ||
module.exports = exportFn; |
var path = require('path'); | ||
var grunt = require('grunt'); | ||
var s3 = require('../tasks/lib/s3').init(grunt); | ||
var S3Task = require("../tasks/lib/S3Task"); | ||
var S3Task = require('../tasks/lib/S3Task'); | ||
var deferred = require('underscore.deferred'); | ||
@@ -11,102 +11,96 @@ | ||
var s3Config = grunt.config("s3"), | ||
config = _.extend({}, s3Config.options, s3Config.test_S3Task.options); | ||
var s3Config = grunt.config('s3'); | ||
var config = _.extend({}, s3Config.options, s3Config.test_S3Task.options); | ||
var makeMockTask = function(taskDef) { | ||
// A fake grunt task | ||
// TODO: Figure out if grunt has a way to mock this. | ||
var mockTask = { | ||
_asyncCalls: 0, | ||
async: function() { | ||
this._asyncCalls++; | ||
return function(result) { | ||
taskDef.resolve(result); | ||
}; | ||
}, | ||
options: function(defaults) { | ||
return _.defaults({}, config, defaults); | ||
}, | ||
data: s3Config.test_S3Task | ||
}; | ||
var makeMockTask = function (taskDef) { | ||
// A fake grunt task | ||
// TODO: Figure out if grunt has a way to mock this. | ||
var mockTask = { | ||
_asyncCalls: 0, | ||
async: function () { | ||
this._asyncCalls++; | ||
return function (result) { | ||
taskDef.resolve(result); | ||
}; | ||
}, | ||
options: function (defaults) { | ||
return _.defaults({}, config, defaults); | ||
}, | ||
data: s3Config.test_S3Task | ||
}; | ||
// Remove the options from the data. | ||
mockTask.data.options = null; | ||
// Remove the options from the data. | ||
mockTask.data.options = null; | ||
return mockTask; | ||
} | ||
return mockTask; | ||
}; | ||
module.exports = { | ||
"run": function(test) { | ||
var taskDef = new _.Deferred(), | ||
asyncCalls = 0; | ||
run: function (test) { | ||
var taskDef = new _.Deferred(); | ||
var asyncCalls = 0; | ||
var mockTask = makeMockTask(taskDef); | ||
var task = new S3Task(mockTask, s3); | ||
// Some vars to hold upload information | ||
var uploadOrig = s3.upload, | ||
uploadFiles = [], | ||
uploadCalls = 0; | ||
var uploadOrig = s3.upload; | ||
var uploadFiles = []; | ||
var uploadCalls = 0; | ||
// Fake the upload functionality; it's tested elsewhere | ||
s3.upload = function(file, dest, opts) { | ||
uploadCalls++; | ||
// Fake the upload functionality; it's tested elsewhere. | ||
s3.upload = function (file, dest, opts) { | ||
uploadCalls++; | ||
var def = new _.Deferred(); | ||
var def = new _.Deferred(); | ||
uploadFiles.push(dest); | ||
uploadFiles.push(dest); | ||
setTimeout(function() { | ||
def.resolve(); | ||
}, 10); | ||
setTimeout(function () { | ||
def.resolve(); | ||
}, 10); | ||
return def.promise(); | ||
return def.promise(); | ||
}; | ||
// Wait for the run() call to complete then test activity | ||
// Wait for the run() call to complete then test activity. | ||
taskDef.then(function(result) { | ||
test.equal(mockTask._asyncCalls, 1, "1 async() call"); | ||
test.equal(uploadFiles.length, 5, "5 uploaded files"); | ||
test.equal(uploadFiles[0], "a.txt", "Correct rel path on uploaded file 1"); | ||
test.equal(uploadFiles[4], "subdir/d.txt", "Correct rel path for subdir file"); | ||
test.equal(mockTask._asyncCalls, 1, '1 async() call'); | ||
test.equal(uploadFiles.length, 5, '5 uploaded files'); | ||
test.equal(uploadFiles[0], 'a.txt', 'Correct rel path on uploaded file 1'); | ||
test.equal(uploadFiles[4], 'subdir/d.txt', 'Correct rel path for subdir file'); | ||
test.ok(result, 'Completed'); | ||
}, function (err) { | ||
test.ok(false, "Error'd"); | ||
}) | ||
.always(function () { | ||
s3.upload = uploadOrig; | ||
test.done(); | ||
}); | ||
test.ok(result, "Completed"); | ||
}, function(err) { | ||
test.ok(false, "Error'd"); | ||
}) | ||
.always(function() { | ||
s3.upload = uploadOrig; | ||
test.done(); | ||
}); | ||
task.run(); | ||
}, | ||
"_parseUploadFiles": function(test) { | ||
_parseUploadFiles: function (test) { | ||
var mockTask = makeMockTask(); | ||
var task = new S3Task(mockTask, s3); | ||
var config = task.getConfig(); | ||
test.equal(config.upload.length, 1, "Has upload to parse"); | ||
test.equal(config.upload.length, 1, 'Has upload to parse'); | ||
var uploadFiles = task._parseUploadFiles(config.upload[0], config); | ||
test.equal(uploadFiles.length, 5, "Has 5 files to be uploaded"); | ||
test.equal(uploadFiles.length, 5, 'Has 5 files to be uploaded'); | ||
// File paths in root | ||
test.equal(uploadFiles[0].file, path.join(process.cwd(), "test", "files", "a.txt")); | ||
test.equal(uploadFiles[1].file, path.join(process.cwd(), "test", "files", "b.txt")); | ||
test.equal(uploadFiles[0].file, path.join(process.cwd(), 'test', 'files', 'a.txt')); | ||
test.equal(uploadFiles[1].file, path.join(process.cwd(), 'test', 'files', 'b.txt')); | ||
// File paths in subdir | ||
test.equal(uploadFiles[3].file, path.join(process.cwd(), "test", "files", "subdir", "c.txt")); | ||
test.equal(uploadFiles[4].file, path.join(process.cwd(), "test", "files", "subdir", "d.txt")); | ||
test.equal(uploadFiles[3].file, path.join(process.cwd(), 'test', 'files', 'subdir', 'c.txt')); | ||
test.equal(uploadFiles[4].file, path.join(process.cwd(), 'test', 'files', 'subdir', 'd.txt')); | ||
// Destinations don't have full path, but have subdir | ||
test.equal(uploadFiles[0].dest, "a.txt"); | ||
test.equal(uploadFiles[1].dest, "b.txt"); | ||
test.equal(uploadFiles[3].dest, path.join("subdir", "c.txt")); | ||
test.equal(uploadFiles[4].dest, path.join("subdir", "d.txt")); | ||
test.equal(uploadFiles[0].dest, 'a.txt'); | ||
test.equal(uploadFiles[1].dest, 'b.txt'); | ||
test.equal(uploadFiles[3].dest, path.join('subdir', 'c.txt')); | ||
test.equal(uploadFiles[4].dest, path.join('subdir', 'd.txt')); | ||
@@ -116,4 +110,3 @@ test.done(); | ||
"getConfig": function (test) { | ||
getConfig: function (test) { | ||
// A fake grunt task | ||
@@ -124,29 +117,28 @@ // TODO: Figure out if grunt has a way to mock this. | ||
var oldVal = { | ||
key: process.env.AWS_ACCESS_KEY_ID, | ||
secret: process.env.AWS_SECRET_ACCESS_KEY | ||
key: process.env.AWS_ACCESS_KEY_ID, | ||
secret: process.env.AWS_SECRET_ACCESS_KEY | ||
}; | ||
// Making sure we choose the options over the process key | ||
process.env.AWS_ACCESS_KEY_ID = "testid"; | ||
process.env.AWS_SECRET_ACCESS_KEY = "secret"; | ||
process.env.AWS_ACCESS_KEY_ID = 'testid'; | ||
process.env.AWS_SECRET_ACCESS_KEY = 'secret'; | ||
var task = new S3Task(mockTask, s3); | ||
var config = task.getConfig(); | ||
// Test the custom things first | ||
test.equal(config.key, s3Config.options.key, "Key"); | ||
test.equal(config.secret, s3Config.options.secret, "Secret"); | ||
test.equal(config.debug, false, "Debug"); | ||
test.equal(config.key, s3Config.options.key, 'Key'); | ||
test.equal(config.secret, s3Config.options.secret, 'Secret'); | ||
test.equal(config.debug, false, 'Debug'); | ||
// Test the data actions | ||
test.equal(config.upload.length, 1, "Upload length"); | ||
test.equal(config.upload.length, 1, 'Upload length'); | ||
// Testing things that are only in the default options. | ||
test.equal(config.bucket, s3Config.options.bucket, "Bucket"); | ||
test.equal(config.endpoint, s3Config.options.endpoint, "Endpoint"); | ||
test.equal(config.bucket, s3Config.options.bucket, 'Bucket'); | ||
test.equal(config.endpoint, s3Config.options.endpoint, 'Endpoint'); | ||
// Newly added options | ||
test.equal(config.maxOperations, 0, "Defaults max operations to 0"); | ||
test.equal(config.encodePaths, false, "Defaults encodePaths to false"); | ||
test.equal(config.maxOperations, 0, 'Defaults max operations to 0'); | ||
test.equal(config.encodePaths, false, 'Defaults encodePaths to false'); | ||
@@ -153,0 +145,0 @@ // Be a good citizen and reset these. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
732
269
36987
5
+ Addedtemporary@0.0.5
+ Addedpackage@1.0.1(transitive)
+ Addedtemporary@0.0.5(transitive)