Skip to content

Commit e0c82d7

Browse files
feat: added handling of next versioning
1 parent ed4ae4f commit e0c82d7

4 files changed

Lines changed: 270 additions & 0 deletions

File tree

messages/bundle_version_create.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,11 @@ No bundle found with name: %s
3737
# invalidVersionNumberFormat
3838

3939
Invalid version number format: %s
40+
41+
# majorVersionMismatch
42+
43+
Major version mismatch: expected %s, found %s
44+
45+
# invalidMinorVersionInExisting
46+
47+
Invalid minor version in existing record: %s

scripts/Icon

Whitespace-only changes.

src/package/packageBundleVersionCreate.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,53 @@ export class PackageBundleVersionCreate {
204204
if (!major || !minor) {
205205
throw new Error(messages.getMessage('invalidVersionNumberFormat', [bundle.versionNumber]));
206206
}
207+
208+
// Check if major is an integer
209+
const majorInt = parseInt(major, 10);
210+
if (isNaN(majorInt) || majorInt.toString() !== major) {
211+
throw new Error(messages.getMessage('invalidVersionNumberFormat', [bundle.versionNumber]));
212+
}
213+
214+
// Check if minor is either an integer or "next"
215+
if (minor === 'NEXT') {
216+
// Query existing bundle versions to find the highest minor version for this major version
217+
const bundleVersionQuery =
218+
'SELECT Id, PackageBundle.Id, PackageBundle.BundleName, VersionName, MajorVersion, MinorVersion, IsReleased ' +
219+
'FROM PackageBundleVersion ' +
220+
`WHERE PackageBundle.BundleName = '${bundleName}' AND MajorVersion = ${major} ` +
221+
'ORDER BY MinorVersion DESC LIMIT 1';
222+
223+
const queryResult = await connection.tooling.query<{
224+
Id: string;
225+
PackageBundle: { Id: string; BundleName: string };
226+
VersionName: string;
227+
MajorVersion: string;
228+
MinorVersion: string;
229+
IsReleased: boolean;
230+
}>(bundleVersionQuery);
231+
232+
if (queryResult.records && queryResult.records.length > 0) {
233+
const highestRecord = queryResult.records[0];
234+
235+
// Get the highest minor version and add 1
236+
const highestMinorVersion = parseInt(highestRecord.MinorVersion, 10);
237+
if (isNaN(highestMinorVersion)) {
238+
throw new Error(messages.getMessage('invalidMinorVersionInExisting', [highestRecord.MinorVersion]));
239+
}
240+
241+
const nextMinorVersion = (highestMinorVersion + 1).toString();
242+
return { MajorVersion: major, MinorVersion: nextMinorVersion };
243+
} else {
244+
// No existing versions found for this major version, start with .0
245+
return { MajorVersion: major, MinorVersion: '0' };
246+
}
247+
} else {
248+
const minorInt = parseInt(minor, 10);
249+
if (isNaN(minorInt) || minorInt.toString() !== minor) {
250+
throw new Error(messages.getMessage('invalidVersionNumberFormat', [bundle.versionNumber]));
251+
}
252+
}
253+
207254
return { MajorVersion: major, MinorVersion: minor };
208255
}
209256
}

test/package/bundleVersionCreate.test.ts

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import { PackageBundleVersion } from '../../src/package/packageBundleVersion';
1414
import { PackageBundleVersionCreate } from '../../src/package/packageBundleVersionCreate';
1515
import { BundleVersionCreateOptions, BundleSObjects } from '../../src/interfaces';
1616

17+
// Type for accessing private methods in tests
18+
interface PackageBundleVersionCreateWithPrivates {
19+
getPackageVersion(
20+
options: BundleVersionCreateOptions,
21+
project: SfProject,
22+
connection: Connection
23+
): Promise<{ MajorVersion: string; MinorVersion: string }>;
24+
}
25+
1726
async function setupProject(setup: (project: SfProject) => void = () => {}) {
1827
const project = await SfProject.resolve();
1928

@@ -543,4 +552,210 @@ describe('PackageBundleVersion.create', () => {
543552
fs.unlinkSync(componentsPath);
544553
});
545554
});
555+
556+
describe('getPackageVersion NEXT functionality', () => {
557+
it('should resolve 0.NEXT to 0.2 when existing version 0.1 exists', async () => {
558+
const testBundleName = 'testBundle';
559+
const testBundleId = '0Ho000000000000';
560+
561+
// Restore the getPackageVersion stub so we can test the real method
562+
testContext.SANDBOX.restore();
563+
564+
// Re-stub parsePackageBundleId since we still need it
565+
testContext.SANDBOX.stub(
566+
PackageBundleVersionCreate,
567+
'parsePackageBundleId' as keyof typeof PackageBundleVersionCreate
568+
).returns(testBundleId);
569+
570+
// Mock project's getSfProjectJson().getPackageBundles() to return a bundle with version "0.NEXT"
571+
testContext.SANDBOX.stub(project.getSfProjectJson(), 'getPackageBundles').returns([
572+
{
573+
name: testBundleName,
574+
versionName: 'ver 0.NEXT',
575+
versionNumber: '0.NEXT',
576+
},
577+
]);
578+
579+
// Mock connection tooling queries
580+
const queryStub = testContext.SANDBOX.stub(connection.tooling, 'query');
581+
582+
// First query: Get bundle name from bundle ID
583+
queryStub.onFirstCall().resolves({
584+
totalSize: 1,
585+
done: true,
586+
records: [{ BundleName: testBundleName }],
587+
});
588+
589+
// Second query: Get existing bundle versions (returns version 0.1)
590+
queryStub.onSecondCall().resolves({
591+
totalSize: 1,
592+
done: true,
593+
records: [
594+
{
595+
Id: '0Ho000000000001',
596+
PackageBundle: { Id: testBundleId, BundleName: testBundleName },
597+
VersionName: 'testBundle@0.1',
598+
MajorVersion: '0',
599+
MinorVersion: '1',
600+
IsReleased: true,
601+
},
602+
],
603+
});
604+
605+
const options: BundleVersionCreateOptions = {
606+
connection,
607+
project,
608+
PackageBundle: testBundleName,
609+
MajorVersion: '0',
610+
MinorVersion: 'NEXT',
611+
Ancestor: null,
612+
BundleVersionComponentsPath: '',
613+
};
614+
615+
// Call the private method through reflection for testing
616+
const result = await (
617+
PackageBundleVersionCreate as unknown as PackageBundleVersionCreateWithPrivates
618+
).getPackageVersion(options, project, connection);
619+
620+
expect(result).to.deep.equal({
621+
MajorVersion: '0',
622+
MinorVersion: '2',
623+
});
624+
625+
// Verify the queries were called correctly
626+
expect(queryStub.firstCall.args[0]).to.include(
627+
`SELECT BundleName FROM PackageBundle WHERE Id = '${testBundleId}'`
628+
);
629+
expect(queryStub.secondCall.args[0]).to.include(
630+
`SELECT Id, PackageBundle.Id, PackageBundle.BundleName, VersionName, MajorVersion, MinorVersion, IsReleased FROM PackageBundleVersion WHERE PackageBundle.BundleName = '${testBundleName}' AND MajorVersion = 0 ORDER BY MinorVersion DESC LIMIT 1`
631+
);
632+
});
633+
634+
it('should resolve 0.NEXT to 0.0 when no existing versions exist', async () => {
635+
const testBundleName = 'newBundle';
636+
const testBundleId = '0Ho000000000000';
637+
638+
// Restore the getPackageVersion stub so we can test the real method
639+
testContext.SANDBOX.restore();
640+
641+
// Re-stub parsePackageBundleId since we still need it
642+
testContext.SANDBOX.stub(
643+
PackageBundleVersionCreate,
644+
'parsePackageBundleId' as keyof typeof PackageBundleVersionCreate
645+
).returns(testBundleId);
646+
647+
// Mock project's getSfProjectJson().getPackageBundles() to return a bundle with version "0.NEXT"
648+
testContext.SANDBOX.stub(project.getSfProjectJson(), 'getPackageBundles').returns([
649+
{
650+
name: testBundleName,
651+
versionName: 'ver 0.NEXT',
652+
versionNumber: '0.NEXT',
653+
},
654+
]);
655+
656+
// Mock connection tooling queries
657+
const queryStub = testContext.SANDBOX.stub(connection.tooling, 'query');
658+
659+
// First query: Get bundle name from bundle ID
660+
queryStub.onFirstCall().resolves({
661+
totalSize: 1,
662+
done: true,
663+
records: [{ BundleName: testBundleName }],
664+
});
665+
666+
// Second query: Get existing bundle versions (returns empty - no existing versions)
667+
queryStub.onSecondCall().resolves({
668+
totalSize: 0,
669+
done: true,
670+
records: [],
671+
});
672+
673+
const options: BundleVersionCreateOptions = {
674+
connection,
675+
project,
676+
PackageBundle: testBundleName,
677+
MajorVersion: '0',
678+
MinorVersion: 'NEXT',
679+
Ancestor: null,
680+
BundleVersionComponentsPath: '',
681+
};
682+
683+
// Call the private method through reflection for testing
684+
const result = await (
685+
PackageBundleVersionCreate as unknown as PackageBundleVersionCreateWithPrivates
686+
).getPackageVersion(options, project, connection);
687+
688+
expect(result).to.deep.equal({
689+
MajorVersion: '0',
690+
MinorVersion: '0',
691+
});
692+
693+
// Verify the queries were called correctly
694+
expect(queryStub.firstCall.args[0]).to.include(
695+
`SELECT BundleName FROM PackageBundle WHERE Id = '${testBundleId}'`
696+
);
697+
expect(queryStub.secondCall.args[0]).to.include(
698+
`SELECT Id, PackageBundle.Id, PackageBundle.BundleName, VersionName, MajorVersion, MinorVersion, IsReleased FROM PackageBundleVersion WHERE PackageBundle.BundleName = '${testBundleName}' AND MajorVersion = 0 ORDER BY MinorVersion DESC LIMIT 1`
699+
);
700+
});
701+
702+
it('should handle numeric minor versions without NEXT', async () => {
703+
const testBundleName = 'simpleBundle';
704+
const testBundleId = '0Ho000000000000';
705+
706+
// Restore the getPackageVersion stub so we can test the real method
707+
testContext.SANDBOX.restore();
708+
709+
// Re-stub parsePackageBundleId since we still need it
710+
testContext.SANDBOX.stub(
711+
PackageBundleVersionCreate,
712+
'parsePackageBundleId' as keyof typeof PackageBundleVersionCreate
713+
).returns(testBundleId);
714+
715+
// Mock project's getSfProjectJson().getPackageBundles() to return a bundle with version "1.5"
716+
testContext.SANDBOX.stub(project.getSfProjectJson(), 'getPackageBundles').returns([
717+
{
718+
name: testBundleName,
719+
versionName: 'ver 1.5',
720+
versionNumber: '1.5',
721+
},
722+
]);
723+
724+
// Mock connection tooling queries
725+
const queryStub = testContext.SANDBOX.stub(connection.tooling, 'query');
726+
727+
// First query: Get bundle name from bundle ID
728+
queryStub.onFirstCall().resolves({
729+
totalSize: 1,
730+
done: true,
731+
records: [{ BundleName: testBundleName }],
732+
});
733+
734+
const options: BundleVersionCreateOptions = {
735+
connection,
736+
project,
737+
PackageBundle: testBundleName,
738+
MajorVersion: '1',
739+
MinorVersion: '5',
740+
Ancestor: null,
741+
BundleVersionComponentsPath: '',
742+
};
743+
744+
// Call the private method through reflection for testing
745+
const result = await (
746+
PackageBundleVersionCreate as unknown as PackageBundleVersionCreateWithPrivates
747+
).getPackageVersion(options, project, connection);
748+
749+
expect(result).to.deep.equal({
750+
MajorVersion: '1',
751+
MinorVersion: '5',
752+
});
753+
754+
// Verify only the first query was called (no need to query existing versions for numeric minor)
755+
expect(queryStub.calledOnce).to.be.true;
756+
expect(queryStub.firstCall.args[0]).to.include(
757+
`SELECT BundleName FROM PackageBundle WHERE Id = '${testBundleId}'`
758+
);
759+
});
760+
});
546761
});

0 commit comments

Comments
 (0)