Skip to content

feat: implement automated monthly releases with YY-MM-patchlevel versioning#7468

Merged
DennisOSRM merged 6 commits intomasterfrom
feat/automated-monthly-releases
Apr 12, 2026
Merged

feat: implement automated monthly releases with YY-MM-patchlevel versioning#7468
DennisOSRM merged 6 commits intomasterfrom
feat/automated-monthly-releases

Conversation

@DennisOSRM
Copy link
Copy Markdown
Collaborator

Summary

This PR implements a new automated monthly release process with YYYY-MM-patchlevel versioning (e.g., 2026-04-0, 2026-04-1).

Changes

New Workflow: .github/workflows/release-monthly.yml

  • Scheduled trigger: 1st of each month @ 08:00 UTC
  • Manual trigger: workflow_dispatch for emergency/out-of-schedule releases
  • Version calculation: Automatic YYYY-MM-patchlevel with patchlevel auto-increment
  • Full automation: Commits, tags, releases, and publishes to npm

Updated Files

  • .github/workflows/osrm-backend.yml: Added tag pattern for new version format
  • CMakeLists.txt: Support both YYYY-MM-patchlevel and legacy semver formats
  • package.json: Updated version to 2026-04-0
  • docs/releasing.md: Complete documentation rewrite for new release process

Versioning Scheme

Format: YYYY-MM-patchlevel

  • YYYY: Current year
  • MM: Current month (01-12)
  • patchlevel: Counter starting at 0 per month (0, 1, 2, ...)

Examples:

  • First release in April 2026: 2026-04-0
  • Second release in April 2026: 2026-04-1
  • First release in May 2026: 2026-05-0

Compatibility

  • ✅ Fully backward compatible with legacy semver tags
  • ✅ CMake build system supports both formats
  • ✅ Existing CI workflow patterns still trigger

Release Process

Automated:

  1. 1st of each month @ 08:00 UTC
  2. Auto-calculate version → Update package.json → Commit → Tag → Release → Publish

Manual:

  1. Actions tab → Monthly Release → Run workflow
  2. Select branch (defaults to master)
  3. Optional version override
  4. Same automated steps execute

Prerequisites

  • Ensure NPM_TOKEN secret is configured in repository settings for npm publishing

Copilot AI review requested due to automatic review settings April 12, 2026 08:24
Copy link
Copy Markdown
Contributor

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

Implements an automated monthly release process that publishes npm releases using a YYYY-MM-patchlevel version scheme, with supporting CI/build-system and documentation updates.

Changes:

  • Adds a scheduled + manual GitHub Actions workflow to compute YYYY-MM-patchlevel, tag, create a GitHub Release, and publish to npm.
  • Updates CI tag triggers to recognize the new tag format.
  • Updates build/version parsing (CMake), package version, and release documentation for the new scheme.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
.github/workflows/release-monthly.yml New workflow to calculate monthly versions, commit/tag/release, and publish to npm.
.github/workflows/osrm-backend.yml Extends tag trigger patterns to include the new release tag format.
CMakeLists.txt Updates version parsing to accept both semver and YYYY-MM-patchlevel.
package.json Bumps npm package version to the new format (2026-04-0).
docs/releasing.md Rewrites release documentation around the new monthly automation and versioning scheme.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/osrm-backend.yml Outdated
Comment thread .github/workflows/release-monthly.yml
Comment thread .github/workflows/release-monthly.yml
Comment thread .github/workflows/release-monthly.yml
Comment thread .github/workflows/release-monthly.yml
Comment thread CMakeLists.txt Outdated
Comment thread CMakeLists.txt Outdated
Comment thread package.json
Comment thread .github/workflows/release-monthly.yml Outdated
Comment thread .github/workflows/release-monthly.yml Outdated
@DennisOSRM DennisOSRM force-pushed the feat/automated-monthly-releases branch 2 times, most recently from 2abf329 to 4e66801 Compare April 12, 2026 08:38
…rsioning

- Add release-monthly.yml workflow with scheduled (1st @ 08:00 UTC) and manual triggers
- Version scheme: YYYY-MM-patchlevel (e.g., 2026-04-0, 2026-04-1)
- Patchlevel auto-increments within same month, resets on month change
- Automatic git operations: commit, tag, push
- Automatic GitHub Release creation with auto-generated notes
- Automatic npm publication
- Update osrm-backend.yml to trigger on new tag pattern v[0-9]{4}-[0-9]{2}-[0-9]+
- Update CMakeLists.txt to support both new and legacy semver formats
- Update package.json to version 2026-04-0
- Update docs/releasing.md with new release process documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@DennisOSRM DennisOSRM force-pushed the feat/automated-monthly-releases branch from 4e66801 to c89ce8b Compare April 12, 2026 08:41
DennisOSRM and others added 2 commits April 12, 2026 10:45
Instead of dual-format versioning (YYYY-MM-patchlevel for git tags,
(YYYY-2000).MM.patchlevel for npm), use a single unified format:

- Git tags: v26.4.0 (matching npm package version exactly)
- npm packages: 26.4.0
- No format conversion needed in release workflow

Changes:
- release-monthly.yml: Calculate version directly in semver format
- CMakeLists.txt: Remove YYYY-MM-patchlevel format handling
- version.hpp.in: Simplify to single semver format
- docs/releasing.md: Document unified versioning approach

Benefits:
- Eliminate format mismatch between git tags and npm
- Simpler release workflow (no conversion step needed)
- Single source of truth for version format
- Clearer documentation

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

Add 'Version History' section documenting:
- Previous scheme: Traditional semver (v6.0, v6.0.1, etc.)
  - Ended in December 2025 with v6.0.0 release
  - Manual release process

- New scheme: Monthly date-based versioning (starting 2026)
  - First release: v26.1.0 (January 2026)
  - Automated monthly releases on 1st of month at 08:00 UTC
  - Year offset format: YYYY-2000 (e.g., 2026 → 26)

Provides clear context for users transitioning between schemes.

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

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

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


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CMakeLists.txt Outdated
set(OSRM_VERSION_PRERELEASE_BUILD ${CMAKE_MATCH_4})

set(OSRM_VERSION_PRERELEASE_BUILD "")
set(OSRM_VERSION packagejson.version)
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

set(OSRM_VERSION packagejson.version) assigns the literal string "packagejson.version" rather than the parsed version value, which will leak into cmake/pkgconfig.in (via @OSRM_VERSION@) and produce an incorrect pkg-config version. Set it from the JSON value (or from the match groups) instead.

Suggested change
set(OSRM_VERSION packagejson.version)
set(OSRM_VERSION "${packagejson.version}")

Copilot uses AI. Check for mistakes.
Comment thread CMakeLists.txt Outdated
Comment on lines 98 to 114
# This regex supports the unified semver version format:
# - (YYYY-2000).MM.patchlevel: e.g., 26.4.0 where 26 = year - 2000, 4 = April
# This format is used for both git tags and npm packages, eliminating format mismatch
# The CMake build system also supports legacy semver (e.g., 6.0.0) for backward compatibility

# Try semver format: (YYYY-2000).MM.patchlevel (e.g., 26.4.0)
if (packagejson.version MATCHES "^([0-9]{1,2})\\.([0-9]{1,2})\\.([0-9]+)$")
# Semver format: (YYYY-2000).MM.patchlevel
# MAJOR is already offset (year - 2000)
set(OSRM_VERSION_MAJOR ${CMAKE_MATCH_1})
set(OSRM_VERSION_MINOR ${CMAKE_MATCH_2})
set(OSRM_VERSION_PATCH ${CMAKE_MATCH_3})
set(OSRM_VERSION_PRERELEASE_BUILD ${CMAKE_MATCH_4})

set(OSRM_VERSION_PRERELEASE_BUILD "")
set(OSRM_VERSION packagejson.version)
else()
message(FATAL_ERROR "Version from package.json cannot be parsed, expected semver compatible label, but found ${packagejson.version}")
message(FATAL_ERROR "Version from package.json cannot be parsed. Expected semver format:\n - (YYYY-2000).MM.patchlevel (e.g., 26.4.0 where 26=year-2000)\n - Legacy semver (e.g., 6.0.0)\nBut found: ${packagejson.version}")
endif()
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The updated regex only accepts X.Y.Z and no longer supports legacy semver variants with pre-release/build metadata, even though the comments/error message claim legacy semver is supported. Either add an additional match branch for legacy semver (including optional prerelease/build) or update the comments/error text to reflect the actual supported formats.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +81
# Calculate semver version automatically
# Use (YYYY-2000).MM.patchlevel format
YEAR=$(date -u +%Y)
MONTH=$(date -u +%m)
YEAR_OFFSET=$((YEAR - 2000))

# Find highest patchlevel for this month
LATEST_TAG=$(git tag -l "v${YEAR_OFFSET}.${MONTH}.*" --sort=-version:refname | head -1)

if [ -z "$LATEST_TAG" ]; then
PATCHLEVEL=0
else
# Extract patchlevel from tag (e.g., v26.4.5 -> 5)
PATCHLEVEL=${LATEST_TAG##*.}
PATCHLEVEL=$((PATCHLEVEL + 1))
fi

VERSION="${YEAR_OFFSET}.${MONTH}.${PATCHLEVEL}"
fi
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

MONTH=$(date -u +%m) produces a zero-padded month (e.g. "04"), which would generate versions like 26.04.0. That is not valid semver (leading zero in a numeric identifier) and may be rejected by npm. Use an unpadded month (%-m) or strip the leading zero before composing the version/tag and tag search pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +161
run: |
# Wait for tag CI to complete before publishing npm package
# The CI workflow is triggered by the tag push and must complete
# to upload release assets (prebuilt binaries)
echo "Waiting for CI workflow to complete..."

# Poll workflow runs for this tag
TAG="${{ steps.version.outputs.tag }}"
MAX_WAIT=300 # 5 minutes
ELAPSED=0
POLL_INTERVAL=10

while [ $ELAPSED -lt $MAX_WAIT ]; do
RUNS=$(gh run list --workflow=osrm-backend.yml --status=completed --json conclusion,status,name 2>/dev/null | jq -r ". | length")
if [ "$RUNS" != "0" ]; then
echo "CI workflow completed, proceeding with npm publish"
break
fi
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done

Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

The CI wait loop is not reliably checking the workflow run for the tag being released: it doesn’t authenticate gh (stderr is discarded), doesn’t filter by tag/commit, and doesn’t verify the conclusion is success. As written it can immediately proceed due to any completed run, or time out after 5 minutes and still publish to npm. Pass GH_TOKEN (or GITHUB_TOKEN), filter runs for the tag’s commit SHA, require a success conclusion, and fail the job if the matching run isn’t successful within the timeout.

Suggested change
run: |
# Wait for tag CI to complete before publishing npm package
# The CI workflow is triggered by the tag push and must complete
# to upload release assets (prebuilt binaries)
echo "Waiting for CI workflow to complete..."
# Poll workflow runs for this tag
TAG="${{ steps.version.outputs.tag }}"
MAX_WAIT=300 # 5 minutes
ELAPSED=0
POLL_INTERVAL=10
while [ $ELAPSED -lt $MAX_WAIT ]; do
RUNS=$(gh run list --workflow=osrm-backend.yml --status=completed --json conclusion,status,name 2>/dev/null | jq -r ". | length")
if [ "$RUNS" != "0" ]; then
echo "CI workflow completed, proceeding with npm publish"
break
fi
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
env:
GH_TOKEN: ${{ github.token }}
run: |
# Wait for tag CI to complete before publishing npm package
# The CI workflow is triggered by the tag push and must complete
# to upload release assets (prebuilt binaries)
echo "Waiting for CI workflow to complete..."
TAG="${{ steps.version.outputs.tag }}"
TAG_SHA="$(git rev-list -n 1 "$TAG")"
MAX_WAIT=300 # 5 minutes
ELAPSED=0
POLL_INTERVAL=10
echo "Waiting for osrm-backend.yml run for tag $TAG (commit $TAG_SHA)"
while [ "$ELAPSED" -lt "$MAX_WAIT" ]; do
RUN_JSON="$(gh run list --workflow=osrm-backend.yml --commit "$TAG_SHA" --json databaseId,status,conclusion,headSha,displayTitle --limit 20)"
MATCHING_RUN="$(echo "$RUN_JSON" | jq -c --arg sha "$TAG_SHA" 'map(select(.headSha == $sha)) | first')"
if [ "$MATCHING_RUN" != "null" ] && [ -n "$MATCHING_RUN" ]; then
STATUS="$(echo "$MATCHING_RUN" | jq -r '.status')"
CONCLUSION="$(echo "$MATCHING_RUN" | jq -r '.conclusion // ""')"
RUN_ID="$(echo "$MATCHING_RUN" | jq -r '.databaseId')"
echo "Found matching CI run $RUN_ID with status=$STATUS conclusion=$CONCLUSION"
if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "CI workflow completed successfully, proceeding with npm publish"
break
fi
echo "CI workflow for tag $TAG completed with conclusion=$CONCLUSION"
exit 1
fi
else
echo "No matching CI run found yet for commit $TAG_SHA"
fi
sleep "$POLL_INTERVAL"
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
echo "Timed out waiting for successful CI workflow for tag $TAG (commit $TAG_SHA)"
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/release-monthly.yml Outdated
Comment on lines +13 to +17
branch:
description: 'Branch to release from (defaults to master)'
required: false
type: string
default: 'master'
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

workflow_dispatch already lets the user choose the ref/branch to run the workflow from; adding a separate branch input that is then used for checkout/push can diverge from github.ref and make it unclear which branch is actually being released. Consider removing the custom branch input and using github.ref_name / github.ref consistently, or clearly documenting the precedence to avoid accidental releases from the wrong branch.

Copilot uses AI. Check for mistakes.
Comment thread docs/releasing.md Outdated
Comment thread .github/workflows/release-monthly.yml Outdated
DennisOSRM and others added 3 commits April 12, 2026 11:06
CMake's MATCHES operator does not support {1,2} quantifier syntax.
Changed regex from '[0-9]{1,2}' to '[0-9]+' to properly match version
numbers in both zero-padded (26.04.0) and non-padded (26.4.0) formats.

This fixes the build error:
  "Version from package.json cannot be parsed... But found: 26.4.0"

The regex now correctly matches:
- 26.4.0 (non-padded month)
- 26.04.0 (zero-padded month)
- 6.0.0 (legacy semver)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Critical fixes:
- Fix OSRM_VERSION CMake assignment to use variable value not literal string
- Restore legacy semver format support (with prerelease/build metadata)
- Enforce month 1-12 in CMake regex (no invalid months like 13)
- Enforce month 1-12 and no leading zeros in version_override validation
- Remove zero-padding from month calculation (use %-m not %m)
- Update package-lock.json in addition to package.json during releases

Improvements:
- Clearer CMake comments explaining both version formats
- Documentation clarifies git tag prefix (v) vs npm unprefixed format
- Documentation clarifies month is 1-12 without leading zeros (not MM)
- Ensure version_override rejects invalid formats like 26.00.0 or 26.13.0

The workflow now correctly:
- Calculates versions like 26.4.0 for April 2026 (not 26.04.0)
- Validates month to be in range 1-12
- Updates both package.json and package-lock.json consistently
- Generates correct OSRM_VERSION in runtime and pkg-config

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevent npm publish from running before prebuilt binaries are available
by implementing proper CI workflow monitoring.

The osrm-backend.yml workflow (triggered by tag push) builds and uploads
prebuilt binaries to GitHub Release assets. If npm is published before
these binaries are available, subsequent installs via node-pre-gyp will fail.

Changes:
- Authenticate gh CLI with GH_TOKEN (github.token)
- Query osrm-backend.yml workflow runs with commit SHA filter
- Find the run matching the tag's commit SHA specifically
- Monitor status and conclusion fields
- Wait up to 5 minutes for completion
- Only proceed to npm publish if conclusion is 'success'
- Fail fast if CI workflow fails or times out

This ensures:
- Binaries are available before npm knows about the version
- npm installs will find prebuilt binaries in release assets
- Failed CI builds prevent broken npm releases
- Manual/scheduled releases don't race and fail mysteriously

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@DennisOSRM DennisOSRM changed the title feat: implement automated monthly releases with YYYY-MM-patchlevel versioning feat: implement automated monthly releases with YY-MM-patchlevel versioning Apr 12, 2026
@DennisOSRM DennisOSRM merged commit 89ae0bd into master Apr 12, 2026
23 checks passed
@DennisOSRM DennisOSRM deleted the feat/automated-monthly-releases branch April 12, 2026 09:36
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