Skip to content

Commit db188f1

Browse files
mcmireGudahtt
andauthored
Discourage postponing release of changed packages (#29)
When the tool generates a release spec, it will list all packages that have changed since their last release. Editing the release spec to remove a package so that it is no longer included in the release is potentially dangerous, because it means that any package which relies on that package that *is* included in the release could be broken in production. Since it's sometimes necessary to delay the release of a package, this commit changes the release spec validation step such that if it detects that a package that should be listed in the release spec *isn't*, it will throw an error, advising the user of the danger, yet provide a hidden option should the user really want to proceed. Co-authored-by: Mark Stacey <[email protected]>
1 parent 7d545df commit db188f1

File tree

7 files changed

+618
-52
lines changed

7 files changed

+618
-52
lines changed

src/functional.test.ts

Lines changed: 284 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ describe('create-release-branch (functional)', () => {
3232
version: '1.2.3',
3333
directoryPath: 'packages/d',
3434
},
35-
e: {
36-
name: '@scope/e',
37-
version: '0.0.3',
38-
directoryPath: 'packages/e',
39-
},
4035
},
4136
workspaces: {
4237
'.': ['packages/*'],
@@ -68,11 +63,6 @@ describe('create-release-branch (functional)', () => {
6863
foo: 'bar',
6964
},
7065
});
71-
await environment.updateJsonFileWithinPackage('e', 'package.json', {
72-
scripts: {
73-
foo: 'bar',
74-
},
75-
});
7666

7767
await environment.runTool({
7868
releaseSpecification: {
@@ -120,13 +110,6 @@ describe('create-release-branch (functional)', () => {
120110
version: '1.2.4',
121111
scripts: { foo: 'bar' },
122112
});
123-
expect(
124-
await environment.readJsonFileWithinPackage('e', 'package.json'),
125-
).toStrictEqual({
126-
name: '@scope/e',
127-
version: '0.0.3',
128-
scripts: { foo: 'bar' },
129-
});
130113
},
131114
);
132115
});
@@ -289,5 +272,289 @@ describe('create-release-branch (functional)', () => {
289272
},
290273
);
291274
});
275+
276+
it('errors before making any changes if the edited release spec omits changed packages', async () => {
277+
await withMonorepoProjectEnvironment(
278+
{
279+
packages: {
280+
$root$: {
281+
name: '@scope/monorepo',
282+
version: '1.0.0',
283+
directoryPath: '.',
284+
},
285+
a: {
286+
name: '@scope/a',
287+
version: '0.1.2',
288+
directoryPath: 'packages/a',
289+
},
290+
b: {
291+
name: '@scope/b',
292+
version: '1.1.4',
293+
directoryPath: 'packages/b',
294+
},
295+
c: {
296+
name: '@scope/c',
297+
version: '2.0.13',
298+
directoryPath: 'packages/c',
299+
},
300+
d: {
301+
name: '@scope/d',
302+
version: '1.2.3',
303+
directoryPath: 'packages/d',
304+
},
305+
},
306+
workspaces: {
307+
'.': ['packages/*'],
308+
},
309+
},
310+
async (environment) => {
311+
await expect(
312+
environment.runTool({
313+
releaseSpecification: {
314+
packages: {
315+
a: 'major',
316+
c: 'patch',
317+
},
318+
},
319+
}),
320+
).toThrowExecaError(
321+
`
322+
Error: Your release spec could not be processed due to the following issues:
323+
324+
* The following packages, which have changed since their latest release, are missing.
325+
326+
- @scope/b
327+
- @scope/d
328+
329+
Consider including them in the release spec so that any packages that rely on them won't break in production.
330+
331+
If you are ABSOLUTELY SURE that this won't occur, however, and want to postpone the release of a package, then list it with a directive of "intentionally-skip". For example:
332+
333+
packages:
334+
"@scope/b": intentionally-skip
335+
"@scope/d": intentionally-skip
336+
337+
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.
338+
339+
<<release-spec-file-path>>
340+
<<stack-trace>>
341+
`.trim(),
342+
{
343+
replacements: [
344+
{
345+
from: `${environment.tempDirectoryPath}/RELEASE_SPEC`,
346+
to: '<<release-spec-file-path>>',
347+
},
348+
],
349+
},
350+
);
351+
352+
expect(await environment.readJsonFile('package.json')).toStrictEqual({
353+
name: '@scope/monorepo',
354+
version: '1.0.0',
355+
private: true,
356+
workspaces: ['packages/*'],
357+
});
358+
expect(
359+
await environment.readJsonFileWithinPackage('a', 'package.json'),
360+
).toStrictEqual({
361+
name: '@scope/a',
362+
version: '0.1.2',
363+
});
364+
expect(
365+
await environment.readJsonFileWithinPackage('b', 'package.json'),
366+
).toStrictEqual({
367+
name: '@scope/b',
368+
version: '1.1.4',
369+
});
370+
expect(
371+
await environment.readJsonFileWithinPackage('c', 'package.json'),
372+
).toStrictEqual({
373+
name: '@scope/c',
374+
version: '2.0.13',
375+
});
376+
expect(
377+
await environment.readJsonFileWithinPackage('d', 'package.json'),
378+
).toStrictEqual({
379+
name: '@scope/d',
380+
version: '1.2.3',
381+
});
382+
},
383+
);
384+
});
385+
386+
it('does not update the versions of any packages that have been tagged with intentionally-skip', async () => {
387+
await withMonorepoProjectEnvironment(
388+
{
389+
packages: {
390+
$root$: {
391+
name: '@scope/monorepo',
392+
version: '1.0.0',
393+
directoryPath: '.',
394+
},
395+
a: {
396+
name: '@scope/a',
397+
version: '0.1.2',
398+
directoryPath: 'packages/a',
399+
},
400+
b: {
401+
name: '@scope/b',
402+
version: '1.1.4',
403+
directoryPath: 'packages/b',
404+
},
405+
c: {
406+
name: '@scope/c',
407+
version: '2.0.13',
408+
directoryPath: 'packages/c',
409+
},
410+
d: {
411+
name: '@scope/d',
412+
version: '1.2.3',
413+
directoryPath: 'packages/d',
414+
},
415+
},
416+
workspaces: {
417+
'.': ['packages/*'],
418+
},
419+
},
420+
async (environment) => {
421+
await environment.runTool({
422+
releaseSpecification: {
423+
packages: {
424+
a: 'major',
425+
b: 'intentionally-skip',
426+
c: 'patch',
427+
d: 'intentionally-skip',
428+
},
429+
},
430+
});
431+
432+
expect(await environment.readJsonFile('package.json')).toStrictEqual({
433+
name: '@scope/monorepo',
434+
version: '2.0.0',
435+
private: true,
436+
workspaces: ['packages/*'],
437+
});
438+
expect(
439+
await environment.readJsonFileWithinPackage('a', 'package.json'),
440+
).toStrictEqual({
441+
name: '@scope/a',
442+
version: '1.0.0',
443+
});
444+
expect(
445+
await environment.readJsonFileWithinPackage('b', 'package.json'),
446+
).toStrictEqual({
447+
name: '@scope/b',
448+
version: '1.1.4',
449+
});
450+
expect(
451+
await environment.readJsonFileWithinPackage('c', 'package.json'),
452+
).toStrictEqual({
453+
name: '@scope/c',
454+
version: '2.0.14',
455+
});
456+
expect(
457+
await environment.readJsonFileWithinPackage('d', 'package.json'),
458+
).toStrictEqual({
459+
name: '@scope/d',
460+
version: '1.2.3',
461+
});
462+
},
463+
);
464+
});
465+
466+
it('does not update the changelogs of any packages that have been tagged with intentionally-skip', async () => {
467+
await withMonorepoProjectEnvironment(
468+
{
469+
packages: {
470+
$root$: {
471+
name: '@scope/monorepo',
472+
version: '1.0.0',
473+
directoryPath: '.',
474+
},
475+
a: {
476+
name: '@scope/a',
477+
version: '1.0.0',
478+
directoryPath: 'packages/a',
479+
},
480+
b: {
481+
name: '@scope/b',
482+
version: '1.0.0',
483+
directoryPath: 'packages/b',
484+
},
485+
},
486+
workspaces: {
487+
'.': ['packages/*'],
488+
},
489+
createInitialCommit: false,
490+
},
491+
async (environment) => {
492+
// Create an initial commit
493+
await environment.writeFileWithinPackage(
494+
'a',
495+
'CHANGELOG.md',
496+
buildChangelog(`
497+
## [Unreleased]
498+
499+
[Unreleased]: https://github.com/example-org/example-repo
500+
`),
501+
);
502+
await environment.writeFileWithinPackage(
503+
'b',
504+
'CHANGELOG.md',
505+
buildChangelog(`
506+
## [Unreleased]
507+
508+
[Unreleased]: https://github.com/example-org/example-repo
509+
`),
510+
);
511+
await environment.createCommit('Initial commit');
512+
513+
// Create another commit that only changes "a"
514+
await environment.writeFileWithinPackage(
515+
'a',
516+
'dummy.txt',
517+
'Some content',
518+
);
519+
await environment.createCommit('Update "a"');
520+
521+
// Run the tool
522+
await environment.runTool({
523+
releaseSpecification: {
524+
packages: {
525+
a: 'major',
526+
b: 'intentionally-skip',
527+
},
528+
},
529+
});
530+
531+
// Only "a" should get updated
532+
expect(
533+
await environment.readFileWithinPackage('a', 'CHANGELOG.md'),
534+
).toStrictEqual(
535+
buildChangelog(`
536+
## [Unreleased]
537+
538+
## [2.0.0]
539+
### Uncategorized
540+
- Update "a"
541+
- Initial commit
542+
543+
[Unreleased]: https://github.com/example-org/example-repo/compare/v2.0.0...HEAD
544+
[2.0.0]: https://github.com/example-org/example-repo/releases/tag/v2.0.0
545+
`),
546+
);
547+
expect(
548+
await environment.readFileWithinPackage('b', 'CHANGELOG.md'),
549+
).toStrictEqual(
550+
buildChangelog(`
551+
## [Unreleased]
552+
553+
[Unreleased]: https://github.com/example-org/example-repo
554+
`),
555+
);
556+
},
557+
);
558+
});
292559
});
293560
});

0 commit comments

Comments
 (0)