-
Notifications
You must be signed in to change notification settings - Fork 70
/
upload-to-s3.js
93 lines (79 loc) · 2.78 KB
/
upload-to-s3.js
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
import setupDebug from 'debug';
import { readdirSync, statSync, createReadStream } from 'fs';
import { join } from 'path';
import { URL } from 'url';
import progress from 'progress-stream';
import ProgressBar from 'progress';
import HTTPClient from './HTTPClient';
import { CHROMATIC_RETRIES } from '../constants';
const debug = setupDebug('chromatic-cli:upload');
const TesterGetUploadUrlsMutation = `
mutation TesterGetUploadUrlsMutation($paths: [String!]!) {
getUploadUrls(paths: $paths) {
domain
urls {
path
url
contentType
}
}
}
`;
// Get all paths in rootDir, starting at dirname.
// We don't want the paths to include rootDir -- so if rootDir = storybook-static,
// paths will be like iframe.html rather than storybook-static/iframe.html
function getPathsInDir(rootDir, dirname = '.') {
return readdirSync(join(rootDir, dirname))
.map(p => join(dirname, p))
.map(pathname => {
const stats = statSync(join(rootDir, pathname));
if (stats.isDirectory()) {
return getPathsInDir(rootDir, pathname);
}
return [{ pathname, contentLength: stats.size }];
})
.reduce((a, b) => [...a, ...b], []); // flatten
}
export async function uploadToS3(source, client) {
debug(`uploading '${source}' to s3`);
const pathAndLengths = getPathsInDir(source);
const {
getUploadUrls: { domain, urls },
} = await client.runQuery(TesterGetUploadUrlsMutation, {
paths: pathAndLengths.map(({ pathname }) => pathname),
});
const total =
pathAndLengths.map(({ contentLength }) => contentLength).reduce((a, b) => a + b, 0) / 1000;
const bar = new ProgressBar('uploading [:bar] :ratekb/s :percent :etas', { width: 20, total });
const uploads = [];
urls.forEach(({ path, url, contentType }) => {
const pathWithDirname = join(source, path);
debug(`uploading '${pathWithDirname}' to '${url}' with content type '${contentType}'`);
const progressStream = progress();
progressStream.on('progress', ({ delta }) => bar.tick(delta / 1000));
const { contentLength } = pathAndLengths.find(({ pathname }) => pathname === path);
uploads.push(
(async () => {
const res = await HTTPClient.fetch(
url,
{
method: 'PUT',
body: createReadStream(pathWithDirname).pipe(progressStream),
headers: {
'content-type': contentType,
'content-length': contentLength,
},
},
{ retries: CHROMATIC_RETRIES }
);
if (!res.ok) {
debug(`Uploading '${path}' failed: %O`, res);
throw new Error(`Failed to upload ${path}`);
}
})()
);
});
await Promise.all(uploads);
// NOTE: Storybook-specific
return new URL('/iframe.html', domain).toString();
}