diff --git a/.eslintignore b/.eslintignore index 38235dc..0d64493 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ lib/**/* src/lib/vendor/**/* *.ts +wallaby.js diff --git a/README.md b/README.md index 571ed10..64dee20 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-creden ```js import Uploader from 's3-batch-upload'; -await new Uploader({ +const files = await new Uploader({ config: './config/configS3.json', // can also use environment variables bucket: 'bucket-name', localPath: './files', @@ -80,6 +80,9 @@ await new Uploader({ }, accessControlLevel: 'bucket-owner-full-control' // optional, not passed if undefined. - available options - "private"|"public-read"|"public-read-write"|"authenticated-read"|"aws-exec-read"|"bucket-owner-read"|"bucket-owner-full-control" }).upload(); + +// the files array contains a list of uploaded keys, which you can use to build up the S3 urls. +// e.g. "remote/path/in/bucket/demo.jpg" ``` ### S3 Authentication diff --git a/docs/assets/js/search.js b/docs/assets/js/search.js index 40827df..40e11f9 100644 --- a/docs/assets/js/search.js +++ b/docs/assets/js/search.js @@ -1,3 +1,3 @@ var typedoc = typedoc || {}; typedoc.search = typedoc.search || {}; - typedoc.search.data = {"kinds":{"32":"Variable","64":"Function","128":"Class","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","2097152":"Object literal","4194304":"Type alias"},"rows":[{"id":0,"kind":4194304,"name":"Options","url":"globals.html#options","classes":"tsd-kind-type-alias"},{"id":1,"kind":64,"name":"streamBatch","url":"globals.html#streambatch","classes":"tsd-kind-function"},{"id":2,"kind":128,"name":"Uploader","url":"classes/uploader.html","classes":"tsd-kind-class"},{"id":3,"kind":1024,"name":"s3","url":"classes/uploader.html#s3","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":4,"kind":1024,"name":"options","url":"classes/uploader.html#options","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":5,"kind":1024,"name":"bar","url":"classes/uploader.html#bar","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":6,"kind":512,"name":"constructor","url":"classes/uploader.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Uploader"},{"id":7,"kind":2048,"name":"upload","url":"classes/uploader.html#upload","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Uploader"},{"id":8,"kind":2048,"name":"run","url":"classes/uploader.html#run","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":9,"kind":2048,"name":"getFiles","url":"classes/uploader.html#getfiles","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":10,"kind":2048,"name":"uploadFile","url":"classes/uploader.html#uploadfile","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":11,"kind":32,"name":"glob","url":"globals.html#glob","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":12,"kind":32,"name":"path","url":"globals.html#path","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":13,"kind":32,"name":"AWS","url":"globals.html#aws","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":14,"kind":32,"name":"ProgressBar","url":"globals.html#progressbar","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":15,"kind":32,"name":"ora","url":"globals.html#ora","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":16,"kind":32,"name":"chalk","url":"globals.html#chalk","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":17,"kind":32,"name":"fs","url":"globals.html#fs","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":18,"kind":32,"name":"mime","url":"globals.html#mime","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":19,"kind":65536,"name":"__type","url":"globals.html#options.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"Options"},{"id":20,"kind":32,"name":"bucket","url":"globals.html#options.__type.bucket","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":21,"kind":32,"name":"localPath","url":"globals.html#options.__type.localpath","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":22,"kind":32,"name":"remotePath","url":"globals.html#options.__type.remotepath","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":23,"kind":32,"name":"config","url":"globals.html#options.__type.config","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":24,"kind":32,"name":"glob","url":"globals.html#options.__type.glob","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":25,"kind":32,"name":"concurrency","url":"globals.html#options.__type.concurrency","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":26,"kind":32,"name":"dryRun","url":"globals.html#options.__type.dryrun","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":27,"kind":2097152,"name":"defaultOptions","url":"globals.html#defaultoptions","classes":"tsd-kind-object-literal tsd-is-not-exported"},{"id":28,"kind":32,"name":"dryRun","url":"globals.html#defaultoptions.dryrun","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":29,"kind":32,"name":"concurrency","url":"globals.html#defaultoptions.concurrency","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":30,"kind":32,"name":"glob","url":"globals.html#defaultoptions.glob","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":31,"kind":32,"name":"yargs","url":"globals.html#yargs","classes":"tsd-kind-variable tsd-is-not-exported"}]}; \ No newline at end of file + typedoc.search.data = {"kinds":{"32":"Variable","64":"Function","128":"Class","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","2097152":"Object literal","4194304":"Type alias"},"rows":[{"id":0,"kind":4194304,"name":"Options","url":"globals.html#options","classes":"tsd-kind-type-alias"},{"id":1,"kind":64,"name":"streamBatch","url":"globals.html#streambatch","classes":"tsd-kind-function tsd-has-type-parameter"},{"id":2,"kind":128,"name":"Uploader","url":"classes/uploader.html","classes":"tsd-kind-class"},{"id":3,"kind":1024,"name":"s3","url":"classes/uploader.html#s3","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":4,"kind":1024,"name":"options","url":"classes/uploader.html#options","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":5,"kind":1024,"name":"bar","url":"classes/uploader.html#bar","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":6,"kind":512,"name":"constructor","url":"classes/uploader.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Uploader"},{"id":7,"kind":2048,"name":"upload","url":"classes/uploader.html#upload","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Uploader"},{"id":8,"kind":2048,"name":"run","url":"classes/uploader.html#run","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":9,"kind":2048,"name":"getFiles","url":"classes/uploader.html#getfiles","classes":"tsd-kind-method tsd-parent-kind-class tsd-is-private","parent":"Uploader"},{"id":10,"kind":2048,"name":"uploadFile","url":"classes/uploader.html#uploadfile","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Uploader"},{"id":11,"kind":2048,"name":"getCacheControlValue","url":"classes/uploader.html#getcachecontrolvalue","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Uploader"},{"id":12,"kind":32,"name":"glob","url":"globals.html#glob","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":13,"kind":32,"name":"minimatch","url":"globals.html#minimatch","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":14,"kind":32,"name":"path","url":"globals.html#path","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":15,"kind":32,"name":"AWS","url":"globals.html#aws","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":16,"kind":32,"name":"ProgressBar","url":"globals.html#progressbar","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":17,"kind":32,"name":"ora","url":"globals.html#ora","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":18,"kind":32,"name":"chalk","url":"globals.html#chalk","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":19,"kind":32,"name":"fs","url":"globals.html#fs","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":20,"kind":32,"name":"mime","url":"globals.html#mime","classes":"tsd-kind-variable tsd-is-not-exported"},{"id":21,"kind":65536,"name":"__type","url":"globals.html#options.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias tsd-is-not-exported","parent":"Options"},{"id":22,"kind":32,"name":"bucket","url":"globals.html#options.__type.bucket","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":23,"kind":32,"name":"localPath","url":"globals.html#options.__type.localpath","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":24,"kind":32,"name":"remotePath","url":"globals.html#options.__type.remotepath","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":25,"kind":32,"name":"config","url":"globals.html#options.__type.config","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":26,"kind":32,"name":"glob","url":"globals.html#options.__type.glob","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":27,"kind":32,"name":"concurrency","url":"globals.html#options.__type.concurrency","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":28,"kind":32,"name":"dryRun","url":"globals.html#options.__type.dryrun","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":29,"kind":32,"name":"cacheControl","url":"globals.html#options.__type.cachecontrol","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":30,"kind":32,"name":"s3Client","url":"globals.html#options.__type.s3client","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":31,"kind":32,"name":"accessControlLevel","url":"globals.html#options.__type.accesscontrollevel","classes":"tsd-kind-variable tsd-parent-kind-type-literal tsd-is-not-exported","parent":"Options.__type"},{"id":32,"kind":2097152,"name":"defaultOptions","url":"globals.html#defaultoptions","classes":"tsd-kind-object-literal tsd-is-not-exported"},{"id":33,"kind":32,"name":"dryRun","url":"globals.html#defaultoptions.dryrun","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":34,"kind":32,"name":"concurrency","url":"globals.html#defaultoptions.concurrency","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":35,"kind":32,"name":"glob","url":"globals.html#defaultoptions.glob","classes":"tsd-kind-variable tsd-parent-kind-object-literal tsd-is-not-exported","parent":"defaultOptions"},{"id":36,"kind":32,"name":"yargs","url":"globals.html#yargs","classes":"tsd-kind-variable tsd-is-not-exported"}]}; \ No newline at end of file diff --git a/docs/classes/uploader.html b/docs/classes/uploader.html index 5614a1f..de6b66e 100644 --- a/docs/classes/uploader.html +++ b/docs/classes/uploader.html @@ -95,10 +95,11 @@
Path to a local file, either relative to cwd, or absolute
+The resolved CacheControl value based on the provided settings
+Based on the local path and the provided glob pattern, this util function will find all relevant + files, which will be used to upload in another step.
+A list of resolved files based on the glob pattern
Executes the upload operation based on the provided options in the Uploader constructor.
+A list of paths of the upload files relative to the bucket.
Uploads a single file to S3 from the local to the remote path with the available options, + and returns the uploaded location.
+Path to the local file, either relative to cwd, or absolute
+The path to upload the file to in the bucket
+The remote path upload location relative to the bucket
import Uploader from 's3-batch-upload';
+ Usage: cli.js <command> [options]
-await new Uploader({
- config: './config/configS3.json', // can also use environment variables
+Commands:
+ cli.js upload Upload files to s3 [default]
+
+Required:
+ -b, --bucket The bucket to upload to. [string] [required]
+ -p, --local-path The path to the folder to upload. [string] [required]
+ -r, --remote-path The remote path in the bucket to upload the files to. [string] [required]
+
+Options:
+ -d, --dry-run Do a dry run, don't do any upload. [boolean] [default: false]
+ -C, --concurrency The amount of simultaneous uploads, increase on faster internet connection.
+ [number] [default: 100]
+ -g, --glob A glob on filename level to filter the files to upload [string] [default: "*.*"]
+ -a, --cache-control Cache control for uploaded files, can be string for single value or list of glob settings
+ [string] [default: ""]
+ -acl, --access-control-level Sets the access control level for uploaded files
+ [string] [default: "undefined"]
+ -c, --config The AWS config json path to load S3 credentials with loadFromPath. [string]
+ -h, --help Show help [boolean]
+
+Examples:
+ cli.js -b bucket-name -p ./files -r /data Upload files from a local folder to a s3 bucket path
+ cli.js ... -a "max-age=300" Set cache-control for all files
+ cli.js ... -a '{ "**/*.json": "max-age=300", "**/*.*": Upload files from a local folder to a s3 bucket path
+ "max-age=3600" }'
+ cli.js -d ... Dry run upload
+
+for more information about AWS authentication, please visit
+https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html
+
API
+ import Uploader from 's3-batch-upload';
+
+const files = await new Uploader({
+ config: './config/configS3.json', // can also use environment variables
bucket: 'bucket-name',
- localPath: './files',
- remotePath: 'remote/path/in/bucket',
- glob: '*.jpg', // default is '*.*'
+ localPath: './files',
+ remotePath: 'remote/path/in/bucket',
+ glob: '*.jpg', // default is '*.*'
concurrency: '200', // default is 100
dryRun: true, // default is false
+ cacheControl: 'max-age=300', // can be a string, for all uploade resources
+ cacheControl: { // or an object with globs as keys to match the input path
+ '**/settings.json': 'max-age=60', // 1 mins for settings, specific matches should go first
+ '**/*.json': 'max-age=300', // 5 mins for other jsons
+ '**/*.*': 'max-age=3600', // 1 hour for everthing else
+ },
+ accessControlLevel: 'bucket-owner-full-control' // optional, not passed if undefined. - available options - "private"|"public-read"|"public-read-write"|"authenticated-read"|"aws-exec-read"|"bucket-owner-read"|"bucket-owner-full-control"
}).upload();
+
+// the files array contains a list of uploaded keys, which you can use to build up the S3 urls.
+// e.g. "remote/path/in/bucket/demo.jpg"
+
+ S3 Authentication
+ As seem above, you can either use environment variables, or a config file.
+ When using a config file, add it to the .gitignore
, because you don't want your credentials
+ in your repo. Use the following template for the config file as stated in the AWS Docs:
+ {
+ "accessKeyId": "<YOUR_ACCESS_KEY_ID>",
+ "secretAccessKey": "<YOUR_SECRET_ACCESS_KEY>",
+ "region": "us-east-1"
+}
+ When using environment variables, check the AWS docs.
Documentation
View the generated documentation.
Building
@@ -160,6 +212,9 @@ LICENSE
mime
+
+ minimatch
+
ora
@@ -169,7 +224,7 @@ LICENSE
yargs
-
+
streamBatch
diff --git a/src/lib/Uploader.ts b/src/lib/Uploader.ts
index 46f3d67..a8d592f 100644
--- a/src/lib/Uploader.ts
+++ b/src/lib/Uploader.ts
@@ -53,11 +53,15 @@ export default class Uploader {
this.s3 = this.options.s3Client || new AWS.S3();
}
- public upload(): Promise {
+ /**
+ * Executes the upload operation based on the provided options in the Uploader constructor.
+ * @returns A list of paths of the upload files relative to the bucket.
+ */
+ public upload(): Promise {
return this.run();
}
- private async run(): Promise {
+ private async run(): Promise {
const files = await this.getFiles();
const { concurrency, localPath, remotePath } = this.options;
@@ -70,10 +74,10 @@ export default class Uploader {
});
// do the work!
- await streamBatch({
+ const results = await streamBatch({
files,
concurrency,
- processItem: (file: string): Promise => {
+ processItem: (file: string): Promise => {
const key = path.join(remotePath, file);
return this.uploadFile(path.resolve(localPath, file), key);
},
@@ -82,8 +86,15 @@ export default class Uploader {
// tslint:disable-next-line no-console
console.log('Upload complete!');
+
+ return results;
}
+ /**
+ * Based on the local path and the provided glob pattern, this util function will find all relevant
+ * files, which will be used to upload in another step.
+ * @returns A list of resolved files based on the glob pattern
+ */
private getFiles(): Promise> {
const { localPath, glob: globPath } = this.options;
const gatheringSpinner = ora(`Gathering files from ${chalk.blue(localPath)} (please wait) ...`);
@@ -106,7 +117,15 @@ export default class Uploader {
});
}
- public uploadFile(localFilePath: string, remotePath: string): Promise {
+ /**
+ * Uploads a single file to S3 from the local to the remote path with the available options,
+ * and returns the uploaded location.
+ *
+ * @param localFilePath Path to the local file, either relative to cwd, or absolute
+ * @param remotePath The path to upload the file to in the bucket
+ * @returns The remote path upload location relative to the bucket
+ */
+ public uploadFile(localFilePath: string, remotePath: string): Promise {
const body = fs.createReadStream(localFilePath);
const { dryRun, bucket: Bucket, accessControlLevel: ACL } = this.options;
const params: S3.PutObjectRequest = {
@@ -125,14 +144,19 @@ export default class Uploader {
this.s3.upload(params, err => {
// tslint:disable-next-line no-console
if (err) console.error('err:', err);
- resolve();
+ resolve(params.Key);
});
} else {
- resolve();
+ resolve(params.Key);
}
});
}
+ /**
+ *
+ * @param file Path to a local file, either relative to cwd, or absolute
+ * @return The resolved CacheControl value based on the provided settings
+ */
public getCacheControlValue(file: string): string {
const { cacheControl } = this.options;
if (cacheControl) {
diff --git a/src/lib/batch.ts b/src/lib/batch.ts
index 37d58e1..ff190d3 100644
--- a/src/lib/batch.ts
+++ b/src/lib/batch.ts
@@ -1,22 +1,24 @@
-export type Options = {
+export type Options = {
concurrency: number;
files: Array;
- processItem: (file: string) => Promise;
+ processItem: (file: string) => Promise;
onProgress: () => void;
};
-export default function streamBatch({
+export default function streamBatch({
concurrency,
files,
processItem,
onProgress,
-}: Options): Promise {
+}: Options): Promise {
return new Promise(resolve => {
let count = 0;
const total = files.length;
+ const results: T[] = [];
// when upload for one item is done, complete or process the next
- const onItemDone = () => {
+ const onItemDone = (result: T) => {
+ results.push(result);
count += 1;
// if completed
@@ -24,7 +26,7 @@ export default function streamBatch({
// temp fix for https://github.com/visionmedia/node-progress/pull/183
setTimeout(() => {
onProgress();
- resolve();
+ resolve(results);
}, 50);
} else {
onProgress();
diff --git a/test/Uploader.spec.ts b/test/Uploader.spec.ts
index 3b865b7..b0d817a 100644
--- a/test/Uploader.spec.ts
+++ b/test/Uploader.spec.ts
@@ -163,5 +163,62 @@ describe('Uploader', () => {
});
});
+ describe('with uploadFile', () => {
+ it('should return the uploaded path', async function() {
+ this.timeout(5000);
+
+ const s3 = {
+ upload(_, cb) {
+ cb(null);
+ }
+ };
+ spy(s3, "upload");
+
+ uploader = new Uploader({
+ localPath: 'test/files',
+ remotePath: 'fake',
+ bucket: 'fake',
+ glob: '**/demo.png',
+ s3Client: s3,
+ });
+
+ const result = await uploader.uploadFile('files/demo.png', 'foo\\bar.png');
+
+ expect(result).to.equal('foo/bar.png');
+
+ (s3.upload).restore();
+ });
+ });
+
+ describe('with uploadFile', () => {
+ it('should return the uploaded paths', async function() {
+ this.timeout(10000);
+
+ const s3 = {
+ upload(_, cb) {
+ cb(null);
+ }
+ };
+ spy(s3, "upload");
+
+ uploader = new Uploader({
+ localPath: 'test/files',
+ remotePath: 'fake',
+ bucket: 'fake',
+ glob: '**/demo.png',
+ s3Client: s3,
+ accessControlLevel: 'bucket-owner-full-control'
+ });
+
+ const results = await uploader.upload();
+
+ expect(results).to.deep.equal([
+ 'fake/demo.png'
+ ]);
+
+ (s3.upload).restore();
+ });
+ });
+
});
});
diff --git a/wallaby.js b/wallaby.js
new file mode 100644
index 0000000..eb1a3f7
--- /dev/null
+++ b/wallaby.js
@@ -0,0 +1,24 @@
+module.exports = function(wallaby) {
+
+ return {
+ files: [
+ 'tsconfig.json',
+ 'src/**/*.*',
+ 'test/**/*.*',
+ '!test/**/*.spec.ts',
+ ],
+
+ tests: ['test/**/*.spec.ts'],
+
+ env: {
+ type: 'node',
+ runner: 'node',
+ },
+
+ compilers: {
+ '**/*.ts': wallaby.compilers.babel()
+ },
+
+ testFramework: 'mocha',
+ };
+};