Skip to content

Commit 8c68696

Browse files
committed
Merge remote-tracking branch 'origin/main' into sh/deploy-zip-size
2 parents 7468cc7 + 5f819ec commit 8c68696

File tree

10 files changed

+700
-667
lines changed

10 files changed

+700
-667
lines changed

METADATA_SUPPORT.md

Lines changed: 639 additions & 636 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"got": "^11.8.6",
3434
"graceful-fs": "^4.2.11",
3535
"ignore": "^5.3.2",
36+
"isbinaryfile": "^5.0.2",
3637
"jszip": "^3.10.1",
3738
"mime": "2.6.0",
3839
"minimatch": "^9.0.5",

src/convert/replacements.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
*/
77
import { readFile } from 'node:fs/promises';
88
import { Transform, Readable } from 'node:stream';
9-
import { sep, posix, join, isAbsolute } from 'node:path';
9+
import { sep, posix, join, isAbsolute, extname } from 'node:path';
1010
import { Lifecycle, Messages, SfError, SfProject } from '@salesforce/core';
1111
import { minimatch } from 'minimatch';
1212
import { Env } from '@salesforce/kit';
1313
import { ensureString, isString } from '@salesforce/ts-types';
14+
import { isBinaryFileSync } from 'isbinaryfile';
1415
import { SourcePath } from '../common/types';
1516
import { SourceComponent } from '../resolve/sourceComponent';
1617
import { MarkedReplacement, ReplacementConfig, ReplacementEvent } from './types';
@@ -20,14 +21,20 @@ const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sd
2021

2122
const fileContentsCache = new Map<string, string>();
2223

24+
// First do a quick check for common text extensions
25+
// If that fails, confirm that it is not a binary file
26+
const textExtensions = new Set(['.cls', '.xml', '.json', '.js', '.css', '.html', '.htm', '.txt', '.md']);
27+
const isTextFile = (path: string): boolean => textExtensions.has(extname(path)) || !isBinaryFileSync(path);
28+
2329
/** If a component has replacements, you get it piped through the replacementStream
2430
* Otherwise, you'll get the original readable stream
31+
* Ignore binary files, they will get corrupted in the replacement process
2532
*/
2633
export const getReplacementStreamForReadable = (
2734
component: SourceComponent,
2835
path: SourcePath
2936
): Readable | ReplacementStream =>
30-
component.replacements?.[path]
37+
component.replacements?.[path] && isTextFile(path)
3138
? component.tree.stream(path).pipe(new ReplacementStream(component.replacements?.[path]))
3239
: component.tree.stream(path);
3340

test/collections/componentSet.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('ComponentSet', () => {
9292
{ members: ['replaceStuff'], name: 'ApexClass' },
9393
{ members: ['TestObj__c.FieldA__c'], name: 'CustomField' },
9494
{ members: ['TestObj__c'], name: 'CustomObject' },
95-
{ members: ['Test'], name: 'StaticResource' },
95+
{ members: ['ImageTest', 'Test'], name: 'StaticResource' },
9696
],
9797
version,
9898
});

test/nuts/local/replacements/replacements.nut.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,29 @@ describe('e2e replacements test', () => {
9595
}
9696
}
9797
});
98+
it('skips images in static resources to prevent file corruption', async () => {
99+
const srZipPath = path.join(session.project.dir, 'unzipped', 'staticresources', 'ImageTest.resource');
100+
expect(fs.existsSync(srZipPath)).to.be.true;
101+
const srZip = await JSZip.loadAsync(fs.readFileSync(srZipPath));
102+
103+
// static resource zip should have 2 files:
104+
// 1. test-image.png, 2. test-image.resource-meta.xml
105+
expect(Object.entries(srZip.files).length).to.equal(2);
106+
107+
const imageMeta = srZip.file('test-image.resource-meta.xml');
108+
if (imageMeta && !imageMeta.dir) {
109+
const content = await imageMeta.async('nodebuffer');
110+
const imageMetaAsString = content.toString();
111+
expect(imageMetaAsString).to.not.include('placeholder');
112+
expect(imageMetaAsString).to.include('foo');
113+
}
114+
115+
const image = srZip.file('test-image.png');
116+
if (image && !image.dir) {
117+
const content = await image.async('nodebuffer');
118+
// The file size would be much larger if it was corrupted via the string replacement method
119+
expect(content.byteLength).to.equal(1562121);
120+
}
121+
});
98122
});
99123
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<cacheControl>Private</cacheControl>
4+
<contentType>application/zip</contentType>
5+
<description>added from sfdx plugin</description>
6+
<fullName>ImageTest</fullName>
7+
</StaticResource>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<cacheControl>Private</cacheControl>
4+
<contentType>image/png</contentType>
5+
<description>Test placeholder</description>
6+
</StaticResource>

test/nuts/local/replacements/testProj/sfdx-project.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
"glob": "**/*.css",
6060
"replaceWithEnv": "THE_REPLACEMENT",
6161
"stringToReplace": "placeholder"
62+
},
63+
{
64+
"glob": "force-app/main/default/staticresources/ImageTest/**/**",
65+
"replaceWithEnv": "THE_REPLACEMENT",
66+
"stringToReplace": "placeholder"
6267
}
6368
],
6469
"sfdcLoginUrl": "https://login.salesforce.com",

yarn.lock

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3233,6 +3233,11 @@ isarray@~1.0.0:
32333233
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
32343234
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
32353235

3236+
isbinaryfile@^5.0.2:
3237+
version "5.0.2"
3238+
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.2.tgz#fe6e4dfe2e34e947ffa240c113444876ba393ae0"
3239+
integrity sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==
3240+
32363241
isexe@^2.0.0:
32373242
version "2.0.0"
32383243
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -5066,16 +5071,7 @@ srcset@^5.0.0:
50665071
resolved "https://registry.yarnpkg.com/srcset/-/srcset-5.0.0.tgz#9df6c3961b5b44a02532ce6ae4544832609e2e3f"
50675072
integrity sha512-SqEZaAEhe0A6ETEa9O1IhSPC7MdvehZtCnTR0AftXk3QhY2UNgb+NApFOUPZILXk/YTDfFxMTNJOBpzrJsEdIA==
50685073

5069-
"string-width-cjs@npm:string-width@^4.2.0":
5070-
version "4.2.3"
5071-
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
5072-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
5073-
dependencies:
5074-
emoji-regex "^8.0.0"
5075-
is-fullwidth-code-point "^3.0.0"
5076-
strip-ansi "^6.0.1"
5077-
5078-
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
5074+
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
50795075
version "4.2.3"
50805076
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
50815077
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -5134,14 +5130,7 @@ string_decoder@~1.1.1:
51345130
dependencies:
51355131
safe-buffer "~5.1.0"
51365132

5137-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
5138-
version "6.0.1"
5139-
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
5140-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
5141-
dependencies:
5142-
ansi-regex "^5.0.1"
5143-
5144-
strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
5133+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
51455134
version "6.0.1"
51465135
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
51475136
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -5631,7 +5620,7 @@ workerpool@^6.5.1:
56315620
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
56325621
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
56335622

5634-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
5623+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
56355624
version "7.0.0"
56365625
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
56375626
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -5649,15 +5638,6 @@ wrap-ansi@^6.2.0:
56495638
string-width "^4.1.0"
56505639
strip-ansi "^6.0.0"
56515640

5652-
wrap-ansi@^7.0.0:
5653-
version "7.0.0"
5654-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
5655-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
5656-
dependencies:
5657-
ansi-styles "^4.0.0"
5658-
string-width "^4.1.0"
5659-
strip-ansi "^6.0.0"
5660-
56615641
wrap-ansi@^8.1.0:
56625642
version "8.1.0"
56635643
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)