1515const https = require ( 'node:https' ) ;
1616const fs = require ( 'node:fs' ) ;
1717const path = require ( 'node:path' ) ;
18+ const zlib = require ( 'node:zlib' ) ;
19+ const { promisify } = require ( 'node:util' ) ;
20+
21+ const gunzip = promisify ( zlib . gunzip ) ;
1822
1923const BASE_CDN = 'https://distributions.crowdin.net' ;
2024const OUTPUT_DIR = path . resolve ( process . env . OUTPUT_DIR || 'dist-pages/crowdin-dist' ) ;
@@ -38,27 +42,44 @@ if (DISTRIBUTIONS.length === 0) {
3842 process . exit ( 1 ) ;
3943}
4044
45+ /**
46+ * Collects all data chunks from an HTTP response stream into a single Buffer.
47+ * @param {import('http').IncomingMessage } res
48+ * @returns {Promise<Buffer> }
49+ */
50+ function collectBody ( res ) {
51+ return new Promise ( ( resolve , reject ) => {
52+ const chunks = [ ] ;
53+ res . on ( 'data' , ( chunk ) => chunks . push ( chunk ) ) ;
54+ res . on ( 'end' , ( ) => resolve ( Buffer . concat ( chunks ) ) ) ;
55+ res . on ( 'error' , reject ) ;
56+ } ) ;
57+ }
58+
4159/**
4260 * Fetches a URL, following redirects, and returns the body as a Buffer.
61+ * Transparently decompresses gzip-encoded responses so callers always receive
62+ * plain bytes (the Crowdin CDN stores content files with Content-Encoding: gzip,
63+ * but jsDelivr re-serves the raw bytes without that header).
4364 * @param {string } url
4465 * @returns {Promise<Buffer> }
4566 */
46- function fetchUrl ( url ) {
67+ async function fetchUrl ( url ) {
4768 return new Promise ( ( resolve , reject ) => {
48- https . get ( url , ( res ) => {
49- // Follow redirects
69+ https . get ( url , async ( res ) => {
5070 if ( res . statusCode >= 300 && res . statusCode < 400 && res . headers . location ) {
51- return fetchUrl ( res . headers . location ) . then ( resolve ) . catch ( reject ) ;
71+ return fetchUrl ( res . headers . location ) . then ( resolve , reject ) ;
72+ }
73+ if ( res . statusCode >= 400 ) {
74+ return reject ( new Error ( `HTTP ${ res . statusCode } for ${ url } ` ) ) ;
75+ }
76+ try {
77+ const buffer = await collectBody ( res ) ;
78+ const isGzip = res . headers [ 'content-encoding' ] === 'gzip' ;
79+ resolve ( isGzip ? await gunzip ( buffer ) : buffer ) ;
80+ } catch ( err ) {
81+ reject ( err ) ;
5282 }
53- const chunks = [ ] ;
54- res . on ( 'data' , ( chunk ) => chunks . push ( chunk ) ) ;
55- res . on ( 'end' , ( ) => {
56- if ( res . statusCode >= 400 ) {
57- return reject ( new Error ( `HTTP ${ res . statusCode } for ${ url } ` ) ) ;
58- }
59- resolve ( Buffer . concat ( chunks ) ) ;
60- } ) ;
61- res . on ( 'error' , reject ) ;
6283 } ) . on ( 'error' , reject ) ;
6384 } ) ;
6485}
0 commit comments