UNPKG

serverless-finch

Version:

Deploy your serverless static website to AWS S3.

176 lines (150 loc) 5.03 kB
'use strict'; const fs = require('fs'); const mime = require('mime'); const minimatch = require('minimatch'); const path = require('path'); const getFileList = require('./get-file-list'); /** * Uploads client files to an S3 bucket * @param {Object} aws - AWS class * @param {string} bucketName - Name of bucket to be configured * @param {string} clientRoot - Full path to the root directory of client files * @param {Object[]} headerSpec - Array of header values to add to files * @param {string[]} orderSpec - Array of regex's to order upload by * @param {string} keyPrefix - A prefix for all files uploaded * @param {string} sse - ServerSideEncryption, AES256 | aws:kms | null */ function uploadDirectory(aws, bucketName, clientRoot, headerSpec, orderSpec, keyPrefix, sse) { const allFiles = getFileList(clientRoot); const filesGroupedByOrder = groupFilesByOrder(allFiles, orderSpec); return filesGroupedByOrder.reduce((existingUploads, files) => { return existingUploads.then(existingResults => { const uploadList = buildUploadList(files, clientRoot, headerSpec, keyPrefix); return Promise.all( uploadList.map(u => uploadFile(aws, bucketName, u.filePath, u.fileKey, u.headers, sse)) ).then(currentResults => existingResults.concat(currentResults)); }); }, Promise.resolve([])); } /** * Uploads a file to an S3 bucket * @param {Object} aws - AWS class * @param {string} bucketName - Name of bucket to be configured * @param {string} filePath - Full path to file to be uploaded * @param {string} fileKey - * @param {string} headers - * @param {string} sse - ServerSideEncryption, AES256 | aws:kms | null */ function uploadFile(aws, bucketName, filePath, fileKey, headers, sse) { const baseHeaderKeys = [ 'Cache-Control', 'Content-Disposition', 'Content-Encoding', 'Content-Language', 'Content-Type', 'Expires', 'Website-Redirect-Location' ]; const fileBuffer = fs.readFileSync(filePath); const params = { Bucket: bucketName, Key: fileKey, Body: fileBuffer, ContentType: mime.getType(filePath) }; if (sse) { params.ServerSideEncryption = sse; } Object.keys(headers).forEach(h => { if (baseHeaderKeys.includes(h)) { params[h.replace('-', '')] = headers[h]; } else { if (!params.Metadata) { params.Metadata = {}; } params.Metadata[h] = headers[h]; } }); return aws.request('S3', 'putObject', params); } function buildUploadList(files, clientRoot, headerSpec, keyPrefix) { clientRoot = path.normalize(clientRoot); if (!clientRoot.endsWith(path.sep)) { clientRoot += path.sep; } const prefix = keyPrefix ? keyPrefix.split(path.sep) : []; return files.map(f => { const filePath = path.normalize(f); const fileRelPath = filePath.replace(clientRoot, ''); const fileKey = path.normalize(fileRelPath).split(path.sep); if (prefix.length) { fileKey.unshift(...prefix); } const upload = { filePath: filePath, fileKey: fileKey.join('/'), headers: {} }; if (!headerSpec) { return upload; } // add bucket-wide headers if (headerSpec.ALL_OBJECTS) { headerSpec.ALL_OBJECTS.forEach(h => { upload.headers[h.name] = h.value; }); } // add headers by glob pattern Object.keys(headerSpec) .filter(s => !!s.match(/[*?[\]]/)) // Potential glob pattern .filter(s => minimatch(fileRelPath, s, { matchBase: true })) .sort((a, b) => a.length - b.length) // sort by length ascending .forEach(s => { headerSpec[s].forEach(h => { upload.headers[h.name] = h.value; }); }); // add folder-level headers Object.keys(headerSpec) .filter(s => s.endsWith(path.sep)) // folders .sort((a, b) => a.length > b.length) // sort by length ascending .forEach(s => { if (fileRelPath.startsWith(path.normalize(s))) { headerSpec[s].forEach(h => { upload.headers[h.name] = h.value; }); } }); // add file-specific headers Object.keys(headerSpec) .filter(s => { return path.normalize(s) === fileRelPath; }) .forEach(s => { headerSpec[s].forEach(h => { upload.headers[h.name] = h.value; }); }); return upload; }); } function groupFilesByOrder(files, orderSpec) { if (!orderSpec) { return [files]; } const orderedFiles = files.map(f => ({ file: f, order: 0 })); orderSpec.forEach((orderRegex, i) => { const re = new RegExp(orderRegex, 'i'); orderedFiles.forEach(f => { if (re.test(f.file)) { f.order = i + 1; } }); }); const unmatchedFiles = orderedFiles.filter(f => f.order === 0).map(f => f.file); const matchedFiles = orderSpec.map((orderRegex, i) => orderedFiles.filter(f => f.order === i + 1).map(f => f.file) ); return [unmatchedFiles].concat(matchedFiles); } module.exports = uploadDirectory;