Skip to content

chore: automate Rust crate versioning in publish-ci#7373

Draft
janechu wants to merge 6 commits intomainfrom
users/janechu/automate-rust-crate-versioning
Draft

chore: automate Rust crate versioning in publish-ci#7373
janechu wants to merge 6 commits intomainfrom
users/janechu/automate-rust-crate-versioning

Conversation

@janechu
Copy link
Copy Markdown
Collaborator

@janechu janechu commented Apr 2, 2026

Summary

When npm run publish-ci runs, it now also handles the microsoft-fast-build Rust crate automatically.

Changes

build/publish-rust.mjs (new)

A Node.js script that:

  1. Finds the latest Beachball-generated tag matching microsoft-fast-build_v* (e.g. microsoft-fast-build_v0.1.0) using git tag --list, falling back to all history if none exists
  2. Inspects git commits touching crates/microsoft-fast-build/ since that tag
  3. Determines the version bump type using conventional commits:
    • feat:minor
    • anything else → patch
  4. Updates the version in crates/microsoft-fast-build/Cargo.toml
  5. Runs cargo package and copies the .crate file to publish_artifacts_cargo/
  6. Exits cleanly (no-op) if no relevant commits are found since the last release

package.json

Updated publish-ci to chain the Rust script after beachball:

beachball publish -y --no-publish && node build/publish-rust.mjs

.gitignore

Added publish_artifacts_cargo/ so the packaged artifacts don't show as untracked files and break clean-tree checks.

Behavior

Scenario Result
Commits touch crates/microsoft-fast-build/ Version bumped, crate packaged to publish_artifacts_cargo/
No commits to crate since last tag Script exits cleanly, no changes

This mirrors the existing pattern: beachball → publish_artifacts (npm), new script → publish_artifacts_cargo (Rust).

When 'npm run publish-ci' runs, it now also:
- Inspects git commits touching crates/microsoft-fast-build/ since the
  last 'microsoft-fast-build-vX.Y.Z' tag using conventional commit rules
- Bumps the version in crates/microsoft-fast-build/Cargo.toml
- Packages the crate via 'cargo package' and copies the .crate file
  to publish_artifacts_cargo/

If no relevant commits are found the script exits cleanly with no changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@janechu janechu changed the title feat: automate Rust crate versioning in publish-ci chore: automate Rust crate versioning in publish-ci Apr 2, 2026
janechu and others added 3 commits April 2, 2026 15:17
…e detection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Beachball

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the existing publish-ci workflow so that, in addition to Beachball’s npm packaging, the microsoft-fast-build Rust crate is version-bumped and packaged automatically during CI publishing.

Changes:

  • Chain a new Rust publish step into npm run publish-ci.
  • Add build/publish-rust.mjs to compute a semver bump from conventional commits affecting crates/microsoft-fast-build/, update Cargo.toml, and run cargo package.
  • Copy the resulting .crate artifact into publish_artifacts_cargo/.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
package.json Updates publish-ci to run the Rust publish script after Beachball.
build/publish-rust.mjs New script to detect crate changes since the last release tag, bump Cargo.toml, and package the Rust crate artifact.

Comment on lines +40 to +56
function getCommitsSinceCrateLastTag(crateName, currentVersion) {
// Beachball generates tags in the format {package-name}_v{version}
const tagName = `${crateName}_v${currentVersion}`;
let fromRef;
try {
execSync(`git -C "${repoRoot}" rev-parse "${tagName}"`, { stdio: "pipe" });
fromRef = tagName;
} catch {
fromRef = null;
}

const range = fromRef ? `${fromRef}..HEAD` : "HEAD";
const log = execSync(
`git -C "${repoRoot}" log ${range} --pretty=format:"%s%n%b" -- crates/microsoft-fast-build/`,
{ encoding: "utf-8" }
);
return log.trim();
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script tries to find the last release tag by constructing ${crateName}_v${currentVersion} from Cargo.toml. If Cargo.toml’s version ever diverges from the most recent tag (e.g., the npm package tag is newer), rev-parse will fail and the script will fall back to scanning all history, causing incorrect bumps. Instead, resolve the latest existing tag matching the crate (e.g., via git describe --tags --match "${crateName}_v*" --abbrev=0 or git tag --list ... --sort=-version:refname | head -n1).

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +8
* Bumps the Rust crate version based on conventional commits since the last
* Beachball-generated release tag, then packages the crate into publish_artifacts_cargo/.
*
* Beachball tags use the format: {package-name}_v{version}
* e.g. microsoft-fast-build_v0.1.0
*
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions tags like microsoft-fast-build-vX.Y.Z, but this script/documentation uses {package-name}_v{version} (underscore) and examples like microsoft-fast-build_v0.1.0. Please align the implementation/docs with the actual tag format being produced/consumed to avoid silently never finding the intended tag.

Copilot uses AI. Check for mistakes.
const repoRoot = join(__dirname, "..");
const crateDir = join(repoRoot, "crates", "microsoft-fast-build");
const cargoTomlPath = join(crateDir, "Cargo.toml");
const outputDir = join(repoRoot, "publish_artifacts_cargo");
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

publish_artifacts_cargo/ is written to the repo root but isn’t currently covered by the existing ignore list (only publish_artifacts/ is ignored). This will leave new untracked files after publish-ci, which can break workflows that enforce a clean working tree. Either add publish_artifacts_cargo/ to .gitignore (preferred) or have the script write into an already-ignored directory / clean up after itself.

Suggested change
const outputDir = join(repoRoot, "publish_artifacts_cargo");
const outputDir = join(repoRoot, "publish_artifacts", "cargo");

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +68
* Version bump rules (conventional commits):
* BREAKING CHANGE / feat!: / fix!: → major
* feat: → minor
* anything else → patch
*
* Only commits that touch crates/microsoft-fast-build/ are considered.
* If no such commits exist since the last Beachball tag, the script exits without change.
*/
import { execSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, globSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(__dirname, "..");
const crateDir = join(repoRoot, "crates", "microsoft-fast-build");
const cargoTomlPath = join(crateDir, "Cargo.toml");
const outputDir = join(repoRoot, "publish_artifacts_cargo");

function getCurrentVersion() {
const content = readFileSync(cargoTomlPath, "utf-8");
const match = content.match(/^version\s*=\s*"([^"]+)"/m);
if (!match) throw new Error("Could not find version in Cargo.toml");
return match[1];
}

function parseVersion(version) {
const [major, minor, patch] = version.split(".").map(Number);
return { major, minor, patch };
}

function getCommitsSinceCrateLastTag(crateName, currentVersion) {
// Beachball generates tags in the format {package-name}_v{version}
const tagName = `${crateName}_v${currentVersion}`;
let fromRef;
try {
execSync(`git -C "${repoRoot}" rev-parse "${tagName}"`, { stdio: "pipe" });
fromRef = tagName;
} catch {
fromRef = null;
}

const range = fromRef ? `${fromRef}..HEAD` : "HEAD";
const log = execSync(
`git -C "${repoRoot}" log ${range} --pretty=format:"%s%n%b" -- crates/microsoft-fast-build/`,
{ encoding: "utf-8" }
);
return log.trim();
}

function determineBump(commits) {
if (!commits) return null;

if (/BREAKING CHANGE/m.test(commits) || /^(feat|fix|refactor|chore)!(\([^)]*\))?:/m.test(commits)) {
return "major";
}
if (/^feat(\([^)]*\))?:/m.test(commits)) {
return "minor";
}
return "patch";
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment’s major-bump rules say BREAKING CHANGE / feat!: / fix!: cause a major, but determineBump also treats refactor! and chore! as major. Update the doc comment to match the implemented behavior (or narrow the regex) so future maintainers don’t misinterpret the bump logic.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +13
* Version bump rules (conventional commits):
* BREAKING CHANGE / feat!: / fix!: → major
* feat: → minor
* anything else → patch
*
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says bump logic is only feat: → minor and everything else → patch, but this script also performs a major bump when it detects BREAKING CHANGE or type!:. Either update the PR description to reflect the major-bump behavior or remove the major path if it’s not intended.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@janechu janechu marked this pull request as draft April 3, 2026 20:23
- Use 'git tag --list' to find latest Beachball tag matching the crate
  instead of constructing a tag name from Cargo.toml version, which can
  diverge from the actual last-released version
- Add publish_artifacts_cargo/ to .gitignore so CI clean-tree checks pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants