Skip to content

Commit fd93e9c

Browse files
authored
fix: include unchanged dependents in release validation (#173)
* fix: include unchanged dependents in release validation * fix: apply peer dependency validation without changes to ui * fix: use set for major bumps check * Address PR feedback * rollback releaseSelections renaming
1 parent c2ef169 commit fd93e9c

File tree

4 files changed

+295
-21
lines changed

4 files changed

+295
-21
lines changed

src/release-specification.test.ts

+226-4
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ Your release spec could not be processed due to the following issues:
602602
});
603603
});
604604

605-
it('throws if there are any packages in the release with a major version bump using the word "major", but any of their dependents defined as "peerDependencies" are not listed in the release', async () => {
605+
it('throws if there are any packages in the release with a major version bump using the word "major", but any of their dependents defined as "peerDependencies" have changes since their latest release and are not listed in the release', async () => {
606606
await withSandbox(async (sandbox) => {
607607
const project = buildMockProject({
608608
workspacePackages: {
@@ -657,7 +657,62 @@ ${releaseSpecificationPath}
657657
});
658658
});
659659

660-
it('throws if there are any packages in the release with a major version bump using a literal version, but any of their dependents defined as "peerDependencies" are not listed in the release', async () => {
660+
it('throws if there are any packages in the release with a major version bump using the word "major", but any of their dependents defined as "peerDependencies" are not listed in the release, even if they have no changes', async () => {
661+
await withSandbox(async (sandbox) => {
662+
const project = buildMockProject({
663+
workspacePackages: {
664+
a: buildMockPackage('a', {
665+
hasChangesSinceLatestRelease: true,
666+
}),
667+
b: buildMockPackage('b', {
668+
hasChangesSinceLatestRelease: false,
669+
validatedManifest: {
670+
peerDependencies: {
671+
a: '1.0.0',
672+
},
673+
},
674+
}),
675+
},
676+
});
677+
const releaseSpecificationPath = path.join(
678+
sandbox.directoryPath,
679+
'release-spec',
680+
);
681+
await fs.promises.writeFile(
682+
releaseSpecificationPath,
683+
YAML.stringify({
684+
packages: {
685+
a: 'major',
686+
},
687+
}),
688+
);
689+
690+
await expect(
691+
validateReleaseSpecification(project, releaseSpecificationPath),
692+
).rejects.toThrow(
693+
`
694+
Your release spec could not be processed due to the following issues:
695+
696+
* The following dependents of package 'a', which is being released with a major version bump, are missing from the release spec.
697+
698+
- b
699+
700+
Consider including them in the release spec so that they are compatible with the new 'a' version.
701+
702+
If you are ABSOLUTELY SURE these packages are safe to omit, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example:
703+
704+
packages:
705+
b: intentionally-skip
706+
707+
The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool.
708+
709+
${releaseSpecificationPath}
710+
`.trim(),
711+
);
712+
});
713+
});
714+
715+
it('throws if there are any packages in the release with a major version bump using a literal version, but any of their dependents defined as "peerDependencies" have changes since their latest release and are not listed in the release', async () => {
661716
await withSandbox(async (sandbox) => {
662717
const project = buildMockProject({
663718
workspacePackages: {
@@ -712,7 +767,62 @@ ${releaseSpecificationPath}
712767
});
713768
});
714769

715-
it('throws if there are any packages in the release with a major version bump using the word "major", but their dependents via "peerDependencies" have their version specified as null in the release spec', async () => {
770+
it('throws if there are any packages in the release with a major version bump using a literal version, but any of their dependents defined as "peerDependencies" are not listed in the release, even if they have no changes', async () => {
771+
await withSandbox(async (sandbox) => {
772+
const project = buildMockProject({
773+
workspacePackages: {
774+
a: buildMockPackage('a', '2.1.4', {
775+
hasChangesSinceLatestRelease: true,
776+
}),
777+
b: buildMockPackage('b', {
778+
hasChangesSinceLatestRelease: true,
779+
validatedManifest: {
780+
peerDependencies: {
781+
a: '2.1.4',
782+
},
783+
},
784+
}),
785+
},
786+
});
787+
const releaseSpecificationPath = path.join(
788+
sandbox.directoryPath,
789+
'release-spec',
790+
);
791+
await fs.promises.writeFile(
792+
releaseSpecificationPath,
793+
YAML.stringify({
794+
packages: {
795+
a: '3.0.0',
796+
},
797+
}),
798+
);
799+
800+
await expect(
801+
validateReleaseSpecification(project, releaseSpecificationPath),
802+
).rejects.toThrow(
803+
`
804+
Your release spec could not be processed due to the following issues:
805+
806+
* The following dependents of package 'a', which is being released with a major version bump, are missing from the release spec.
807+
808+
- b
809+
810+
Consider including them in the release spec so that they are compatible with the new 'a' version.
811+
812+
If you are ABSOLUTELY SURE these packages are safe to omit, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example:
813+
814+
packages:
815+
b: intentionally-skip
816+
817+
The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool.
818+
819+
${releaseSpecificationPath}
820+
`.trim(),
821+
);
822+
});
823+
});
824+
825+
it('throws if there are any packages in the release with a major version bump using the word "major", but their dependents via "peerDependencies" have changes since their latest release and have their version specified as null in the release spec', async () => {
716826
await withSandbox(async (sandbox) => {
717827
const project = buildMockProject({
718828
workspacePackages: {
@@ -768,7 +878,63 @@ ${releaseSpecificationPath}
768878
});
769879
});
770880

771-
it('throws if there are any packages in the release with a major version bump using a literal version, but their dependents via "peerDependencies" have their version specified as null in the release spec', async () => {
881+
it('throws if there are any packages in the release with a major version bump using the word "major", but their dependents via "peerDependencies" have their version specified as null in the release spec, even if they have no changes', async () => {
882+
await withSandbox(async (sandbox) => {
883+
const project = buildMockProject({
884+
workspacePackages: {
885+
a: buildMockPackage('a', {
886+
hasChangesSinceLatestRelease: true,
887+
}),
888+
b: buildMockPackage('b', {
889+
hasChangesSinceLatestRelease: false,
890+
validatedManifest: {
891+
peerDependencies: {
892+
a: '1.0.0',
893+
},
894+
},
895+
}),
896+
},
897+
});
898+
const releaseSpecificationPath = path.join(
899+
sandbox.directoryPath,
900+
'release-spec',
901+
);
902+
await fs.promises.writeFile(
903+
releaseSpecificationPath,
904+
YAML.stringify({
905+
packages: {
906+
a: 'major',
907+
b: null,
908+
},
909+
}),
910+
);
911+
912+
await expect(
913+
validateReleaseSpecification(project, releaseSpecificationPath),
914+
).rejects.toThrow(
915+
`
916+
Your release spec could not be processed due to the following issues:
917+
918+
* The following dependents of package 'a', which is being released with a major version bump, are missing from the release spec.
919+
920+
- b
921+
922+
Consider including them in the release spec so that they are compatible with the new 'a' version.
923+
924+
If you are ABSOLUTELY SURE these packages are safe to omit, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example:
925+
926+
packages:
927+
b: intentionally-skip
928+
929+
The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool.
930+
931+
${releaseSpecificationPath}
932+
`.trim(),
933+
);
934+
});
935+
});
936+
937+
it('throws if there are any packages in the release with a major version bump using a literal version, but their dependents via "peerDependencies" have changes since their latest release and have their version specified as null in the release spec', async () => {
772938
await withSandbox(async (sandbox) => {
773939
const project = buildMockProject({
774940
workspacePackages: {
@@ -818,6 +984,62 @@ Your release spec could not be processed due to the following issues:
818984
819985
The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool.
820986
987+
${releaseSpecificationPath}
988+
`.trim(),
989+
);
990+
});
991+
});
992+
993+
it('throws if there are any packages in the release with a major version bump using a literal version, but their dependents via "peerDependencies" have their version specified as null in the release spec, even if they have no changes', async () => {
994+
await withSandbox(async (sandbox) => {
995+
const project = buildMockProject({
996+
workspacePackages: {
997+
a: buildMockPackage('a', '2.1.4', {
998+
hasChangesSinceLatestRelease: true,
999+
}),
1000+
b: buildMockPackage('b', {
1001+
hasChangesSinceLatestRelease: false,
1002+
validatedManifest: {
1003+
peerDependencies: {
1004+
a: '2.1.4',
1005+
},
1006+
},
1007+
}),
1008+
},
1009+
});
1010+
const releaseSpecificationPath = path.join(
1011+
sandbox.directoryPath,
1012+
'release-spec',
1013+
);
1014+
await fs.promises.writeFile(
1015+
releaseSpecificationPath,
1016+
YAML.stringify({
1017+
packages: {
1018+
a: '3.0.0',
1019+
b: null,
1020+
},
1021+
}),
1022+
);
1023+
1024+
await expect(
1025+
validateReleaseSpecification(project, releaseSpecificationPath),
1026+
).rejects.toThrow(
1027+
`
1028+
Your release spec could not be processed due to the following issues:
1029+
1030+
* The following dependents of package 'a', which is being released with a major version bump, are missing from the release spec.
1031+
1032+
- b
1033+
1034+
Consider including them in the release spec so that they are compatible with the new 'a' version.
1035+
1036+
If you are ABSOLUTELY SURE these packages are safe to omit, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example:
1037+
1038+
packages:
1039+
b: intentionally-skip
1040+
1041+
The release spec file has been retained for you to edit again and make the necessary fixes. Once you've done this, re-run this tool.
1042+
8211043
${releaseSpecificationPath}
8221044
`.trim(),
8231045
);

src/release-specification.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,15 @@ export async function waitForUserToEditReleaseSpecification(
168168
}
169169

170170
/**
171-
* Finds all workspace packages that depend on the given package and have changes since their latest release.
171+
* Finds all workspace packages that depend on the given package.
172172
*
173173
* @param project - The project containing workspace packages.
174174
* @param packageName - The name of the package to find dependents for.
175-
* @param unvalidatedReleaseSpecificationPackages - The packages in the release specification.
176-
* @returns An array of package names that depend on the given package and are missing from the release spec.
175+
* @returns An array of package names that depend on the given package.
177176
*/
178-
export function findMissingUnreleasedDependents(
177+
export function findAllWorkspacePackagesThatDependOnPackage(
179178
project: Project,
180179
packageName: string,
181-
unvalidatedReleaseSpecificationPackages: Record<string, string | null>,
182180
): string[] {
183181
const dependentNames = Object.keys(project.workspacePackages).filter(
184182
(possibleDependentName) => {
@@ -189,14 +187,28 @@ export function findMissingUnreleasedDependents(
189187
},
190188
);
191189

192-
const changedDependentNames = dependentNames.filter(
193-
(possibleDependentName) => {
194-
return project.workspacePackages[possibleDependentName]
195-
.hasChangesSinceLatestRelease;
196-
},
190+
return dependentNames;
191+
}
192+
193+
/**
194+
* Finds all workspace packages that depend on the given package.
195+
*
196+
* @param project - The project containing workspace packages.
197+
* @param packageName - The name of the package to find dependents for.
198+
* @param unvalidatedReleaseSpecificationPackages - The packages in the release specification.
199+
* @returns An array of package names that depend on the given package and are missing from the release spec.
200+
*/
201+
export function findMissingUnreleasedDependents(
202+
project: Project,
203+
packageName: string,
204+
unvalidatedReleaseSpecificationPackages: Record<string, string | null>,
205+
): string[] {
206+
const dependentNames = findAllWorkspacePackagesThatDependOnPackage(
207+
project,
208+
packageName,
197209
);
198210

199-
return changedDependentNames.filter((dependentName) => {
211+
return dependentNames.filter((dependentName) => {
200212
return !unvalidatedReleaseSpecificationPackages[dependentName];
201213
});
202214
}

src/ui.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './project.js';
1111
import { Package } from './package.js';
1212
import {
13+
findAllWorkspacePackagesThatDependOnPackage,
1314
findMissingUnreleasedDependenciesForRelease,
1415
findMissingUnreleasedDependentsForBreakingChanges,
1516
IncrementableVersionParts,
@@ -130,15 +131,29 @@ function createApp({
130131
app.use(express.static(UI_BUILD_DIR));
131132
app.use(express.json());
132133

133-
app.get('/api/packages', (_req, res) => {
134+
app.get('/api/packages', (req, res) => {
135+
const { majorBumps } = req.query;
136+
137+
const majorBumpsArray =
138+
typeof majorBumps === 'string'
139+
? majorBumps.split(',').filter(Boolean)
140+
: (req.query.majorBumps as string[] | undefined) || [];
141+
142+
const requiredDependents = new Set(
143+
majorBumpsArray.flatMap((majorBump) =>
144+
findAllWorkspacePackagesThatDependOnPackage(project, majorBump),
145+
),
146+
);
147+
134148
const pkgs = Object.values(project.workspacePackages).filter(
135-
(pkg) => pkg.hasChangesSinceLatestRelease,
149+
(pkg) =>
150+
pkg.hasChangesSinceLatestRelease ||
151+
requiredDependents.has(pkg.validatedManifest.name),
136152
);
137153

138154
const packages = pkgs.map((pkg) => ({
139155
name: pkg.validatedManifest.name,
140156
version: pkg.validatedManifest.version.version,
141-
location: pkg.directoryPath,
142157
}));
143158

144159
res.json(packages);

0 commit comments

Comments
 (0)