|
| 1 | +import path from 'path'; |
| 2 | +import { brotliCompressSync } from 'zlib'; |
| 3 | +import { inspect } from 'util'; |
| 4 | +import { |
| 5 | + ALLOWED_PUBLISH_ENVIRONMENTS, |
| 6 | + DOWNLOADS_BUCKET, |
| 7 | + DOWNLOADS_BUCKET_PUBLIC_HOST, |
| 8 | + ENTRYPOINT_FILENAME, |
| 9 | + MANIFEST_FILENAME, |
| 10 | + PUBLISH_ENVIRONMENT, |
| 11 | + RELEASE_COMMIT, |
| 12 | + asyncPutObject, |
| 13 | + getObjectKey, |
| 14 | +} from './utils.mts'; |
| 15 | + |
| 16 | +console.log( |
| 17 | + 'Publishing compass-web@%s to %s environment', |
| 18 | + RELEASE_COMMIT, |
| 19 | + PUBLISH_ENVIRONMENT |
| 20 | +); |
| 21 | + |
| 22 | +if (!ALLOWED_PUBLISH_ENVIRONMENTS.includes(PUBLISH_ENVIRONMENT ?? '')) { |
| 23 | + throw new Error( |
| 24 | + `Unknown publish environment: expected ${inspect( |
| 25 | + ALLOWED_PUBLISH_ENVIRONMENTS |
| 26 | + )}, got ${inspect(PUBLISH_ENVIRONMENT)}` |
| 27 | + ); |
| 28 | +} |
| 29 | + |
| 30 | +const publicManifestUrl = new URL( |
| 31 | + getObjectKey(MANIFEST_FILENAME), |
| 32 | + DOWNLOADS_BUCKET_PUBLIC_HOST |
| 33 | +); |
| 34 | + |
| 35 | +const publicEntrypointUrl = new URL( |
| 36 | + getObjectKey(ENTRYPOINT_FILENAME), |
| 37 | + DOWNLOADS_BUCKET_PUBLIC_HOST |
| 38 | +); |
| 39 | + |
| 40 | +let assets: URL[]; |
| 41 | + |
| 42 | +function assertResponseIsOk(res: Response) { |
| 43 | + if (res.status !== 200) { |
| 44 | + throw new Error( |
| 45 | + `Request returned a non-OK response: ${res.status} ${res.statusText}` |
| 46 | + ); |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +console.log('Fetching manifest from %s', publicManifestUrl); |
| 51 | + |
| 52 | +try { |
| 53 | + const res = await fetch(publicManifestUrl); |
| 54 | + assertResponseIsOk(res); |
| 55 | + const manifest = await res.json(); |
| 56 | + |
| 57 | + if ( |
| 58 | + !( |
| 59 | + Array.isArray(manifest) && |
| 60 | + manifest.every((asset) => { |
| 61 | + return typeof asset === 'string'; |
| 62 | + }) |
| 63 | + ) |
| 64 | + ) { |
| 65 | + throw new Error( |
| 66 | + `Manifest schema is not matching: expected string[], got ${inspect( |
| 67 | + manifest |
| 68 | + )}` |
| 69 | + ); |
| 70 | + } |
| 71 | + |
| 72 | + console.log('Checking that assets in manifest exist'); |
| 73 | + |
| 74 | + assets = manifest.map((asset) => { |
| 75 | + return new URL(getObjectKey(asset), DOWNLOADS_BUCKET_PUBLIC_HOST); |
| 76 | + }); |
| 77 | + |
| 78 | + await Promise.all( |
| 79 | + assets.map(async (assetUrl) => { |
| 80 | + const res = await fetch(assetUrl, { method: 'HEAD' }); |
| 81 | + assertResponseIsOk(res); |
| 82 | + }) |
| 83 | + ); |
| 84 | +} catch (err) { |
| 85 | + throw new AggregateError( |
| 86 | + [err], |
| 87 | + `Aborting publish, failed to resolve manifest ${publicManifestUrl}` |
| 88 | + ); |
| 89 | +} |
| 90 | + |
| 91 | +function buildProxyEntrypointFile() { |
| 92 | + return ( |
| 93 | + assets |
| 94 | + .map((asset) => { |
| 95 | + return `import ${JSON.stringify(asset)};`; |
| 96 | + }) |
| 97 | + .concat( |
| 98 | + `export * from ${JSON.stringify(publicEntrypointUrl)};`, |
| 99 | + `/** Compass version: https://github.com/mongodb-js/compass/tree/${RELEASE_COMMIT} */` |
| 100 | + ) |
| 101 | + .join('\n') + '\n' |
| 102 | + ); |
| 103 | +} |
| 104 | + |
| 105 | +const fileKey = getObjectKey('index.mjs', PUBLISH_ENVIRONMENT); |
| 106 | +const fileContent = buildProxyEntrypointFile(); |
| 107 | +const compressedFileContent = brotliCompressSync(fileContent); |
| 108 | + |
| 109 | +console.log( |
| 110 | + 'Uploading entrypoint to s3://%s/%s ...', |
| 111 | + DOWNLOADS_BUCKET, |
| 112 | + fileKey |
| 113 | +); |
| 114 | + |
| 115 | +const ENTRYPOINT_CACHE_MAX_AGE_SECONDS = 1 * 60 * 3; // 3mins |
| 116 | + |
| 117 | +const res = await asyncPutObject({ |
| 118 | + ACL: 'private', |
| 119 | + Bucket: DOWNLOADS_BUCKET, |
| 120 | + Key: fileKey, |
| 121 | + Body: compressedFileContent, |
| 122 | + ContentType: 'text/javascript', |
| 123 | + ContentEncoding: 'br', |
| 124 | + ContentLength: compressedFileContent.byteLength, |
| 125 | + // "Latest" entrypoint files can change quite often, so max-age is quite |
| 126 | + // short and browser should always revalidate on stale |
| 127 | + CacheControl: `public, max-age=${ENTRYPOINT_CACHE_MAX_AGE_SECONDS}, must-revalidate`, |
| 128 | +}); |
| 129 | + |
| 130 | +console.log( |
| 131 | + 'Successfully uploaded %s (ETag: %s)', |
| 132 | + path.basename(fileKey), |
| 133 | + res.ETag |
| 134 | +); |
0 commit comments