From d2f10666f6b7042d287da6d97694d6980495cca2 Mon Sep 17 00:00:00 2001 From: Jamie Feingold Date: Fri, 13 Mar 2026 13:51:07 -0500 Subject: [PATCH 1/2] feat: enforces that only pinned dependencies should be pinned @W-21064297@ --- src/commands/cli/release/build.ts | 6 +++-- src/package.ts | 4 ++- test/package.test.ts | 42 +++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/commands/cli/release/build.ts b/src/commands/cli/release/build.ts index 98c5ff4a1..1b1fb3f72 100644 --- a/src/commands/cli/release/build.ts +++ b/src/commands/cli/release/build.ts @@ -7,7 +7,7 @@ import { promisify } from 'node:util'; import { exec as execSync, ExecException } from 'node:child_process'; -import { arrayWithDeprecation, Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; +import { Flags, SfCommand, Ux } from '@salesforce/sf-plugins-core'; import { ensureString } from '@salesforce/ts-types'; import { Env } from '@salesforce/kit'; import { Octokit } from '@octokit/core'; @@ -51,8 +51,10 @@ export default class build extends SfCommand { default: true, allowNo: true, }), - only: arrayWithDeprecation({ + only: Flags.string({ summary: messages.getMessage('flags.only.summary'), + multiple: true, + delimiter: ',', }), 'pinned-deps': Flags.boolean({ summary: messages.getMessage('flags.pinned-deps.summary'), diff --git a/src/package.ts b/src/package.ts index 159321f72..9cae0f36d 100644 --- a/src/package.ts +++ b/src/package.ts @@ -214,8 +214,10 @@ export class Package extends AsyncOptionalCreatable { // find dependency in package.json (could be an npm alias) const depInfo = this.getDependencyInfo(name, { ...dependencies, ...resolutions, ...jitPlugins }); + const isPinned: boolean = (this.packageJson.pinnedDependencies ?? []).includes(depInfo.packageName); + // if a version is not provided, we'll look up the "latest" version - depInfo.finalVersion = version ?? this.getDistTags(depInfo.packageName).latest; + depInfo.finalVersion = `${isPinned ? '' : '^'}${version ?? this.getDistTags(depInfo.packageName).latest}`; // return if version did not change if (depInfo.currentVersion === depInfo.finalVersion) return; diff --git a/test/package.test.ts b/test/package.test.ts index efd1830d6..7da85f0fe 100644 --- a/test/package.test.ts +++ b/test/package.test.ts @@ -130,6 +130,7 @@ describe('Package', () => { '@salesforce/plugin-config': '1.2.3', 'left-pad': '1.1.1', }, + pinnedDependencies: ['left-pad'], resolutions: { '@salesforce/source-deploy-retrieve': '1.0.0', }, @@ -149,17 +150,25 @@ describe('Package', () => { }); it('should look up latest version if not provided', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config', '@salesforce/jit-me']); + const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config', '@salesforce/jit-me', 'left-pad']); expect(results).to.deep.equal([ { packageName: '@salesforce/plugin-config', currentVersion: '1.2.3', - finalVersion: '9.9.9', + // Dependency should be unpinned because it is not listed in `pinnedDependencies` + finalVersion: '^9.9.9', }, { packageName: '@salesforce/jit-me', currentVersion: '1.0.0', + // Dependency should be unpinned because it is not listed in `pinnedDependencies` + finalVersion: '^9.9.9', + }, + { + packageName: 'left-pad', + currentVersion: '1.1.1', + // Dependency should be pinned because it is listed in `pinnedDependencies` finalVersion: '9.9.9', }, ]); @@ -167,33 +176,40 @@ describe('Package', () => { it('should used passed in version', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@11.0.0']); + const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@11.0.0', 'left-pad@11.0.0']); expect(results).to.deep.equal([ { packageName: '@salesforce/plugin-config', currentVersion: '1.2.3', + // Dependency should be unpinned because it's not in `pinnedDependencies` + finalVersion: '^11.0.0', + }, + { + packageName: 'left-pad', + currentVersion: '1.1.1', + // Dependency should be pinned because it's in `pinnedDependencies` finalVersion: '11.0.0', }, ]); }); - it('should work with non-namespaced package', async () => { + it('should unpin a pinned version even if it is already up-to-date', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['left-pad']); + const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@1.2.3']); expect(results).to.deep.equal([ { - packageName: 'left-pad', - currentVersion: '1.1.1', - finalVersion: '9.9.9', + packageName: '@salesforce/plugin-config', + currentVersion: '1.2.3', + finalVersion: '^1.2.3', }, ]); }); - it('should return an empty array if all versions are already up to date', async () => { + it('should return an empty array if all bumped versions are already up to date', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@1.2.3']); + const results = pkg.bumpDependencyVersions(['left-pad@1.1.1']); expect(results).to.deep.equal([]); }); @@ -202,21 +218,21 @@ describe('Package', () => { const pkg = await Package.create(); pkg.bumpDependencyVersions(['@salesforce/plugin-config@3.3.3']); - expect(pkg.packageJson.dependencies['@salesforce/plugin-config']).to.equal('3.3.3'); + expect(pkg.packageJson.dependencies['@salesforce/plugin-config']).to.equal('^3.3.3'); }); it('should update resolutions in package.json', async () => { const pkg = await Package.create(); pkg.bumpDependencyVersions(['@salesforce/source-deploy-retrieve@1.0.1']); assert(pkg.packageJson.resolutions); - expect(pkg.packageJson.resolutions['@salesforce/source-deploy-retrieve']).to.equal('1.0.1'); + expect(pkg.packageJson.resolutions['@salesforce/source-deploy-retrieve']).to.equal('^1.0.1'); }); it('should update jit in package.json', async () => { const pkg = await Package.create(); pkg.bumpDependencyVersions(['@salesforce/jit-me@1.0.1']); assert(pkg.packageJson.oclif?.jitPlugins); - expect(pkg.packageJson.oclif.jitPlugins['@salesforce/jit-me']).to.equal('1.0.1'); + expect(pkg.packageJson.oclif.jitPlugins['@salesforce/jit-me']).to.equal('^1.0.1'); }); }); From 2c39146c2fe943f89142df5dc1afb3d5daef56ac Mon Sep 17 00:00:00 2001 From: Jamie Feingold Date: Thu, 26 Mar 2026 13:30:53 -0500 Subject: [PATCH 2/2] fix: feedback from code review @W-21064297@ --- src/package.ts | 15 +++++++++++++-- test/package.test.ts | 46 ++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/package.ts b/src/package.ts index 9cae0f36d..c5336ca82 100644 --- a/src/package.ts +++ b/src/package.ts @@ -214,10 +214,10 @@ export class Package extends AsyncOptionalCreatable { // find dependency in package.json (could be an npm alias) const depInfo = this.getDependencyInfo(name, { ...dependencies, ...resolutions, ...jitPlugins }); - const isPinned: boolean = (this.packageJson.pinnedDependencies ?? []).includes(depInfo.packageName); + const shouldPin: boolean = this.shouldPinDependency(depInfo.packageName); // if a version is not provided, we'll look up the "latest" version - depInfo.finalVersion = `${isPinned ? '' : '^'}${version ?? this.getDistTags(depInfo.packageName).latest}`; + depInfo.finalVersion = `${shouldPin ? '' : '^'}${version ?? this.getDistTags(depInfo.packageName).latest}`; // return if version did not change if (depInfo.currentVersion === depInfo.finalVersion) return; @@ -343,6 +343,17 @@ export class Package extends AsyncOptionalCreatable { 'dist-tags': {}, }; } + + private shouldPinDependency(dependencyName: string): boolean { + const pinnedDependencies: string[] = this.packageJson.pinnedDependencies ?? []; + const jitDependencies: string[] = this.packageJson.oclif?.jitPlugins + ? Object.keys(this.packageJson.oclif.jitPlugins) + : []; + + const dependenciesThatShouldBePinned = [...pinnedDependencies, ...jitDependencies]; + + return dependenciesThatShouldBePinned.includes(dependencyName); + } } const getNameAndTag = (plugin: string): [name: string, tag: string | undefined] => { diff --git a/test/package.test.ts b/test/package.test.ts index 7da85f0fe..6c605c78c 100644 --- a/test/package.test.ts +++ b/test/package.test.ts @@ -130,7 +130,7 @@ describe('Package', () => { '@salesforce/plugin-config': '1.2.3', 'left-pad': '1.1.1', }, - pinnedDependencies: ['left-pad'], + pinnedDependencies: ['@salesforce/plugin-config'], resolutions: { '@salesforce/source-deploy-retrieve': '1.0.0', }, @@ -156,20 +156,20 @@ describe('Package', () => { { packageName: '@salesforce/plugin-config', currentVersion: '1.2.3', - // Dependency should be unpinned because it is not listed in `pinnedDependencies` - finalVersion: '^9.9.9', + // Dependency should be pinned because it is listed in `pinnedDependencies` + finalVersion: '9.9.9', }, { packageName: '@salesforce/jit-me', currentVersion: '1.0.0', - // Dependency should be unpinned because it is not listed in `pinnedDependencies` - finalVersion: '^9.9.9', + // Dependency should be pinned, even though it's not in `pinnedDependencies`, because it's in `oclif.jitPlugins` + finalVersion: '9.9.9', }, { packageName: 'left-pad', currentVersion: '1.1.1', - // Dependency should be pinned because it is listed in `pinnedDependencies` - finalVersion: '9.9.9', + // Dependency should be unpinned because it is not listed in `pinnedDependencies` + finalVersion: '^9.9.9', }, ]); }); @@ -182,57 +182,57 @@ describe('Package', () => { { packageName: '@salesforce/plugin-config', currentVersion: '1.2.3', - // Dependency should be unpinned because it's not in `pinnedDependencies` - finalVersion: '^11.0.0', + // Dependency should be pinned because it's in `pinnedDependencies` + finalVersion: '11.0.0', }, { packageName: 'left-pad', currentVersion: '1.1.1', - // Dependency should be pinned because it's in `pinnedDependencies` - finalVersion: '11.0.0', + // Dependency should be unpinned because it's not in `pinnedDependencies` + finalVersion: '^11.0.0', }, ]); }); - it('should unpin a pinned version even if it is already up-to-date', async () => { + it('should unpin a not-explicitly-pinned version even if it is already up-to-date', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@1.2.3']); + const results = pkg.bumpDependencyVersions(['left-pad@11.0.0']); expect(results).to.deep.equal([ { - packageName: '@salesforce/plugin-config', - currentVersion: '1.2.3', - finalVersion: '^1.2.3', + packageName: 'left-pad', + currentVersion: '1.1.1', + finalVersion: '^11.0.0', }, ]); }); it('should return an empty array if all bumped versions are already up to date', async () => { const pkg = await Package.create(); - const results = pkg.bumpDependencyVersions(['left-pad@1.1.1']); + const results = pkg.bumpDependencyVersions(['@salesforce/plugin-config@1.2.3']); expect(results).to.deep.equal([]); }); - it('should update dependencies in package.json', async () => { + it('should update unpinned dependencies in package.json to unpinned version', async () => { const pkg = await Package.create(); - pkg.bumpDependencyVersions(['@salesforce/plugin-config@3.3.3']); + pkg.bumpDependencyVersions(['left-pad@3.3.3']); - expect(pkg.packageJson.dependencies['@salesforce/plugin-config']).to.equal('^3.3.3'); + expect(pkg.packageJson.dependencies['left-pad']).to.equal('^3.3.3'); }); - it('should update resolutions in package.json', async () => { + it('should update resolutions in package.json to unpinned version', async () => { const pkg = await Package.create(); pkg.bumpDependencyVersions(['@salesforce/source-deploy-retrieve@1.0.1']); assert(pkg.packageJson.resolutions); expect(pkg.packageJson.resolutions['@salesforce/source-deploy-retrieve']).to.equal('^1.0.1'); }); - it('should update jit in package.json', async () => { + it('should update jit in package.json to pinned version', async () => { const pkg = await Package.create(); pkg.bumpDependencyVersions(['@salesforce/jit-me@1.0.1']); assert(pkg.packageJson.oclif?.jitPlugins); - expect(pkg.packageJson.oclif.jitPlugins['@salesforce/jit-me']).to.equal('^1.0.1'); + expect(pkg.packageJson.oclif.jitPlugins['@salesforce/jit-me']).to.equal('1.0.1'); }); });