Implement the Gitea extension

The repository was a bare Nova scaffold. This builds it out to match the
capabilities of the official Gitea VS Code extension.

Sidebar sections:
  - Current Branch: pull requests and workflow runs for the checked-out
    branch, with a current/all/pinned branch filter
  - Workflows: runs grouped by workflow file, expanding into jobs, steps,
    and artifacts
  - Pull Requests: open pull requests across repositories, expanding into
    reviews, review comments, and changed files
  - Settings: per-instance connection state, plus repository Actions
    secrets and variables

Commands cover run control (re-run, re-run failed jobs, re-run a job,
cancel), job logs, artifact download/reveal/open, pull request overview,
diff, checkout, creation, merge and close, the full review cycle, and
secret and variable management. Multiple instances are supported, routed
by git remote host, with tokens held per instance in the Keychain.

Nova exposes no webview, diff editor, editor decorations, or extension
status bar, so four features are shaped differently from the VS Code
original: the pull request timeline renders as Markdown, diffs open as
unified .diff documents, review comments are published through an
IssueCollection so they appear in the gutter and the Issues sidebar, and
a failed run posts a notification. OAuth and insecureSkipVerify have no
Nova equivalent and are omitted. README.md records all of this.

Endpoints were taken from Gitea's published swagger.v1.json. Servers
predating the workflow runs API fall back to /actions/tasks.

Tests/ runs the extension's real code under Node against a stubbed Nova
runtime and a canned Gitea instance: 88 checks, no install step and no
network. Images are generated by Tools/make-icons.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MQuusXgZC2dzwpJJ1qhtti
This commit is contained in:
2026-08-28 19:55:54 -03:00
co-authored by Claude Opus 5
commit 694609a3cc
58 changed files with 6231 additions and 0 deletions
+342
View File
@@ -0,0 +1,342 @@
{
"identifier": "unsupervised.giteanova",
"name": "Gitea",
"organization": "Unsupervised",
"description": "Gitea Actions, pull requests, reviews, secrets, and variables — without leaving Nova.",
"version": "1.0",
"license": "MIT",
"categories": ["sidebars", "commands", "issues"],
"repository": "https://git.unsupervised.ca/unsupervised/gitea-nova.novaextension",
"bugs": "https://git.unsupervised.ca/unsupervised/gitea-nova.novaextension/issues",
"main": "main.js",
"activationEvents": ["*"],
"entitlements": {
"requests": true,
"process": true,
"filesystem": "readwrite",
"clipboard": true
},
"commands": {
"extensions": [
{ "title": "Refresh Gitea", "command": "gitea.refresh", "shortcut": "ctrl-alt-r" },
{ "title": "Set Token…", "command": "gitea.setToken" },
{ "title": "Sign Out…", "command": "gitea.clearToken" },
{ "title": "Test Connection", "command": "gitea.testConnection" },
{ "separator": true },
{ "title": "Add Gitea Instance…", "command": "gitea.addInstance" },
{ "title": "Remove Gitea Instance…", "command": "gitea.removeInstance" },
{ "title": "Open Gitea Preferences", "command": "gitea.openPreferences" },
{ "separator": true },
{ "title": "Change Branch Filter…", "command": "gitea.switchBranchFilter" },
{ "title": "Open Latest Failed Job Logs", "command": "gitea.openLatestFailedJobLogs" },
{ "separator": true },
{ "title": "Create Pull Request…", "command": "gitea.createPullRequest" },
{ "title": "Add Secret…", "command": "gitea.createSecret" },
{ "title": "Add Variable…", "command": "gitea.createVariable" }
],
"editor": [
{
"title": "Add Gitea Review Comment…",
"command": "gitea.addReviewComment",
"when": "editorHasFocus && documentHasPath"
}
]
},
"sidebars": [
{
"id": "gitea",
"name": "Gitea",
"smallImage": "gitea-small",
"largeImage": "gitea-large",
"sections": [
{
"id": "gitea.branch",
"name": "Current Branch",
"placeholderText": "No Gitea repository detected in this workspace.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh", "command": "gitea.refresh" },
{ "title": "Branch Filter", "image": "branch", "tooltip": "Change branch filter", "command": "gitea.switchBranchFilter" }
],
"contextCommands": [
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'pr' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'pr' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment'" },
{ "separator": true },
{ "title": "View Job Logs", "command": "gitea.viewJobLogs", "when": "viewItem == 'job'" },
{ "title": "Re-run Workflow Run", "command": "gitea.rerunRun", "when": "viewItem == 'run'" },
{ "title": "Re-run Failed Jobs", "command": "gitea.rerunFailedJobs", "when": "viewItem == 'run'" },
{ "title": "Re-run Job", "command": "gitea.rerunJob", "when": "viewItem == 'job'" },
{ "title": "Cancel Run", "command": "gitea.cancelRun", "when": "viewItem == 'run'" },
{ "separator": true },
{ "title": "Download Artifact", "command": "gitea.downloadArtifact", "when": "viewItem == 'artifact'" },
{ "separator": true },
{ "title": "Open Pull Request Overview", "command": "gitea.openPullRequestOverview", "when": "viewItem == 'pr'" },
{ "title": "Open Pull Request Diff", "command": "gitea.openPullRequestDiff", "when": "viewItem == 'pr'" },
{ "title": "Checkout Branch", "command": "gitea.checkoutPrBranch", "when": "viewItem == 'pr'" },
{ "title": "Approve", "command": "gitea.reviewApprove", "when": "viewItem == 'pr'" },
{ "title": "Request Changes…", "command": "gitea.reviewRequestChanges", "when": "viewItem == 'pr'" },
{ "title": "Comment…", "command": "gitea.reviewComment", "when": "viewItem == 'pr'" },
{ "title": "Merge…", "command": "gitea.mergePullRequest", "when": "viewItem == 'pr'" },
{ "title": "Close", "command": "gitea.closePullRequest", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Reply…", "command": "gitea.replyToReviewComment", "when": "viewItem == 'comment'" },
{ "title": "Resolve Thread", "command": "gitea.resolveReviewThread", "when": "viewItem == 'comment'" },
{ "title": "Unresolve Thread", "command": "gitea.unresolveReviewThread", "when": "viewItem == 'comment'" }
]
},
{
"id": "gitea.workflows",
"name": "Workflows",
"placeholderText": "No workflow runs found.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh workflow runs", "command": "gitea.refreshRuns" }
],
"contextCommands": [
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'workflow'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'run' || viewItem == 'job' || viewItem == 'artifact' || viewItem == 'repo' || viewItem == 'workflow'" },
{ "separator": true },
{ "title": "View Job Logs", "command": "gitea.viewJobLogs", "when": "viewItem == 'job'" },
{ "title": "Re-run Workflow Run", "command": "gitea.rerunRun", "when": "viewItem == 'run'" },
{ "title": "Re-run Failed Jobs", "command": "gitea.rerunFailedJobs", "when": "viewItem == 'run'" },
{ "title": "Re-run Job", "command": "gitea.rerunJob", "when": "viewItem == 'job'" },
{ "title": "Cancel Run", "command": "gitea.cancelRun", "when": "viewItem == 'run'" },
{ "separator": true },
{ "title": "Download Artifact", "command": "gitea.downloadArtifact", "when": "viewItem == 'artifact'" },
{ "title": "Open Artifact", "command": "gitea.openArtifact", "when": "viewItem == 'artifact'" },
{ "title": "Reveal Artifact in Finder", "command": "gitea.revealArtifact", "when": "viewItem == 'artifact'" },
{ "separator": true },
{ "title": "Refresh Repository", "command": "gitea.refreshRepo", "when": "viewItem == 'repo'" }
]
},
{
"id": "gitea.pullRequests",
"name": "Pull Requests",
"placeholderText": "No open pull requests.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh pull requests", "command": "gitea.refreshPullRequests" },
{ "title": "Create", "image": "__builtin.add", "tooltip": "Create a pull request", "command": "gitea.createPullRequest" }
],
"contextCommands": [
{ "title": "Open Pull Request Overview", "command": "gitea.openPullRequestOverview", "when": "viewItem == 'pr'" },
{ "title": "Open Pull Request Diff", "command": "gitea.openPullRequestDiff", "when": "viewItem == 'pr'" },
{ "title": "Open Changed File", "command": "gitea.openChangedFile", "when": "viewItem == 'prFile'" },
{ "separator": true },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'pr' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment' || viewItem == 'prFile'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'pr' || viewItem == 'repo' || viewItem == 'review' || viewItem == 'comment' || viewItem == 'prFile'" },
{ "separator": true },
{ "title": "Checkout Branch", "command": "gitea.checkoutPrBranch", "when": "viewItem == 'pr'" },
{ "title": "Approve", "command": "gitea.reviewApprove", "when": "viewItem == 'pr'" },
{ "title": "Request Changes…", "command": "gitea.reviewRequestChanges", "when": "viewItem == 'pr'" },
{ "title": "Comment…", "command": "gitea.reviewComment", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Merge…", "command": "gitea.mergePullRequest", "when": "viewItem == 'pr'" },
{ "title": "Close", "command": "gitea.closePullRequest", "when": "viewItem == 'pr'" },
{ "separator": true },
{ "title": "Reply…", "command": "gitea.replyToReviewComment", "when": "viewItem == 'comment'" },
{ "title": "Resolve Thread", "command": "gitea.resolveReviewThread", "when": "viewItem == 'comment'" },
{ "title": "Unresolve Thread", "command": "gitea.unresolveReviewThread", "when": "viewItem == 'comment'" }
]
},
{
"id": "gitea.settings",
"name": "Settings",
"placeholderText": "No Gitea instances configured.",
"headerCommands": [
{ "title": "Refresh", "image": "refresh", "tooltip": "Refresh settings", "command": "gitea.refreshSettings" },
{ "title": "Add Instance", "image": "__builtin.add", "tooltip": "Add a Gitea instance", "command": "gitea.addInstance" }
],
"contextCommands": [
{ "title": "Set Token…", "command": "gitea.setToken", "when": "viewItem == 'instance'" },
{ "title": "Sign Out…", "command": "gitea.clearToken", "when": "viewItem == 'instance'" },
{ "title": "Test Connection", "command": "gitea.testConnection", "when": "viewItem == 'instance'" },
{ "title": "Remove Instance…", "command": "gitea.removeInstance", "when": "viewItem == 'instance'" },
{ "separator": true },
{ "title": "Refresh Secrets", "command": "gitea.refreshSecrets", "when": "viewItem == 'secretsFolder' || viewItem == 'secret'" },
{ "title": "Add Secret…", "command": "gitea.createSecret", "when": "viewItem == 'secretsFolder' || viewItem == 'secret'" },
{ "title": "Update Secret…", "command": "gitea.updateSecret", "when": "viewItem == 'secret'" },
{ "title": "Delete Secret…", "command": "gitea.deleteSecret", "when": "viewItem == 'secret'" },
{ "separator": true },
{ "title": "Refresh Variables", "command": "gitea.refreshVariables", "when": "viewItem == 'variablesFolder' || viewItem == 'variable'" },
{ "title": "Add Variable…", "command": "gitea.createVariable", "when": "viewItem == 'variablesFolder' || viewItem == 'variable'" },
{ "title": "Update Variable…", "command": "gitea.updateVariable", "when": "viewItem == 'variable'" },
{ "title": "Delete Variable…", "command": "gitea.deleteVariable", "when": "viewItem == 'variable'" },
{ "separator": true },
{ "title": "Open in Browser", "command": "gitea.openInBrowser", "when": "viewItem == 'instance' || viewItem == 'repo'" },
{ "title": "Copy URL", "command": "gitea.copyUrl", "when": "viewItem == 'instance' || viewItem == 'repo'" }
]
}
]
}
],
"config": [
{
"key": "gitea.section.connection",
"title": "Connection",
"type": "section",
"children": [
{
"key": "gitea.baseUrl",
"title": "Default Instance URL",
"description": "The Gitea instance used when a repository's remote host does not match any other configured instance.",
"type": "string",
"default": "https://gitea.com",
"placeholder": "https://gitea.com"
},
{
"key": "gitea.instances",
"title": "Additional Instances",
"description": "Extra Gitea instance URLs. Repositories are routed to an instance by matching their git remote host. Tokens are stored per instance in the macOS Keychain.",
"type": "stringArray"
},
{
"key": "gitea.setToken",
"title": "Sign In",
"description": "Store a personal access token for a configured instance.",
"type": "command",
"command": "gitea.setToken"
},
{
"key": "gitea.testConnection",
"title": "Test Connection",
"description": "Verify the stored token against each configured instance.",
"type": "command",
"command": "gitea.testConnection"
}
]
},
{
"key": "gitea.section.discovery",
"title": "Repositories",
"type": "section",
"children": [
{
"key": "gitea.discovery.mode",
"title": "Discovery Mode",
"description": "How repositories are found. \"Workspace\" reads git remotes from the open workspace. \"All Accessible\" lists every repository your token can reach.",
"type": "enum",
"values": [
["workspace", "Workspace Git Remotes"],
["allAccessible", "All Accessible Repositories"]
],
"default": "workspace"
},
{
"key": "gitea.maxRunsPerRepo",
"title": "Max Runs per Repository",
"type": "number",
"default": 20,
"min": 1,
"max": 200
},
{
"key": "gitea.maxJobsPerRun",
"title": "Max Jobs per Run",
"type": "number",
"default": 50,
"min": 1,
"max": 200
}
]
},
{
"key": "gitea.section.refresh",
"title": "Refresh",
"type": "section",
"children": [
{
"key": "gitea.refresh.runningIntervalSeconds",
"title": "Active Interval (seconds)",
"description": "Polling interval while runs are queued or in progress.",
"type": "number",
"default": 15,
"min": 5,
"max": 3600
},
{
"key": "gitea.refresh.idleIntervalSeconds",
"title": "Idle Interval (seconds)",
"description": "Polling interval when nothing is running.",
"type": "number",
"default": 60,
"min": 10,
"max": 3600
},
{
"key": "gitea.notifications.failedRuns",
"title": "Notify on Failed Runs",
"description": "Post a notification when a workflow run finishes with a failure.",
"type": "boolean",
"default": true
}
]
},
{
"key": "gitea.section.review",
"title": "Reviews & Logs",
"type": "section",
"children": [
{
"key": "gitea.reviewComments.enabled",
"title": "Show Review Comments Inline",
"description": "Surface pull request review comments for the current branch as issues in the editor gutter and the Issues sidebar.",
"type": "boolean",
"default": true
},
{
"key": "gitea.jobLogs.saveToRepo",
"title": "Save Job Logs to Repository",
"description": "Write job logs to .tmp/gitea-logs/ inside the repository so they open as saved files. When off, logs open in an untitled tab.",
"type": "boolean",
"default": true
},
{
"key": "gitea.artifacts.downloadPath",
"title": "Artifact Download Path",
"description": "Where downloaded artifacts are written. Relative paths resolve against the repository root.",
"type": "string",
"default": ".tmp/gitea-artifacts/"
}
]
},
{
"key": "gitea.logging.debug",
"title": "Enable Debug Logging",
"description": "Write verbose diagnostics to the extension console.",
"type": "boolean",
"default": false
}
],
"configWorkspace": [
{
"key": "gitea.baseUrl",
"title": "Instance URL",
"description": "Override the default Gitea instance for this workspace.",
"type": "string",
"placeholder": "Inherit from global preferences"
},
{
"key": "gitea.branchFilter",
"title": "Branch Filter",
"description": "Which branch the Current Branch section tracks.",
"type": "enum",
"values": [
["current", "Checked-out Branch"],
["all", "All Branches"]
],
"default": "current"
},
{
"key": "gitea.branchFilter.custom",
"title": "Custom Branch",
"description": "When set, the Current Branch section tracks this branch instead of the checked-out one.",
"type": "string",
"placeholder": "e.g. main"
}
]
}