Skip to content

Commit

Permalink
implement publish workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
nknapp committed Aug 5, 2023
1 parent 1c75903 commit c5e5cad
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 175 deletions.
23 changes: 0 additions & 23 deletions tasks/aws-s3-builds-page/createS3Client.js

This file was deleted.

5 changes: 0 additions & 5 deletions tasks/aws-s3-builds-page/index.js

This file was deleted.

47 changes: 47 additions & 0 deletions tasks/aws-s3-builds-page/publish-test.js
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)}"`
);
}
}
37 changes: 37 additions & 0 deletions tasks/aws-s3-builds-page/publish.js
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 };
65 changes: 0 additions & 65 deletions tasks/aws-s3-builds-page/s3-test.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
const { createS3Client } = require('./createS3Client');
const { DeleteObjectCommand } = require('@aws-sdk/client-s3');

async function deleteFile(name) {
const { s3Client, bucket } = createS3Client();
async function deleteFile(s3Client, bucket, remoteName) {
const command = new DeleteObjectCommand({
Bucket: bucket,
Key: name
Key: remoteName
});
await s3Client.send(command);
}
Expand Down
10 changes: 10 additions & 0 deletions tasks/aws-s3-builds-page/s3client/fetchFile.js
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 };
37 changes: 37 additions & 0 deletions tasks/aws-s3-builds-page/s3client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const { listFiles } = require('./listFiles');
const { uploadFile } = 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) {
await uploadFile(s3Client, bucket, localName, remoteName);
},
async deleteFile(remoteName) {
await deleteFile(s3Client, bucket, remoteName);
},
async fetchFile(remoteName) {
return fetchFile(bucket, remoteName);
},
fileUrl(remoteName) {
return fileUrl(bucket, remoteName);
}
};
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const { ListObjectsV2Command } = require('@aws-sdk/client-s3');
const { createS3Client } = require('./createS3Client');

async function listFiles() {
const { s3Client, bucket } = createS3Client();
async function listFiles(s3Client, bucket) {
const command = new ListObjectsV2Command({
Bucket: bucket
});
Expand Down
8 changes: 8 additions & 0 deletions tasks/aws-s3-builds-page/s3client/requireEnvVar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
function requireEnvVar(name) {
if (!process.env[name]) {
throw new Error(`Environment variable "${name}" is required.`);
}
return process.env[name];
}

module.exports = { requireEnvVar };
47 changes: 47 additions & 0 deletions tasks/aws-s3-builds-page/s3client/s3-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* eslint-disable no-console */
const { createS3Client } = require('./index');
const crypto = require('crypto');
const { runTest } = require('../test-utils/runTest');

// This is a test file. It is intended to be run manually
// with the proper environment variables set
// It tests whether the upload/list/delete methods in this directory
// work properly.
//
// Run it from the project root using "node tasks/aws-s3-builds-page/s3client/s3-test.js"

const client = createS3Client();

runTest(async ({ log }) => {
const uuid = crypto.randomUUID();
const filename = `test-file-${uuid}`;
log(`Starting test with target file "${filename}"`);

log(`Uploading "${filename}"`);
await client.uploadFile('package.json', filename);

log(`Check if uploaded "${filename}"`);
const listing = await client.listFiles();
if (!listing.includes(filename)) {
throw new Error(`File "${filename}" has not been uploaded`);
}

log(`Check contents of "${filename}"`);
const uploadedContents = await client.fetchFile(filename);
expectStringContains('"name": "handlebars"', uploadedContents);

log(`Delete "${filename}"`);
await client.deleteFile(filename);

log(`Check if deleted "${filename}"`);
const listingAfterDelete = await client.listFiles();
if (listingAfterDelete.includes(filename)) {
throw new Error(`File "${filename}" has not been deleted`);
}
});

function expectStringContains(needle, haystack) {
if (!haystack.includes(needle)) {
throw new Error(`Expecting to find "${needle}" in string "${haystack}"`);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const { createS3Client } = require('./createS3Client');
const { PutObjectCommand } = require('@aws-sdk/client-s3');
const fs = require('node:fs/promises');

async function uploadFile(localName, targetName) {
const { s3Client, bucket } = createS3Client();
async function uploadFile(s3Client, bucket, localName, remoteName) {
const fileContents = await fs.readFile(localName);
const command = new PutObjectCommand({
Bucket: bucket,
Key: targetName,
Key: remoteName,
Body: fileContents
});
await s3Client.send(command);
Expand Down
33 changes: 33 additions & 0 deletions tasks/aws-s3-builds-page/test-utils/runTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable no-console */
const { createS3Client } = require('../s3client/index');

const s3Client = createS3Client();

function runTest(asyncFn) {
asyncFn({ log: console.log.bind(console) })
.finally(detectSurplusFiles)
.catch(error => {
console.error(error);
process.exit(1);
});
}

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) {
if (process.argv[2] === '--delete-surplus') {
await s3Client.deleteFile(filename);
} else {
console.log(`Detected surplus file "${filename}"`);
surplusFileDetected = true;
}
}
if (surplusFileDetected) {
console.log(`run with --delete-surplus to delete surplus files`);
}
console.log('DONE');
}

module.exports = { runTest };
Loading

0 comments on commit c5e5cad

Please sign in to comment.