Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
124 changes: 97 additions & 27 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,43 +67,113 @@ jobs:
mode: auto
iac-scan: disabled

determine-version:
<<: *go_image
steps:
- checkout
- run:
name: Determine version bump and save to workspace
command: |
chmod +x ./script/version-bump.sh
./script/version-bump.sh

# Source the environment to make variables available
source $BASH_ENV

# Save to workspace
mkdir -p /tmp/workspace
echo "$BUMP_TYPE" > /tmp/workspace/bump_type
echo "$NEW_VERSION" > /tmp/workspace/new_version
echo "$NEW_TAG" > /tmp/workspace/new_tag
echo "$PREVIOUS_TAG" > /tmp/workspace/previous_tag
- persist_to_workspace:
root: /tmp/workspace
paths:
- bump_type
- new_version
- new_tag
- previous_tag

tag-release:
<<: *go_image
steps:
- checkout
- add_ssh_keys:
fingerprints:
- "SHA256:w5lYpE8DMWxUdasN8yMbbFdiz6s50PPBJMkV0a1iyZ8"
- attach_workspace:
at: /tmp/workspace
- run:
name: Configure git
command: |
git config user.email "[email protected]"
git config user.name "Snyk CI"
- run:
name: Create and push tag
command: |
BUMP_TYPE=$(cat /tmp/workspace/bump_type)
NEW_TAG=$(cat /tmp/workspace/new_tag)

if [ "$BUMP_TYPE" = "none" ]; then
echo "Chore commit detected - skipping tag creation"
circleci-agent step halt
fi

echo "Creating tag: $NEW_TAG"
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin "$NEW_TAG"

# Filters for branches
filters_pr_only: &filters_pr_only
filters:
branches:
ignore:
- main

filters_main_only: &filters_main_only
filters:
branches:
only:
- main

workflows:
version: 2
CI:
test-and-tag:
jobs:
# PR-only jobs - all testing
- lint:
<<: *filters_pr_only

- unit_test:
<<: *filters_pr_only

- prodsec/secrets-scan:
name: Scan repository for secrets
context:
- snyk-bot-slack
channel: snyk-vuln-alerts-unify
filters:
branches:
ignore:
- main
- security-scans:
name: Security Scans
context:
- analysis_unify
- lint:
name: Lint
filters:
branches:
ignore:
- main
- unit_test:
name: Unit tests
filters:
branches:
ignore:
- main
<<: *filters_pr_only

- python_integration_test:
name: Python << matrix.python_version >> integration tests
requires:
- Unit tests
- unit_test
matrix:
parameters:
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
filters:
branches:
ignore:
- main
<<: *filters_pr_only

# Main branch only - security scans and tagging (no test re-runs)
- security-scans:
context:
- analysis_unify
<<: *filters_main_only

- determine-version:
requires:
- security-scans
<<: *filters_main_only

- tag-release:
requires:
- determine-version
<<: *filters_main_only
117 changes: 117 additions & 0 deletions .github/workflows/pr-title-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: PR Title Check

on:
pull_request:
types:
- opened
- edited
- synchronize
- reopened

jobs:
validate-pr-title:
name: Validate PR Title
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Validate PR Title Format
uses: actions/github-script@v7
with:
script: |
const prTitle = context.payload.pull_request.title;

// Define valid types and their version bump effect
const types = {
'fix': 'patch',
'feat': 'minor',
'feature': 'minor',
'chore': 'none',
'docs': 'none',
'style': 'none',
'refactor': 'none',
'test': 'none',
'ci': 'none',
'perf': 'patch',
'build': 'none',
'revert': 'patch'
};

// Regular expressions for validation
const conventionalCommitPattern = /^(\w+)(\(\w+\))?:\s.{3,}$/;
const majorPrefixPattern = /^\[major\]\s.{3,}$/i;
const majorTypePattern = /^major:\s.{3,}$/i;

let isValid = false;
let bumpType = 'none';
let errorMessage = '';

// Check for major version indicators
if (majorPrefixPattern.test(prTitle)) {
isValid = true;
bumpType = 'major';
console.log('✓ Valid PR title with [major] prefix');
} else if (majorTypePattern.test(prTitle)) {
isValid = true;
bumpType = 'major';
console.log('✓ Valid PR title with major: prefix');
} else if (conventionalCommitPattern.test(prTitle)) {
// Extract type from conventional commit format
const match = prTitle.match(/^(\w+)/);
const type = match ? match[1].toLowerCase() : '';

if (types[type]) {
isValid = true;
bumpType = types[type];
console.log(`✓ Valid PR title with type '${type}' (${bumpType} bump)`);
} else {
errorMessage = `Invalid commit type '${type}'. Valid types are: ${Object.keys(types).join(', ')}`;
}
} else {
errorMessage = 'PR title does not follow conventional commit format';
}

// Helper to post/update comment
const marker = '<!-- pr-title-check -->';
const postComment = async (body) => {
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
});
const existing = comments.data.find(c => c.body.includes(marker));

const payload = {
owner: context.repo.owner,
repo: context.repo.repo,
body: `${marker}\n${body}`
};

if (existing) {
await github.rest.issues.updateComment({...payload, comment_id: existing.id});
} else {
await github.rest.issues.createComment({...payload, issue_number: context.payload.pull_request.number});
}
};

// Post result
if (isValid) {
const emoji = {'major': '🔴', 'minor': '🟡', 'patch': '🟢', 'none': '⚪'}[bumpType];
const releaseNote = bumpType === 'none' ? ' (no release)' : '';
await postComment(`✅ **Valid** — ${emoji} ${bumpType.toUpperCase()}${releaseNote}`);
console.log(`✓ Valid: ${bumpType} bump`);
} else {
const errorBody = [
'❌ **Invalid**',
'',
`**Error:** ${errorMessage}`,
'',
'**Format:** `type: description` or `[major] description`',
'',
'**Valid types:** fix, feat, major, chore, docs, style, refactor, test, ci, perf, build, revert',
'',
'See [CONTRIBUTING.md](https://github.com/snyk/cli-extension-dep-graph/blob/main/CONTRIBUTING.md) for examples.'
].join('\n');
await postComment(errorBody);
core.setFailed(errorMessage);
}
53 changes: 53 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,56 @@
This repo is intended for internal (Snyk) contributions only at this time.

Please [reach our support](SUPPORT.md) to give any feedback.

## Commit Message Convention

This project uses **Conventional Commits** for automated versioning and releases. Please follow this format when committing or creating pull requests:

### Format
```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

### Types and Their Effect on Versioning

- **`fix:`** - Bug fixes (bumps **PATCH** version: 1.0.0 → 1.0.1)
- **`feat:`** - New features (bumps **MINOR** version: 1.0.0 → 1.1.0)
- **`[major]`** or **`major:`** - Breaking changes (bumps **MAJOR** version: 1.0.0 → 2.0.0)
- **`chore:`**, **`docs:`**, **`style:`**, **`refactor:`**, **`test:`**, **`ci:`** - No release created

### Examples

```bash
# Patch release
fix: resolve memory leak in dependency parser

# Minor release
feat: add support for Python 3.13

# Major release (option 1)
[major] redesign API interface

# Major release (option 2)
major: remove deprecated functions

# No release
chore: update CI configuration
```

### Pull Request Guidelines

When creating a pull request:
1. **Use a descriptive title** following the conventional commit format (examples above)
2. **Check the GitHub Action** - A bot will automatically validate your PR title and post a comment showing:
- Whether the title is valid ✅ or invalid ❌
- The version bump type (MAJOR, MINOR, PATCH, or NO RELEASE)
3. **Edit the title if needed** - If validation fails or shows the wrong version bump, edit your PR title
4. **Use "Squash and merge"** - The PR title will become the commit message
5. **Ensure all tests pass** before merging
6. **Releases are automatic** - When merged to `main`, CircleCI will automatically create a release

For more details, see the [Release Process Documentation](docs/RELEASE.md).
Loading