@jackdbd/eleventy-plugin-text-to-speech
Version:
Eleventy plugin that uses text-to-speech to generate audio assets for your website, then injects audio players in your HTML.
108 lines • 4.52 kB
JavaScript
import { Readable } from 'node:stream';
import defDebug from 'debug';
import { z } from 'zod';
import { S3Client } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import { account, api_token, r2 } from '@jackdbd/zod-schemas/cloudflare';
import { asset_name } from '../schemas/common.js';
import { DEBUG_PREFIX } from '../constants.js';
import { validatedDataOrThrow, validatedResult } from '../validation.js';
const debug = defDebug(`${DEBUG_PREFIX}:cloudflare-r2`);
export const bucket_config = z.object({
/**
* Name of the Cloudflare R2 bucket to upload the audio file to.
*/
bucketName: r2.bucket_name,
/**
* Domain under your control to make your Cloudflare R2 bucket publicly accessible.
*
* @see [developers.cloudflare.com - Create public buckets on R2](https://developers.cloudflare.com/r2/buckets/public-buckets/)
*/
customDomain: r2.custom_domain
});
export const write_config = z
.object({
/**
* Name of the audio asset to be uploaded to Cloudflare R2.
*/
assetName: asset_name,
/**
* Readable stream containing the audio data to be uploaded to Cloudflare R2.
*/
readable: z.instanceof(Readable)
})
.describe('Cloudflare R2 write config');
export const write = async (s3, cfg, config) => {
const { bucketName, customDomain } = cfg;
const result = validatedResult(config, write_config);
if (result.error) {
return { error: result.error };
}
const { assetName, readable } = result.value;
debug(`upload ${assetName} to Cloudflare R2 bucket ${bucketName}`);
const upload = new Upload({
client: s3,
params: { Bucket: bucketName, Key: assetName, Body: readable }
});
// use multipart upload instead of PutObjectCommand
// https://github.com/aws/aws-sdk-js/issues/2961#issuecomment-868352176
// const command = new PutObjectCommand({ Bucket: bucketName, Key: assetName })
// const res = await s3.send(command)
// https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/example-code/file-upload.ts
upload.on('httpUploadProgress', (progress) => {
debug(`upload progress %O`, progress);
});
// There are 2 ways to expose the contents of a R2 bucket directly to the Internet:
// 1. Expose your bucket as a custom domain under your control.
// 2. Expose your bucket as a Cloudflare-managed subdomain under https://r2.dev.
// To configure WAF custom rules, caching, access controls, or bot management
// for your bucket, you must do so through a custom domain.
// https://developers.cloudflare.com/r2/buckets/public-buckets/#connect-a-bucket-to-a-custom-domain
const href = `https://${customDomain}/${assetName}`;
let uri;
try {
const output = await upload.done();
uri = output.Location; // when is this undefined?
// https://developers.cloudflare.com/r2/buckets/public-buckets/#managed-public-buckets-through-r2dev
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (err) {
return { error: err };
}
const message = `asset uploaded to ${uri} and publicly accessible at ${href}`;
return { value: { message, href } };
};
export const auth_config = z
.object({
accessKeyId: api_token.access_key_id,
accountId: account.id,
secretAccessKey: api_token.secret_access_key
})
.describe('Cloudflare R2 auth config');
export const client_config = auth_config
.merge(bucket_config)
.describe('Cloudflare R2 client config');
/**
* Client for Cloudflare R2.
*
*/
export const defClient = (config) => {
const data = validatedDataOrThrow(config, client_config);
const { accessKeyId, accountId, bucketName, customDomain, secretAccessKey } = data;
const region = 'auto';
debug(`audio assets will be written to ${bucketName} (region: ${region}) and hosted at ${customDomain}`);
// https://developers.cloudflare.com/r2/api/s3/tokens/
const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;
const credentials = { accessKeyId, secretAccessKey };
// https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/
const s3 = new S3Client({ region, endpoint, credentials });
const writeWithS3AndBucketConfig = write.bind(null, s3, {
bucketName,
customDomain
});
return {
config: { bucketName, customDomain },
write: writeWithS3AndBucketConfig
};
};
//# sourceMappingURL=cloudflare-r2.js.map