From dcd2ad40d0da30a981053b7289d634666b62cf6f Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 26 Sep 2026 10:40:19 +0100 Subject: [PATCH 1/2] Add a TinyGo fallback for the GitLab provider --- .github/workflows/ci.yml | 11 + README.md | 2 + gitlab/branches.go | 2 + gitlab/ci.go | 2 + gitlab/collaborators.go | 2 + gitlab/collaborators_test.go | 2 + gitlab/commit_statuses.go | 2 + gitlab/commit_statuses_test.go | 2 + gitlab/commits.go | 2 + gitlab/commits_test.go | 2 + gitlab/contributors_test.go | 2 + gitlab/deploy_keys.go | 2 + gitlab/files.go | 2 + gitlab/files_test.go | 2 + gitlab/forks_test.go | 2 + gitlab/gitlab.go | 22 +- gitlab/gitlab_test.go | 2 + gitlab/gitlab_tinygo.go | 427 +++++++++++++++++++++++++++++++++ gitlab/gitlab_tinygo_test.go | 79 ++++++ gitlab/helpers_test.go | 2 + gitlab/issues.go | 6 +- gitlab/issues_test.go | 2 + gitlab/labels.go | 6 +- gitlab/milestones.go | 2 + gitlab/milestones_test.go | 2 + gitlab/notifications.go | 2 + gitlab/notifications_test.go | 2 + gitlab/prs.go | 6 +- gitlab/prs_test.go | 2 + gitlab/rate_limit.go | 2 + gitlab/rate_limit_test.go | 2 + gitlab/reactions.go | 2 + gitlab/reactions_test.go | 2 + gitlab/releases.go | 2 + gitlab/reviews.go | 2 + gitlab/secrets.go | 2 + gitlab/urls.go | 33 +++ gitlab/urls_portable_test.go | 39 +++ gitlab/users.go | 2 + 39 files changed, 657 insertions(+), 32 deletions(-) create mode 100644 gitlab/gitlab_tinygo.go create mode 100644 gitlab/gitlab_tinygo_test.go create mode 100644 gitlab/urls.go create mode 100644 gitlab/urls_portable_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acc6c8d..3124760 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,17 @@ jobs: - name: Test run: go test -v -race ./... + - name: Test TinyGo GitLab fallback + run: go test -tags=tinygo ./gitlab + + - name: Build WebAssembly + if: runner.os == 'Linux' + run: GOOS=js GOARCH=wasm go build ./... + + - name: Build WASI + if: runner.os == 'Linux' + run: GOOS=wasip1 GOARCH=wasm go build ./... + lint: runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 9b70c9c..96bb9ef 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ repo, err := client.FetchRepository(ctx, "https://github.com/octocat/hello-world The `Forge` interface exposes services for repos, issues, pull requests, reviews, releases, CI, branches, labels, milestones, deploy keys, secrets, notifications, files, collaborators, commit statuses and commits. Each backend implements these using its native SDK. +Under TinyGo, `gitlab.New` retains its signature and returns a backend whose API operations report `forges.ErrNotSupported`. GitLab URL parsing and web link builders remain available. Native Go retains the full GitLab implementation. + ```go f, _ := client.ForgeFor("github.com") issues, _ := f.Issues().List(ctx, "octocat", "hello-world", forges.ListIssueOpts{State: "open"}) diff --git a/gitlab/branches.go b/gitlab/branches.go index e07a059..54e9840 100644 --- a/gitlab/branches.go +++ b/gitlab/branches.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/ci.go b/gitlab/ci.go index 8027dd8..af39b5a 100644 --- a/gitlab/ci.go +++ b/gitlab/ci.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/collaborators.go b/gitlab/collaborators.go index c3a7bcf..10f76b4 100644 --- a/gitlab/collaborators.go +++ b/gitlab/collaborators.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/collaborators_test.go b/gitlab/collaborators_test.go index 150d0d0..7e25e26 100644 --- a/gitlab/collaborators_test.go +++ b/gitlab/collaborators_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/commit_statuses.go b/gitlab/commit_statuses.go index 0ad1943..30344c1 100644 --- a/gitlab/commit_statuses.go +++ b/gitlab/commit_statuses.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/commit_statuses_test.go b/gitlab/commit_statuses_test.go index 5c2c35f..8a830fb 100644 --- a/gitlab/commit_statuses_test.go +++ b/gitlab/commit_statuses_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/commits.go b/gitlab/commits.go index 88bd584..f3edeea 100644 --- a/gitlab/commits.go +++ b/gitlab/commits.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/commits_test.go b/gitlab/commits_test.go index 6863c63..0990c74 100644 --- a/gitlab/commits_test.go +++ b/gitlab/commits_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/contributors_test.go b/gitlab/contributors_test.go index d1cbd5a..73428be 100644 --- a/gitlab/contributors_test.go +++ b/gitlab/contributors_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/deploy_keys.go b/gitlab/deploy_keys.go index 986a838..1203a8c 100644 --- a/gitlab/deploy_keys.go +++ b/gitlab/deploy_keys.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/files.go b/gitlab/files.go index ef8f55c..46074d5 100644 --- a/gitlab/files.go +++ b/gitlab/files.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/files_test.go b/gitlab/files_test.go index ef0b09d..e518137 100644 --- a/gitlab/files_test.go +++ b/gitlab/files_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/forks_test.go b/gitlab/forks_test.go index 0ffb832..d386967 100644 --- a/gitlab/forks_test.go +++ b/gitlab/forks_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/gitlab.go b/gitlab/gitlab.go index 288476e..6725111 100644 --- a/gitlab/gitlab.go +++ b/gitlab/gitlab.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( @@ -442,23 +444,3 @@ func (s *gitLabRepoService) Search(ctx context.Context, opts forge.SearchRepoOpt } return repos, nil } - -func (s *gitLabRepoService) SettingsURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/settings" -} - -func (s *gitLabRepoService) WikiURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/wikis" -} - -func (s *gitLabRepoService) ActionsURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/pipelines" -} - -func (s *gitLabRepoService) ReleasesURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/releases" -} - -func (s *gitLabRepoService) BlobURL(repoHTMLURL, ref, path string) string { - return repoHTMLURL + "/-/blob/" + ref + "/" + path -} diff --git a/gitlab/gitlab_test.go b/gitlab/gitlab_test.go index a033843..9a7b1cb 100644 --- a/gitlab/gitlab_test.go +++ b/gitlab/gitlab_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/gitlab_tinygo.go b/gitlab/gitlab_tinygo.go new file mode 100644 index 0000000..a24e07f --- /dev/null +++ b/gitlab/gitlab_tinygo.go @@ -0,0 +1,427 @@ +//go:build tinygo + +package gitlab + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "strings" + + forge "github.com/git-pkgs/forge" +) + +var errTinyGo = fmt.Errorf("gitlab: API operations are unavailable under TinyGo: %w", forge.ErrNotSupported) + +type gitLabForge struct{ apiBaseURL string } + +// New creates a GitLab backend whose API operations return forge.ErrNotSupported. +func New(baseURL, _ string, _ *http.Client) forge.Forge { + return &gitLabForge{apiBaseURL: strings.TrimRight(baseURL, "/") + "/api/v4"} +} + +func (f *gitLabForge) APIBaseURL() string { return f.apiBaseURL } + +func (f *gitLabForge) GetRateLimit(context.Context) (*forge.RateLimit, error) { + return nil, errTinyGo +} + +type gitLabBranchService struct{} + +func (f *gitLabForge) Branches() forge.BranchService { return &gitLabBranchService{} } + +func (s *gitLabBranchService) List(ctx context.Context, owner, repo string, opts forge.ListBranchOpts) ([]forge.Branch, error) { + return nil, errTinyGo +} + +func (s *gitLabBranchService) Create(ctx context.Context, owner, repo, name, from string) (*forge.Branch, error) { + return nil, errTinyGo +} + +func (s *gitLabBranchService) Delete(ctx context.Context, owner, repo, name string) error { + return errTinyGo +} + +type gitLabCIService struct{} + +func (f *gitLabForge) CI() forge.CIService { return &gitLabCIService{} } + +func (s *gitLabCIService) ListRuns(ctx context.Context, owner, repo string, opts forge.ListCIRunOpts) ([]forge.CIRun, error) { + return nil, errTinyGo +} + +func (s *gitLabCIService) GetRun(ctx context.Context, owner, repo string, runID int64) (*forge.CIRun, error) { + return nil, errTinyGo +} + +func (s *gitLabCIService) TriggerRun(ctx context.Context, owner, repo string, opts forge.TriggerCIRunOpts) error { + return errTinyGo +} + +func (s *gitLabCIService) CancelRun(ctx context.Context, owner, repo string, runID int64) error { + return errTinyGo +} + +func (s *gitLabCIService) RetryRun(ctx context.Context, owner, repo string, runID int64) error { + return errTinyGo +} + +func (s *gitLabCIService) GetJobLog(ctx context.Context, owner, repo string, jobID int64) (io.ReadCloser, error) { + return nil, errTinyGo +} + +type gitLabCollaboratorService struct{} + +func (f *gitLabForge) Collaborators() forge.CollaboratorService { return &gitLabCollaboratorService{} } + +func (s *gitLabCollaboratorService) List(ctx context.Context, owner, repo string, opts forge.ListCollaboratorOpts) ([]forge.Collaborator, error) { + return nil, errTinyGo +} + +func (s *gitLabCollaboratorService) Add(ctx context.Context, owner, repo, username string, opts forge.AddCollaboratorOpts) error { + return errTinyGo +} + +func (s *gitLabCollaboratorService) Remove(ctx context.Context, owner, repo, username string) error { + return errTinyGo +} + +type gitLabCommitStatusService struct{} + +func (f *gitLabForge) CommitStatuses() forge.CommitStatusService { return &gitLabCommitStatusService{} } + +func (s *gitLabCommitStatusService) List(ctx context.Context, owner, repo, sha string) ([]forge.CommitStatus, error) { + return nil, errTinyGo +} + +func (s *gitLabCommitStatusService) Set(ctx context.Context, owner, repo, sha string, opts forge.SetCommitStatusOpts) (*forge.CommitStatus, error) { + return nil, errTinyGo +} + +type gitLabCommitService struct{} + +func (f *gitLabForge) Commits() forge.CommitService { return &gitLabCommitService{} } + +func (s *gitLabCommitService) ResolveCommit(ctx context.Context, owner, repo, ref string) (string, error) { + return "", errTinyGo +} + +type gitLabDeployKeyService struct{} + +func (f *gitLabForge) DeployKeys() forge.DeployKeyService { return &gitLabDeployKeyService{} } + +func (s *gitLabDeployKeyService) List(ctx context.Context, owner, repo string, opts forge.ListDeployKeyOpts) ([]forge.DeployKey, error) { + return nil, errTinyGo +} + +func (s *gitLabDeployKeyService) Get(ctx context.Context, owner, repo string, id int64) (*forge.DeployKey, error) { + return nil, errTinyGo +} + +func (s *gitLabDeployKeyService) Create(ctx context.Context, owner, repo string, opts forge.CreateDeployKeyOpts) (*forge.DeployKey, error) { + return nil, errTinyGo +} + +func (s *gitLabDeployKeyService) Delete(ctx context.Context, owner, repo string, id int64) error { + return errTinyGo +} + +type gitLabFileService struct{} + +func (f *gitLabForge) Files() forge.FileService { return &gitLabFileService{} } + +func (s *gitLabFileService) Get(ctx context.Context, owner, repo, path, ref string) (*forge.FileContent, error) { + return nil, errTinyGo +} + +func (s *gitLabFileService) List(ctx context.Context, owner, repo, path, ref string) ([]forge.FileEntry, error) { + return nil, errTinyGo +} + +type gitLabRepoService struct{} + +func (f *gitLabForge) Repos() forge.RepoService { return &gitLabRepoService{} } + +func (s *gitLabRepoService) Get(ctx context.Context, owner, repo string) (*forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) List(ctx context.Context, owner string, opts forge.ListRepoOpts) ([]forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) Create(ctx context.Context, opts forge.CreateRepoOpts) (*forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) Edit(ctx context.Context, owner, repo string, opts forge.EditRepoOpts) (*forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) Delete(ctx context.Context, owner, repo string) error { return errTinyGo } + +func (s *gitLabRepoService) Fork(ctx context.Context, owner, repo string, opts forge.ForkRepoOpts) (*forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) ListForks(ctx context.Context, owner, repo string, opts forge.ListForksOpts) ([]forge.Repository, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) ListTags(ctx context.Context, owner, repo string) ([]forge.Tag, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) ListContributors(ctx context.Context, owner, repo string) ([]forge.Contributor, error) { + return nil, errTinyGo +} + +func (s *gitLabRepoService) Search(ctx context.Context, opts forge.SearchRepoOpts) ([]forge.Repository, error) { + return nil, errTinyGo +} + +type gitLabIssueService struct{} + +func (f *gitLabForge) Issues() forge.IssueService { return &gitLabIssueService{} } + +func (s *gitLabIssueService) Get(ctx context.Context, owner, repo string, number int) (*forge.Issue, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) List(ctx context.Context, owner, repo string, opts forge.ListIssueOpts) ([]forge.Issue, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) Create(ctx context.Context, owner, repo string, opts forge.CreateIssueOpts) (*forge.Issue, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) Update(ctx context.Context, owner, repo string, number int, opts forge.UpdateIssueOpts) (*forge.Issue, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) Close(ctx context.Context, owner, repo string, number int) error { + return errTinyGo +} + +func (s *gitLabIssueService) Reopen(ctx context.Context, owner, repo string, number int) error { + return errTinyGo +} + +func (s *gitLabIssueService) Delete(ctx context.Context, owner, repo string, number int) error { + return errTinyGo +} + +func (s *gitLabIssueService) CreateComment(ctx context.Context, owner, repo string, number int, body string) (*forge.Comment, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) ListComments(ctx context.Context, owner, repo string, number int) ([]forge.Comment, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) ListReactions(ctx context.Context, owner, repo string, number int, commentID int64) ([]forge.Reaction, error) { + return nil, errTinyGo +} + +func (s *gitLabIssueService) AddReaction(ctx context.Context, owner, repo string, number int, commentID int64, reaction string) (*forge.Reaction, error) { + return nil, errTinyGo +} + +type gitLabLabelService struct{} + +func (f *gitLabForge) Labels() forge.LabelService { return &gitLabLabelService{} } + +func (s *gitLabLabelService) List(ctx context.Context, owner, repo string, opts forge.ListLabelOpts) ([]forge.Label, error) { + return nil, errTinyGo +} + +func (s *gitLabLabelService) Get(ctx context.Context, owner, repo, name string) (*forge.Label, error) { + return nil, errTinyGo +} + +func (s *gitLabLabelService) Create(ctx context.Context, owner, repo string, opts forge.CreateLabelOpts) (*forge.Label, error) { + return nil, errTinyGo +} + +func (s *gitLabLabelService) Update(ctx context.Context, owner, repo, name string, opts forge.UpdateLabelOpts) (*forge.Label, error) { + return nil, errTinyGo +} + +func (s *gitLabLabelService) Delete(ctx context.Context, owner, repo, name string) error { + return errTinyGo +} + +type gitLabMilestoneService struct{} + +func (f *gitLabForge) Milestones() forge.MilestoneService { return &gitLabMilestoneService{} } + +func (s *gitLabMilestoneService) List(ctx context.Context, owner, repo string, opts forge.ListMilestoneOpts) ([]forge.Milestone, error) { + return nil, errTinyGo +} + +func (s *gitLabMilestoneService) Get(ctx context.Context, owner, repo string, id int) (*forge.Milestone, error) { + return nil, errTinyGo +} + +func (s *gitLabMilestoneService) Create(ctx context.Context, owner, repo string, opts forge.CreateMilestoneOpts) (*forge.Milestone, error) { + return nil, errTinyGo +} + +func (s *gitLabMilestoneService) Update(ctx context.Context, owner, repo string, id int, opts forge.UpdateMilestoneOpts) (*forge.Milestone, error) { + return nil, errTinyGo +} + +func (s *gitLabMilestoneService) Close(ctx context.Context, owner, repo string, id int) error { + return errTinyGo +} + +func (s *gitLabMilestoneService) Reopen(ctx context.Context, owner, repo string, id int) error { + return errTinyGo +} + +func (s *gitLabMilestoneService) Delete(ctx context.Context, owner, repo string, id int) error { + return errTinyGo +} + +type gitLabNotificationService struct{} + +func (f *gitLabForge) Notifications() forge.NotificationService { return &gitLabNotificationService{} } + +func (s *gitLabNotificationService) List(ctx context.Context, opts forge.ListNotificationOpts) ([]forge.Notification, error) { + return nil, errTinyGo +} + +func (s *gitLabNotificationService) MarkRead(ctx context.Context, opts forge.MarkNotificationOpts) error { + return errTinyGo +} + +func (s *gitLabNotificationService) Get(ctx context.Context, id string) (*forge.Notification, error) { + return nil, errTinyGo +} + +type gitLabPRService struct{} + +func (f *gitLabForge) PullRequests() forge.PullRequestService { return &gitLabPRService{} } + +func (s *gitLabPRService) Get(ctx context.Context, owner, repo string, number int) (*forge.PullRequest, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) List(ctx context.Context, owner, repo string, opts forge.ListPROpts) ([]forge.PullRequest, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) Create(ctx context.Context, owner, repo string, opts forge.CreatePROpts) (*forge.PullRequest, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) Update(ctx context.Context, owner, repo string, number int, opts forge.UpdatePROpts) (*forge.PullRequest, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) Close(ctx context.Context, owner, repo string, number int) error { + return errTinyGo +} + +func (s *gitLabPRService) Reopen(ctx context.Context, owner, repo string, number int) error { + return errTinyGo +} + +func (s *gitLabPRService) Merge(ctx context.Context, owner, repo string, number int, opts forge.MergePROpts) error { + return errTinyGo +} + +func (s *gitLabPRService) Diff(ctx context.Context, owner, repo string, number int) (string, error) { + return "", errTinyGo +} + +func (s *gitLabPRService) CreateComment(ctx context.Context, owner, repo string, number int, body string) (*forge.Comment, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) ListComments(ctx context.Context, owner, repo string, number int) ([]forge.Comment, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) ListReactions(ctx context.Context, owner, repo string, number int, commentID int64) ([]forge.Reaction, error) { + return nil, errTinyGo +} + +func (s *gitLabPRService) AddReaction(ctx context.Context, owner, repo string, number int, commentID int64, reaction string) (*forge.Reaction, error) { + return nil, errTinyGo +} + +type gitLabReleaseService struct{} + +func (f *gitLabForge) Releases() forge.ReleaseService { return &gitLabReleaseService{} } + +func (s *gitLabReleaseService) List(ctx context.Context, owner, repo string, opts forge.ListReleaseOpts) ([]forge.Release, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) Get(ctx context.Context, owner, repo, tag string) (*forge.Release, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) GetLatest(ctx context.Context, owner, repo string) (*forge.Release, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) Create(ctx context.Context, owner, repo string, opts forge.CreateReleaseOpts) (*forge.Release, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) Update(ctx context.Context, owner, repo, tag string, opts forge.UpdateReleaseOpts) (*forge.Release, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) Delete(ctx context.Context, owner, repo, tag string) error { + return errTinyGo +} + +func (s *gitLabReleaseService) UploadAsset(ctx context.Context, owner, repo, tag string, file *os.File) (*forge.ReleaseAsset, error) { + return nil, errTinyGo +} + +func (s *gitLabReleaseService) DownloadAsset(ctx context.Context, owner, repo string, assetID int64) (io.ReadCloser, error) { + return nil, errTinyGo +} + +type gitLabReviewService struct{} + +func (f *gitLabForge) Reviews() forge.ReviewService { return &gitLabReviewService{} } + +func (s *gitLabReviewService) List(ctx context.Context, owner, repo string, number int, opts forge.ListReviewOpts) ([]forge.Review, error) { + return nil, errTinyGo +} + +func (s *gitLabReviewService) Submit(ctx context.Context, owner, repo string, number int, opts forge.SubmitReviewOpts) (*forge.Review, error) { + return nil, errTinyGo +} + +func (s *gitLabReviewService) RequestReviewers(ctx context.Context, owner, repo string, number int, users []string) error { + return errTinyGo +} + +func (s *gitLabReviewService) RemoveReviewers(ctx context.Context, owner, repo string, number int, users []string) error { + return errTinyGo +} + +type gitLabSecretService struct{} + +func (f *gitLabForge) Secrets() forge.SecretService { return &gitLabSecretService{} } + +func (s *gitLabSecretService) List(ctx context.Context, owner, repo string, opts forge.ListSecretOpts) ([]forge.Secret, error) { + return nil, errTinyGo +} + +func (s *gitLabSecretService) Set(ctx context.Context, owner, repo string, opts forge.SetSecretOpts) error { + return errTinyGo +} + +func (s *gitLabSecretService) Delete(ctx context.Context, owner, repo, name string) error { + return errTinyGo +} diff --git a/gitlab/gitlab_tinygo_test.go b/gitlab/gitlab_tinygo_test.go new file mode 100644 index 0000000..a4afc39 --- /dev/null +++ b/gitlab/gitlab_tinygo_test.go @@ -0,0 +1,79 @@ +//go:build tinygo + +package gitlab_test + +import ( + "context" + "errors" + "net/http" + "testing" + + forge "github.com/git-pkgs/forge" + "github.com/git-pkgs/forge/gitlab" +) + +type unexpectedTransport struct{ t *testing.T } + +func (u unexpectedTransport) RoundTrip(*http.Request) (*http.Response, error) { + u.t.Error("unsupported GitLab operation made an HTTP request") + return nil, errors.New("unexpected HTTP request") +} + +func TestTinyGoClientRouting(t *testing.T) { + transport := unexpectedTransport{t} + original := http.DefaultTransport + http.DefaultTransport = transport + t.Cleanup(func() { http.DefaultTransport = original }) + for _, hc := range []*http.Client{nil, {Transport: transport}} { + backend := gitlab.New("https://gitlab.example.test/", "fixture-token", hc) + client := forge.NewClient(forge.WithForge("gitlab.example.test", backend)) + repo, err := client.FetchRepository(context.Background(), "https://gitlab.example.test/group/subgroup/project") + if repo != nil || !errors.Is(err, forge.ErrNotSupported) { + t.Errorf("FetchRepository = %+v, %v; want ErrNotSupported", repo, err) + } + sha, err := client.ResolveCommit(context.Background(), "https://gitlab.example.test/group/subgroup/project", "main") + if sha != "" || !errors.Is(err, forge.ErrNotSupported) { + t.Errorf("ResolveCommit = %q, %v; want ErrNotSupported", sha, err) + } + } +} + +func TestTinyGoServicesReturnUnsupported(t *testing.T) { + f := gitlab.New("https://gitlab.example.test", "", &http.Client{Transport: unexpectedTransport{t}}) + ctx := context.Background() + tests := []struct { + name string + run func() error + }{ + {"repository create", func() error { _, err := f.Repos().Create(ctx, forge.CreateRepoOpts{Name: "project"}); return err }}, + {"issues", func() error { _, err := f.Issues().Get(ctx, "group", "project", 12); return err }}, + {"pull requests", func() error { _, err := f.PullRequests().Get(ctx, "group", "project", 12); return err }}, + {"labels", func() error { _, err := f.Labels().List(ctx, "group", "project", forge.ListLabelOpts{}); return err }}, + {"milestones", func() error { _, err := f.Milestones().Get(ctx, "group", "project", 12); return err }}, + {"releases", func() error { _, err := f.Releases().Get(ctx, "group", "project", "v1.0.0"); return err }}, + {"CI", func() error { _, err := f.CI().GetRun(ctx, "group", "project", 12); return err }}, + {"branches", func() error { _, err := f.Branches().List(ctx, "group", "project", forge.ListBranchOpts{}); return err }}, + {"deploy keys", func() error { _, err := f.DeployKeys().Get(ctx, "group", "project", 12); return err }}, + {"secrets", func() error { _, err := f.Secrets().List(ctx, "group", "project", forge.ListSecretOpts{}); return err }}, + {"notifications", func() error { _, err := f.Notifications().Get(ctx, "12"); return err }}, + {"reviews", func() error { + _, err := f.Reviews().List(ctx, "group", "project", 12, forge.ListReviewOpts{}) + return err + }}, + {"files", func() error { _, err := f.Files().Get(ctx, "group", "project", "README.md", "main"); return err }}, + {"collaborators", func() error { + _, err := f.Collaborators().List(ctx, "group", "project", forge.ListCollaboratorOpts{}) + return err + }}, + {"commit statuses", func() error { _, err := f.CommitStatuses().List(ctx, "group", "project", "main"); return err }}, + {"commits", func() error { _, err := f.Commits().ResolveCommit(ctx, "group", "project", "main"); return err }}, + {"rate limit", func() error { _, err := f.GetRateLimit(ctx); return err }}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.run(); !errors.Is(err, forge.ErrNotSupported) { + t.Errorf("error = %v, want ErrNotSupported", err) + } + }) + } +} diff --git a/gitlab/helpers_test.go b/gitlab/helpers_test.go index 814640f..b4bc450 100644 --- a/gitlab/helpers_test.go +++ b/gitlab/helpers_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import "testing" diff --git a/gitlab/issues.go b/gitlab/issues.go index 5c170f4..b9fd032 100644 --- a/gitlab/issues.go +++ b/gitlab/issues.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( @@ -341,7 +343,3 @@ func (s *gitLabIssueService) ListComments(ctx context.Context, owner, repo strin } return all, nil } - -func (s *gitLabIssueService) ListURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/issues" -} diff --git a/gitlab/issues_test.go b/gitlab/issues_test.go index c4ee084..6acbdb8 100644 --- a/gitlab/issues_test.go +++ b/gitlab/issues_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/labels.go b/gitlab/labels.go index 70aa6ca..6363c67 100644 --- a/gitlab/labels.go +++ b/gitlab/labels.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( @@ -145,7 +147,3 @@ func (s *gitLabLabelService) Delete(ctx context.Context, owner, repo, name strin } return nil } - -func (s *gitLabLabelService) ListURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/labels" -} diff --git a/gitlab/milestones.go b/gitlab/milestones.go index ead096e..922e885 100644 --- a/gitlab/milestones.go +++ b/gitlab/milestones.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/milestones_test.go b/gitlab/milestones_test.go index 8b23b5c..d9b06ac 100644 --- a/gitlab/milestones_test.go +++ b/gitlab/milestones_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/notifications.go b/gitlab/notifications.go index 5e29ddc..bfb7575 100644 --- a/gitlab/notifications.go +++ b/gitlab/notifications.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/notifications_test.go b/gitlab/notifications_test.go index 6b9d324..ef5a4af 100644 --- a/gitlab/notifications_test.go +++ b/gitlab/notifications_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/prs.go b/gitlab/prs.go index be8d0a9..d05e430 100644 --- a/gitlab/prs.go +++ b/gitlab/prs.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( @@ -484,7 +486,3 @@ func (s *gitLabPRService) ListComments(ctx context.Context, owner, repo string, } return all, nil } - -func (s *gitLabPRService) ListURL(repoHTMLURL string) string { - return repoHTMLURL + "/-/merge_requests" -} diff --git a/gitlab/prs_test.go b/gitlab/prs_test.go index 94102a8..3776258 100644 --- a/gitlab/prs_test.go +++ b/gitlab/prs_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/rate_limit.go b/gitlab/rate_limit.go index b0b649d..7850326 100644 --- a/gitlab/rate_limit.go +++ b/gitlab/rate_limit.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/rate_limit_test.go b/gitlab/rate_limit_test.go index 464ee66..e7e5ae7 100644 --- a/gitlab/rate_limit_test.go +++ b/gitlab/rate_limit_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/reactions.go b/gitlab/reactions.go index d4028c1..dcd7968 100644 --- a/gitlab/reactions.go +++ b/gitlab/reactions.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/reactions_test.go b/gitlab/reactions_test.go index 6133161..0c73f2e 100644 --- a/gitlab/reactions_test.go +++ b/gitlab/reactions_test.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/releases.go b/gitlab/releases.go index 65ee6ac..7f1f68b 100644 --- a/gitlab/releases.go +++ b/gitlab/releases.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/reviews.go b/gitlab/reviews.go index a710cc2..9694008 100644 --- a/gitlab/reviews.go +++ b/gitlab/reviews.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/secrets.go b/gitlab/secrets.go index f4cac60..d183814 100644 --- a/gitlab/secrets.go +++ b/gitlab/secrets.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( diff --git a/gitlab/urls.go b/gitlab/urls.go new file mode 100644 index 0000000..56c8ce9 --- /dev/null +++ b/gitlab/urls.go @@ -0,0 +1,33 @@ +package gitlab + +func (s *gitLabRepoService) SettingsURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/settings" +} + +func (s *gitLabRepoService) WikiURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/wikis" +} + +func (s *gitLabRepoService) ActionsURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/pipelines" +} + +func (s *gitLabRepoService) ReleasesURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/releases" +} + +func (s *gitLabRepoService) BlobURL(repoHTMLURL, ref, path string) string { + return repoHTMLURL + "/-/blob/" + ref + "/" + path +} + +func (s *gitLabIssueService) ListURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/issues" +} + +func (s *gitLabLabelService) ListURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/labels" +} + +func (s *gitLabPRService) ListURL(repoHTMLURL string) string { + return repoHTMLURL + "/-/merge_requests" +} diff --git a/gitlab/urls_portable_test.go b/gitlab/urls_portable_test.go new file mode 100644 index 0000000..cafe1ac --- /dev/null +++ b/gitlab/urls_portable_test.go @@ -0,0 +1,39 @@ +package gitlab_test + +import ( + "testing" + + forge "github.com/git-pkgs/forge" + "github.com/git-pkgs/forge/gitlab" +) + +func TestGitLabPortableURLs(t *testing.T) { + f := gitlab.New("https://gitlab.example.test/gitlab/", "", nil) + api, ok := f.(forge.APIBaseURLProvider) + if !ok || api.APIBaseURL() != "https://gitlab.example.test/gitlab/api/v4" { + t.Fatalf("APIBaseURL provider = %#v", f) + } + ref, err := f.ParsePath([]string{"group", "subgroup", "project", "-", "merge_requests", "42"}) + if err != nil { + t.Fatal(err) + } + if ref.Owner != "group/subgroup" || ref.Repo != "project" || ref.Type != forge.ResourceTypePR || ref.Number != 42 { + t.Errorf("ParsePath = %+v", ref) + } + const repo = "https://gitlab.example.test/group/subgroup/project" + urls := map[string]string{ + "/-/settings": f.Repos().SettingsURL(repo), + "/-/wikis": f.Repos().WikiURL(repo), + "/-/pipelines": f.Repos().ActionsURL(repo), + "/-/releases": f.Repos().ReleasesURL(repo), + "/-/blob/main/file.go": f.Repos().BlobURL(repo, "main", "file.go"), + "/-/issues": f.Issues().ListURL(repo), + "/-/merge_requests": f.PullRequests().ListURL(repo), + "/-/labels": f.Labels().ListURL(repo), + } + for suffix, got := range urls { + if got != repo+suffix { + t.Errorf("URL = %q, want %q", got, repo+suffix) + } + } +} diff --git a/gitlab/users.go b/gitlab/users.go index 07fd029..3666ed1 100644 --- a/gitlab/users.go +++ b/gitlab/users.go @@ -1,3 +1,5 @@ +//go:build !tinygo + package gitlab import ( From ac36746d38e2922f13bf434fae2e1cd9fcbd9f49 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 26 Sep 2026 21:15:10 +0100 Subject: [PATCH 2/2] Run GitLab fallback tests with TinyGo in CI --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3124760..f999b13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,44 @@ jobs: if: runner.os == 'Linux' run: GOOS=wasip1 GOARCH=wasm go build ./... + tinygo: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + + - name: Set up Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + package-manager-cache: false + + - name: Install TinyGo and Wasmtime + working-directory: ${{ runner.temp }} + run: | + curl --fail --location --silent --show-error --output tinygo.tar.gz https://github.com/tinygo-org/tinygo/releases/download/v0.42.0/tinygo0.42.0.linux-amd64.tar.gz + echo 'b87688fa2e19cee7d813cad7fd7dadb71dff3198e47125aba66ba4af5e490438 tinygo.tar.gz' | sha256sum --check + tar -xzf tinygo.tar.gz + echo "$RUNNER_TEMP/tinygo/bin" >> "$GITHUB_PATH" + curl --fail --location --silent --show-error --output wasmtime.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux.tar.xz + echo 'afd58715f105e3a7f454169daed22168c5736ec5f225fb04c4ac62c54c9508a3 wasmtime.tar.xz' | sha256sum --check + tar -xJf wasmtime.tar.xz + echo "$RUNNER_TEMP/wasmtime-v44.0.1-x86_64-linux" >> "$GITHUB_PATH" + + - name: Test TinyGo WebAssembly GitLab fallback and URLs + run: tinygo test -target=wasm -run '^(TestTinyGoClientRouting|TestTinyGoServicesReturnUnsupported|TestGitLabPortableURLs)$' -v ./gitlab + + - name: Test TinyGo WASI GitLab fallback and URLs + run: tinygo test -target=wasip1 -run '^(TestTinyGoClientRouting|TestTinyGoServicesReturnUnsupported|TestGitLabPortableURLs)$' -v ./gitlab + lint: runs-on: ubuntu-latest steps: