diff --git a/packages/config/src/cargo.ts b/packages/config/src/cargo.ts index d0b9a54a..6527d0bd 100644 --- a/packages/config/src/cargo.ts +++ b/packages/config/src/cargo.ts @@ -14,6 +14,12 @@ export function parseCargoToml(cargoPath: string): CargoManifest { return TOML.parse(content) as CargoManifest; } +export function isCratePublishable(manifest: CargoManifest): boolean { + const publish = manifest.package?.publish; + const noRegistries = Array.isArray(publish) && publish.length === 0; + return publish !== false && !noRegistries; +} + export function isCargoToml(filePath: string): boolean { return path.basename(filePath) === 'Cargo.toml'; } diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 2aa9f02b..beb395cb 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -1,4 +1,4 @@ -export { type CargoManifest, isCargoToml, parseCargoToml } from './cargo.js'; +export { type CargoManifest, isCargoToml, isCratePublishable, parseCargoToml } from './cargo.js'; export { ConfigError } from './errors.js'; export { type LoadOptions, diff --git a/packages/publish/src/registry/cargo.ts b/packages/publish/src/registry/cargo.ts index 6cefe8a2..7c0ddf43 100644 --- a/packages/publish/src/registry/cargo.ts +++ b/packages/publish/src/registry/cargo.ts @@ -1,5 +1,6 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; +import { isCratePublishable } from '@releasekit/config'; import { buildDependencyGraph, debug, type GraphPackage } from '@releasekit/core'; import { createGitCli } from '@releasekit/git'; import { createPublishError, PublishErrorCode } from '../errors/index.js'; @@ -129,6 +130,10 @@ function findCrates(updates: Array<{ newVersion: string; filePath: string; dir: if (!cargo.package?.name) { continue; } + if (!isCratePublishable(cargo)) { + debug(`Skipping crate ${cargo.package.name}: publish = false`); + continue; + } crates.push({ packageName: cargo.package.name, diff --git a/packages/publish/test/unit/stages/cargo-publish.spec.ts b/packages/publish/test/unit/stages/cargo-publish.spec.ts index 6d7e6ab1..16166157 100644 --- a/packages/publish/test/unit/stages/cargo-publish.spec.ts +++ b/packages/publish/test/unit/stages/cargo-publish.spec.ts @@ -399,6 +399,28 @@ describe('cargo-publish stage', () => { expect((init?.headers as Record)?.['User-Agent']).toMatch(/releasekit/); }); + it.each(['publish = false', 'publish = []'])('should skip a crate with %s', async (publishLine) => { + const { execCommand } = await import('../../../src/utils/exec.js'); + const dir = createTmpDir(); + const pkgDir = path.join(dir, 'packages', 'napi'); + fs.mkdirSync(pkgDir, { recursive: true }); + fs.writeFileSync(path.join(pkgDir, 'package.json'), '{"name":"napi","version":"1.0.0"}'); + fs.writeFileSync(path.join(pkgDir, 'Cargo.toml'), `[package]\nname = "napi"\nversion = "1.0.0"\n${publishLine}\n`); + + const ctx = createContext(dir, { + input: { + dryRun: false, + updates: [{ packageName: 'napi', newVersion: '1.1.0', filePath: 'packages/napi/package.json' }], + changelogs: [], + tags: [], + }, + }); + await runCargoPublishStage(ctx); + + expect(vi.mocked(execCommand).mock.calls.filter((c) => c[0] === 'cargo')).toHaveLength(0); + expect(ctx.output.cargo).toHaveLength(0); + }); + it('should skip packages without Cargo.toml', async () => { const { execCommand } = await import('../../../src/utils/exec.js'); const dir = createTmpDir(); diff --git a/packages/version/src/core/versionEngine.ts b/packages/version/src/core/versionEngine.ts index ce9a4655..4bfe58d8 100644 --- a/packages/version/src/core/versionEngine.ts +++ b/packages/version/src/core/versionEngine.ts @@ -3,7 +3,7 @@ import * as path from 'node:path'; import { cwd } from 'node:process'; import { inspect } from 'node:util'; import { getPackagesSync, type Package, type Packages } from '@manypkg/get-packages'; -import { filterPackagesByConfig, parseCargoToml, parsePubspec } from '@releasekit/config'; +import { filterPackagesByConfig, isCratePublishable, parseCargoToml, parsePubspec } from '@releasekit/config'; import { isPrivatePackageJson, shouldMatchPackageTargets } from '@releasekit/core'; import { minimatch } from 'minimatch'; import * as yaml from 'yaml'; @@ -187,9 +187,7 @@ export class VersionEngine { // Parse Cargo.toml const cargoData = parseCargoToml(fullCargoPath); - // Skip crates that explicitly opt out of publishing (`publish = false` or `publish = []`). - const publishField = cargoData.package?.publish; - if (publishField === false || (Array.isArray(publishField) && publishField.length === 0)) { + if (!isCratePublishable(cargoData)) { log(`Skipping Rust package at ${packageDir}: publish = false`, 'debug'); continue; }