Skip to content

Commit fdc7732

Browse files
committed
build: validate file size limits for release built files
1 parent 693c538 commit fdc7732

2 files changed

Lines changed: 194 additions & 69 deletions

File tree

gulpfile.js/index.js

Lines changed: 6 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const zip = require('gulp-zip');
3131
const Translate = require("./translateStrings");
3232
const copyThirdPartyLibs = require("./thirdparty-lib-copy");
3333
const optionalBuild = require("./optional-build");
34+
const validateBuild = require("./validate-build");
3435
const minify = require('gulp-minify');
3536
const glob = require("glob");
3637
const crypto = require("crypto");
@@ -1057,71 +1058,6 @@ function validatePackageVersions() {
10571058
});
10581059
}
10591060

1060-
function validateDistSizeRestrictions() {
1061-
return new Promise((resolve, reject) => {
1062-
const MAX_FILE_SIZE_MB = 1;
1063-
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
1064-
const MAX_TOTAL_SIZE_MB = 80;
1065-
const MAX_TOTAL_SIZE_BYTES = MAX_TOTAL_SIZE_MB * 1024 * 1024;
1066-
1067-
_listFilesInDir('dist').then((files) => {
1068-
const oversizedFiles = [];
1069-
let totalSize = 0;
1070-
1071-
for (let file of files) {
1072-
const stats = fs.statSync(file);
1073-
totalSize += stats.size;
1074-
1075-
if (stats.size > MAX_FILE_SIZE_BYTES) {
1076-
oversizedFiles.push({
1077-
path: file,
1078-
sizeBytes: stats.size,
1079-
sizeMB: (stats.size / (1024 * 1024)).toFixed(2)
1080-
});
1081-
}
1082-
}
1083-
1084-
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
1085-
const hasOversizedFiles = oversizedFiles.length > 0;
1086-
const hasTotalSizeExceeded = totalSize > MAX_TOTAL_SIZE_BYTES;
1087-
1088-
if (hasOversizedFiles || hasTotalSizeExceeded) {
1089-
console.error(`\n========================================`);
1090-
console.error(`SIZE VALIDATION FAILED`);
1091-
console.error(`========================================`);
1092-
1093-
if (hasTotalSizeExceeded) {
1094-
console.error(`\nTotal dist folder size: ${totalSizeMB} MB (exceeds ${MAX_TOTAL_SIZE_MB} MB limit)`);
1095-
}
1096-
1097-
if (hasOversizedFiles) {
1098-
// Sort by size in descending order
1099-
oversizedFiles.sort((a, b) => b.sizeBytes - a.sizeBytes);
1100-
console.error(`\nFound ${oversizedFiles.length} file(s) exceeding ${MAX_FILE_SIZE_MB} MB:\n`);
1101-
1102-
for (let file of oversizedFiles) {
1103-
console.error(` ${file.path} (${file.sizeMB} MB)`);
1104-
}
1105-
}
1106-
1107-
console.error(`\n========================================\n`);
1108-
1109-
const errors = [];
1110-
if (hasTotalSizeExceeded) {
1111-
errors.push(`Total dist size ${totalSizeMB} MB exceeds ${MAX_TOTAL_SIZE_MB} MB limit`);
1112-
}
1113-
if (hasOversizedFiles) {
1114-
errors.push(`${oversizedFiles.length} file(s) exceed ${MAX_FILE_SIZE_MB} MB size limit`);
1115-
}
1116-
1117-
reject(`Build validation failed: ${errors.join('; ')}`);
1118-
} else {
1119-
console.log(`Size validation passed: Total dist size is ${totalSizeMB} MB (under ${MAX_TOTAL_SIZE_MB} MB), all files under ${MAX_FILE_SIZE_MB} MB`);
1120-
resolve();
1121-
}
1122-
}).catch(reject);
1123-
});
1124-
}
11251061

11261062
function _patchMinifiedCSSInDistIndex() {
11271063
return new Promise((resolve)=>{
@@ -1150,17 +1086,18 @@ exports.reset = series(cleanAll);
11501086

11511087
exports.releaseDev = series(cleanDist, exports.buildDebug, makeBracketsConcatJS, makeConcatExtensions, _compileLessSrc,
11521088
makeDistAll, cleanUnwantedFilesInDistDev, releaseDev, _renameConcatExtensionsinDist,
1153-
createDistCacheManifest, createDistTest, _cleanPhoenixProGitFolder, _cleanReleaseBuildArtefactsInSrc, validateDistSizeRestrictions);
1089+
createDistCacheManifest, createDistTest,
1090+
_cleanPhoenixProGitFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions);
11541091
exports.releaseStaging = series(cleanDist, exports.build, makeBracketsConcatJSWithMinifiedBrowserScripts,
11551092
makeConcatExtensions, _compileLessSrc, makeDistNonJS, makeJSDist, makeJSPrettierDist, makeNonMinifyDist,
11561093
cleanUnwantedFilesInDistProd, _renameBracketsConcatAsBracketsJSInDist, _renameConcatExtensionsinDist,
11571094
_patchMinifiedCSSInDistIndex, releaseStaging, createDistCacheManifest, createDistTest,
1158-
_deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateDistSizeRestrictions);
1095+
_deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions);
11591096
exports.releaseProd = series(cleanDist, exports.build, makeBracketsConcatJSWithMinifiedBrowserScripts,
11601097
makeConcatExtensions, _compileLessSrc, makeDistNonJS, makeJSDist, makeJSPrettierDist, makeNonMinifyDist,
11611098
cleanUnwantedFilesInDistProd, _renameBracketsConcatAsBracketsJSInDist, _renameConcatExtensionsinDist,
11621099
_patchMinifiedCSSInDistIndex, releaseProd, createDistCacheManifest, createDistTest,
1163-
_deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateDistSizeRestrictions);
1100+
_deletePhoenixProSourceFolder, _cleanReleaseBuildArtefactsInSrc, validateBuild.validateDistSizeRestrictions);
11641101
exports.releaseWebCache = series(makeDistWebCache);
11651102
exports.serve = series(exports.build, serve);
11661103
exports.zipTestFiles = series(zipTestFiles);
@@ -1171,4 +1108,4 @@ exports.default = series(exports.build);
11711108
exports.patchVersionBump = series(patchVersionBump);
11721109
exports.minorVersionBump = series(minorVersionBump);
11731110
exports.majorVersionBump = series(majorVersionBump);
1174-
exports.validateDistSizeRestrictions = series(validateDistSizeRestrictions);
1111+
exports.validateDistSizeRestrictions = series(validateBuild.validateDistSizeRestrictions);

gulpfile.js/validate-build.js

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2022 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it
7+
* under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
14+
* for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
18+
*
19+
*/
20+
21+
/* eslint-env node */
22+
23+
const fs = require('fs');
24+
const glob = require('glob');
25+
26+
// Size limits for development builds (in MB)
27+
const DEV_MAX_FILE_SIZE_MB = 6;
28+
const DEV_MAX_TOTAL_SIZE_MB = 100;
29+
// Custom size limits for known large files (size in MB) For development builds
30+
const LARGE_FILE_LIST_DEV = {
31+
'dist/thirdparty/no-minify/language-worker.js.map': 10,
32+
'dist/brackets-min.js': 15
33+
};
34+
35+
// Size limits for production/staging builds (in MB)
36+
const PROD_MAX_FILE_SIZE_MB = 2;
37+
const PROD_MAX_TOTAL_SIZE_MB = 80;
38+
// Custom size limits for known large files (size in MB) For staging/production builds
39+
const LARGE_FILE_LIST_PROD = {
40+
'dist/brackets.js': 9, // this is the full minified file itself renamed in prod
41+
'dist/phoenix/virtualfs.js.map': 3
42+
};
43+
44+
function _listFilesInDir(dir) {
45+
return new Promise((resolve, reject)=>{
46+
glob(dir + '/**/*', {
47+
nodir: true
48+
}, (err, res)=>{
49+
if(err){
50+
reject(err);
51+
return;
52+
}
53+
resolve(res);
54+
});
55+
});
56+
}
57+
58+
function _scanDistFiles(environment, largeFileList, maxFileSizeMB, maxTotalSizeMB) {
59+
return new Promise((resolve, reject) => {
60+
const maxTotalSizeBytes = maxTotalSizeMB * 1024 * 1024;
61+
62+
_listFilesInDir('dist').then((files) => {
63+
const oversizedFiles = [];
64+
let totalSize = 0;
65+
66+
for (let file of files) {
67+
const stats = fs.statSync(file);
68+
totalSize += stats.size;
69+
70+
// Check if file has a custom size limit
71+
const customLimitMB = largeFileList[file];
72+
const fileLimitMB = customLimitMB !== undefined ? customLimitMB : maxFileSizeMB;
73+
const fileLimitBytes = fileLimitMB * 1024 * 1024;
74+
75+
if (stats.size > fileLimitBytes) {
76+
oversizedFiles.push({
77+
path: file,
78+
sizeMB: (stats.size / (1024 * 1024)).toFixed(2),
79+
limitMB: fileLimitMB,
80+
isCustomLimit: customLimitMB !== undefined
81+
});
82+
}
83+
}
84+
85+
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
86+
87+
resolve({
88+
oversizedFiles,
89+
totalSizeMB,
90+
totalLimitMB: maxTotalSizeMB,
91+
hasTotalSizeExceeded: totalSize > maxTotalSizeBytes
92+
});
93+
}).catch(reject);
94+
});
95+
}
96+
97+
function _displayValidationResults(scanResults, environment) {
98+
const {
99+
oversizedFiles,
100+
totalSizeMB,
101+
totalLimitMB,
102+
hasTotalSizeExceeded
103+
} = scanResults;
104+
105+
if (oversizedFiles.length || hasTotalSizeExceeded) {
106+
console.error(`\n========================================`);
107+
console.error(`SIZE VALIDATION FAILED (${environment})`);
108+
console.error(`========================================`);
109+
110+
if (hasTotalSizeExceeded) {
111+
console.error(`\nTotal dist folder size: ${totalSizeMB} MB ` +
112+
`(exceeds ${totalLimitMB} MB limit for ${environment})`);
113+
}
114+
115+
if (oversizedFiles.length) {
116+
// Sort by size in descending order
117+
oversizedFiles.sort((a, b) => b.sizeBytes - a.sizeBytes);
118+
console.error(`\nFound ${oversizedFiles.length} file(s) exceeding size limits for ${environment}:\n`);
119+
120+
for (let file of oversizedFiles) {
121+
const limitInfo = file.isCustomLimit ?
122+
` [custom limit: ${file.limitMB} MB]` : ` [limit: ${file.limitMB} MB]`;
123+
console.error(` ${file.path} (${file.sizeMB} MB)${limitInfo}`);
124+
}
125+
}
126+
127+
console.error(`\n========================================\n`);
128+
129+
const errors = [];
130+
if (hasTotalSizeExceeded) {
131+
errors.push(`Total dist size ${totalSizeMB} MB exceeds ${totalLimitMB} MB limit`);
132+
}
133+
if (oversizedFiles.length) {
134+
errors.push(`${oversizedFiles.length} file(s) exceed size limit`);
135+
}
136+
137+
return {
138+
passed: false,
139+
errorMessage: `Build validation failed for ${environment}: ${errors.join('; ')}`
140+
};
141+
}
142+
143+
console.log(`Size validation passed for ${environment}: Total dist size is ${totalSizeMB} MB ` +
144+
`(under ${totalLimitMB} MB), all files under required limits.`);
145+
return {
146+
passed: true
147+
};
148+
}
149+
150+
function validateDistSizeRestrictions() {
151+
return new Promise((resolve, reject) => {
152+
// Read config to determine environment
153+
let config;
154+
try {
155+
config = JSON.parse(fs.readFileSync('dist/config.json', 'utf8'));
156+
} catch (err) {
157+
reject(`Failed to read dist/config.json for size validation: ${err.message}`);
158+
return;
159+
}
160+
161+
const environment = config.config?.environment || 'production';
162+
const isDev = environment === 'dev';
163+
164+
// Set limits based on environment
165+
const MAX_FILE_SIZE_MB = isDev ? DEV_MAX_FILE_SIZE_MB : PROD_MAX_FILE_SIZE_MB;
166+
const MAX_TOTAL_SIZE_MB = isDev ? DEV_MAX_TOTAL_SIZE_MB : PROD_MAX_TOTAL_SIZE_MB;
167+
const LARGE_FILE_LIST = isDev ? LARGE_FILE_LIST_DEV : LARGE_FILE_LIST_PROD;
168+
169+
console.log(`Validating dist size for ${environment} environment
170+
(File limit: ${MAX_FILE_SIZE_MB} MB, Total limit: ${MAX_TOTAL_SIZE_MB} MB)`);
171+
172+
_scanDistFiles(environment, LARGE_FILE_LIST, MAX_FILE_SIZE_MB, MAX_TOTAL_SIZE_MB)
173+
.then((scanResults) => {
174+
const result = _displayValidationResults(scanResults, environment);
175+
176+
if (result.passed) {
177+
resolve();
178+
} else {
179+
reject(result.errorMessage);
180+
}
181+
})
182+
.catch(reject);
183+
});
184+
}
185+
186+
module.exports = {
187+
validateDistSizeRestrictions
188+
};

0 commit comments

Comments
 (0)