-
-
Notifications
You must be signed in to change notification settings - Fork 149
feat: add GitHub Direct Commit support #333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add GitHub Direct Commit support #333
Conversation
- Add github_commit authentication type for direct file commits - Implement GithubRepository module for GitHub API integration - Add UI components for branch, filepath, and commit message configuration - Update README documentation with github_commit auth option - Supports automatic branch creation if not exists - Handles both file creation and updates via GitHub API
4673cd4 to
4e5df87
Compare
- Remove simple owner/repo format from URL parsing logic - Update README to document only supported URL formats - Keep support for https://api.github.com/repos/owner/repo - Keep support for https://github.com/owner/repo - Remove :username/:repo format that fails HTML5 validation
975e2ad to
f4aa38e
Compare
f4aa38e to
60a858a
Compare
|
@igort78, thanks for the PR, I will be reviewing it and will get back to any questions. |
Pull Request Test Coverage Report for Build 19331724053Details
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds support for direct GitHub file commits alongside the existing GitHub Actions workflow dispatch method. Users can now choose to commit design tokens directly to a GitHub repository branch instead of triggering a workflow.
- Introduces a new
GithubRepositoryclass similar to the existingGitlabRepositoryimplementation - Adds a new
github_commitauthentication type option in the UI - Updates documentation to clarify the two GitHub integration methods
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ui/modules/githubRepository.ts | New class implementing GitHub API operations for branch management and file commits |
| src/ui/modules/urlExport.ts | Adds URL parsing logic and integration point for GitHub direct commits |
| src/ui/components/UrlExportSettings.tsx | Adds UI controls for the GitHub Direct Commit authentication type and branch selection |
| src/config/config.ts | Adds githubCommit authentication type constant |
| README.md | Updates documentation with GitHub Direct Commit usage instructions and formatting fixes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| ) { | ||
| const encodedContent = utf8ToBase64(clientPayload.tokens) | ||
| const filepath = clientPayload.filename |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filepath is not URI-encoded before being used in GitHub API URLs (lines 44, 210, 265). This will cause API errors if the filename contains special characters like spaces. The GitlabRepository implementation uses encodeURIComponent(clientPayload.filename) to handle this correctly. Apply the same encoding here.
| const filepath = clientPayload.filename | |
| const filepath = encodeURIComponent(clientPayload.filename) |
src/ui/modules/githubRepository.ts
Outdated
|
|
||
| try { | ||
| // Use reference field (branch name) from settings, consistent with GitLab implementation | ||
| const targetBranch = branch |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable targetBranch is redundant. The branch parameter is already destructured from reference with clear semantics. Remove this intermediate variable and use branch directly throughout the method.
| let owner: string, repo: string | ||
|
|
||
| const urlParts = exportSettings.url.split('/') | ||
| const reposIndex = urlParts.indexOf('repos') | ||
| if (reposIndex !== -1 && urlParts.length > reposIndex + 2) { | ||
| owner = urlParts[reposIndex + 1] | ||
| repo = urlParts[reposIndex + 2] | ||
| } else { | ||
| // Try to parse from github.com/owner/repo format | ||
| owner = urlParts[urlParts.length - 2] | ||
| repo = urlParts[urlParts.length - 1] | ||
| } | ||
|
|
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The URL parsing logic has no validation and will silently produce incorrect results for malformed URLs. For example, if the URL ends with a trailing slash, urlParts[urlParts.length - 1] will be an empty string. Additionally, variables owner and repo might not be assigned if the array is too short. Add validation to ensure the URL is properly formatted and the required parts exist before instantiating GithubRepository.
| let owner: string, repo: string | |
| const urlParts = exportSettings.url.split('/') | |
| const reposIndex = urlParts.indexOf('repos') | |
| if (reposIndex !== -1 && urlParts.length > reposIndex + 2) { | |
| owner = urlParts[reposIndex + 1] | |
| repo = urlParts[reposIndex + 2] | |
| } else { | |
| // Try to parse from github.com/owner/repo format | |
| owner = urlParts[urlParts.length - 2] | |
| repo = urlParts[urlParts.length - 1] | |
| } | |
| let owner: string | undefined, repo: string | undefined | |
| const urlParts = exportSettings.url.split('/').filter(Boolean) // Remove empty parts (e.g., from trailing slash) | |
| const reposIndex = urlParts.indexOf('repos') | |
| if (reposIndex !== -1 && urlParts.length > reposIndex + 2) { | |
| owner = urlParts[reposIndex + 1] | |
| repo = urlParts[reposIndex + 2] | |
| } else if (urlParts.length >= 2) { | |
| // Try to parse from github.com/owner/repo format | |
| owner = urlParts[urlParts.length - 2] | |
| repo = urlParts[urlParts.length - 1] | |
| } | |
| // Validate owner and repo | |
| if (!owner || !repo) { | |
| // @ts-ignore | |
| parent.postMessage({ | |
| pluginMessage: { | |
| command: commands.closePlugin, | |
| payload: { | |
| notification: '🚨 Invalid GitHub URL format. Could not determine owner and repository. Push aborted!' | |
| } | |
| } as PluginMessage | |
| }, '*') | |
| return | |
| } |
src/ui/modules/githubRepository.ts
Outdated
| if (statusCode === 201 || statusCode === 422) { | ||
| resolve() | ||
| return | ||
| } |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Treating status code 422 (Unprocessable Entity) as a success is incorrect. Status code 422 indicates the request was well-formed but semantically invalid - this is an error condition that should be handled, not silently treated as success. If the intent is to handle the case where the branch already exists, check for a more specific error message in the response rather than accepting all 422 errors.
| if (statusCode === 201 || statusCode === 422) { | |
| resolve() | |
| return | |
| } | |
| if (statusCode === 201) { | |
| resolve() | |
| return | |
| } | |
| if (statusCode === 422) { | |
| // Only treat as success if branch already exists | |
| try { | |
| const response = JSON.parse(request.responseText) | |
| if ( | |
| response && | |
| typeof response.message === 'string' && | |
| response.message.includes('Reference already exists') | |
| ) { | |
| resolve() | |
| return | |
| } | |
| } catch (_e) { | |
| // fall through to reject | |
| } | |
| } |
| }) { | ||
| const { request, branch, content, commitMessage, filepath, fileSHA } = args | ||
|
|
||
| const body: any = { |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using any type defeats the purpose of TypeScript's type safety. Define a proper interface for the request body with properties message, content, branch, and optional sha, or use an inline type annotation.
|
|
||
| if (exportSettings.authType === config.key.authType.githubCommit) { | ||
| let owner: string, repo: string | ||
|
|
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Inconsistent blank line usage. This empty line after the variable declaration doesn't match the formatting style used elsewhere in the file.
|
@igort78, the test coverage has decreased by about 8%. |
- Add proper URI encoding for filenames with special characters - Improve 422 status handling to only accept 'Reference already exists' - Remove redundant targetBranch variable for code clarity
60a858a to
30278af
Compare
- Test constructor initialization - Test upload to existing branch with new/existing files - Test branch creation when branch doesn't exist - Test default commit message generation - Test URL encoding for special characters and unicode - Mock XMLHttpRequest for isolated unit testing - Coverage increased from 1.76% to 46.01% Addresses code review comment about test coverage decrease
- Add tests for URL encoding with slashes - Add tests for 422 Reference already exists handling - Add tests for other 422 validation errors - Add tests for invalid JSON in 422 responses - Add tests for 401 and 403 error handling - Coverage improved from 46.01% to 50.44% This addresses the code review comment about test coverage decrease
- Add 18 tests for githubRepository module covering upload, error handling, and URL encoding - Add 6 tests for urlExport githubCommit auth type integration - Improve githubRepository branch coverage from 22% to 40.74% - Test URI encoding fix (encodeURIComponent) - Test 422 error handling with 'Reference already exists' message - Test URL parsing for both API and web URL formats
Updates: Copilot AI Review + Test Coverage ✅Copilot AI Code Review Fixes ImplementedI've implemented the key suggestions from GitHub Copilot:
Test Coverage Added
Coverage ResultsgithubRepository.ts:
urlExport.ts: 65.15% statements, 76.19% branches Overall project: 77.71% coverage (maintained) All 194 tests passing ✅ |
|
@0m4r I've updated the PR 👍 |
0m4r
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please include the built files.
|
@0m4r Thanks! Done ✅ |
|
@lukasoppermann up to you now! |
|
@lukasoppermann Could you please check? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
@0m4r did you test it?
hey @lukasoppermann yes I did. it worked :) |

Summary
Adds support for committing design tokens directly to GitHub repositories without requiring GitHub Actions workflows.
This feature bypasses the 65KB payload limitation of the GitHub Actions dispatch API, allowing larger design token files to be committed directly to repositories.
Changes
github_commitauthentication type for direct file commits via GitHub APIGithubRepositorymodule for GitHub API integrationgithub_commitauth optionUse Case
This allows users to commit design tokens directly to a GitHub repository from Figma, similar to the existing GitLab commit functionality. Useful for teams who want direct commits without setting up GitHub Actions workflows.
Testing