From 6ad87d2db19ef72aa00878b94115d77813eb1f56 Mon Sep 17 00:00:00 2001 From: Shane McLaughlin Date: Tue, 30 Apr 2024 17:14:59 -0500 Subject: [PATCH 1/2] feat: warn about legacy suffix use (#1298) * feat: warn about legacy suffix use * fix: more support for legacy suffix * test: snapshot for bigObject indexe * test: extra ut for legacy suffix * test: handle potentially undefined childType * chore: resolve a TODO * fix: legacy suffix warning in the correct place * feat: also check legacy suffix for strict folder matching --- .../convertContext/recompositionFinalizer.ts | 1 + .../defaultMetadataTransformer.ts | 6 +- src/resolve/metadataResolver.ts | 30 ++++++++-- src/resolve/sourceComponent.ts | 17 ++++-- src/utils/path.ts | 15 ++--- .../objects/LogEntryArchive__b.object | 59 +++++++++++++++++++ .../verify-md-files.expected/package.xml | 18 ++++++ .../LogEntryArchive__b.object-meta.xml | 8 +++ .../fields/Timestamp__c.field-meta.xml | 11 ++++ .../TransactionEntryNumber__c.field-meta.xml | 15 +++++ .../fields/TransactionId__c.field-meta.xml | 13 ++++ .../LogEntryArchiveIndex.indexe-meta.xml | 17 ++++++ .../legacySuffixSupport/sfdx-project.json | 9 +++ .../legacySuffixSupport/snapshots.test.ts | 31 ++++++++++ test/utils/path.test.ts | 39 ++++++++++-- 15 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/objects/LogEntryArchive__b.object create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/package.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/LogEntryArchive__b.object-meta.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/Timestamp__c.field-meta.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionEntryNumber__c.field-meta.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionId__c.field-meta.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/indexes/LogEntryArchiveIndex.indexe-meta.xml create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/sfdx-project.json create mode 100644 test/snapshot/sampleProjects/legacySuffixSupport/snapshots.test.ts diff --git a/src/convert/convertContext/recompositionFinalizer.ts b/src/convert/convertContext/recompositionFinalizer.ts index f74d4f3cf0..3b256e16c7 100644 --- a/src/convert/convertContext/recompositionFinalizer.ts +++ b/src/convert/convertContext/recompositionFinalizer.ts @@ -118,6 +118,7 @@ const ensureStateValueWithParent = ( ); }; +/** throw if the child has no parent component */ const ensureMetadataComponentWithParent = ( child: MetadataComponent ): child is SourceComponent & { parent: SourceComponent } => { diff --git a/src/convert/transformers/defaultMetadataTransformer.ts b/src/convert/transformers/defaultMetadataTransformer.ts index 67e7b15cd4..6f7aba1794 100644 --- a/src/convert/transformers/defaultMetadataTransformer.ts +++ b/src/convert/transformers/defaultMetadataTransformer.ts @@ -5,7 +5,8 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { basename, dirname, join } from 'node:path'; -import { Messages } from '@salesforce/core'; +import { Messages } from '@salesforce/core/messages'; +import { Lifecycle } from '@salesforce/core/lifecycle'; import { SourcePath } from '../../common/types'; import { META_XML_SUFFIX } from '../../common/constants'; import { SfdxFileFormat, WriteInfo } from '../types'; @@ -118,6 +119,9 @@ const getXmlDestination = ( } } if (legacySuffix && suffix && xmlDestination.includes(legacySuffix)) { + void Lifecycle.getInstance().emitWarning( + `The ${component.type.name} component ${component.fullName} uses the legacy suffix ${legacySuffix}. This suffix is deprecated and will be removed in a future release.` + ); xmlDestination = xmlDestination.replace(legacySuffix, suffix); } return xmlDestination; diff --git a/src/resolve/metadataResolver.ts b/src/resolve/metadataResolver.ts index 93b4511035..622f2c61f8 100644 --- a/src/resolve/metadataResolver.ts +++ b/src/resolve/metadataResolver.ts @@ -385,20 +385,38 @@ const resolveTypeFromStrictFolder = .filter(folderTypeFilter(fsPath)) .find( (type) => - // any of the following 3 options is considered a good match - isMixedContentOrBundle(type) || suffixMatches(type, fsPath) || childSuffixMatches(type, fsPath) + // any of the following options is considered a good match + isMixedContentOrBundle(type) || + suffixMatches(type, fsPath) || + childSuffixMatches(type, fsPath) || + legacySuffixMatches(type, fsPath) ); }; /** the type has children and the file suffix (in source format) matches a child type suffix of the type we think it is */ const childSuffixMatches = (type: MetadataType, fsPath: string): boolean => - Object.values(type.children?.types ?? {}) - .map((childType) => `${childType.suffix}${META_XML_SUFFIX}`) - .some((s) => fsPath.endsWith(s)); + Object.values(type.children?.types ?? {}).some( + (childType) => suffixMatches(childType, fsPath) || legacySuffixMatches(childType, fsPath) + ); /** the file suffix (in source or mdapi format) matches the type suffix we think it is */ const suffixMatches = (type: MetadataType, fsPath: string): boolean => - typeof type.suffix === 'string' && [type.suffix, `${type.suffix}${META_XML_SUFFIX}`].some((s) => fsPath.endsWith(s)); + typeof type.suffix === 'string' && + (fsPath.endsWith(type.suffix) || fsPath.endsWith(appendMetaXmlSuffix(type.suffix))); + +const legacySuffixMatches = (type: MetadataType, fsPath: string): boolean => { + if ( + typeof type.legacySuffix === 'string' && + (fsPath.endsWith(type.legacySuffix) || fsPath.endsWith(appendMetaXmlSuffix(type.legacySuffix))) + ) { + void Lifecycle.getInstance().emitWarning( + `The ${type.name} component at ${fsPath} uses the legacy suffix ${type.legacySuffix}. This suffix is deprecated and will be removed in a future release.` + ); + return true; + } + return false; +}; +const appendMetaXmlSuffix = (suffix: string): string => `${suffix}${META_XML_SUFFIX}`; const isMixedContentOrBundle = (type: MetadataType): boolean => typeof type.strategies?.adapter === 'string' && ['mixedContent', 'bundle'].includes(type.strategies.adapter); diff --git a/src/resolve/sourceComponent.ts b/src/resolve/sourceComponent.ts index 366a8c2678..bc459256d6 100644 --- a/src/resolve/sourceComponent.ts +++ b/src/resolve/sourceComponent.ts @@ -5,7 +5,10 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { join, dirname } from 'node:path'; -import { Messages, SfError } from '@salesforce/core'; +import { SfError } from '@salesforce/core/sfError'; +import { Messages } from '@salesforce/core/messages'; +import { Lifecycle } from '@salesforce/core/lifecycle'; + import { XMLParser, XMLValidator } from 'fast-xml-parser'; import { get, getString, JsonMap } from '@salesforce/ts-types'; import { ensureArray } from '@salesforce/kit'; @@ -310,14 +313,18 @@ export class SourceComponent implements MetadataComponent { const children: SourceComponent[] = []; for (const fsPath of this.walk(dirPath)) { const childXml = parseMetadataXml(fsPath); - const fileIsRootXml = childXml?.suffix === this.type.suffix; + const fileIsRootXml = childXml?.suffix === this.type.suffix || childXml?.suffix === this.type.legacySuffix; if (childXml && !fileIsRootXml && this.type.children && childXml.suffix) { - // TODO: Log warning if missing child type definition const childTypeId = this.type.children?.suffixes[childXml.suffix]; - const childSuffix = this.type.children.types[childTypeId]?.suffix; + const childType = this.type.children.types[childTypeId]; + if (!childTypeId || !childType) { + void Lifecycle.getInstance().emitWarning( + `${fsPath}: Expected a child type for ${childXml.suffix} in ${this.type.name} but none was found.` + ); + } const childComponent = new SourceComponent( { - name: childSuffix ? baseWithoutSuffixes(fsPath, childSuffix) : baseName(fsPath), + name: childType?.suffix ? baseWithoutSuffixes(fsPath, childType) : baseName(fsPath), type: this.type.children.types[childTypeId], xml: fsPath, parent: this, diff --git a/src/utils/path.ts b/src/utils/path.ts index b44e20b017..69312c130d 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -25,18 +25,19 @@ export function baseName(fsPath: SourcePath): string { /** * the above baseName function doesn't handle components whose names have a `.` in them. - * this will handle that, but requires you to specify the expected suffix from the mdType. + * this will handle that, but requires you to specify the mdType to check suffixes for. * * @param fsPath The path to evaluate */ -export function baseWithoutSuffixes(fsPath: SourcePath, suffix: string): string { - return basename(fsPath) - .replace(META_XML_SUFFIX, '') - .split('.') - .filter((part) => part !== suffix) - .join('.'); +export function baseWithoutSuffixes(fsPath: SourcePath, mdType: MetadataType): string { + return basename(fsPath).replace(META_XML_SUFFIX, '').split('.').filter(stringIsNotSuffix(mdType)).join('.'); } +const stringIsNotSuffix = + (type: MetadataType) => + (part: string): boolean => + part !== type.suffix && (!type.legacySuffix || part !== type.legacySuffix); + /** * Get the name of file path extension. Different from path.extname in that it * does not include the '.' in the extension name. Returns an empty string if diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/objects/LogEntryArchive__b.object b/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/objects/LogEntryArchive__b.object new file mode 100644 index 0000000000..aa4854abf1 --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/objects/LogEntryArchive__b.object @@ -0,0 +1,59 @@ + + + Deployed + Big Object representation of Logger data, used as an alternative to the platform event LogEntryEvent__e, as well as a way to archive Logger data stored in Log__c, LogEntry__, and LogEntryTag__c + + Log Entry Archives + + Timestamp__c + Active + None + false + + true + Confidential + DateTime + + + TransactionEntryNumber__c + Active + None + The sequential number of this log entry within the transaction + false + + 10 + true + 0 + Confidential + Number + false + + + TransactionId__c + Active + None + false + + 36 + true + Confidential + Text + false + + + LogEntryArchiveIndex + + Timestamp__c + DESC + + + TransactionId__c + ASC + + + TransactionEntryNumber__c + DESC + + + + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/package.xml b/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/package.xml new file mode 100644 index 0000000000..aa2a864389 --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/__snapshots__/verify-md-files.expected/package.xml @@ -0,0 +1,18 @@ + + + + LogEntryArchive__b.Timestamp__c + LogEntryArchive__b.TransactionEntryNumber__c + LogEntryArchive__b.TransactionId__c + CustomField + + + LogEntryArchive__b + CustomObject + + + LogEntryArchive__b.LogEntryArchiveIndex + Index + + 60.0 + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/LogEntryArchive__b.object-meta.xml b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/LogEntryArchive__b.object-meta.xml new file mode 100644 index 0000000000..01a7b7f72a --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/LogEntryArchive__b.object-meta.xml @@ -0,0 +1,8 @@ + + + Deployed + Big Object representation of Logger data, used as an alternative to the platform event LogEntryEvent__e, as well as a way to archive Logger data stored in Log__c, LogEntry__, and LogEntryTag__c + + Log Entry Archives + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/Timestamp__c.field-meta.xml b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/Timestamp__c.field-meta.xml new file mode 100644 index 0000000000..afea3320ca --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/Timestamp__c.field-meta.xml @@ -0,0 +1,11 @@ + + + Timestamp__c + Active + None + false + + true + Confidential + DateTime + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionEntryNumber__c.field-meta.xml b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionEntryNumber__c.field-meta.xml new file mode 100644 index 0000000000..2ea03a28ff --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionEntryNumber__c.field-meta.xml @@ -0,0 +1,15 @@ + + + TransactionEntryNumber__c + Active + None + The sequential number of this log entry within the transaction + false + + 10 + true + 0 + Confidential + Number + false + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionId__c.field-meta.xml b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionId__c.field-meta.xml new file mode 100644 index 0000000000..b22a659f71 --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/fields/TransactionId__c.field-meta.xml @@ -0,0 +1,13 @@ + + + TransactionId__c + Active + None + false + + 36 + true + Confidential + Text + false + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/indexes/LogEntryArchiveIndex.indexe-meta.xml b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/indexes/LogEntryArchiveIndex.indexe-meta.xml new file mode 100644 index 0000000000..b8c0ff879f --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/force-app/objects/objects/LogEntryArchive__b/indexes/LogEntryArchiveIndex.indexe-meta.xml @@ -0,0 +1,17 @@ + + + LogEntryArchiveIndex + + Timestamp__c + DESC + + + TransactionId__c + ASC + + + TransactionEntryNumber__c + DESC + + + diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/sfdx-project.json b/test/snapshot/sampleProjects/legacySuffixSupport/sfdx-project.json new file mode 100644 index 0000000000..fc5278b51d --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/sfdx-project.json @@ -0,0 +1,9 @@ +{ + "packageDirectories": [ + { + "default": true, + "path": "force-app" + } + ], + "sourceApiVersion": "59.0" +} diff --git a/test/snapshot/sampleProjects/legacySuffixSupport/snapshots.test.ts b/test/snapshot/sampleProjects/legacySuffixSupport/snapshots.test.ts new file mode 100644 index 0000000000..d116c9730d --- /dev/null +++ b/test/snapshot/sampleProjects/legacySuffixSupport/snapshots.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { MDAPI_OUT, fileSnap, sourceToMdapi } from '../../helper/conversions'; + +// we don't want failing tests outputting over each other +/* eslint-disable no-await-in-loop */ + +describe('legacy suffix support (.indexe for bigObject index)', () => { + const testDir = path.join('test', 'snapshot', 'sampleProjects', 'legacySuffixSupport'); + let mdFiles: string[]; + + before(async () => { + mdFiles = await sourceToMdapi(testDir); + }); + + it('verify md files', async () => { + for (const file of mdFiles) { + await fileSnap(file, testDir); + } + }); + + after(async () => { + await Promise.all([fs.promises.rm(path.join(testDir, MDAPI_OUT), { recursive: true, force: true })]); + }); +}); diff --git a/test/utils/path.test.ts b/test/utils/path.test.ts index 52c139cd3e..03bd92c558 100644 --- a/test/utils/path.test.ts +++ b/test/utils/path.test.ts @@ -8,6 +8,7 @@ import { join } from 'node:path'; import { expect } from 'chai'; import { META_XML_SUFFIX } from '../../src/common'; import { parseMetadataXml, trimUntil, baseName, parseNestedFullName, baseWithoutSuffixes } from '../../src/utils'; +import { MetadataType } from '../../src/registry/types'; describe('Path Utils', () => { const root = join('path', 'to', 'whatever'); @@ -25,31 +26,57 @@ describe('Path Utils', () => { }); describe('baseWithoutSuffixes', () => { + const mdTypeCommon: MetadataType = { + id: 'test', + name: 'Test', + directoryName: 'tests', + }; + const mdType: MetadataType = { + ...mdTypeCommon, + suffix: 'xyz', + }; + const mdTypeLegacySuffix: MetadataType = { + ...mdType, + suffix: 'xyz', + legacySuffix: 'xyzz', + }; + it('Should strip specified suffixes from a file path with a dot', () => { const path = join(root, 'a.ext.xyz'); - expect(baseWithoutSuffixes(path, 'xyz')).to.equal('a.ext'); + expect(baseWithoutSuffixes(path, mdType)).to.equal('a.ext'); }); it('Should strip specified suffixes from a file path with a dot and standard ending', () => { const path = join(root, `a.ext.xyz${META_XML_SUFFIX}`); - expect(baseWithoutSuffixes(path, 'xyz')).to.equal('a.ext'); + expect(baseWithoutSuffixes(path, mdType)).to.equal('a.ext'); }); it('Should handle paths with no suffixes', () => { const path = join(root, 'a'); - expect(baseWithoutSuffixes(path, 'ext')).to.equal('a'); + expect(baseWithoutSuffixes(path, mdTypeCommon)).to.equal('a'); }); it('Should preserve non-matching suffixes', () => { const path = join(root, 'a.xyz'); - expect(baseWithoutSuffixes(path, 'ext')).to.equal('a.xyz'); + expect(baseWithoutSuffixes(path, mdTypeCommon)).to.equal('a.xyz'); }); it('Should remove the standard suffix and a custom suffix', () => { - const path = join(root, `a.ext${META_XML_SUFFIX}`); - expect(baseWithoutSuffixes(path, 'ext')).to.equal('a'); + const path = join(root, `a.xyz${META_XML_SUFFIX}`); + expect(baseWithoutSuffixes(path, mdType)).to.equal('a'); + }); + + it('should remove a legacy suffix', () => { + const path = join(root, 'a.xyzz'); + expect(baseWithoutSuffixes(path, mdTypeLegacySuffix)).to.equal('a'); + }); + + it('should remove a legacy suffix with the standard meta', () => { + const path = join(root, `a.xyzz${META_XML_SUFFIX}`); + expect(baseWithoutSuffixes(path, mdTypeLegacySuffix)).to.equal('a'); }); }); + describe('trimUntil', () => { it('should return given path if part is not found', () => { expect(trimUntil(root, 'test')).to.equal(root); From b21915114289c41951ec50324221cabe9f66ffd7 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Tue, 30 Apr 2024 22:15:28 +0000 Subject: [PATCH 2/2] chore(release): 11.2.0 [skip ci] --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 915efcfed4..b8c6e40bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [11.2.0](https://github.com/forcedotcom/source-deploy-retrieve/compare/11.1.3...11.2.0) (2024-04-30) + + +### Features + +* warn about legacy suffix use ([#1298](https://github.com/forcedotcom/source-deploy-retrieve/issues/1298)) ([6ad87d2](https://github.com/forcedotcom/source-deploy-retrieve/commit/6ad87d2db19ef72aa00878b94115d77813eb1f56)) + + + ## [11.1.3](https://github.com/forcedotcom/source-deploy-retrieve/compare/11.1.2...11.1.3) (2024-04-30) diff --git a/package.json b/package.json index a9e6cf713e..6002703034 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@salesforce/source-deploy-retrieve", - "version": "11.1.3", + "version": "11.2.0", "description": "JavaScript library to run Salesforce metadata deploys and retrieves", "main": "lib/src/index.js", "author": "Salesforce",