@salesforce/packaging 4.17.0 → 4.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -247,6 +247,12 @@ class Package {
247
247
  if (opts.RecommendedVersionId !== undefined && this.options.connection.getApiVersion() < '66.0') {
248
248
  throw messages.createError('recommendedVersionIdApiPriorTo66Error');
249
249
  }
250
+ if (opts.RecommendedVersionId !== undefined) {
251
+ const trimmedId = opts.RecommendedVersionId.trim();
252
+ if (trimmedId === '' || !trimmedId.startsWith('04t') || (trimmedId.length !== 15 && trimmedId.length !== 18)) {
253
+ throw messages.createError('invalidRecommendedVersionError');
254
+ }
255
+ }
250
256
  if (skipAncestorCheck === true && opts.RecommendedVersionId === undefined) {
251
257
  throw messages.createError('skipAncestorCheckRequiresRecommendedVersionIdError');
252
258
  }
@@ -305,23 +311,30 @@ class Package {
305
311
  return;
306
312
  }
307
313
  const graph = new graphology_1.DirectedGraph();
308
- const stack = [opts.RecommendedVersionId];
309
- const visited = new Set([opts.RecommendedVersionId]);
314
+ const truncatedRecommendedVersionId = opts.RecommendedVersionId.substring(0, 15);
315
+ const stack = [truncatedRecommendedVersionId];
316
+ const visited = new Set([truncatedRecommendedVersionId]);
310
317
  result.records.forEach((record) => {
311
- graph.addNode(record.SubscriberPackageVersionId);
318
+ const truncatedSubscriberId = record.SubscriberPackageVersionId.substring(0, 15);
319
+ graph.addNode(truncatedSubscriberId);
312
320
  if (record.AncestorId) {
313
- if (!graph.hasNode(record.AncestorId)) {
314
- graph.addNode(record.AncestorId);
321
+ const truncatedAncestorId = record.AncestorId.substring(0, 15);
322
+ if (!graph.hasNode(truncatedAncestorId)) {
323
+ graph.addNode(truncatedAncestorId);
315
324
  }
316
- graph.addEdge(record.SubscriberPackageVersionId, record.AncestorId);
325
+ graph.addEdge(truncatedSubscriberId, truncatedAncestorId);
317
326
  }
318
327
  });
319
- if (graph.outDegree(opts.RecommendedVersionId) > 0 &&
320
- result.records.some((record) => record.SubscriberPackageVersionId === priorRecommendedVersionId)) {
328
+ if (!graph.hasNode(truncatedRecommendedVersionId)) {
329
+ throw messages.createError('unassociatedRecommendedVersionError');
330
+ }
331
+ const truncatedPriorRecommendedVersionId = priorRecommendedVersionId?.substring(0, 15);
332
+ if (graph.outDegree(truncatedRecommendedVersionId) > 0 &&
333
+ result.records.some((record) => record.SubscriberPackageVersionId.substring(0, 15) === truncatedPriorRecommendedVersionId)) {
321
334
  while (stack.length > 0) {
322
335
  const node = stack.pop();
323
336
  for (const neighbor of graph.neighbors(node)) {
324
- if (neighbor === priorRecommendedVersionId) {
337
+ if (neighbor === truncatedPriorRecommendedVersionId) {
325
338
  return;
326
339
  }
327
340
  if (!visited.has(neighbor)) {
@@ -331,7 +344,7 @@ class Package {
331
344
  }
332
345
  }
333
346
  }
334
- else if (graph.outDegree(opts.RecommendedVersionId) === 0 && result.records.length === 1) {
347
+ else if (graph.outDegree(truncatedRecommendedVersionId) === 0 && result.records.length === 1) {
335
348
  return;
336
349
  }
337
350
  throw messages.createError('recommendedVersionNotAncestorOfPriorVersionError');
@@ -17,4 +17,4 @@ export declare function createPackageVersionCreateRequest(context: {
17
17
  seedmetadata?: string;
18
18
  patchversion?: string;
19
19
  codecoverage?: boolean;
20
- }, packageId: string, apiVersion: string): Promise<PackagingSObjects.Package2VersionCreateRequest>;
20
+ }, packageId: string, apiVersion: string, project?: SfProject): Promise<PackagingSObjects.Package2VersionCreateRequest>;
@@ -59,6 +59,7 @@ const node_os_1 = __importDefault(require("node:os"));
59
59
  const node_fs_1 = __importDefault(require("node:fs"));
60
60
  const core_1 = require("@salesforce/core");
61
61
  const kit_1 = require("@salesforce/kit");
62
+ const project_1 = require("@salesforce/core/project");
62
63
  const pkgUtils = __importStar(require("../utils/packageUtils"));
63
64
  const packageUtils_1 = require("../utils/packageUtils");
64
65
  const interfaces_1 = require("../interfaces");
@@ -125,7 +126,7 @@ async function convertPackage(pkg, connection, options, project) {
125
126
  }, packageId,
126
127
  // TODO: createPackageVersionCreateRequest requires apiVersion exist.
127
128
  // UT fail if we validate that it exists (there might not even be a project)
128
- apiVersion);
129
+ apiVersion, project);
129
130
  // TODO: a lot of this is duplicated from PC, PVC, and PVCR.
130
131
  const createResult = await connection.tooling.create('Package2VersionCreateRequest', request);
131
132
  if (!createResult.success) {
@@ -163,7 +164,7 @@ async function convertPackage(pkg, connection, options, project) {
163
164
  * @returns {{Package2Id: string, Package2VersionMetadata: *, Tag: *, Branch: number}}
164
165
  * @private
165
166
  */
166
- async function createPackageVersionCreateRequest(context, packageId, apiVersion) {
167
+ async function createPackageVersionCreateRequest(context, packageId, apiVersion, project) {
167
168
  const uniqueId = (0, packageUtils_1.uniqid)({ template: `${packageId}-%s` });
168
169
  const packageVersTmpRoot = node_path_1.default.join(node_os_1.default.tmpdir(), uniqueId);
169
170
  const packageVersMetadataFolder = node_path_1.default.join(packageVersTmpRoot, 'md-files');
@@ -173,10 +174,12 @@ async function createPackageVersionCreateRequest(context, packageId, apiVersion)
173
174
  const settingsZipFile = node_path_1.default.join(packageVersBlobDirectory, 'settings.zip');
174
175
  const metadataZipFile = node_path_1.default.join(packageVersBlobDirectory, 'package.zip');
175
176
  const packageVersBlobZipFile = node_path_1.default.join(packageVersTmpRoot, 'package-version-info.zip');
176
- let packageDescriptorJson = {
177
- id: packageId,
178
- versionNumber: context.patchversion,
179
- };
177
+ const packageObject = project ? (0, packageUtils_1.findPackageDirectory)(project, packageId) : undefined;
178
+ let packageDescriptorJson = buildPackageDescriptorJson({
179
+ packageId,
180
+ base: { versionNumber: context.patchversion },
181
+ packageObject,
182
+ });
180
183
  const settingsGenerator = new core_1.ScratchOrgSettingsGenerator({ asDirectory: true });
181
184
  const definitionFile = context.definitionfile;
182
185
  let definitionFileJson;
@@ -211,11 +214,31 @@ async function createPackageVersionCreateRequest(context, packageId, apiVersion)
211
214
  await node_fs_1.default.promises.writeFile(node_path_1.default.join(packageVersMetadataFolder, 'package.xml'), currentPackageXml, 'utf-8');
212
215
  // Zip the packageVersMetadataFolder folder and put the zip in {packageVersBlobDirectory}/package.zip
213
216
  await pkgUtils.zipDir(packageVersMetadataFolder, metadataZipFile);
217
+ packageDescriptorJson = (0, packageUtils_1.resolveBuildUserPermissions)(packageDescriptorJson, context.codecoverage ?? false);
214
218
  await node_fs_1.default.promises.writeFile(node_path_1.default.join(packageVersBlobDirectory, 'package2-descriptor.json'), JSON.stringify(packageDescriptorJson, undefined, 2));
215
219
  // Zip the Version Info and package.zip files into another zip
216
220
  await pkgUtils.zipDir(packageVersBlobDirectory, packageVersBlobZipFile);
217
221
  return createRequestObject(packageId, context, packageVersTmpRoot, packageVersBlobZipFile);
218
222
  }
223
+ function buildPackageDescriptorJson(args) {
224
+ const { packageId, base, packageObject } = args;
225
+ const descriptor = {
226
+ id: packageId,
227
+ ...(base ?? {}),
228
+ };
229
+ if (packageObject && (0, project_1.isPackagingDirectory)(packageObject)) {
230
+ const allowedKeys = ['apexTestAccess'];
231
+ for (const key of allowedKeys) {
232
+ if (Object.prototype.hasOwnProperty.call(packageObject, key)) {
233
+ const value = packageObject[key];
234
+ if (value !== undefined) {
235
+ descriptor[key] = value;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ return descriptor;
241
+ }
219
242
  async function createRequestObject(packageId, options, packageVersTmpRoot, packageVersBlobZipFile) {
220
243
  const zipFileBase64 = (await node_fs_1.default.promises.readFile(packageVersBlobZipFile)).toString('base64');
221
244
  const requestObject = {
@@ -36,8 +36,6 @@ export declare class PackageVersionCreate {
36
36
  private createPackageVersionCreateRequestFromOptions;
37
37
  private verifyHasSource;
38
38
  private cleanGeneratedPackage;
39
- /** side effect: modifies the passed in parameter! */
40
- private resolveBuildUserPermissions;
41
39
  private packageVersionCreate;
42
40
  private getPackageDirFromId;
43
41
  private getPackageType;
@@ -350,7 +350,7 @@ class PackageVersionCreate {
350
350
  }
351
351
  packageDescriptorJson = (0, packageUtils_1.copyDescriptorProperties)(packageDescriptorJson, definitionFileJson);
352
352
  }
353
- this.resolveBuildUserPermissions(packageDescriptorJson);
353
+ packageDescriptorJson = pkgUtils.resolveBuildUserPermissions(packageDescriptorJson, this.options.codecoverage ?? false);
354
354
  // All dependencies for the packaging dir should be resolved to an 04t id to be passed to the server.
355
355
  // (see resolveSubscriberPackageVersionId() for details)
356
356
  const dependencies = packageDescriptorJson.dependencies;
@@ -476,45 +476,6 @@ class PackageVersionCreate {
476
476
  // Zip the Version Info and package.zip files into another zip
477
477
  await (0, packageUtils_1.zipDir)(packageVersBlobDirectory, packageVersBlobZipFile);
478
478
  }
479
- /** side effect: modifies the passed in parameter! */
480
- resolveBuildUserPermissions(packageDescriptorJson) {
481
- // Process permissionSet and permissionSetLicenses that should be enabled when running Apex tests
482
- // This only applies if code coverage is enabled
483
- if (this.options.codecoverage) {
484
- // Assuming no permission sets are named 0, 0n, null, undefined, false, NaN, and the empty string
485
- if (packageDescriptorJson.apexTestAccess?.permissionSets) {
486
- let permSets = packageDescriptorJson.apexTestAccess.permissionSets;
487
- if (!Array.isArray(permSets)) {
488
- permSets = permSets.split(',');
489
- }
490
- packageDescriptorJson.permissionSetNames = permSets.map((s) => s.trim());
491
- }
492
- if (packageDescriptorJson.apexTestAccess?.permissionSetLicenses) {
493
- let permissionSetLicenses = packageDescriptorJson.apexTestAccess.permissionSetLicenses;
494
- if (!Array.isArray(permissionSetLicenses)) {
495
- permissionSetLicenses = permissionSetLicenses.split(',');
496
- }
497
- packageDescriptorJson.permissionSetLicenseDeveloperNames = permissionSetLicenses.map((s) => s.trim());
498
- }
499
- }
500
- // Process permissionSet and permissionsetLicenses that should be enabled for the package metadata deploy
501
- if (packageDescriptorJson.packageMetadataAccess?.permissionSets) {
502
- let permSets = packageDescriptorJson.packageMetadataAccess.permissionSets;
503
- if (!Array.isArray(permSets)) {
504
- permSets = permSets.split(',');
505
- }
506
- packageDescriptorJson.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
507
- }
508
- if (packageDescriptorJson.packageMetadataAccess?.permissionSetLicenses) {
509
- let permissionSetLicenses = packageDescriptorJson.packageMetadataAccess.permissionSetLicenses;
510
- if (!Array.isArray(permissionSetLicenses)) {
511
- permissionSetLicenses = permissionSetLicenses.split(',');
512
- }
513
- packageDescriptorJson.packageMetadataPermissionSetLicenseNames = permissionSetLicenses.map((s) => s.trim());
514
- }
515
- delete packageDescriptorJson.apexTestAccess;
516
- delete packageDescriptorJson.packageMetadataAccess;
517
- }
518
479
  // eslint-disable-next-line complexity
519
480
  async packageVersionCreate() {
520
481
  // For the first rollout of validating sfdx-project.json data against schema, make it optional and defaulted
@@ -1,5 +1,6 @@
1
1
  import { Connection, ScratchOrgInfo, SfdcUrl, SfError, SfProject } from '@salesforce/core';
2
- import { Many } from '@salesforce/ts-types';
2
+ import { NamedPackageDir } from '@salesforce/core/project';
3
+ import { Many, Optional } from '@salesforce/ts-types';
3
4
  import type { SaveError } from '@jsforce/jsforce-node';
4
5
  import { Duration } from '@salesforce/kit';
5
6
  import { PackageDescriptorJson, PackageType, PackagingSObjects } from '../interfaces';
@@ -106,6 +107,28 @@ export declare function copyDir(src: string, dest: string): void;
106
107
  * overridden from definition file based on case-insensitive matches.
107
108
  */
108
109
  export declare function copyDescriptorProperties(packageDescriptorJson: PackageDescriptorJson, definitionFileJson: ScratchOrgInfo): PackageDescriptorJson;
110
+ /**
111
+ * Resolve descriptor permissions for build/test execution.
112
+ *
113
+ * When {@link codecoverage} is true, converts {@link apexTestAccess} settings into
114
+ * permissionSetNames and permissionSetLicenseDeveloperNames.
115
+ * Always converts {@link packageMetadataAccess} settings into
116
+ * packageMetadataPermissionSetNames and packageMetadataPermissionSetLicenseNames.
117
+ * Removes apexTestAccess and packageMetadataAccess from the returned descriptor.
118
+ *
119
+ * @param descriptor Package descriptor to normalize
120
+ * @param codecoverage Whether to enable apexTestAccess-based permission processing
121
+ * @returns A normalized copy of the descriptor with flattened permission fields
122
+ */
123
+ export declare function resolveBuildUserPermissions(descriptor: PackageDescriptorJson, codecoverage: boolean): PackageDescriptorJson;
124
+ /**
125
+ * This function finds the package directory in the project that matches the given packageId
126
+ *
127
+ * @param project The SfProject instance to search
128
+ * @param packageId The package ID to match against
129
+ * @returns A NamedPackageDir or empty object if not found
130
+ */
131
+ export declare function findPackageDirectory(project: SfProject | undefined, packageId: string): Optional<NamedPackageDir>;
109
132
  /**
110
133
  * Brand new SFDX projects contain a force-app directory tree containing empty folders
111
134
  * and a few .eslintrc.json files. We still want to consider such a directory tree
@@ -54,6 +54,8 @@ exports.numberToDuration = numberToDuration;
54
54
  exports.zipDir = zipDir;
55
55
  exports.copyDir = copyDir;
56
56
  exports.copyDescriptorProperties = copyDescriptorProperties;
57
+ exports.resolveBuildUserPermissions = resolveBuildUserPermissions;
58
+ exports.findPackageDirectory = findPackageDirectory;
57
59
  exports.isPackageDirectoryEffectivelyEmpty = isPackageDirectoryEffectivelyEmpty;
58
60
  /*
59
61
  * Copyright 2025, Salesforce, Inc.
@@ -77,6 +79,7 @@ const node_stream_1 = require("node:stream");
77
79
  const node_util_1 = __importStar(require("node:util"));
78
80
  const node_crypto_1 = require("node:crypto");
79
81
  const core_1 = require("@salesforce/core");
82
+ const project_1 = require("@salesforce/core/project");
80
83
  const ts_types_1 = require("@salesforce/ts-types");
81
84
  const kit_1 = require("@salesforce/kit");
82
85
  const globby_1 = __importDefault(require("globby"));
@@ -450,6 +453,69 @@ function copyDescriptorProperties(packageDescriptorJson, definitionFileJson) {
450
453
  return [[prop], matchCase ? definitionFileJsonCopy[matchCase] : undefined];
451
454
  })));
452
455
  }
456
+ /**
457
+ * Resolve descriptor permissions for build/test execution.
458
+ *
459
+ * When {@link codecoverage} is true, converts {@link apexTestAccess} settings into
460
+ * permissionSetNames and permissionSetLicenseDeveloperNames.
461
+ * Always converts {@link packageMetadataAccess} settings into
462
+ * packageMetadataPermissionSetNames and packageMetadataPermissionSetLicenseNames.
463
+ * Removes apexTestAccess and packageMetadataAccess from the returned descriptor.
464
+ *
465
+ * @param descriptor Package descriptor to normalize
466
+ * @param codecoverage Whether to enable apexTestAccess-based permission processing
467
+ * @returns A normalized copy of the descriptor with flattened permission fields
468
+ */
469
+ function resolveBuildUserPermissions(descriptor, codecoverage) {
470
+ const copy = structuredClone(descriptor);
471
+ if (codecoverage) {
472
+ if (copy.apexTestAccess?.permissionSets) {
473
+ let permSets = copy.apexTestAccess.permissionSets;
474
+ if (!Array.isArray(permSets))
475
+ permSets = permSets.split(',');
476
+ copy.permissionSetNames = permSets.map((s) => s.trim());
477
+ }
478
+ if (copy.apexTestAccess?.permissionSetLicenses) {
479
+ let psl = copy.apexTestAccess.permissionSetLicenses;
480
+ if (!Array.isArray(psl))
481
+ psl = psl.split(',');
482
+ copy.permissionSetLicenseDeveloperNames = psl.map((s) => s.trim());
483
+ }
484
+ }
485
+ if (copy.packageMetadataAccess?.permissionSets) {
486
+ let permSets = copy.packageMetadataAccess.permissionSets;
487
+ if (!Array.isArray(permSets))
488
+ permSets = permSets.split(',');
489
+ copy.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
490
+ }
491
+ if (copy.packageMetadataAccess?.permissionSetLicenses) {
492
+ let psl = copy.packageMetadataAccess.permissionSetLicenses;
493
+ if (!Array.isArray(psl))
494
+ psl = psl.split(',');
495
+ copy.packageMetadataPermissionSetLicenseNames = psl.map((s) => s.trim());
496
+ }
497
+ delete copy.apexTestAccess;
498
+ delete copy.packageMetadataAccess;
499
+ return copy;
500
+ }
501
+ /**
502
+ * This function finds the package directory in the project that matches the given packageId
503
+ *
504
+ * @param project The SfProject instance to search
505
+ * @param packageId The package ID to match against
506
+ * @returns A NamedPackageDir or empty object if not found
507
+ */
508
+ function findPackageDirectory(project, packageId) {
509
+ if (!project) {
510
+ return undefined;
511
+ }
512
+ return project.findPackage((namedPackageDir) => {
513
+ if (!(0, project_1.isPackagingDirectory)(namedPackageDir))
514
+ return false;
515
+ const dirPackageId = project.getPackageIdFromAlias(namedPackageDir.package) ?? namedPackageDir.package;
516
+ return dirPackageId === packageId;
517
+ });
518
+ }
453
519
  /**
454
520
  * Brand new SFDX projects contain a force-app directory tree containing empty folders
455
521
  * and a few .eslintrc.json files. We still want to consider such a directory tree
@@ -61,3 +61,11 @@ No package versions were found for the given Package 2 ID (0Ho). At least one re
61
61
  # recommendedVersionNotAncestorOfPriorVersionError
62
62
 
63
63
  The new recommended version is not a descendant of the previous recommended version. To bypass this check, use the --skip-ancestor-check CLI flag.
64
+
65
+ # invalidRecommendedVersionError
66
+
67
+ Provide a valid subscriber package version (04t) for the recommended version.
68
+
69
+ # unassociatedRecommendedVersionError
70
+
71
+ The provided recommended version isn't associated with this package.
@@ -92,11 +92,11 @@ Provide a valid positive number for %s. %d
92
92
 
93
93
  # errorAncestorNoneNotAllowed
94
94
 
95
- Can’t create package version because you didn’t specify a package ancestor. Set the ancestor version to %s, and try creating the package version. You can also specify --skipancestorcheck to override the ancestry requirement.
95
+ Can’t create package version because you didn’t specify a package ancestor. Set the ancestor version to %s and try creating the package version again. You can also specify --skip-ancestor-check to override the ancestry requirement.
96
96
 
97
97
  # errorAncestorNotHighest
98
98
 
99
- Can’t create package version. The ancestor version [%s] you specified isn’t the highest released package version. Set the ancestor version to %s, and try creating the package version again. You can also specify --skipancestorcheck to override the ancestry requirement.
99
+ Can’t create package version. The ancestor version [%s] you specified isn’t the highest released package version. Set the ancestor version to %s and try creating the package version again. You can also specify --skip-ancestor-check to override the ancestry requirement.
100
100
 
101
101
  # errorInvalidBuildNumberForKeywords
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/packaging",
3
- "version": "4.17.0",
3
+ "version": "4.17.2",
4
4
  "description": "Packaging library for the Salesforce packaging platform",
5
5
  "main": "lib/exported",
6
6
  "types": "lib/exported.d.ts",
@@ -43,8 +43,8 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "@jsforce/jsforce-node": "^3.10.8",
46
- "@salesforce/core": "^8.23.2",
47
- "@salesforce/kit": "^3.2.3",
46
+ "@salesforce/core": "^8.23.3",
47
+ "@salesforce/kit": "^3.2.4",
48
48
  "@salesforce/schemas": "^1.10.3",
49
49
  "@salesforce/source-deploy-retrieve": "^12.24.0",
50
50
  "@salesforce/ts-types": "^2.0.11",