-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft: Generate s3 file listing after each upload #1980
Open
nknapp
wants to merge
8
commits into
4.x
Choose a base branch
from
generate-s3-file-listing
base: 4.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
ab920cc
wip: refactor s3 access and generate file listing on build
nknapp 1c75903
add delete and upload functions
nknapp c5e5cad
implement publish workflow
nknapp 6118a3c
fixup: revert accidental webpack-config changes
nknapp bde5506
more functions that help uploading a file listing.
nknapp 7989a5e
function to upload a file list
nknapp e8299e0
generate file list after publish
nknapp f637dc7
remove old aws-sdk
nknapp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<!doctype html> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe activate prettier formatting for this file? |
||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" | ||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> | ||
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||
<style> | ||
table { | ||
column-gap: 1rem; | ||
} | ||
thead { | ||
position: sticky; | ||
top: 0; | ||
background: #efefef; | ||
} | ||
|
||
th:not(:last-child), td:not(:last-child) { | ||
margin-right: 1rem; | ||
} | ||
|
||
th { | ||
text-align: left; | ||
} | ||
</style> | ||
<title>Handlebars.js Builds</title> | ||
</head> | ||
<body> | ||
<h1>Handlebars.js builds</h1> | ||
<p>See <a href="https://handlebarsjs.com">https://handlebarsjs.com</a> for documentation.</p> | ||
<p>Machine-readable version: <a href="{{jsonListUrl}}">{{jsonListUrl}}</a></p> | ||
<table> | ||
<thead> | ||
<tr> | ||
<th data-col="key"><a href="#" onclick="return toggleSort('key')">Name</a></th> | ||
<th data-col="size"><a href="#" onclick="return toggleSort('size')">Size</a></th> | ||
<th data-col="lastModified"><a href="#" onclick="return toggleSort('lastModified')">Last-Modified</a></th> | ||
</tr> | ||
</thead> | ||
<tbody id="files"> | ||
{{#each fileList as | file |}} | ||
<tr> | ||
<td data-col="key"><a href="{{file.key}}">{{key}}</a></td> | ||
<td data-col="size">{{file.size}}</td> | ||
<td data-col="lastModified">{{file.lastModified}}</td> | ||
</tr> | ||
{{/each}} | ||
</tbody> | ||
|
||
</table> | ||
<script type="application/javascript"> | ||
const files = {{{json fileList}}}; | ||
const fileElements = Array.from(document.querySelectorAll("#files > tr")); | ||
|
||
|
||
applyNewOrder() | ||
|
||
function getSearchParams() { | ||
return new URLSearchParams(window.location.hash.slice(1)); | ||
} | ||
|
||
function toggleSort(newSortProperty) { | ||
const params = getSearchParams() | ||
|
||
const oldSortProperty = params.get("sort"); | ||
if (oldSortProperty === newSortProperty) { | ||
const newDir = params.get("dir") === "asc" ? "desc" : "asc" | ||
window.location.hash = "sort=" + newSortProperty + "&dir=" + newDir | ||
} else { | ||
window.location.hash = "sort=" + newSortProperty | ||
|
||
} | ||
setTimeout(() => applyNewOrder()) | ||
|
||
|
||
return false | ||
} | ||
|
||
function applyNewOrder() { | ||
const params = getSearchParams() | ||
const sortProperty = params.get("sort") ?? "lastModified" | ||
const ascending = params.get("dir") === "asc" | ||
sortFilesArray(sortProperty, ascending); | ||
updateRows(); | ||
} | ||
|
||
function sortFilesArray(propertyName, ascending) { | ||
|
||
files.sort(compareByProp(propertyName)) | ||
if (!ascending) { | ||
files.reverse() | ||
} | ||
} | ||
|
||
function compareByProp(propertyName) { | ||
return (file1, file2) => { | ||
if (file1[propertyName] === file2[propertyName]) { | ||
return 0 | ||
} | ||
if (file1[propertyName] > file2[propertyName]) { | ||
return 1 | ||
} | ||
return -1 | ||
} | ||
} | ||
|
||
function updateRows() { | ||
let index = 0; | ||
for (const rowElement of fileElements) { | ||
update(rowElement, files[index++]) | ||
} | ||
} | ||
|
||
function update(rowElement, file) { | ||
const link = rowElement.querySelector('[data-col="key"] a') | ||
link.setAttribute("href", file.key) | ||
link.innerText = file.key | ||
|
||
const size = rowElement.querySelector('[data-col="size"]') | ||
size.innerText = file.size | ||
|
||
const lastModified = rowElement.querySelector('[data-col="lastModified"]') | ||
lastModified.innerText = file.lastModified | ||
} | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
const crypto = require('crypto'); | ||
|
||
const { runTest } = require('./test-utils/runTest'); | ||
const { createS3Client } = require('./s3client'); | ||
const { generateFileList } = require('./generateFileList'); | ||
const assert = require('node:assert'); | ||
|
||
// This is a test file. It is intended to be run manually with the proper environment variables set | ||
// | ||
// Run it from the project root using "node tasks/aws-s3-builds-page/generateFileList-test.js" | ||
|
||
const s3Client = createS3Client(); | ||
|
||
runTest(async ({ log }) => { | ||
log('Generate file list'); | ||
const filename = `test-file-list-${crypto.randomUUID()}`; | ||
await generateFileList(filename); | ||
|
||
log(`Checking JSON at ${s3Client.fileUrl(`${filename}.json`)}`); | ||
const jsonList = JSON.parse(await s3Client.fetchFile(`${filename}.json`)); | ||
assert(jsonList.find(s3obj => s3obj.key === 'handlebars-v4.7.7.js')); | ||
|
||
log(`Checking HTML at ${s3Client.fileUrl(`${filename}.html`)}`); | ||
const htmlList = await s3Client.fetchFile(`${filename}.html`); | ||
assert(htmlList.includes('handlebars-v4.7.7.js')); | ||
assert(htmlList.includes('handlebarsjs.com')); | ||
assert(!htmlList.includes('index.html')); | ||
|
||
log(`Deleting file ${filename}.json`); | ||
await s3Client.deleteFile(`${filename}.json`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* eslint-disable no-console */ | ||
const { createS3Client } = require('./s3client'); | ||
const Handlebars = require('../..'); | ||
const fs = require('node:fs/promises'); | ||
const path = require('path'); | ||
|
||
async function generateFileList(nameWithoutExtension) { | ||
const s3Client = createS3Client(); | ||
const fileList = await s3Client.listFiles(); | ||
const relevantFiles = fileList.filter(s3obj => s3obj.key.endsWith('.js')); | ||
|
||
await uploadJson(s3Client, relevantFiles, nameWithoutExtension); | ||
await uploadHtml(s3Client, relevantFiles, nameWithoutExtension); | ||
} | ||
|
||
async function uploadJson(s3Client, fileList, nameWithoutExtension) { | ||
const fileListJson = JSON.stringify(fileList, null, 2); | ||
await s3Client.uploadData(fileListJson, nameWithoutExtension + '.json', { | ||
contentType: 'application/json' | ||
}); | ||
} | ||
|
||
async function uploadHtml(s3Client, fileList, nameWithoutExtension) { | ||
const templateStr = await fs.readFile( | ||
path.join(__dirname, 'fileList.hbs'), | ||
'utf-8' | ||
); | ||
const template = Handlebars.compile(templateStr); | ||
Handlebars.registerHelper('json', obj => JSON.stringify(obj)); | ||
const fileListHtml = template({ | ||
fileList, | ||
jsonListUrl: nameWithoutExtension + '.json' | ||
}); | ||
await s3Client.uploadData(fileListHtml, nameWithoutExtension + '.html', { | ||
contentType: 'text/html' | ||
}); | ||
} | ||
|
||
module.exports = { generateFileList }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
const crypto = require('crypto'); | ||
const { publishWithSuffixes } = require('./publish'); | ||
const { runTest } = require('./test-utils/runTest'); | ||
const { createS3Client } = require('./s3client'); | ||
const fs = require('node:fs/promises'); | ||
|
||
// This is a test file. It is intended to be run manually with the proper environment variables set | ||
// | ||
// Run it from the project root using "node tasks/aws-s3-builds-page/publish-test.js" | ||
|
||
const s3Client = createS3Client(); | ||
|
||
runTest(async ({ log }) => { | ||
const suffix1 = `-test-file-` + crypto.randomUUID(); | ||
const suffix2 = `-test-file-` + crypto.randomUUID(); | ||
log(`Publish ${suffix1} and ${suffix2}`); | ||
await publishWithSuffixes([suffix1, suffix2]); | ||
await compareAndDeleteFiles(suffix1, log); | ||
await compareAndDeleteFiles(suffix2, log); | ||
}); | ||
|
||
async function compareAndDeleteFiles(suffix, log) { | ||
const pairs = [ | ||
['dist/handlebars.js', `handlebars${suffix}.js`], | ||
['dist/handlebars.min.js', `handlebars.min${suffix}.js`], | ||
['dist/handlebars.runtime.js', `handlebars.runtime${suffix}.js`], | ||
['dist/handlebars.runtime.min.js', `handlebars.runtime.min${suffix}.js`] | ||
]; | ||
for (const [localFile, remoteFile] of pairs) { | ||
await expectSameContents(localFile, remoteFile, log); | ||
log(`Deleting "${remoteFile}"`); | ||
await s3Client.deleteFile(remoteFile); | ||
} | ||
} | ||
|
||
async function expectSameContents(localFile, remoteFile, log) { | ||
log( | ||
`Checking file contents "${localFile}" vs "${s3Client.fileUrl(remoteFile)}"` | ||
); | ||
const remoteContents = await s3Client.fetchFile(remoteFile); | ||
const localContents = await fs.readFile(localFile, 'utf-8'); | ||
if (remoteContents !== localContents) { | ||
throw new Error( | ||
`Files do not match: ${localFile}" vs "${s3Client.fileUrl(remoteFile)}"` | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable no-console */ | ||
const { createS3Client } = require('./s3client'); | ||
|
||
const filenames = [ | ||
'handlebars.js', | ||
'handlebars.min.js', | ||
'handlebars.runtime.js', | ||
'handlebars.runtime.min.js' | ||
]; | ||
|
||
async function publishWithSuffixes(suffixes) { | ||
const s3Client = createS3Client(); | ||
const publishPromises = suffixes.map(suffix => | ||
publishSuffix(s3Client, suffix) | ||
); | ||
return Promise.all(publishPromises); | ||
} | ||
|
||
async function publishSuffix(s3client, suffix) { | ||
const publishPromises = filenames.map(async filename => { | ||
const nameInBucket = getNameInBucket(filename, suffix); | ||
const localFile = getLocalFile(filename); | ||
await s3client.uploadFile(localFile, nameInBucket); | ||
console.log(`Published ${localFile} to build server (${nameInBucket})`); | ||
}); | ||
return Promise.all(publishPromises); | ||
} | ||
|
||
function getNameInBucket(filename, suffix) { | ||
return filename.replace(/\.js$/, suffix + '.js'); | ||
} | ||
|
||
function getLocalFile(filename) { | ||
return 'dist/' + filename; | ||
} | ||
|
||
module.exports = { publishWithSuffixes }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); | ||
|
||
async function deleteFile(s3Client, bucket, remoteName) { | ||
const command = new DeleteObjectCommand({ | ||
Bucket: bucket, | ||
Key: remoteName | ||
}); | ||
await s3Client.send(command); | ||
} | ||
|
||
module.exports = { deleteFile }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
async function fetchFile(bucket, remoteName) { | ||
return (await fetch(fileUrl(bucket, remoteName))).text(); | ||
} | ||
|
||
function fileUrl(bucket, remoteName) { | ||
const bucketUrl = `https://s3.amazonaws.com/${bucket}`; | ||
return `${bucketUrl}/${remoteName}`; | ||
} | ||
|
||
module.exports = { fetchFile, fileUrl }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
const { listFiles } = require('./listFiles'); | ||
const { uploadFile, uploadData } = require('./uploadFile'); | ||
const { deleteFile } = require('./deleteFile'); | ||
const { S3Client } = require('@aws-sdk/client-s3'); | ||
const { requireEnvVar } = require('./requireEnvVar'); | ||
const { fetchFile, fileUrl } = require('./fetchFile'); | ||
|
||
module.exports = { createS3Client }; | ||
|
||
function createS3Client() { | ||
// https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html | ||
requireEnvVar('AWS_ACCESS_KEY_ID'); | ||
requireEnvVar('AWS_SECRET_ACCESS_KEY'); | ||
|
||
const bucket = requireEnvVar('S3_BUCKET_NAME'); | ||
const s3Client = new S3Client({ | ||
region: 'us-east-1' | ||
}); | ||
|
||
return { | ||
async listFiles() { | ||
return listFiles(s3Client, bucket); | ||
}, | ||
async uploadFile(localName, remoteName, { contentType } = {}) { | ||
await uploadFile(s3Client, bucket, localName, remoteName, { | ||
contentType | ||
}); | ||
}, | ||
async uploadData(data, remoteName, { contentType } = {}) { | ||
await uploadData(s3Client, bucket, data, remoteName, { contentType }); | ||
}, | ||
async deleteFile(remoteName) { | ||
await deleteFile(s3Client, bucket, remoteName); | ||
}, | ||
async fetchFile(remoteName) { | ||
return fetchFile(bucket, remoteName); | ||
}, | ||
fileUrl(remoteName) { | ||
return fileUrl(bucket, remoteName); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
const { ListObjectsV2Command } = require('@aws-sdk/client-s3'); | ||
|
||
async function listFiles(s3Client, bucket) { | ||
const command = new ListObjectsV2Command({ | ||
Bucket: bucket | ||
}); | ||
|
||
let isTruncated = true; | ||
const files = []; | ||
|
||
while (isTruncated) { | ||
const { | ||
Contents, | ||
IsTruncated, | ||
NextContinuationToken | ||
} = await s3Client.send(command); | ||
files.push(...Contents.map(dataFromS3Object)); | ||
isTruncated = IsTruncated; | ||
command.input.ContinuationToken = NextContinuationToken; | ||
} | ||
return files; | ||
} | ||
|
||
function dataFromS3Object(s3obj) { | ||
return { | ||
key: s3obj.Key, | ||
size: s3obj.Size, | ||
lastModified: s3obj.LastModified.toISOString() | ||
}; | ||
} | ||
|
||
module.exports = { listFiles }; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you update all deps? Seems to have lots of changes...