diff --git a/docs/manifest-releaser.md b/docs/manifest-releaser.md index 62221a474..1299d9b91 100644 --- a/docs/manifest-releaser.md +++ b/docs/manifest-releaser.md @@ -564,6 +564,15 @@ workspace, and updates any packages that were directly bumped by release-please, or that should be patch-bumped because one of their transitive dependencies was bumped. The cargo lockfile is also updated. +A workspace member whose manifest sets `publish = false` is still bumped so that +its own dependents stay consistent, but it does not get a release of its own: no +release pull request, tag, or `.release-please-manifest.json` entry is created +for it when it is pulled in only as a dependent. A `publish = false` crate that +is listed in your release-please config and has changes of its own is released +as usual (its version is tracked and tagged); `publish = false` only suppresses +the release of a crate that would otherwise be released solely because a +dependency was bumped. + Note: when the Rust releaser is used standalone (with the `release-pr` / `github-release` commands), it also tries to update monorepo dependencies, but it doesn't build a crate graph. When the Rust releaser is used in conjunction diff --git a/src/plugins/cargo-workspace.ts b/src/plugins/cargo-workspace.ts index 0a6efe6f2..32ebfe708 100644 --- a/src/plugins/cargo-workspace.ts +++ b/src/plugins/cargo-workspace.ts @@ -72,6 +72,11 @@ interface CrateInfo { * Parsed cargo manifest */ manifest: CargoManifest; + + /** + * `false` when the manifest sets `publish = false`. + */ + publishable: boolean; } /** @@ -163,6 +168,7 @@ export class CargoWorkspace extends WorkspacePlugin { manifest, manifestContent: manifestContent.parsedContent, manifestPath, + publishable: manifest.package?.publish !== false, }); } return { @@ -394,6 +400,10 @@ export class CargoWorkspace extends WorkspacePlugin { return candidate.config.releaseType === 'rust'; } + protected shouldCreateReleasePullRequest(pkg: CrateInfo): boolean { + return pkg.publishable; + } + protected packageNameFromPackage(pkg: CrateInfo): string { return pkg.name; } diff --git a/src/plugins/workspace.ts b/src/plugins/workspace.ts index 5e58b73b7..e2aa96862 100644 --- a/src/plugins/workspace.ts +++ b/src/plugins/workspace.ts @@ -147,6 +147,14 @@ export abstract class WorkspacePlugin extends ManifestPlugin { newCandidatePaths.add(newCandidate.path); newCandidates.push(newCandidate); } + } else if (!this.shouldCreateReleasePullRequest(pkg)) { + // Dependent-only bump: its version is already in `updatedVersions`, so + // dependents are still updated, but it opts out of its own release. + this.logger.info( + `Skipping release pull request for ${this.packageNameFromPackage( + pkg + )}` + ); } else { // otherwise, build a new pull request with changelog and entry update this.logger.info( @@ -266,7 +274,10 @@ export abstract class WorkspacePlugin extends ManifestPlugin { const version = this.bumpVersion(pkg); this.logger.debug(`version: ${version} forced bump`); updatedVersions.set(packageName, version); - if (this.isReleaseVersion(version)) { + if ( + this.isReleaseVersion(version) && + this.shouldCreateReleasePullRequest(pkg) + ) { updatedPathVersions.set(this.pathFromPackage(pkg), version); } } @@ -286,6 +297,15 @@ export abstract class WorkspacePlugin extends ManifestPlugin { return true; } + /** + * Whether a dependent-only package should get its own release pull request. + * Defaults to `true`; the package is version-bumped regardless. + * @param {T} _pkg The dependent package being considered + */ + protected shouldCreateReleasePullRequest(_pkg: T): boolean { + return true; + } + /** * Given a package, return the new bumped version after updating * the dependency. diff --git a/src/updaters/rust/common.ts b/src/updaters/rust/common.ts index 9713a5977..b104abcc1 100644 --- a/src/updaters/rust/common.ts +++ b/src/updaters/rust/common.ts @@ -45,6 +45,9 @@ export interface CargoWorkspace { export interface CargoPackage { name?: string; version?: string; + // `publish = false` (or an allow-list of registries). When `false`, the + // crate is a workspace member that is never published to a registry. + publish?: boolean | string[]; } export interface CargoDependencies { diff --git a/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.lock b/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.lock new file mode 100644 index 000000000..f30b89953 --- /dev/null +++ b/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.lock @@ -0,0 +1,7 @@ +[[package]] +name = "pkgA" +version = "1.1.1" + +[[package]] +name = "pkgUnpub" +version = "6.6.6" diff --git a/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.toml b/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.toml new file mode 100644 index 000000000..f7cfa7392 --- /dev/null +++ b/test/fixtures/plugins/cargo-workspace-publish-false/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["packages/rustA", "packages/rustUnpub"] diff --git a/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustA/Cargo.toml b/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustA/Cargo.toml new file mode 100644 index 000000000..a12461937 --- /dev/null +++ b/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustA/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "pkgA" +version = "1.1.1" + +[dependencies] +tracing = "1.0.0" diff --git a/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustUnpub/Cargo.toml b/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustUnpub/Cargo.toml new file mode 100644 index 000000000..31acb232f --- /dev/null +++ b/test/fixtures/plugins/cargo-workspace-publish-false/packages/rustUnpub/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pkgUnpub" +version = "6.6.6" +publish = false + +[dependencies] +pkgA = { version = "1.1.1", path = "../pkgA" } +tracing = "1.0.0" diff --git a/test/plugins/cargo-workspace.ts b/test/plugins/cargo-workspace.ts index 65f46ad22..2cba3f578 100644 --- a/test/plugins/cargo-workspace.ts +++ b/test/plugins/cargo-workspace.ts @@ -312,6 +312,59 @@ describe('CargoWorkspace plugin', () => { assertHasUpdate(updates, 'packages/rustE/Cargo.toml', RawContent); snapshot(dateSafe(rustCandidate!.pullRequest.body.toString())); }); + it('does not open a release pull request for a publish = false dependent', async () => { + const publishFalsePath = + './test/fixtures/plugins/cargo-workspace-publish-false'; + // Only pkgA has a release candidate; pkgUnpub (publish = false) depends + // on it and is pulled into the graph only as a dependent. + const candidates: CandidateReleasePullRequest[] = [ + buildMockCandidatePullRequest('packages/rustA', 'rust', '1.1.2', { + component: 'pkgA', + updates: [ + buildMockPackageUpdate( + 'packages/rustA/Cargo.toml', + 'packages/rustA/Cargo.toml' + ), + ], + }), + ]; + stubFilesFromFixtures({ + sandbox, + github, + fixturePath: publishFalsePath, + files: [ + 'Cargo.toml', + 'packages/rustA/Cargo.toml', + 'packages/rustUnpub/Cargo.toml', + ], + flatten: false, + targetBranch: 'main', + }); + sandbox + .stub(github, 'findFilesByGlobAndRef') + .withArgs('packages/rustA', 'main') + .resolves(['packages/rustA']) + .withArgs('packages/rustUnpub', 'main') + .resolves(['packages/rustUnpub']); + plugin = new CargoWorkspace( + github, + 'main', + { + 'packages/rustA': { + releaseType: 'rust', + }, + }, + { + merge: false, + } + ); + const newCandidates = await plugin.run(candidates); + // Only pkgA is released — no standalone candidate for the publish = false + // crate. + expect(newCandidates).lengthOf(1); + expect(newCandidates.find(c => c.path === 'packages/rustUnpub')).to.be + .undefined; + }); it('can skip merging rust packages', async () => { // This is the same setup as 'walks dependency tree and updates previously untouched packages' const candidates: CandidateReleasePullRequest[] = [