Skip to content

Commit af31911

Browse files
feat: check ancestry when setting recommended version
1 parent 79634c8 commit af31911

4 files changed

Lines changed: 102 additions & 0 deletions

File tree

messages/package.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,15 @@ Can't retrieve package metadata. The org you specified doesn't have the required
4949
# recommendedVersionIdApiPriorTo66Error
5050

5151
To enable Recommended Version, use API version 66.0 or higher.
52+
53+
# skipAncestorCheckRequiresRecommendedVersionIdError
54+
55+
Skip ancestor check requires a Recommended Version ID.
56+
57+
# noPackageVersionsForGivenPackage2FoundError
58+
59+
No package versions were found for the given Package 2 ID (0Ho). At least one released package version must exist.
60+
61+
# recommendedVersionNotAncestorOfPriorVersionError
62+
63+
The new recommended version is not an ancestor of the previous recommended version. To bypass this check, use the --skip-ancestor-check CLI flag.

src/interfaces/packagingInterfacesAndType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export type PackageUpdateOptions = {
6363
PackageErrorUsername?: string;
6464
AppAnalyticsEnabled?: boolean;
6565
RecommendedVersionId?: string;
66+
SkipAncestorCheck?: boolean;
6667
};
6768

6869
export type PackageIdType =

src/package/package.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
import type { Schema } from '@jsforce/jsforce-node';
1717
import { Connection, Messages, SfError, SfProject } from '@salesforce/core';
18+
import { DirectedGraph } from 'graphology';
1819
import {
1920
ConvertPackageOptions,
2021
PackageCreateOptions,
@@ -48,6 +49,9 @@ const packagePrefixes = {
4849
Messages.importMessagesDirectory(__dirname);
4950
const messages = Messages.loadMessages('@salesforce/packaging', 'package');
5051

52+
const packageVersionQuery = 'SELECT SubscriberPackageVersionId, AncestorId, RecommendedVersionId FROM Package2Version';
53+
const package2Query = 'SELECT RecommendedVersionId FROM Package2';
54+
5155
export const Package2Fields = [
5256
'Id',
5357
'IsDeleted',
@@ -325,6 +329,14 @@ export class Package {
325329
throw messages.createError('recommendedVersionIdApiPriorTo66Error');
326330
}
327331

332+
if (opts.SkipAncestorCheck !== undefined && opts.RecommendedVersionId === undefined) {
333+
throw messages.createError('skipAncestorCheckRequiresRecommendedVersionIdError');
334+
}
335+
336+
if (opts.SkipAncestorCheck === undefined && opts.RecommendedVersionId !== undefined) {
337+
await this.checkRecommendedVersionAncestors(opts);
338+
}
339+
328340
const result = await this.options.connection.tooling.update('Package2', opts);
329341
if (!result.success) {
330342
throw new SfError(result.errors.join(', '));
@@ -354,4 +366,67 @@ export class Package {
354366
}
355367
return this.packageData;
356368
}
369+
370+
/**
371+
*
372+
* @param options
373+
*/
374+
private async checkRecommendedVersionAncestors(opts: PackageUpdateOptions): Promise<void> {
375+
if (opts.RecommendedVersionId === undefined) {
376+
return;
377+
}
378+
379+
const queryP2 = `${package2Query} WHERE Id = '${this.packageId}'`;
380+
const priorRecommendedVersionId = (await this.options.connection.tooling.query<PackagingSObjects.Package2>(queryP2))
381+
.records[0].RecommendedVersionId;
382+
383+
if (priorRecommendedVersionId === undefined || opts.RecommendedVersionId === priorRecommendedVersionId) {
384+
return;
385+
}
386+
387+
const queryP2V = `${packageVersionQuery} WHERE Package2Id = '${this.packageId}'`;
388+
const result = await this.options.connection.tooling.query<PackageVersionListResult>(queryP2V);
389+
390+
if (result.totalSize === 0) {
391+
throw messages.createError('noPackageVersionsForGivenPackage2FoundError');
392+
} else if (result.totalSize === 1) {
393+
return;
394+
}
395+
396+
const graph = new DirectedGraph();
397+
const stack: string[] = [opts.RecommendedVersionId];
398+
const visited: Set<string> = new Set([opts.RecommendedVersionId]);
399+
400+
result.records.forEach((record) => {
401+
graph.addNode(record.SubscriberPackageVersionId);
402+
403+
if (record.AncestorId) {
404+
graph.addNode(record.AncestorId);
405+
graph.addEdge(record.SubscriberPackageVersionId, record.AncestorId);
406+
}
407+
});
408+
409+
if (
410+
graph.outDegree(opts.RecommendedVersionId) > 0 &&
411+
result.records.some((record) => record.SubscriberPackageVersionId === priorRecommendedVersionId)
412+
) {
413+
while (stack.length > 0) {
414+
const node = stack.pop()!;
415+
416+
for (const neighbor of graph.neighbors(node)) {
417+
if (neighbor === priorRecommendedVersionId) {
418+
return;
419+
}
420+
421+
if (!visited.has(neighbor)) {
422+
visited.add(neighbor);
423+
stack.push(neighbor);
424+
}
425+
}
426+
}
427+
throw messages.createError('recommendedVersionNotAncestorOfPriorVersionError');
428+
} else if (graph.outDegree(opts.RecommendedVersionId) === 0) {
429+
return;
430+
}
431+
}
357432
}

test/package/package.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,26 @@ describe('Package', () => {
192192

193193
let objProvided = '';
194194
let optsProvided: PackageUpdateOptions = { Id: '' };
195+
let queryProvided = '';
195196
const conn = {
196197
tooling: {
197198
update: (obj: string, opts: PackageUpdateOptions) => {
198199
objProvided = obj;
199200
optsProvided = opts;
200201
return { success: true };
201202
},
203+
query: (query: string) => {
204+
queryProvided = query;
205+
return {
206+
records: [
207+
{
208+
Id: pkgId,
209+
RecommendedVersionId: '04tasdsadfasdf',
210+
},
211+
],
212+
totalSize: 1,
213+
};
214+
},
202215
},
203216
getApiVersion: () => '66.0',
204217
} as unknown as Connection;
@@ -212,6 +225,7 @@ describe('Package', () => {
212225
expect(objProvided).to.equal('Package2');
213226
expect(optsProvided.Id).to.equal(pkgId);
214227
expect(optsProvided.RecommendedVersionId).to.equal('04tasdsadfasdf');
228+
expect(queryProvided).to.equal(`SELECT RecommendedVersionId FROM Package2 WHERE Id = '${pkgId}'`);
215229
});
216230

217231
it('should error if RecommendedVersionId is defined for api version < 66.0', async () => {

0 commit comments

Comments
 (0)