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
8 changes: 8 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@
color: 'd73a4a'
description: 'Introduces breaking changes'

- name: 'security'
color: 'ee0701'
description: 'Security-related issues or improvements'

- name: 'branch-protection'
color: 'ee0701'
description: 'Related to branch protection configuration'

- name: 'backport'
color: 'c5def5'
description: 'Should be backported to a previous version'
Expand Down
27 changes: 6 additions & 21 deletions .github/workflows/auto-merge-dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Automatically approves and merges Dependabot PRs based on semantic versioning rules.
# Supports configurable merge methods and granular control over patch, minor, and major updates.
name: Auto-merge Dependabot PRs

on:
Expand Down Expand Up @@ -56,28 +58,11 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge for patch updates
- name: Enable auto-merge
if: |
inputs.auto-merge-patch &&
steps.metadata.outputs.update-type == 'version-update:semver-patch'
run: gh pr merge --auto --${{ inputs.merge-method }} "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge for minor updates
if: |
inputs.auto-merge-minor &&
steps.metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --${{ inputs.merge-method }} "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge for major updates
if: |
inputs.auto-merge-major &&
steps.metadata.outputs.update-type == 'version-update:semver-major'
(inputs.auto-merge-patch && steps.metadata.outputs.update-type == 'version-update:semver-patch') ||
(inputs.auto-merge-minor && steps.metadata.outputs.update-type == 'version-update:semver-minor') ||
(inputs.auto-merge-major && steps.metadata.outputs.update-type == 'version-update:semver-major')
run: gh pr merge --auto --${{ inputs.merge-method }} "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
Expand Down
112 changes: 112 additions & 0 deletions .github/workflows/branch-protection-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Validates branch protection settings weekly and creates issues if configuration problems are found.
# Checks for required settings like PR reviews, status checks, and other security configurations.
name: Branch Protection Check
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

This workflow file is missing a descriptive comment at the top explaining its purpose. Add a comment similar to other workflows to maintain consistency.

Copilot uses AI. Check for mistakes.

on:
schedule:
- cron: "0 0 * * 1" # Weekly on Monday
workflow_dispatch:

permissions:
contents: read
issues: write

concurrency:
group: branch-protection-check-${{ github.workflow }}
cancel-in-progress: true

jobs:
check:
name: Check Branch Protection
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check branch protection rules
uses: actions/github-script@v7
with:
script: |
const branch = 'main';

// Helper function to find or create/update issues (DRY principle)
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

[nitpick] The comment mentions 'DRY principle' but the function name 'findOrCreateIssue' doesn't clearly indicate it also updates existing issues. Consider renaming to 'findCreateOrUpdateIssue' or updating the comment to be more descriptive.

Copilot uses AI. Check for mistakes.
async function findOrCreateIssue(titleFragment, issueBody, labels) {
const { data: existingIssues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: labels,
per_page: 10
});

const existingIssue = existingIssues.find(issue =>
issue.title.includes(titleFragment)
);

if (existingIssue) {
// Update existing issue
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssue.number,
body: `## πŸ”„ Updated Check Results\n\n${issueBody}`
});
console.log(`Updated existing issue #${existingIssue.number}`);
} else {
// Create new issue
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${titleFragment} for ${branch}`,
body: issueBody,
labels: labels
});
console.log('Created new issue');
}
}

try {
const { data: protection } = await github.rest.repos.getBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: branch
});

console.log('βœ… Branch protection is enabled for', branch);
console.log('Protection rules:', JSON.stringify(protection, null, 2));

// Check required settings
const issues = [];

if (!protection.required_pull_request_reviews) {
issues.push('❌ Pull request reviews are not required');
}

if (!protection.enforce_admins?.enabled) {
issues.push('⚠️ Rules do not apply to administrators');
}

if (!protection.required_status_checks) {
issues.push('⚠️ No required status checks configured');
}

if (issues.length > 0) {
console.log('\n⚠️ Issues found:');
issues.forEach(issue => console.log(issue));

const issueBody = `## Branch Protection Check Results\n\nThe following issues were found with branch protection for \`${branch}\`:\n\n${issues.map(i => `- ${i}`).join('\n')}\n\n**Recommendations:**\n- Enable pull request reviews\n- Apply rules to administrators\n- Configure required status checks\n\n---\n*Automated check run on ${new Date().toISOString()}*`;

await findOrCreateIssue('Branch Protection Configuration Issues', issueBody, ['security', 'branch-protection', 'chore']);
} else {
console.log('\nβœ… All branch protection checks passed!');
}

} catch (error) {
if (error.status === 404) {
console.error(`❌ No branch protection found for ${branch}`);

const issueBody = `## ⚠️ Branch Protection Missing\n\nBranch protection is not configured for \`${branch}\`.\n\n**Recommended settings:**\n- Require pull request reviews\n- Require status checks to pass\n- Enforce rules for administrators\n- Require linear history\n\n---\n*Automated check run on ${new Date().toISOString()}*`;

await findOrCreateIssue('Branch Protection Not Configured', issueBody, ['security', 'branch-protection', 'priority: high']);
} else {
throw error;
}
}
20 changes: 17 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Comprehensive CI workflow with linting, type checking, testing, and building.
# Supports Node.js version matrix testing and optional coverage upload.
# All steps are optional and can be configured individually.
name: CI

on:
Expand All @@ -9,6 +12,12 @@ on:
If not specified, the version will be read from package.json.
type: string
required: false
node-version-matrix:
description: |
JSON array of Node.js versions to test against (e.g. '["18", "20", "22"]')
If specified, overrides node-version and runs tests across multiple versions.
type: string
required: false
pnpm-version:
description: |
The version of pnpm to use (supports the Semantic Versioning Specification, e.g. 8, 10.7.0, latest)
Expand Down Expand Up @@ -105,15 +114,20 @@ jobs:
run: ${{ inputs.typecheck-command }}

test:
name: Test
name: Test${{ inputs.node-version-matrix && format(' (Node {0})', matrix.node-version) || '' }}
if: inputs.run-test
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

This complex conditional logic for handling matrix vs single node version is hard to read and maintain. Consider breaking this into multiple lines or adding a comment explaining the logic.

Suggested change
matrix:
matrix:
# If inputs.node-version-matrix is set, use it as the matrix.
# Otherwise, use a single node version (inputs.node-version or 'default').

Copilot uses AI. Check for mistakes.
node-version: ${{ inputs.node-version-matrix && fromJSON(inputs.node-version-matrix) || fromJSON(format('["{0}"]', inputs.node-version || 'default')) }}
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The complex conditional logic for determining node-version creates a hard-to-read expression. Consider extracting this logic into a separate step or simplifying the approach.

Copilot uses AI. Check for mistakes.
steps:
- name: Setup Node.js with pnpm
uses: benhigham/.github/.github/actions/setup-node-pnpm@main
with:
node-version: ${{ inputs.node-version }}
# Use matrix.node-version if set and not 'default', otherwise use inputs.node-version
node-version: ${{ matrix.node-version != 'default' && matrix.node-version || inputs.node-version }}
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The use of 'default' as a magic string and the conditional logic makes this expression unclear. Consider using a more explicit approach or documenting the 'default' value behavior.

Suggested change
node-version: ${{ matrix.node-version != 'default' && matrix.node-version || inputs.node-version }}
# Use matrix.node-version if set, otherwise fall back to inputs.node-version
node-version: ${{ matrix.node-version && matrix.node-version != '' && matrix.node-version || inputs.node-version }}

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +130
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

The use of 'default' as a magic string value makes the logic unclear. Consider using a more explicit approach or documenting why 'default' is used as a sentinel value.

Copilot uses AI. Check for mistakes.
pnpm-version: ${{ inputs.pnpm-version }}

- name: Run tests
Expand All @@ -123,7 +137,7 @@ jobs:
if: inputs.upload-coverage && always()
uses: actions/upload-artifact@v4
with:
name: coverage-report
name: coverage-report${{ inputs.node-version-matrix && format('-node-{0}', matrix.node-version) || '' }}
path: ${{ inputs.coverage-path }}
retention-days: 30

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Security code scanning with CodeQL for multiple programming languages.
# Configurable languages, queries, and build commands for compiled languages.
name: CodeQL Security Scanning

on:
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Automatically reviews dependencies for security vulnerabilities in pull requests.
# Configurable severity thresholds, license checking, and PR comment settings.
name: Dependency Review

on:
workflow_call:
inputs:
fail-on-severity:
description: 'Minimum severity to fail the check (low, moderate, high, critical)'
type: string
default: 'moderate'
required: false
comment-summary-in-pr:
description: 'Whether to comment summary in PR (always, on-failure, never)'
type: string
default: 'always'
required: false
allow-licenses:
description: 'Comma-separated list of allowed licenses'
type: string
required: false
deny-licenses:
description: 'Comma-separated list of denied licenses'
type: string
required: false

permissions:
contents: read
pull-requests: write

concurrency:
group: dependency-review-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
dependency-review:
name: Review Dependencies
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
fail-on-severity: ${{ inputs.fail-on-severity }}
comment-summary-in-pr: ${{ inputs.comment-summary-in-pr }}
allow-licenses: ${{ inputs.allow-licenses }}
deny-licenses: ${{ inputs.deny-licenses }}
89 changes: 65 additions & 24 deletions .github/workflows/first-time-contributor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,74 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Greet on first PR
if: github.event_name == 'pull_request_target'
uses: actions/first-interaction@v1
- name: Greet first-time contributors
uses: actions/github-script@v7
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |
πŸ‘‹ Thanks for opening your first pull request! We're excited to have you contribute.
script: |
const issue_number = context.issue.number;
const is_pr = context.eventName === 'pull_request_target';

A maintainer will review your PR soon. In the meantime, please make sure:
- [ ] Your code follows the project's style guidelines
- [ ] You've added tests if applicable
- [ ] You've updated documentation if needed
- [ ] All CI checks pass
// Get the author
const author = context.payload.sender.login;

Feel free to ask questions if you need any help! πŸš€
// Check if this is the author's first contribution
// Note: Fetching up to 100 items should be sufficient for most repositories.
// For extremely active repos, this might miss older contributions, but it's a reasonable tradeoff.
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

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

[nitpick] The comment explains the limitation but doesn't mention the API rate limiting benefit. Consider adding a note about why this limit helps with API consumption efficiency.

Suggested change
// For extremely active repos, this might miss older contributions, but it's a reasonable tradeoff.
// For extremely active repos, this might miss older contributions, but it's a reasonable tradeoff.
// Limiting to 100 items also helps avoid excessive API requests, improving efficiency and reducing the risk of hitting API rate limits.

Copilot uses AI. Check for mistakes.
const contributions = is_pr
? await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} is:pr author:${author}`,
per_page: 100,
})
: await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
creator: author,
state: 'all',
per_page: 100,
});

- name: Greet on first issue
if: github.event_name == 'issues'
uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: |
πŸ‘‹ Thanks for opening your first issue! We appreciate you taking the time to report this.
const data = is_pr ? contributions.data.items : contributions.data;

// Count previous contributions by this author
const previousContributions = data.filter(item => {
if (is_pr) {
// For PRs, exclude the current PR (already filtered by author in query)
return item.number !== issue_number;
} else {
// For issues, exclude the current issue and any pull requests
return item.number !== issue_number && !item.pull_request;
}
});

// Only greet if this is their first contribution
if (previousContributions.length === 0) {
const message = is_pr
? `πŸ‘‹ Thanks for opening your first pull request! We're excited to have you contribute.

A maintainer will review your PR soon. In the meantime, please make sure:
- [ ] Your code follows the project's style guidelines
- [ ] You've added tests if applicable
- [ ] You've updated documentation if needed
- [ ] All CI checks pass

Feel free to ask questions if you need any help! πŸš€`
: `πŸ‘‹ Thanks for opening your first issue! We appreciate you taking the time to report this.

A maintainer will review your issue and respond as soon as possible. To help us address this quickly, please make sure you've provided:
- A clear description of the issue
- Steps to reproduce (if applicable)
- Any relevant context or screenshots

Thanks for contributing to the project! πŸ™`;

A maintainer will review your issue and respond as soon as possible. To help us address this quickly, please make sure you've provided:
- A clear description of the issue
- Steps to reproduce (if applicable)
- Any relevant context or screenshots
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
body: message
});

Thanks for contributing to the project! πŸ™
console.log(`Greeted first-time contributor: ${author}`);
} else {
console.log(`Not a first-time contributor: ${author} (${previousContributions.length} previous contributions)`);
}
2 changes: 2 additions & 0 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Automatically labels pull requests based on size (lines changed) and file paths.
# Supports enabling/disabling size and path-based labeling independently.
name: PR Labeler

on:
Expand Down
Loading