Skip to content

Commit 2f8dac3

Browse files
authored
fix: handle nulls/undefined in template literal (#1370)
* fix: handle nulls/undefined in template literal * refactor: placeholders for errors * chore: bump deps for xnuts
1 parent 9b034b1 commit 2f8dac3

15 files changed

+90
-86
lines changed

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"node": ">=18.0.0"
2626
},
2727
"dependencies": {
28-
"@salesforce/core": "^8.1.2",
28+
"@salesforce/core": "^8.2.1",
2929
"@salesforce/kit": "^3.1.6",
3030
"@salesforce/ts-types": "^2.0.10",
3131
"fast-levenshtein": "^3.0.0",
@@ -39,7 +39,7 @@
3939
"proxy-agent": "^6.4.0"
4040
},
4141
"devDependencies": {
42-
"@jsforce/jsforce-node": "^3.2.2",
42+
"@jsforce/jsforce-node": "^3.2.4",
4343
"@salesforce/cli-plugins-testkit": "^5.3.18",
4444
"@salesforce/dev-scripts": "^10.2.2",
4545
"@types/deep-equal-in-any-order": "^1.0.1",

Diff for: src/client/metadataApiDeploy.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ export class MetadataApiDeploy extends MetadataTransfer<
272272
} catch (err) {
273273
const error = err as Error;
274274
this.logger.debug(
275-
`Error trying to compile/send deploy telemetry data for deploy ID: ${this.id}\nError: ${error.message}`
275+
`Error trying to compile/send deploy telemetry data for deploy ID: ${this.id ?? '<not provided>'}\nError: ${
276+
error.message
277+
}`
276278
);
277279
}
278280
const deployResult = new DeployResult(
@@ -394,7 +396,9 @@ const warnIfUnmatchedServerResult =
394396

395397
// warn that this component is found in server response, but not in component set
396398
void Lifecycle.getInstance().emitWarning(
397-
`${deployMessage.componentType}, ${deployMessage.fullName}, returned from org, but not found in the local project`
399+
`${deployMessage.componentType ?? '<no component type in deploy message>'}, ${
400+
deployMessage.fullName
401+
}, returned from org, but not found in the local project`
398402
);
399403
}
400404
});

Diff for: src/client/metadataTransfer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export abstract class MetadataTransfer<
7171
this.canceled = false;
7272
const asyncResult = await this.pre();
7373
this.transferId = asyncResult.id;
74-
this.logger.debug(`Started metadata transfer. ID = ${this.id}`);
74+
this.logger.debug(`Started metadata transfer. ID = ${this.id ?? '<no id>'}`);
7575
return asyncResult;
7676
}
7777

@@ -105,7 +105,7 @@ export abstract class MetadataTransfer<
105105
});
106106

107107
try {
108-
this.logger.debug(`Polling for metadata transfer status. ID = ${this.id}`);
108+
this.logger.debug(`Polling for metadata transfer status. ID = ${this.id ?? '<no id>'}`);
109109
this.logger.debug(`Polling frequency (ms): ${normalizedOptions.frequency.milliseconds}`);
110110
this.logger.debug(`Polling timeout (min): ${normalizedOptions.timeout.minutes}`);
111111
const completedMdapiStatus = (await pollingClient.subscribe()) as unknown as Status;

Diff for: src/collections/componentSetBuilder.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ const logComponents = (logger: Logger, componentSet: ComponentSet): void => {
261261
.map((m) => logger.debug(m));
262262
if (components.length > 20) logger.debug(`(showing 20 of ${componentSet.size} matches)`);
263263

264-
logger.debug(`ComponentSet apiVersion = ${componentSet.apiVersion}`);
265-
logger.debug(`ComponentSet sourceApiVersion = ${componentSet.sourceApiVersion}`);
264+
logger.debug(`ComponentSet apiVersion = ${componentSet.apiVersion ?? '<not set>'}`);
265+
logger.debug(`ComponentSet sourceApiVersion = ${componentSet.sourceApiVersion ?? '<not set>'}`);
266266
};
267267

268268
const getOrgComponentFilter = (

Diff for: src/convert/convertContext/nonDecompositionFinalizer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ const recompose = (children: Map<string, JsonMap>, parentSourceComponent: Source
197197
const getDefaultOutput = (component: SourceComponent): string => {
198198
const { fullName } = component;
199199
const [baseName] = fullName.split('.');
200-
const output = `${baseName}.${component.type.suffix}${META_XML_SUFFIX}`;
200+
const output = `${baseName}.${component.type.suffix ?? ''}${META_XML_SUFFIX}`;
201201

202202
return join(component.getPackageRelativePath('', 'source'), output);
203203
};

Diff for: src/convert/convertContext/recompositionFinalizer.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const stateValueToWriterFormat =
6363
source: new JsToXml(await recompose(cache)(stateValue)),
6464
output: join(
6565
stateValue.component.type.directoryName,
66-
`${stateValue.component.fullName}.${stateValue.component.type.suffix}`
66+
`${stateValue.component.fullName}.${stateValue.component.type.suffix ?? ''}`
6767
),
6868
},
6969
],
@@ -139,10 +139,15 @@ const ensureStateValueWithParent = (
139139
return true;
140140
}
141141
throw new Error(
142-
`The parent component is missing from the recomposition state entry. The children are ${stateValue.children
143-
?.toArray()
144-
.map((c) => c.fullName)
145-
.join(', ')}`
142+
`The parent component is missing from the recomposition state entry. ${
143+
stateValue.children
144+
? `The children are ${stateValue.children
145+
?.toArray()
146+
.map((c) => c.fullName)
147+
.join(', ')}
148+
`
149+
: 'There are no children.'
150+
}`
146151
);
147152
};
148153

Diff for: src/convert/transformers/decomposedMetadataTransformer.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ const getChildWriteInfos =
168168
return [
169169
{
170170
source,
171-
output: join(dirname(mergeWith.xml), `${entryName}.${childComponent.type.suffix}${META_XML_SUFFIX}`),
171+
output: join(
172+
dirname(mergeWith.xml),
173+
`${entryName}.${ensureString(childComponent.type.suffix)}${META_XML_SUFFIX}`
174+
),
172175
},
173176
];
174177
}
@@ -259,7 +262,7 @@ const getDefaultOutput = (component: MetadataComponent): SourcePath => {
259262
const childName = tail.length ? tail.join('.') : undefined;
260263
const output = join(
261264
parent?.type.strategies?.decomposition === DecompositionStrategy.FolderPerType ? type.directoryName : '',
262-
`${childName ?? baseName}.${component.type.suffix}${META_XML_SUFFIX}`
265+
`${childName ?? baseName}.${ensureString(component.type.suffix)}${META_XML_SUFFIX}`
263266
);
264267
return join(calculateRelativePath('source')({ self: parent?.type ?? type })(fullName)(baseName), output);
265268
};
@@ -289,7 +292,7 @@ type ComposedMetadata = { tagKey: string; tagValue: AnyJson; parentType: Metadat
289292
type ComposedMetadataWithChildType = ComposedMetadata & { childType: MetadataType };
290293

291294
type InfoContainer = {
292-
entryName?: string;
295+
entryName: string;
293296
childComponent: MetadataComponent;
294297
/** the parsed xml */
295298
value: JsonMap;

Diff for: src/convert/transformers/defaultMetadataTransformer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const getXmlDestination = (
101101
if (!component.content && !['digitalexperiencebundle'].includes(component.type.id)) {
102102
if (targetFormat === 'metadata') {
103103
if (folderContentType) {
104-
xmlDestination = xmlDestination.replace(`.${suffix}`, '');
104+
xmlDestination = xmlDestination.replace(`.${suffix ?? ''}`, '');
105105
} else if (xmlDestination.includes(META_XML_SUFFIX)) {
106106
xmlDestination = xmlDestination.slice(0, xmlDestination.lastIndexOf(META_XML_SUFFIX));
107107
} else {
@@ -111,7 +111,7 @@ const getXmlDestination = (
111111
}
112112
} else {
113113
xmlDestination = folderContentType
114-
? xmlDestination.replace(META_XML_SUFFIX, `.${suffix}${META_XML_SUFFIX}`)
114+
? xmlDestination.replace(META_XML_SUFFIX, `.${suffix ?? ''}${META_XML_SUFFIX}`)
115115
: `${xmlDestination}${META_XML_SUFFIX}`;
116116
}
117117
} else if (suffix) {

Diff for: src/convert/transformers/staticResourceMetadataTransformer.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class StaticResourceMetadataTransformer extends BaseMetadataTransformer {
5252

5353
// Zip the static resource from disk to a stream, compressing at level 9.
5454
const zipIt = (): Readable => {
55-
getLogger().debug(`zipping static resource: ${component.content}`);
55+
getLogger().debug(`zipping static resource: ${content}`);
5656
const zip = JSZip();
5757

5858
// JSZip does not have an API for adding a directory of files recursively so we always
@@ -78,7 +78,7 @@ export class StaticResourceMetadataTransformer extends BaseMetadataTransformer {
7878
streamFiles: true,
7979
})
8080
.on('end', () => {
81-
getLogger().debug(`zip complete for: ${component.content}`);
81+
getLogger().debug(`zip complete for: ${content}`);
8282
})
8383
);
8484
};
@@ -88,7 +88,7 @@ export class StaticResourceMetadataTransformer extends BaseMetadataTransformer {
8888
source: (await componentIsExpandedArchive(component))
8989
? zipIt()
9090
: getReplacementStreamForReadable(component, content),
91-
output: join(type.directoryName, `${baseName(content)}.${type.suffix}`),
91+
output: join(type.directoryName, `${baseName(content)}.${type.suffix ?? ''}`),
9292
},
9393
{
9494
source: getReplacementStreamForReadable(component, xml),
@@ -197,7 +197,9 @@ const getContentType = async (component: SourceComponent): Promise<string> => {
197197

198198
if (typeof output !== 'string') {
199199
throw new SfError(
200-
`Expected a string for contentType in ${component.name} (${component.xml}) but got ${JSON.stringify(output)}`
200+
`Expected a string for contentType in ${component.name} (${component.xml ?? '<no xml>'}) but got ${JSON.stringify(
201+
output
202+
)}`
201203
);
202204
}
203205
return output;
@@ -211,7 +213,7 @@ const getBaseContentPath = (component: SourceComponent, mergeWith?: SourceCompon
211213
const baseContentPath = component.getPackageRelativePath(component.content, 'source');
212214
return join(dirname(baseContentPath), baseName(baseContentPath));
213215
}
214-
throw new SfError(`Expected a content path for ${component.name} (${component.xml})`);
216+
throw new SfError(`Expected a content path for ${component.name} (${component.xml ?? '<no xml>'})`);
215217
};
216218

217219
const getExtensionFromType = (contentType: string): string =>
@@ -238,8 +240,10 @@ async function getStaticResourceZip(component: SourceComponent, content: string)
238240
const staticResourceZip = await component.tree.readFile(content);
239241
return await JSZip.loadAsync(staticResourceZip, { createFolders: true });
240242
} catch (e) {
241-
throw new SfError(`Unable to open zip file ${content} for ${component.name} (${component.xml})`, 'BadZipFile', [
242-
'Check that your file really is a valid zip archive',
243-
]);
243+
throw new SfError(
244+
`Unable to open zip file ${content} for ${component.name} (${component.xml ?? '<no xml>'})`,
245+
'BadZipFile',
246+
['Check that your file really is a valid zip archive']
247+
);
244248
}
245249
}

Diff for: src/resolve/adapters/digitalExperienceSourceAdapter.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
import { dirname, join, sep } from 'node:path';
88
import { Messages } from '@salesforce/core';
9+
import { ensureString } from '@salesforce/ts-types';
910
import { META_XML_SUFFIX } from '../../common/constants';
1011
import { SourcePath } from '../../common/types';
1112
import { SourceComponent } from '../sourceComponent';
@@ -147,7 +148,9 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
147148
// 3 because we want 'digitalExperiences' directory, 'baseType' directory and 'bundleName' directory
148149
const basePath = pathParts.slice(0, typeFolderIndex + 3).join(sep);
149150
const bundleFileName = pathParts[typeFolderIndex + 2];
150-
const suffix = this.isBundleType() ? this.type.suffix : this.registry.getParentType(this.type.id)?.suffix;
151+
const suffix = ensureString(
152+
this.isBundleType() ? this.type.suffix : this.registry.getParentType(this.type.id)?.suffix
153+
);
151154
return `${basePath}${sep}${bundleFileName}.${suffix}${META_XML_SUFFIX}`;
152155
}
153156

Diff for: src/resolve/connectionResolver.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,17 @@ export class ConnectionResolver {
112112

113113
return {
114114
components: Aggregator.filter(componentFilter).map((component) => ({
115-
fullName: ensureString(component.fullName, `Component fullName was not set for ${component.fileName}`),
115+
fullName: ensureString(
116+
component.fullName,
117+
`Component fullName was not set for ${component.fileName ?? '<missing filename>'}`
118+
),
116119
type: this.registry.getTypeByName(
117-
ensureString(component.type, `Component type was not set for ${component.fullName} (${component.fileName})`)
120+
ensureString(
121+
component.type,
122+
`Component type was not set for ${component.fullName ?? '<missing fullname>'} (${
123+
component.fileName ?? '<missing filename>'
124+
})`
125+
)
118126
),
119127
})),
120128
apiVersion: this.connection.getApiVersion(),
@@ -142,7 +150,7 @@ const listMembers =
142150
return (
143151
standardValueSetRecord.Metadata.standardValue.length && {
144152
fullName: standardValueSetRecord.MasterLabel,
145-
fileName: `${mdType.directoryName}/${standardValueSetRecord.MasterLabel}.${mdType.suffix}`,
153+
fileName: `${mdType.directoryName}/${standardValueSetRecord.MasterLabel}.${mdType.suffix ?? ''}`,
146154
type: mdType.name,
147155
}
148156
);
@@ -172,7 +180,7 @@ const listMembers =
172180
const inferFilenamesFromType =
173181
(metadataType: MetadataType) =>
174182
(member: RelevantFileProperties): RelevantFileProperties =>
175-
typeof member.fileName === 'object'
183+
typeof member.fileName === 'object' && metadataType.suffix
176184
? { ...member, fileName: `${metadataType.directoryName}/${member.fullName}.${metadataType.suffix}` }
177185
: member;
178186

Diff for: src/resolve/sourceComponent.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class SourceComponent implements MetadataComponent {
7575
}
7676
if (this.parent && this.type.ignoreParentName) {
7777
if (!this.name) {
78-
throw new SfError(`Component was initialized without a name: ${this.xml} (${this.type.name})`);
78+
throw new SfError(`Component was initialized without a name: ${this.xml ?? '<no xml>'} (${this.type.name})`);
7979
}
8080
return this.name;
8181
} else {
@@ -350,17 +350,17 @@ export class SourceComponent implements MetadataComponent {
350350
}
351351

352352
return Object.values(this.type.children.types).flatMap((childType) => {
353-
const uniqueIdElement = childType.uniqueIdElement;
353+
const { uniqueIdElement, xmlElementName } = childType;
354354

355-
if (!uniqueIdElement) {
355+
if (!uniqueIdElement || !xmlElementName) {
356356
return [];
357357
}
358-
const xmlPathToChildren = `${this.type.name}.${childType.xmlElementName}`;
358+
const xmlPathToChildren = `${this.type.name}.${xmlElementName}`;
359359
const elements = ensureArray(get(parsed, xmlPathToChildren, []));
360360
return elements.map((element) => {
361361
const name = getString(element, uniqueIdElement);
362362
if (!name) {
363-
throw new SfError(`Missing ${uniqueIdElement} on ${childType.name} in ${this.xml}`);
363+
throw new SfError(`Missing ${uniqueIdElement} on ${childType.name} in ${this.xml ?? '<no xml>'}`);
364364
}
365365
return new SourceComponent(
366366
{

Diff for: src/utils/filePathGenerator.ts

+13-11
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const filePathsFromMetadataComponent = (
4444

4545
if (type.strategies?.adapter === 'digitalExperience') {
4646
// child MD Type, the metafile is a JSON, not an XML
47-
if (type.id === 'digitalexperience') {
47+
if (type.id === 'digitalexperience' && type.metaFileSuffix) {
4848
// metafile name = metaFileSuffix for DigitalExperience.
4949
return [
5050
join(
@@ -55,12 +55,12 @@ export const filePathsFromMetadataComponent = (
5555
}
5656

5757
// parent MD Type
58-
if (type.id === 'digitalexperiencebundle') {
58+
if (type.id === 'digitalexperiencebundle' && type.suffix) {
5959
return [join(packageDirWithTypeDir, `${fullName}${sep}${basename(fullName)}.${type.suffix}${META_XML_SUFFIX}`)];
6060
}
6161
}
6262

63-
if (type.strategies?.adapter === 'decomposed') {
63+
if (type.strategies?.adapter === 'decomposed' && type.suffix) {
6464
return [join(packageDirWithTypeDir, `${fullName}${sep}${fullName}.${type.suffix}${META_XML_SUFFIX}`)];
6565
}
6666

@@ -70,19 +70,19 @@ export const filePathsFromMetadataComponent = (
7070
}
7171

7272
// Non-decomposed parents (i.e., any type that defines children and not a decomposed transformer)
73-
if (type.children) {
73+
if (type.children && type.suffix) {
7474
return [join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`)];
7575
}
7676

7777
// basic metadata (with or without folders)
78-
if (!type.children && !type.strategies) {
78+
if (!type.children && !type.strategies && type.suffix) {
7979
return (type.inFolder ?? type.folderType ? generateFolders({ fullName, type }, packageDirWithTypeDir) : []).concat([
8080
join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`),
8181
]);
8282
}
8383

8484
// matching content (with or without folders)
85-
if (type.strategies?.adapter === 'matchingContentFile') {
85+
if (type.strategies?.adapter === 'matchingContentFile' && type.suffix) {
8686
return (type.inFolder ? generateFolders({ fullName, type }, packageDirWithTypeDir) : []).concat([
8787
join(packageDirWithTypeDir, `${fullName}.${type.suffix}${META_XML_SUFFIX}`),
8888
join(packageDirWithTypeDir, `${fullName}.${type.suffix}`),
@@ -102,8 +102,10 @@ export const filePathsFromMetadataComponent = (
102102
return [
103103
join(
104104
packageDirWithTypeDir,
105-
// registry doesn't have a suffix for EB (it comes down inside the mdapi response)
106-
`${fullName}.${type.strategies?.transformer === 'staticResource' ? type.suffix : 'site'}${META_XML_SUFFIX}`
105+
// registry doesn't have a suffix for EB (it comes down inside the mdapi response). // staticResource has a suffix
106+
`${fullName}.${
107+
type.strategies?.transformer === 'staticResource' ? (type.suffix as string) : 'site'
108+
}${META_XML_SUFFIX}`
107109
),
108110
join(packageDirWithTypeDir, `${fullName}`),
109111
];
@@ -140,7 +142,7 @@ const generateFolders = ({ fullName, type }: MetadataComponent, packageDirWithTy
140142
join(
141143
packageDirWithTypeDir,
142144
`${originalArray.slice(0, index + 1).join(sep)}.${
143-
registryAccess.getTypeByName(folderType).suffix
145+
registryAccess.getTypeByName(folderType).suffix ?? ''
144146
}${META_XML_SUFFIX}`
145147
)
146148
);
@@ -159,14 +161,14 @@ const getDecomposedChildType = ({ fullName, type }: MetadataComponent, packageDi
159161
// parent
160162
join(
161163
topLevelTypeDir,
162-
`${fullName.split('.')[0]}${sep}${fullName.split('.')[0]}.${topLevelType.suffix}${META_XML_SUFFIX}`
164+
`${fullName.split('.')[0]}${sep}${fullName.split('.')[0]}.${topLevelType.suffix ?? ''}${META_XML_SUFFIX}`
163165
),
164166
// child
165167
join(
166168
topLevelTypeDir,
167169
fullName.split('.')[0],
168170
type.directoryName,
169-
`${fullName.split('.')[1]}.${type.suffix}${META_XML_SUFFIX}`
171+
`${fullName.split('.')[1]}.${type.suffix ?? ''}${META_XML_SUFFIX}`
170172
),
171173
];
172174
};

Diff for: test/snapshot/sampleProjects/customLabels-multiple/snapshots.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('Multiple large custom labels files', () => {
5353
genUniqueDir: false,
5454
});
5555
// Longer than 10 seconds could indicate a regression
56-
expect(Date.now() - convertStartTime, 'conversion should take less than 10 seconds').to.be.lessThan(10000);
56+
expect(Date.now() - convertStartTime, 'conversion should take less than 10 seconds').to.be.lessThan(10_000);
5757

5858
const convertedFiles = await getConvertedFilePaths(testOutput);
5959
for (const file of convertedFiles) {

0 commit comments

Comments
 (0)