@forge/cli

14.1.0-next.1-experimental-13c2f8d14.1.0-next.21-experimental-554d417
out/deploy/deployer/deployer.js
~out/deploy/deployer/deployer.jsModified
+38
Index: package/out/deploy/deployer/deployer.js
===================================================================
--- package/out/deploy/deployer/deployer.js
+++ package/out/deploy/deployer/deployer.js
@@ -61,8 +61,41 @@
 const CONTROL_CHARS_RE = /\x1B(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1B]*(?:\x07|\x1B\\)|[^[\]])?|[\x00-\x08\x0B\x0C\x0E-\x1A\x1C-\x1F\x7F]/g;
 function stripControlSequences(input) {
     return input.replace(CONTROL_CHARS_RE, '');
 }
+function formatArtifactBytes(bytes) {
+    if (bytes >= 1_000_000_000) {
+        return `${Number((bytes / 1_000_000_000).toFixed(2))} GB`;
+    }
+    if (bytes >= 1_000_000) {
+        return `${Number((bytes / 1_000_000).toFixed(2))} MB`;
+    }
+    return `${bytes} ${bytes === 1 ? 'byte' : 'bytes'}`;
+}
+function getArtifactSizeMessage(errCode, fields) {
+    const text = cli_shared_1.Text.deploy.error.artifactTooLarge;
+    const formatSize = (bytes) => typeof bytes === 'number' && Number.isFinite(bytes) && bytes > 0 ? formatArtifactBytes(bytes) : undefined;
+    const { maxBytes, skillPath } = fields ?? {};
+    const limit = formatSize(maxBytes);
+    const actual = formatSize({
+        COMBINED_ARTIFACT_TOO_LARGE: fields?.sizeBytes,
+        COMBINED_ARTIFACT_ENTRY_TOO_LARGE: fields?.uncompressedSize,
+        COMBINED_ARTIFACT_TOTAL_UNCOMPRESSED_TOO_LARGE: fields?.totalUncompressedSize
+    }[errCode]);
+    const path = typeof skillPath === 'string'
+        ? stripControlSequences(skillPath)
+            .replace(/[\r\n\t]+/g, ' ')
+            .trim()
+        : undefined;
+    switch (errCode) {
+        case 'COMBINED_ARTIFACT_TOO_LARGE':
+            return text.package(limit, actual);
+        case 'COMBINED_ARTIFACT_ENTRY_TOO_LARGE':
+            return text.entry(limit, actual, path);
+        default:
+            return text.totalUncompressed(limit, actual);
+    }
+}
 function parseDisplayMarkup(message) {
     const sanitized = stripControlSequences(message);
     return sanitized.replace(/\{([\w.]+)\s([^}]*)\}/g, (match, styles, text) => {
         const styleNames = styles.split('.');
@@ -181,8 +214,13 @@
     handleErrorEvent(errorDetails, requestId) {
         if (errorDetails) {
             const { code, message } = errorDetails;
             switch (code) {
+                case 'COMBINED_ARTIFACT_TOO_LARGE':
+                case 'COMBINED_ARTIFACT_ENTRY_TOO_LARGE':
+                case 'COMBINED_ARTIFACT_TOTAL_UNCOMPRESSED_TOO_LARGE': {
+                    throw new AppDeploymentFailedError(true, requestId, getArtifactSizeMessage(code, errorDetails.fields));
+                }
                 case 'ENVIRONMENT_UPDATE_VALIDATION_FAILED': {
                     const causeMessage = errorDetails?.fields?.validationResult.message.replace(/Upsert.*Error message: /, '');
                     if (causeMessage?.toLowerCase().includes('egress')) {
                         throw new AppDeploymentFailedError(true, requestId, cli_shared_1.Text.deploy.egressURL.error(causeMessage));