Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: PR Validation

on:
pull_request_target:
types: [opened, edited, reopened, synchronize]

jobs:
validate-and-label:
runs-on: ubuntu-latest
permissions:
issues: write

steps:
- name: Validate PR title and assign label
uses: actions/github-script@v7
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
script: |
const title = context.payload.pull_request.title;
const prNumber = context.payload.pull_request.number;

const allowedScopes = ['feature', 'fix', 'docs', 'improvement', 'revert', 'breaking', 'ci'];

const scopeToLabel = {
feature: 'feature',
ci: 'ci',
fix: 'bug',
docs: 'documentation',
improvement: 'improvement',
revert: 'revert',
breaking: 'breaking-change',
};

const match = title.match(/^\[([^\]]+)\]\s+\S+/);

if (!match) {
core.setFailed(
`PR title must follow the format: [scope] description\n` +
`Example: [Feat] Add SeaweedFS bucket auto-creation\n` +
`Allowed scopes: ${allowedScopes.join(', ')}`
);
return;
}

const scope = match[1].toLowerCase();

if (!allowedScopes.includes(scope)) {
core.setFailed(
`Invalid scope "[${match[1]}]".\n` +
`Allowed scopes: ${allowedScopes.join(', ')}\n` +
`Example: [Feat] Add SeaweedFS bucket auto-creation`
);
return;
}

const labelName = scopeToLabel[scope];

// Remove any stale scope labels from a previous title edit
const allScopeLabels = Object.values(scopeToLabel);
const currentLabels = context.payload.pull_request.labels.map(l => l.name);

for (const stale of currentLabels) {
if (allScopeLabels.includes(stale) && stale !== labelName) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: stale,
});
core.info(`Removed stale label: ${stale}`);
}
}

// Apply the correct label if not already present
if (!currentLabels.includes(labelName)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [labelName],
});
}

core.info(`PR title valid — scope: [${scope}] → label: ${labelName}`);
54 changes: 45 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ jobs:
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

- name: Extract Chart Version and check RC
id: version_check
run: |
CHART_VERSION=$(grep '^version:' charts/mlrun-ce/Chart.yaml | awk '{print $2}')
if [[ -z "$CHART_VERSION" ]]; then
echo "Error: Failed to extract version from Chart.yaml" >&2
exit 1
fi
echo "version=$CHART_VERSION" >> $GITHUB_OUTPUT
if [[ "$CHART_VERSION" =~ -rc ]]; then
echo "is_rc=true" >> $GITHUB_OUTPUT
else
echo "is_rc=false" >> $GITHUB_OUTPUT
fi

- name: Add Helm Repos
run: |
helm repo add stable https://charts.helm.sh/stable
Expand All @@ -52,18 +67,39 @@ jobs:
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

- name: Extract Chart Version from Chart.yaml
id: extract_version
- name: Find first RC tag for this version
if: steps.version_check.outputs.is_rc == 'false'
id: first_rc
run: |
CHART_VERSION=$(grep '^version:' charts/mlrun-ce/Chart.yaml | awk '{print $2}')
if [[ -z "$CHART_VERSION" ]]; then
echo "Error: Failed to extract version from Chart.yaml" >&2
exit 1
fi
echo "version=$CHART_VERSION" >> $GITHUB_OUTPUT
VERSION="${{ steps.version_check.outputs.version }}"
FIRST_RC=$(git tag --sort=version:refname \
| grep "^mlrun-ce-${VERSION}-rc." \
| head -1)
echo "tag=${FIRST_RC}" >> $GITHUB_OUTPUT
echo "First RC tag: ${FIRST_RC}"

- name: Generate release notes with git-cliff
if: steps.version_check.outputs.is_rc == 'false'
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: >-
${{ steps.first_rc.outputs.tag }}^..HEAD
--tag mlrun-ce-${{ steps.version_check.outputs.version }}
--ignore-tags "mlrun-ce-${{ steps.version_check.outputs.version }}-rc.*"
env:
OUTPUT: RELEASE_NOTES.md

- name: Update GitHub Release with release notes
if: steps.version_check.outputs.is_rc == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release edit "mlrun-ce-${{ steps.version_check.outputs.version }}" \
--notes-file RELEASE_NOTES.md

outputs:
version: ${{ steps.extract_version.outputs.version }}
version: ${{ steps.version_check.outputs.version }}

deploy_ce_onprem:
needs: release
Expand Down
67 changes: 67 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[changelog]
header = ""
body = """
{% for group, commits in commits | sort(attribute="group") | group_by(attribute="group") %}\
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}\
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | split(pat="\n") | first | upper_first }} \
([{{ commit.id | truncate(length=7, end="") }}](https://github.com/mlrun/ce/commit/{{ commit.id }}))\
\n \
{% endfor %}
{% endfor %}\n
"""
trim = true
footer = ""

[git]
conventional_commits = false
filter_unconventional = false
split_commits = false
commit_preprocessors = []
commit_parsers = [
# skip merge commits
{ message = "(?i)^merge", skip = true },

# new PR title format (enforced going forward): [scope] ...
{ message = "(?i)^\\[feat\\]", group = "Features" },
{ message = "(?i)^\\[fix\\]", group = "Bug Fixes" },
{ message = "(?i)^\\[perf\\]", group = "Performance" },
{ message = "(?i)^\\[refactor\\]", group = "Refactor" },
{ message = "(?i)^\\[docs?\\]", group = "Documentation" },
{ message = "(?i)^\\[chore\\]", group = "Miscellaneous" },
{ message = "(?i)^\\[revert\\]", group = "Reverts" },
{ message = "(?i)^\\[breaking\\]", group = "Breaking Changes" },

# conventional commits format: feat: / fix: ...
{ message = "(?i)^feat", group = "Features" },
{ message = "(?i)^fix", group = "Bug Fixes" },
{ message = "(?i)^perf", group = "Performance" },
{ message = "(?i)^refactor", group = "Refactor" },
{ message = "(?i)^docs?", group = "Documentation" },
{ message = "(?i)^chore\\(deps\\)", group = "Dependencies" },
{ message = "(?i)^chore", group = "Miscellaneous" },
{ message = "(?i)^revert", group = "Reverts" },

# historical [ComponentName] format — infer type from the verb in the message
{ message = "(?i)^\\[[^\\]]+\\].*(fix|bug|broken|regression)", group = "Bug Fixes" },
{ message = "(?i)^\\[[^\\]]+\\].*(add|support|enable|upgrade|update|migrate|connect|expose|allow)", group = "Features" },
{ message = "(?i)^\\[[^\\]]+\\].*(disable|remove|clean|deprecat)", group = "Miscellaneous" },
{ message = "(?i)^\\[[^\\]]+\\]", group = "Changes" },

# plain English fallback
{ message = "(?i)^(fix|bug)", group = "Bug Fixes" },
{ message = "(?i)^(add|update|upgrade|support|enable|migrate|expose|allow)", group = "Features" },
{ message = "(?i)^(remove|disable|clean|deprecat)", group = "Miscellaneous" },

# catch-all — anything that didn't match above
{ message = ".*", group = "Other" },
]
protect_breaking_commits = false
filter_commits = false
tag_pattern = "mlrun-ce-[0-9].*"
skip_tags = ""
ignore_tags = ""
topo_order = false
sort_commits = "newest"
Loading