generate file list after publish
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<!doctype html>
|
||||
<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>
|
||||
@@ -13,12 +13,18 @@ const s3Client = createS3Client();
|
||||
|
||||
runTest(async ({ log }) => {
|
||||
log('Generate file list');
|
||||
const filename = `test-file-list${crypto.randomUUID()}`;
|
||||
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.includes('handlebars-v4.7.7.js'));
|
||||
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`);
|
||||
|
||||
@@ -1,13 +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 };
|
||||
|
||||
@@ -14,11 +14,19 @@ async function listFiles(s3Client, bucket) {
|
||||
IsTruncated,
|
||||
NextContinuationToken
|
||||
} = await s3Client.send(command);
|
||||
files.push(...Contents.map(s3obj => s3obj.Key));
|
||||
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 };
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const { createS3Client } = require('./index');
|
||||
const crypto = require('crypto');
|
||||
const { runTest } = require('../test-utils/runTest');
|
||||
const assert = require('node:assert');
|
||||
|
||||
// This is a test file. It is intended to be run manually
|
||||
// with the proper environment variables set
|
||||
@@ -12,6 +13,8 @@ const { runTest } = require('../test-utils/runTest');
|
||||
|
||||
const client = createS3Client();
|
||||
|
||||
const ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
|
||||
|
||||
runTest(async ({ log }) => {
|
||||
const uuid = crypto.randomUUID();
|
||||
const filename = `test-file-${uuid}`;
|
||||
@@ -22,7 +25,7 @@ runTest(async ({ log }) => {
|
||||
|
||||
log(`Check if uploaded "${filename}"`);
|
||||
const listing = await client.listFiles();
|
||||
if (!listing.includes(filename)) {
|
||||
if (!listing.find(s3obj => s3obj.key === filename)) {
|
||||
throw new Error(`File "${filename}" has not been uploaded`);
|
||||
}
|
||||
|
||||
@@ -46,13 +49,24 @@ runTest(async ({ log }) => {
|
||||
await expectContentType(filename, 'text/plain');
|
||||
log(`Check contents of "${filename}"`);
|
||||
expectStringContains('Hello world', await client.fetchFile(filename));
|
||||
const helloWorldObj = (await client.listFiles()).find(
|
||||
s3obj => s3obj.key === filename
|
||||
);
|
||||
assert.equal(helloWorldObj.size, 11, 'Checking file size of hello world');
|
||||
assert.match(
|
||||
helloWorldObj.lastModified,
|
||||
ISO_DATE,
|
||||
'Last modified must be an iso-date'
|
||||
);
|
||||
|
||||
log(`Delete "${filename}"`);
|
||||
await client.deleteFile(filename);
|
||||
|
||||
log(`Check if deleted "${filename}"`);
|
||||
const listingAfterDelete = await client.listFiles();
|
||||
if (listingAfterDelete.includes(filename)) {
|
||||
const foundFile = (await client.listFiles()).find(
|
||||
s3obj => s3obj.key === filename
|
||||
);
|
||||
if (foundFile != null) {
|
||||
throw new Error(`File "${filename}" has not been deleted`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,8 +18,10 @@ function runTest(asyncFn) {
|
||||
async function detectSurplusFiles() {
|
||||
const listing = await s3Client.listFiles();
|
||||
let surplusFileDetected = false;
|
||||
const testFilesInBucket = listing.filter(name => name.includes('test-file'));
|
||||
for (const filename of testFilesInBucket) {
|
||||
const testFilesInBucket = listing.filter(name =>
|
||||
name.key.includes('test-file')
|
||||
);
|
||||
for (const { key: filename } of testFilesInBucket) {
|
||||
if (process.argv[2] === '--delete-surplus') {
|
||||
await s3Client.deleteFile(filename);
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@ const git = require('./util/git');
|
||||
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
|
||||
const semver = require('semver');
|
||||
const { publishWithSuffixes } = require('./aws-s3-builds-page/publish');
|
||||
const { generateFileList } = require('./aws-s3-builds-page/generateFileList');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
|
||||
@@ -28,6 +29,7 @@ module.exports = function(grunt) {
|
||||
|
||||
if (suffixes.length > 0) {
|
||||
await publishWithSuffixes(suffixes);
|
||||
await generateFileList('index');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user