Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compel users to release new versions of dependencies alongside their dependents #102

Merged
merged 13 commits into from
Oct 11, 2023
Merged
65 changes: 65 additions & 0 deletions src/release-specification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,71 @@ Your release spec could not be processed due to the following issues:

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.

${releaseSpecificationPath}
`.trim(),
);
});
});

it('throws if there are any packages listed in the release but another package that uses them is not listed', async () => {
await withSandbox(async (sandbox) => {
const project = buildMockProject({
workspacePackages: {
a: buildMockPackage('a', {
hasChangesSinceLatestRelease: true,
unvalidatedManifest: {
dependencies: {
b: '1.0.0',
},
},
}),
b: buildMockPackage('b', {
hasChangesSinceLatestRelease: true,
}),
},
});
const releaseSpecificationPath = path.join(
sandbox.directoryPath,
'release-spec',
);
await fs.promises.writeFile(
releaseSpecificationPath,
YAML.stringify({
packages: {
a: 'minor',
},
}),
);

await expect(
validateReleaseSpecification(project, releaseSpecificationPath),
).rejects.toThrow(
`
Your release spec could not be processed due to the following issues:

* The following packages, which have changed since their latest release, are missing.

- b

Consider including them in the release spec so that any packages that rely on them won't break in production.

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:

packages:
b: intentionally-skip
* The following packages, which uses a released package a, are missing.

- b

Consider including them in the release spec so that they won't break in production.

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:

packages:
b: intentionally-skip

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.

${releaseSpecificationPath}
`.trim(),
);
Expand Down
58 changes: 54 additions & 4 deletions src/release-specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,8 @@ export async function validateReleaseSpecification(
});
}

Object.keys(unvalidatedReleaseSpecification.packages).forEach(
(packageName, index) => {
const versionSpecifierOrDirective =
unvalidatedReleaseSpecification.packages[packageName];
Object.entries(unvalidatedReleaseSpecification.packages).forEach(
([packageName, versionSpecifierOrDirective], index) => {
const lineNumber = indexOfFirstUsableLine + index + 2;
const pkg = project.workspacePackages[packageName];

Expand Down Expand Up @@ -301,6 +299,58 @@ export async function validateReleaseSpecification(
});
}
}

// Check to compel users to release new versions of dependencies alongside their dependents
if (
pkg &&
versionSpecifierOrDirective &&
(hasProperty(IncrementableVersionParts, versionSpecifierOrDirective) ||
isValidSemver(versionSpecifierOrDirective))
) {
const missingDependencies = Object.keys({
...(pkg.unvalidatedManifest.dependencies || {}),
...(pkg.unvalidatedManifest.peerDependencies || {}),
}).filter((dependency) => {
const dependencyVersionSpecifierOrDirective =
unvalidatedReleaseSpecification.packages[dependency];

return (
dependencyVersionSpecifierOrDirective !== SKIP_PACKAGE_DIRECTIVE &&
dependencyVersionSpecifierOrDirective !==
INTENTIONALLY_SKIP_PACKAGE_DIRECTIVE &&
!hasProperty(
IncrementableVersionParts,
dependencyVersionSpecifierOrDirective,
) &&
!isValidSemver(dependencyVersionSpecifierOrDirective)
);
});

if (missingDependencies.length > 0) {
errors.push({
message: [
`The following packages, which uses a released package ${packageName}, are missing.`,
missingDependencies
.map((dependency) => ` - ${dependency}`)
.join('\n'),
" Consider including them in the release spec so that they won't break in production.",
` 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:`,
YAML.stringify({
packages: missingDependencies.reduce((object, dependency) => {
return {
...object,
[dependency]: INTENTIONALLY_SKIP_PACKAGE_DIRECTIVE,
};
}, {}),
})
.trim()
.split('\n')
.map((line) => ` ${line}`)
.join('\n'),
].join('\n\n'),
});
}
}
},
);

Expand Down
9 changes: 7 additions & 2 deletions tests/unit/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { SemVer } from 'semver';
import { isPlainObject } from '@metamask/utils';
import type { Package } from '../../src/package';
import { PackageManifestFieldNames } from '../../src/package-manifest';
import type { ValidatedPackageManifest } from '../../src/package-manifest';
import type {
UnvalidatedPackageManifest,
ValidatedPackageManifest,
} from '../../src/package-manifest';
import type { Project } from '../../src/project';

/**
Expand Down Expand Up @@ -35,6 +38,7 @@ type MockPackageOverrides = Omit<
Partial<ValidatedPackageManifest>,
PackageManifestFieldNames.Name | PackageManifestFieldNames.Version
>;
unvalidatedManifest?: UnvalidatedPackageManifest;
};

/**
Expand Down Expand Up @@ -102,6 +106,7 @@ export function buildMockPackage(

const {
validatedManifest = {},
unvalidatedManifest = {},
directoryPath = `/path/to/packages/${name}`,
manifestPath = path.join(directoryPath, 'package.json'),
changelogPath = path.join(directoryPath, 'CHANGELOG.md'),
Expand All @@ -110,7 +115,7 @@ export function buildMockPackage(

return {
directoryPath,
unvalidatedManifest: {},
unvalidatedManifest,
validatedManifest: buildMockManifest({
...validatedManifest,
[PackageManifestFieldNames.Name]: name,
Expand Down