Skip to content

Commit 2a3692b

Browse files
committed
refactor: change scope of reused utility
1 parent 62d2eac commit 2a3692b

6 files changed

Lines changed: 162 additions & 282 deletions

File tree

src/package/packageConvert.ts

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ import {
3030
ScratchOrgSettingsGenerator,
3131
} from '@salesforce/core';
3232
import { camelCaseToTitleCase, Duration, env } from '@salesforce/kit';
33+
import { isPackagingDirectory } from '@salesforce/core/project';
3334
import { Many } from '@salesforce/ts-types';
3435
import * as pkgUtils from '../utils/packageUtils';
35-
import { copyDescriptorProperties, generatePackageAliasEntry, uniqid } from '../utils/packageUtils';
36+
import {
37+
copyDescriptorProperties,
38+
generatePackageAliasEntry,
39+
uniqid,
40+
resolveBuildUserPermissions,
41+
} from '../utils/packageUtils';
3642
import {
3743
ConvertPackageOptions,
3844
PackageDescriptorJson,
@@ -199,7 +205,7 @@ export async function createPackageVersionCreateRequest(
199205
const metadataZipFile = path.join(packageVersBlobDirectory, 'package.zip');
200206
const packageVersBlobZipFile = path.join(packageVersTmpRoot, 'package-version-info.zip');
201207

202-
let packageDescriptorJson: PackageDescriptorJson = pkgUtils.buildPackageDescriptorJson({
208+
let packageDescriptorJson: PackageDescriptorJson = buildPackageDescriptorJson({
203209
packageId,
204210
base: { versionNumber: context.patchversion },
205211
project,
@@ -259,7 +265,7 @@ export async function createPackageVersionCreateRequest(
259265
// Zip the packageVersMetadataFolder folder and put the zip in {packageVersBlobDirectory}/package.zip
260266
await pkgUtils.zipDir(packageVersMetadataFolder, metadataZipFile);
261267

262-
resolveBuildUserPermissions(packageDescriptorJson, context.codecoverage ?? false);
268+
packageDescriptorJson = resolveBuildUserPermissions(packageDescriptorJson, context.codecoverage ?? false);
263269

264270
await fs.promises.writeFile(
265271
path.join(packageVersBlobDirectory, 'package2-descriptor.json'),
@@ -270,48 +276,38 @@ export async function createPackageVersionCreateRequest(
270276
return createRequestObject(packageId, context, packageVersTmpRoot, packageVersBlobZipFile);
271277
}
272278

273-
/** side effect: modifies the passed in parameter! */
274-
function resolveBuildUserPermissions(packageDescriptorJson: PackageDescriptorJson, codecoverage: boolean): void {
275-
// Process permissionSet and permissionSetLicenses that should be enabled when running Apex tests
276-
// This only applies if code coverage is enabled
277-
if (codecoverage) {
278-
// Assuming no permission sets are named 0, 0n, null, undefined, false, NaN, and the empty string
279-
if (packageDescriptorJson.apexTestAccess?.permissionSets) {
280-
let permSets = packageDescriptorJson.apexTestAccess.permissionSets;
281-
if (!Array.isArray(permSets)) {
282-
permSets = permSets.split(',');
283-
}
284-
packageDescriptorJson.permissionSetNames = permSets.map((s) => s.trim());
285-
}
286-
287-
if (packageDescriptorJson.apexTestAccess?.permissionSetLicenses) {
288-
let permissionSetLicenses = packageDescriptorJson.apexTestAccess.permissionSetLicenses;
289-
if (!Array.isArray(permissionSetLicenses)) {
290-
permissionSetLicenses = permissionSetLicenses.split(',');
291-
}
292-
packageDescriptorJson.permissionSetLicenseDeveloperNames = permissionSetLicenses.map((s) => s.trim());
293-
}
294-
}
279+
function buildPackageDescriptorJson(args: {
280+
packageId: string;
281+
base?: Partial<PackageDescriptorJson>;
282+
project?: SfProject;
283+
}): PackageDescriptorJson {
284+
const { packageId, base, project } = args;
285+
const descriptor: Partial<PackageDescriptorJson> = {
286+
id: packageId,
287+
...(base ?? {}),
288+
};
295289

296-
// Process permissionSet and permissionsetLicenses that should be enabled for the package metadata deploy
297-
if (packageDescriptorJson.packageMetadataAccess?.permissionSets) {
298-
let permSets = packageDescriptorJson.packageMetadataAccess.permissionSets;
299-
if (!Array.isArray(permSets)) {
300-
permSets = permSets.split(',');
301-
}
302-
packageDescriptorJson.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
303-
}
290+
if (project) {
291+
const packageObject = project.findPackage((namedPackageDir) => {
292+
if (!isPackagingDirectory(namedPackageDir)) return false;
293+
const dirPackageId = project.getPackageIdFromAlias(namedPackageDir.package) ?? namedPackageDir.package;
294+
return dirPackageId === packageId;
295+
});
304296

305-
if (packageDescriptorJson.packageMetadataAccess?.permissionSetLicenses) {
306-
let permissionSetLicenses = packageDescriptorJson.packageMetadataAccess.permissionSetLicenses;
307-
if (!Array.isArray(permissionSetLicenses)) {
308-
permissionSetLicenses = permissionSetLicenses.split(',');
297+
if (packageObject && isPackagingDirectory(packageObject)) {
298+
const allowedKeys: Array<keyof PackageDescriptorJson> = ['apexTestAccess'];
299+
for (const key of allowedKeys) {
300+
if (Object.prototype.hasOwnProperty.call(packageObject, key)) {
301+
const value = (packageObject as unknown as PackageDescriptorJson)[key];
302+
if (value !== undefined) {
303+
(descriptor as PackageDescriptorJson)[key] = value as never;
304+
}
305+
}
306+
}
309307
}
310-
packageDescriptorJson.packageMetadataPermissionSetLicenseNames = permissionSetLicenses.map((s) => s.trim());
311308
}
312309

313-
delete packageDescriptorJson.apexTestAccess;
314-
delete packageDescriptorJson.packageMetadataAccess;
310+
return descriptor as PackageDescriptorJson;
315311
}
316312

317313
async function createRequestObject(

src/package/packageVersionCreate.ts

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,10 @@ export class PackageVersionCreate {
409409
packageDescriptorJson = copyDescriptorProperties(packageDescriptorJson, definitionFileJson);
410410
}
411411

412-
this.resolveBuildUserPermissions(packageDescriptorJson);
412+
packageDescriptorJson = pkgUtils.resolveBuildUserPermissions(
413+
packageDescriptorJson,
414+
this.options.codecoverage ?? false
415+
);
413416

414417
// All dependencies for the packaging dir should be resolved to an 04t id to be passed to the server.
415418
// (see resolveSubscriberPackageVersionId() for details)
@@ -609,49 +612,7 @@ export class PackageVersionCreate {
609612
await zipDir(packageVersBlobDirectory, packageVersBlobZipFile);
610613
}
611614

612-
/** side effect: modifies the passed in parameter! */
613-
private resolveBuildUserPermissions(packageDescriptorJson: PackageDescriptorJson): void {
614-
// Process permissionSet and permissionSetLicenses that should be enabled when running Apex tests
615-
// This only applies if code coverage is enabled
616-
if (this.options.codecoverage) {
617-
// Assuming no permission sets are named 0, 0n, null, undefined, false, NaN, and the empty string
618-
if (packageDescriptorJson.apexTestAccess?.permissionSets) {
619-
let permSets = packageDescriptorJson.apexTestAccess.permissionSets;
620-
if (!Array.isArray(permSets)) {
621-
permSets = permSets.split(',');
622-
}
623-
packageDescriptorJson.permissionSetNames = permSets.map((s) => s.trim());
624-
}
625-
626-
if (packageDescriptorJson.apexTestAccess?.permissionSetLicenses) {
627-
let permissionSetLicenses = packageDescriptorJson.apexTestAccess.permissionSetLicenses;
628-
if (!Array.isArray(permissionSetLicenses)) {
629-
permissionSetLicenses = permissionSetLicenses.split(',');
630-
}
631-
packageDescriptorJson.permissionSetLicenseDeveloperNames = permissionSetLicenses.map((s) => s.trim());
632-
}
633-
}
634-
635-
// Process permissionSet and permissionsetLicenses that should be enabled for the package metadata deploy
636-
if (packageDescriptorJson.packageMetadataAccess?.permissionSets) {
637-
let permSets = packageDescriptorJson.packageMetadataAccess.permissionSets;
638-
if (!Array.isArray(permSets)) {
639-
permSets = permSets.split(',');
640-
}
641-
packageDescriptorJson.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
642-
}
643-
644-
if (packageDescriptorJson.packageMetadataAccess?.permissionSetLicenses) {
645-
let permissionSetLicenses = packageDescriptorJson.packageMetadataAccess.permissionSetLicenses;
646-
if (!Array.isArray(permissionSetLicenses)) {
647-
permissionSetLicenses = permissionSetLicenses.split(',');
648-
}
649-
packageDescriptorJson.packageMetadataPermissionSetLicenseNames = permissionSetLicenses.map((s) => s.trim());
650-
}
651-
652-
delete packageDescriptorJson.apexTestAccess;
653-
delete packageDescriptorJson.packageMetadataAccess;
654-
}
615+
// removed: resolveBuildUserPermissions - replaced by pkgUtils.normalizeBuildUserPermissions
655616

656617
// eslint-disable-next-line complexity
657618
private async packageVersionCreate(): Promise<PackageVersionCreateRequestResult> {

src/utils/packageUtils.ts

Lines changed: 44 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { pipeline as cbPipeline } from 'node:stream';
2020
import util, { promisify } from 'node:util';
2121
import { randomBytes } from 'node:crypto';
2222
import { Connection, Logger, Messages, ScratchOrgInfo, SfdcUrl, SfError, SfProject } from '@salesforce/core';
23-
import { isPackagingDirectory } from '@salesforce/core/project';
2423
import { isNumber, isString, Many, Optional } from '@salesforce/ts-types';
2524
import type { SaveError } from '@jsforce/jsforce-node';
2625
import { Duration, ensureArray } from '@salesforce/kit';
@@ -51,9 +50,7 @@ const ID_REGISTRY = [
5150
label: 'Subscriber Package Version Id',
5251
},
5352
];
54-
const PACKAGE_DESCRIPTOR_FIELD_ALLOWLIST: ReadonlyArray<keyof PackageDescriptorJson> = [
55-
'apexTestAccess',
56-
] as const satisfies ReadonlyArray<keyof PackageDescriptorJson>;
53+
//
5754

5855
export type IdRegistryValue = { prefix: string; label: string };
5956
export type IdRegistry = {
@@ -527,66 +524,57 @@ export function copyDescriptorProperties(
527524
}
528525

529526
/**
530-
* Copies only the allowlisted properties from source to target. Mutates and returns target.
527+
* Resolve descriptor permissions for build/test execution.
528+
*
529+
* When {@link codecoverage} is true, converts {@link apexTestAccess} settings into
530+
* permissionSetNames and permissionSetLicenseDeveloperNames.
531+
* Always converts {@link packageMetadataAccess} settings into
532+
* packageMetadataPermissionSetNames and packageMetadataPermissionSetLicenseNames.
533+
* Removes apexTestAccess and packageMetadataAccess from the returned descriptor.
534+
*
535+
* @param descriptor Package descriptor to normalize
536+
* @param codecoverage Whether to enable apexTestAccess-based permission processing
537+
* @returns A normalized copy of the descriptor with flattened permission fields
531538
*/
532-
function copyPropsAllowlist<T extends object, K extends keyof T>(
533-
target: Partial<T>,
534-
source: T | undefined,
535-
allowed: readonly K[]
536-
): Partial<T> {
537-
if (!source) return target;
538-
for (const key of allowed) {
539-
if (Object.prototype.hasOwnProperty.call(source, key)) {
540-
const value = source[key];
541-
if (value !== undefined) {
542-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
543-
// @ts-ignore - index type on generic target
544-
target[key] = value;
545-
}
539+
export function resolveBuildUserPermissions(
540+
descriptor: PackageDescriptorJson,
541+
codecoverage: boolean
542+
): PackageDescriptorJson {
543+
const copy = structuredClone(descriptor);
544+
545+
if (codecoverage) {
546+
if (copy.apexTestAccess?.permissionSets) {
547+
let permSets = copy.apexTestAccess.permissionSets;
548+
if (!Array.isArray(permSets)) permSets = permSets.split(',');
549+
copy.permissionSetNames = permSets.map((s) => s.trim());
546550
}
547-
}
548-
return target;
549-
}
550551

551-
/**
552-
* Builds a base PackageDescriptorJson for a given packageId and optionally copies
553-
* an allowlist of properties from the matching packaging directory in the project.
554-
*
555-
* @param packageId
556-
* @param base - a partial package descriptor json with predefined properties
557-
* @param project
558-
* @returns a package descriptor json with the predefined properties and allowed properties from the matching packaging directory in the project
559-
*/
560-
export function buildPackageDescriptorJson(args: {
561-
packageId: string;
562-
base?: Partial<PackageDescriptorJson>;
563-
project?: SfProject;
564-
}): PackageDescriptorJson {
565-
const { packageId, base, project } = args;
566-
const descriptor: Partial<PackageDescriptorJson> = {
567-
id: packageId,
568-
...(base ?? {}),
569-
};
570-
571-
if (project) {
572-
const packageObject = project.findPackage((namedPackageDir) => {
573-
if (!isPackagingDirectory(namedPackageDir)) return false;
574-
const dirPackageId = project.getPackageIdFromAlias(namedPackageDir.package) ?? namedPackageDir.package;
575-
return dirPackageId === packageId;
576-
});
577-
578-
if (packageObject && isPackagingDirectory(packageObject)) {
579-
copyPropsAllowlist(
580-
descriptor as PackageDescriptorJson,
581-
packageObject as unknown as PackageDescriptorJson,
582-
PACKAGE_DESCRIPTOR_FIELD_ALLOWLIST
583-
);
552+
if (copy.apexTestAccess?.permissionSetLicenses) {
553+
let psl = copy.apexTestAccess.permissionSetLicenses;
554+
if (!Array.isArray(psl)) psl = psl.split(',');
555+
copy.permissionSetLicenseDeveloperNames = psl.map((s) => s.trim());
584556
}
585557
}
586558

587-
return descriptor as PackageDescriptorJson;
559+
if (copy.packageMetadataAccess?.permissionSets) {
560+
let permSets = copy.packageMetadataAccess.permissionSets;
561+
if (!Array.isArray(permSets)) permSets = permSets.split(',');
562+
copy.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
563+
}
564+
565+
if (copy.packageMetadataAccess?.permissionSetLicenses) {
566+
let psl = copy.packageMetadataAccess.permissionSetLicenses;
567+
if (!Array.isArray(psl)) psl = psl.split(',');
568+
copy.packageMetadataPermissionSetLicenseNames = psl.map((s) => s.trim());
569+
}
570+
571+
delete copy.apexTestAccess;
572+
delete copy.packageMetadataAccess;
573+
return copy;
588574
}
589575

576+
//
577+
590578
/**
591579
* Brand new SFDX projects contain a force-app directory tree containing empty folders
592580
* and a few .eslintrc.json files. We still want to consider such a directory tree

test/package/packageConvert.test.ts

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -223,67 +223,6 @@ describe('packageConvert', () => {
223223
// apexTestAccess should be removed from the descriptor
224224
expect(package2DescriptorJson).to.not.have.string('apexTestAccess');
225225
});
226-
227-
it('should NOT process apexTestAccess permissions when codecoverage is false', async () => {
228-
$$.inProject(true);
229-
const project = SfProject.getInstance();
230-
231-
// Create the force-app directory that's referenced in sfdx-project.json
232-
await fs.promises.mkdir(path.join(project.getPath(), 'force-app'), { recursive: true });
233-
234-
// Set up sfdx-project.json with packageDirectory containing apexTestAccess
235-
project.getSfProjectJson().set('packageDirectories', [
236-
{
237-
path: 'force-app',
238-
package: '0Ho3i000000Gmj6CAC',
239-
apexTestAccess: {
240-
permissionSets: ['Test_Permission_Set'],
241-
permissionSetLicenses: ['TestPsl'],
242-
},
243-
},
244-
]);
245-
await project.getSfProjectJson().write();
246-
247-
// Definition file is for scratch org settings only (no apexTestAccess)
248-
const definitionFile = {
249-
orgName: 'test org name',
250-
edition: 'Developer',
251-
};
252-
const packageVersTmpRoot = path.join(os.tmpdir(), 'config-no-codecoverage');
253-
await fs.promises.mkdir(packageVersTmpRoot, { recursive: true });
254-
const scratchDefPath = path.join(packageVersTmpRoot, 'scratch-no-codecoverage.json');
255-
await fs.promises.writeFile(scratchDefPath, JSON.stringify(definitionFile, undefined, 2));
256-
257-
// Create a spy to capture what's written to package2-descriptor.json
258-
const writeFileSpy = $$.SANDBOX.spy(fs.promises, 'writeFile');
259-
260-
const request = await createPackageVersionCreateRequest(
261-
{ installationkey: '123', definitionfile: scratchDefPath, buildinstance: 'myInstance', codecoverage: false },
262-
'0Ho3i000000Gmj6CAC',
263-
'54.0',
264-
project
265-
);
266-
267-
expect(request).to.have.all.keys(
268-
'CalculateCodeCoverage',
269-
'InstallKey',
270-
'Instance',
271-
'IsConversionRequest',
272-
'Package2Id',
273-
'VersionInfo'
274-
);
275-
expect(request.CalculateCodeCoverage).to.equal(false);
276-
277-
// Get the contents of package2-descriptor.json
278-
const package2DescriptorJson = writeFileSpy.secondCall.args[1];
279-
280-
// Verify package2-descriptor.json does NOT contain permission set fields when codecoverage is false
281-
expect(package2DescriptorJson).to.not.be.undefined;
282-
expect(package2DescriptorJson).to.not.have.string('permissionSetNames');
283-
expect(package2DescriptorJson).to.not.have.string('permissionSetLicenseDeveloperNames');
284-
// apexTestAccess should still be removed from the descriptor
285-
expect(package2DescriptorJson).to.not.have.string('apexTestAccess');
286-
});
287226
});
288227

289228
describe('findOrCreatePackage2', () => {

0 commit comments

Comments
 (0)