Skip to content

Commit 7a50a25

Browse files
committed
Return the list of uploaded files from upload and uploadFile
1 parent 12a6fe2 commit 7a50a25

File tree

4 files changed

+100
-14
lines changed

4 files changed

+100
-14
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-creden
6464
```js
6565
import Uploader from 's3-batch-upload';
6666

67-
await new Uploader({
67+
const files = await new Uploader({
6868
config: './config/configS3.json', // can also use environment variables
6969
bucket: 'bucket-name',
7070
localPath: './files',
@@ -80,6 +80,9 @@ await new Uploader({
8080
},
8181
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"
8282
}).upload();
83+
84+
// the files array contains a list of uploaded keys, which you can use to build up the S3 urls.
85+
// e.g. "remote/path/in/bucket/demo.jpg"
8386
```
8487

8588
### S3 Authentication

src/lib/Uploader.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,15 @@ export default class Uploader {
5353
this.s3 = this.options.s3Client || new AWS.S3();
5454
}
5555

56-
public upload(): Promise<void> {
56+
/**
57+
* Executes the upload operation based on the provided options in the Uploader constructor.
58+
* @returns A list of paths of the upload files relative to the bucket.
59+
*/
60+
public upload(): Promise<string[]> {
5761
return this.run();
5862
}
5963

60-
private async run(): Promise<void> {
64+
private async run(): Promise<string[]> {
6165
const files = await this.getFiles();
6266
const { concurrency, localPath, remotePath } = this.options;
6367

@@ -70,10 +74,10 @@ export default class Uploader {
7074
});
7175

7276
// do the work!
73-
await streamBatch({
77+
const results = await streamBatch({
7478
files,
7579
concurrency,
76-
processItem: (file: string): Promise<void> => {
80+
processItem: (file: string): Promise<string> => {
7781
const key = path.join(remotePath, file);
7882
return this.uploadFile(path.resolve(localPath, file), key);
7983
},
@@ -82,8 +86,15 @@ export default class Uploader {
8286

8387
// tslint:disable-next-line no-console
8488
console.log('Upload complete!');
89+
90+
return results;
8591
}
8692

93+
/**
94+
* Based on the local path and the provided glob pattern, this util function will find all relevant
95+
* files, which will be used to upload in another step.
96+
* @returns A list of resolved files based on the glob pattern
97+
*/
8798
private getFiles(): Promise<Array<string>> {
8899
const { localPath, glob: globPath } = this.options;
89100
const gatheringSpinner = ora(`Gathering files from ${chalk.blue(localPath)} (please wait) ...`);
@@ -106,7 +117,15 @@ export default class Uploader {
106117
});
107118
}
108119

109-
public uploadFile(localFilePath: string, remotePath: string): Promise<void> {
120+
/**
121+
* Uploads a single file to S3 from the local to the remote path with the available options,
122+
* and returns the uploaded location.
123+
*
124+
* @param localFilePath Path to the local file, either relative to cwd, or absolute
125+
* @param remotePath The path to upload the file to in the bucket
126+
* @returns The remote path upload location relative to the bucket
127+
*/
128+
public uploadFile(localFilePath: string, remotePath: string): Promise<string> {
110129
const body = fs.createReadStream(localFilePath);
111130
const { dryRun, bucket: Bucket, accessControlLevel: ACL } = this.options;
112131
const params: S3.PutObjectRequest = {
@@ -125,14 +144,19 @@ export default class Uploader {
125144
this.s3.upload(params, err => {
126145
// tslint:disable-next-line no-console
127146
if (err) console.error('err:', err);
128-
resolve();
147+
resolve(params.Key);
129148
});
130149
} else {
131-
resolve();
150+
resolve(params.Key);
132151
}
133152
});
134153
}
135154

155+
/**
156+
*
157+
* @param file Path to a local file, either relative to cwd, or absolute
158+
* @return The resolved CacheControl value based on the provided settings
159+
*/
136160
public getCacheControlValue(file: string): string {
137161
const { cacheControl } = this.options;
138162
if (cacheControl) {

src/lib/batch.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
1-
export type Options = {
1+
export type Options<T> = {
22
concurrency: number;
33
files: Array<string>;
4-
processItem: (file: string) => Promise<void>;
4+
processItem: (file: string) => Promise<T>;
55
onProgress: () => void;
66
};
77

8-
export default function streamBatch({
8+
export default function streamBatch<T>({
99
concurrency,
1010
files,
1111
processItem,
1212
onProgress,
13-
}: Options): Promise<void> {
13+
}: Options<T>): Promise<T[]> {
1414
return new Promise(resolve => {
1515
let count = 0;
1616
const total = files.length;
17+
const results: T[] = [];
1718

1819
// when upload for one item is done, complete or process the next
19-
const onItemDone = () => {
20+
const onItemDone = (result: T) => {
21+
results.push(result);
2022
count += 1;
2123

2224
// if completed
2325
if (!files.length && count === total) {
2426
// temp fix for https://github.com/visionmedia/node-progress/pull/183
2527
setTimeout(() => {
2628
onProgress();
27-
resolve();
29+
resolve(results);
2830
}, 50);
2931
} else {
3032
onProgress();

test/Uploader.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,62 @@ describe('Uploader', () => {
163163
});
164164
});
165165

166+
describe('with uploadFile', () => {
167+
it('should return the uploaded path', async function() {
168+
this.timeout(5000);
169+
170+
const s3 = {
171+
upload(_, cb) {
172+
cb(null);
173+
}
174+
};
175+
spy(s3, "upload");
176+
177+
uploader = new Uploader({
178+
localPath: 'test/files',
179+
remotePath: 'fake',
180+
bucket: 'fake',
181+
glob: '**/demo.png',
182+
s3Client: <any>s3,
183+
});
184+
185+
const result = await uploader.uploadFile('files/demo.png', 'foo\\bar.png');
186+
187+
expect(result).to.equal('foo/bar.png');
188+
189+
(<any>s3.upload).restore();
190+
});
191+
});
192+
193+
describe('with uploadFile', () => {
194+
it('should return the uploaded paths', async function() {
195+
this.timeout(10000);
196+
197+
const s3 = {
198+
upload(_, cb) {
199+
cb(null);
200+
}
201+
};
202+
spy(s3, "upload");
203+
204+
uploader = new Uploader({
205+
localPath: 'test/files',
206+
remotePath: 'fake',
207+
bucket: 'fake',
208+
glob: '**/demo.png',
209+
s3Client: <any>s3,
210+
accessControlLevel: 'bucket-owner-full-control'
211+
});
212+
213+
const results = await uploader.upload();
214+
215+
expect(results).to.deep.equal([
216+
'fake/demo.png'
217+
]);
218+
219+
(<any>s3.upload).restore();
220+
});
221+
});
222+
166223
});
167224
});

0 commit comments

Comments
 (0)