From 4126ce805e0de99f88751e59c3153dd9fc44c95b Mon Sep 17 00:00:00 2001 From: Greg Mohler Date: Tue, 10 Feb 2026 18:50:11 -0500 Subject: [PATCH] updated workflows, codeowners, copilot instructions --- .github/CODEOWNERS | 2 + .github/copilot-instructions.md | 333 +----------------- .../general-coding.instructions.md | 15 - .../github-graphql-query.instructions.md | 316 ++++++++++++++++- .github/pull_request_template.md | 11 + .github/workflows/dependency-review.yml | 22 ++ .github/workflows/release.yml | 101 +++++- .gitignore | 4 +- release.sh | 5 - 9 files changed, 460 insertions(+), 349 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 .github/instructions/general-coding.instructions.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/dependency-review.yml delete mode 100755 release.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..f24d9ee --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Default owner for all files in the repository +* @CallMeGreg \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e0900d9..34c0eab 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -6,328 +6,19 @@ - Always describe changes in a detailed plan before making them. - Update the README file with any new features or changes. +# Naming Conventions +- Use PascalCase for component names, interfaces, and type aliases +- Use camelCase for variables, functions, and methods +- Prefix private class members with underscore (_) +- Use ALL_CAPS for constants + +# Error Handling +- Use try/catch blocks for async operations +- Always log errors with contextual information +- Use logging to debug instead of print statements + # API preference - When using the GitHub API, prefer using the REST API over the GraphQL API. - An exception to this rule is when listing organizations for an enterprise. For this kind of task, always use the GraphQL API. -- If there is no confirmed API endpoint, do not fabricate an endpoint. - -# GitHub GraphQL API Rate Limits - -- If you exceed your primary rate limit, the response status will still be 200, but you will receive an error message, and the value of the x-ratelimit-remaining header will be 0. You should not retry your request until after the time specified by the x-ratelimit-reset header. -- If you exceed a secondary rate limit, the response status will be 200 or 403, and you will receive an error message that indicates that you hit a secondary rate limit. If the retry-after response header is present, you should not retry your request until after that many seconds has elapsed. If the x-ratelimit-remaining header is 0, you should not retry your request until after the time, in UTC epoch seconds, specified by the x-ratelimit-reset header. Otherwise, wait for at least one minute before retrying. If your request continues to fail due to a secondary rate limit, wait for an exponentially increasing amount of time between retries, and throw an error after a specific number of retries. -- Continuing to make requests while you are rate limited may result in the banning of your integration. - -# GitHub GraphQL API Enterprise endpoint schema - -""" -An account to manage multiple organizations with consolidated policy and billing. -""" -type Enterprise implements Node { - """ - The announcement banner set on this enterprise, if any. Only visible to members of the enterprise. - """ - announcementBanner: AnnouncementBanner - - """ - A URL pointing to the enterprise's public avatar. - """ - avatarUrl( - """ - The size of the resulting square image. - """ - size: Int - ): URI! - - """ - The enterprise's billing email. - """ - billingEmail: String - - """ - Enterprise billing information visible to enterprise billing managers. - """ - billingInfo: EnterpriseBillingInfo - - """ - Identifies the date and time when the object was created. - """ - createdAt: DateTime! - - """ - Identifies the primary key from the database. - """ - databaseId: Int - - """ - The description of the enterprise. - """ - description: String - - """ - The description of the enterprise as HTML. - """ - descriptionHTML: HTML! - - """ - The Node ID of the Enterprise object - """ - id: ID! - - """ - The location of the enterprise. - """ - location: String - - """ - A list of users who are members of this enterprise. - """ - members( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - - """ - Returns the elements in the list that come before the specified cursor. - """ - before: String - - """ - Only return members within the selected GitHub Enterprise deployment - """ - deployment: EnterpriseUserDeployment - - """ - Returns the first _n_ elements from the list. - """ - first: Int - - """ - Only return members with this two-factor authentication status. Does not - include members who only have an account on a GitHub Enterprise Server instance. - - **Upcoming Change on 2025-04-01 UTC** - **Description:** `hasTwoFactorEnabled` will be removed. Use `two_factor_method_security` instead. - **Reason:** `has_two_factor_enabled` will be removed. - """ - hasTwoFactorEnabled: Boolean = null - - """ - Returns the last _n_ elements from the list. - """ - last: Int - - """ - Ordering options for members returned from the connection. - """ - orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} - - """ - Only return members within the organizations with these logins - """ - organizationLogins: [String!] - - """ - The search string to look for. - """ - query: String - - """ - The role of the user in the enterprise organization or server. - """ - role: EnterpriseUserAccountMembershipRole - - """ - Only return members with this type of two-factor authentication method. Does - not include members who only have an account on a GitHub Enterprise Server instance. - """ - twoFactorMethodSecurity: TwoFactorCredentialSecurityType = null - ): EnterpriseMemberConnection! - - """ - The name of the enterprise. - """ - name: String! - - """ - A list of organizations that belong to this enterprise. - """ - organizations( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - - """ - Returns the elements in the list that come before the specified cursor. - """ - before: String - - """ - Returns the first _n_ elements from the list. - """ - first: Int - - """ - Returns the last _n_ elements from the list. - """ - last: Int - - """ - Ordering options for organizations returned from the connection. - """ - orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} - - """ - The search string to look for. - """ - query: String - - """ - The viewer's role in an organization. - """ - viewerOrganizationRole: RoleInOrganization - ): OrganizationConnection! - - """ - Enterprise information visible to enterprise owners or enterprise owners' - personal access tokens (classic) with read:enterprise or admin:enterprise scope. - """ - ownerInfo: EnterpriseOwnerInfo - - """ - The raw content of the enterprise README. - """ - readme: String - - """ - The content of the enterprise README as HTML. - """ - readmeHTML: HTML! - - """ - The HTTP path for this enterprise. - """ - resourcePath: URI! - - """ - Returns a single ruleset from the current enterprise by ID. - """ - ruleset( - """ - The ID of the ruleset to be returned. - """ - databaseId: Int! - ): RepositoryRuleset - - """ - A list of rulesets for this enterprise. - """ - rulesets( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - - """ - Returns the elements in the list that come before the specified cursor. - """ - before: String - - """ - Returns the first _n_ elements from the list. - """ - first: Int - - """ - Returns the last _n_ elements from the list. - """ - last: Int - ): RepositoryRulesetConnection - - """ - The URL-friendly identifier for the enterprise. - """ - slug: String! - - """ - Identifies the date and time when the object was last updated. - """ - updatedAt: DateTime! - - """ - The HTTP URL for this enterprise. - """ - url: URI! - - """ - A list of repositories that belong to users. Only available for enterprises with Enterprise Managed Users. - """ - userNamespaceRepositories( - """ - Returns the elements in the list that come after the specified cursor. - """ - after: String - - """ - Returns the elements in the list that come before the specified cursor. - """ - before: String - - """ - Returns the first _n_ elements from the list. - """ - first: Int - - """ - Returns the last _n_ elements from the list. - """ - last: Int - - """ - Ordering options for repositories returned from the connection. - """ - orderBy: RepositoryOrder = {field: NAME, direction: ASC} - - """ - The search string to look for. - """ - query: String - ): UserNamespaceRepositoryConnection! - - """ - Is the current viewer an admin of this enterprise? - """ - viewerIsAdmin: Boolean! - - """ - The URL of the enterprise website. - """ - websiteUrl: URI -} - -""" -The connection type for User. -""" -type EnterpriseAdministratorConnection { - """ - A list of edges. - """ - edges: [EnterpriseAdministratorEdge] - - """ - A list of nodes. - """ - nodes: [User] - - """ - Information to aid in pagination. - """ - pageInfo: PageInfo! - - """ - Identifies the total count of items in the connection. - """ - totalCount: Int! -} \ No newline at end of file +- If there is no confirmed API endpoint in the GitHub documentation, ask the user to provide one. Do NOT make assumptions about the endpoint. \ No newline at end of file diff --git a/.github/instructions/general-coding.instructions.md b/.github/instructions/general-coding.instructions.md deleted file mode 100644 index 932c52e..0000000 --- a/.github/instructions/general-coding.instructions.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -applyTo: "**" ---- -# Project general coding standards - -## Naming Conventions -- Use PascalCase for component names, interfaces, and type aliases -- Use camelCase for variables, functions, and methods -- Prefix private class members with underscore (_) -- Use ALL_CAPS for constants - -## Error Handling -- Use try/catch blocks for async operations -- Always log errors with contextual information -- Use logging to debug instead of print statements \ No newline at end of file diff --git a/.github/instructions/github-graphql-query.instructions.md b/.github/instructions/github-graphql-query.instructions.md index a3d9698..56ba2a5 100644 --- a/.github/instructions/github-graphql-query.instructions.md +++ b/.github/instructions/github-graphql-query.instructions.md @@ -30,4 +30,318 @@ query GetRepositories($org: String!, $repoCount: Int!, $cursor: String) { If you exceed your primary rate limit, the response status will still be 200, but you will receive an error message, and the value of the x-ratelimit-remaining header will be 0. You should not retry your request until after the time specified by the x-ratelimit-reset header. -If you exceed a secondary rate limit, the response status will be 200 or 403, and you will receive an error message that indicates that you hit a secondary rate limit. If the retry-after response header is present, you should not retry your request until after that many seconds has elapsed. If the x-ratelimit-remaining header is 0, you should not retry your request until after the time, in UTC epoch seconds, specified by the x-ratelimit-reset header. Otherwise, wait for at least one minute before retrying. If your request continues to fail due to a secondary rate limit, wait for an exponentially increasing amount of time between retries, and throw an error after a specific number of retries. \ No newline at end of file +If you exceed a secondary rate limit, the response status will be 200 or 403, and you will receive an error message that indicates that you hit a secondary rate limit. If the retry-after response header is present, you should not retry your request until after that many seconds has elapsed. If the x-ratelimit-remaining header is 0, you should not retry your request until after the time, in UTC epoch seconds, specified by the x-ratelimit-reset header. Otherwise, wait for at least one minute before retrying. If your request continues to fail due to a secondary rate limit, wait for an exponentially increasing amount of time between retries, and throw an error after a specific number of retries. + +# GitHub GraphQL API Enterprise endpoint schema + +""" +An account to manage multiple organizations with consolidated policy and billing. +""" +type Enterprise implements Node { + """ + The announcement banner set on this enterprise, if any. Only visible to members of the enterprise. + """ + announcementBanner: AnnouncementBanner + + """ + A URL pointing to the enterprise's public avatar. + """ + avatarUrl( + """ + The size of the resulting square image. + """ + size: Int + ): URI! + + """ + The enterprise's billing email. + """ + billingEmail: String + + """ + Enterprise billing information visible to enterprise billing managers. + """ + billingInfo: EnterpriseBillingInfo + + """ + Identifies the date and time when the object was created. + """ + createdAt: DateTime! + + """ + Identifies the primary key from the database. + """ + databaseId: Int + + """ + The description of the enterprise. + """ + description: String + + """ + The description of the enterprise as HTML. + """ + descriptionHTML: HTML! + + """ + The Node ID of the Enterprise object + """ + id: ID! + + """ + The location of the enterprise. + """ + location: String + + """ + A list of users who are members of this enterprise. + """ + members( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Only return members within the selected GitHub Enterprise deployment + """ + deployment: EnterpriseUserDeployment + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Only return members with this two-factor authentication status. Does not + include members who only have an account on a GitHub Enterprise Server instance. + + **Upcoming Change on 2025-04-01 UTC** + **Description:** `hasTwoFactorEnabled` will be removed. Use `two_factor_method_security` instead. + **Reason:** `has_two_factor_enabled` will be removed. + """ + hasTwoFactorEnabled: Boolean = null + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for members returned from the connection. + """ + orderBy: EnterpriseMemberOrder = {field: LOGIN, direction: ASC} + + """ + Only return members within the organizations with these logins + """ + organizationLogins: [String!] + + """ + The search string to look for. + """ + query: String + + """ + The role of the user in the enterprise organization or server. + """ + role: EnterpriseUserAccountMembershipRole + + """ + Only return members with this type of two-factor authentication method. Does + not include members who only have an account on a GitHub Enterprise Server instance. + """ + twoFactorMethodSecurity: TwoFactorCredentialSecurityType = null + ): EnterpriseMemberConnection! + + """ + The name of the enterprise. + """ + name: String! + + """ + A list of organizations that belong to this enterprise. + """ + organizations( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for organizations returned from the connection. + """ + orderBy: OrganizationOrder = {field: LOGIN, direction: ASC} + + """ + The search string to look for. + """ + query: String + + """ + The viewer's role in an organization. + """ + viewerOrganizationRole: RoleInOrganization + ): OrganizationConnection! + + """ + Enterprise information visible to enterprise owners or enterprise owners' + personal access tokens (classic) with read:enterprise or admin:enterprise scope. + """ + ownerInfo: EnterpriseOwnerInfo + + """ + The raw content of the enterprise README. + """ + readme: String + + """ + The content of the enterprise README as HTML. + """ + readmeHTML: HTML! + + """ + The HTTP path for this enterprise. + """ + resourcePath: URI! + + """ + Returns a single ruleset from the current enterprise by ID. + """ + ruleset( + """ + The ID of the ruleset to be returned. + """ + databaseId: Int! + ): RepositoryRuleset + + """ + A list of rulesets for this enterprise. + """ + rulesets( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): RepositoryRulesetConnection + + """ + The URL-friendly identifier for the enterprise. + """ + slug: String! + + """ + Identifies the date and time when the object was last updated. + """ + updatedAt: DateTime! + + """ + The HTTP URL for this enterprise. + """ + url: URI! + + """ + A list of repositories that belong to users. Only available for enterprises with Enterprise Managed Users. + """ + userNamespaceRepositories( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + + """ + Ordering options for repositories returned from the connection. + """ + orderBy: RepositoryOrder = {field: NAME, direction: ASC} + + """ + The search string to look for. + """ + query: String + ): UserNamespaceRepositoryConnection! + + """ + Is the current viewer an admin of this enterprise? + """ + viewerIsAdmin: Boolean! + + """ + The URL of the enterprise website. + """ + websiteUrl: URI +} + +""" +The connection type for User. +""" +type EnterpriseAdministratorConnection { + """ + A list of edges. + """ + edges: [EnterpriseAdministratorEdge] + + """ + A list of nodes. + """ + nodes: [User] + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + Identifies the total count of items in the connection. + """ + totalCount: Int! +} \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..3ca500a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Description + + + +## Release Type + + + +- [ ] **Major** - Breaking changes +- [ ] **Minor** - New features, backwards compatible +- [ ] **Patch** - Bug fixes, backwards compatible \ No newline at end of file diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..19853cd --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,22 @@ +name: Dependency Review + +on: [pull_request] + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6.0.1 + + - name: Dependency Review + uses: actions/dependency-review-action@v4.8.2 + with: + # Block pull requests with vulnerabilities at moderate severity or higher + fail-on-severity: low + # Post detailed summary in PR comments + comment-summary-in-pr: on-failure \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40b54ff..df534b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,18 +1,111 @@ -name: release +name: Release + on: push: - tags: - - "v*" + branches: + - main + permissions: contents: write id-token: write attestations: write + pull-requests: read jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5.0.0 + - uses: actions/checkout@v6.0.1 + with: + fetch-depth: 0 + + - name: Determine release type + id: release_type + env: + GH_TOKEN: ${{ github.token }} + run: | + # Get the PR number from the merge commit + PR_NUMBER=$(gh pr list --state merged --search "${{ github.sha }}" --json number --jq '.[0].number') + + if [ -z "$PR_NUMBER" ]; then + echo "No PR found for this commit, defaulting to patch" + echo "type=patch" >> $GITHUB_OUTPUT + exit 0 + fi + + # Get PR details + PR_AUTHOR=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login') + PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body') + + # Check if PR was opened by Dependabot + if [[ "$PR_AUTHOR" == "dependabot"* ]]; then + echo "Dependabot PR detected, using patch release" + echo "type=patch" >> $GITHUB_OUTPUT + exit 0 + fi + + # Look for Release Type section in PR body + RELEASE_TYPE="patch" + + # Check for checked boxes in Release Type section (case-insensitive) + if echo "$PR_BODY" | grep -qi "## Release Type"; then + # Extract the Release Type section (from header to next ## or end) + RELEASE_SECTION=$(echo "$PR_BODY" | sed -n '/## Release Type/,/^##/p' | sed '$d') + + # Check for checked boxes (supports [x], [X]) + if echo "$RELEASE_SECTION" | grep -Eq '\[[xX]\].*[Mm]ajor'; then + RELEASE_TYPE="major" + elif echo "$RELEASE_SECTION" | grep -Eq '\[[xX]\].*[Mm]inor'; then + RELEASE_TYPE="minor" + elif echo "$RELEASE_SECTION" | grep -Eq '\[[xX]\].*[Pp]atch'; then + RELEASE_TYPE="patch" + fi + fi + + echo "Detected release type: $RELEASE_TYPE" + echo "type=$RELEASE_TYPE" >> $GITHUB_OUTPUT + + - name: Calculate next version + id: next_version + run: | + # Get the latest tag + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "Latest tag: $LATEST_TAG" + + # Strip 'v' prefix and split into parts + VERSION=${LATEST_TAG#v} + MAJOR=$(echo $VERSION | cut -d. -f1) + MINOR=$(echo $VERSION | cut -d. -f2) + PATCH=$(echo $VERSION | cut -d. -f3) + + # Increment based on release type + RELEASE_TYPE="${{ steps.release_type.outputs.type }}" + case $RELEASE_TYPE in + major) + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + esac + + NEXT_VERSION="v${MAJOR}.${MINOR}.${PATCH}" + echo "Next version: $NEXT_VERSION" + echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT + + - name: Create and push tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag ${{ steps.next_version.outputs.version }} + git push origin ${{ steps.next_version.outputs.version }} + - uses: cli/gh-extension-precompile@v2.1.0 with: go_version_file: go.mod diff --git a/.gitignore b/.gitignore index c3f8484..d7b6891 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,4 @@ go.work # Executable /gh-language -/gh-language.exe - -gh-language-* \ No newline at end of file +/gh-language.exe \ No newline at end of file diff --git a/release.sh b/release.sh deleted file mode 100755 index 63adcd5..0000000 --- a/release.sh +++ /dev/null @@ -1,5 +0,0 @@ -export version=v3.0.1 -git switch main -git pull -git tag $version -git push origin $version \ No newline at end of file