mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
Gitea support (#45)
This commit is contained in:
parent
b1e0ab088d
commit
82730f1cb0
14 changed files with 414 additions and 9 deletions
BIN
.github/images/gitea-pat-creation.png
vendored
Normal file
BIN
.github/images/gitea-pat-creation.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 KiB |
|
|
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Gitea support ([#45](https://github.com/sourcebot-dev/sourcebot/pull/45))
|
||||
|
||||
## [2.0.2] - 2024-10-18
|
||||
|
||||
### Added
|
||||
|
|
|
|||
53
README.md
53
README.md
|
|
@ -30,7 +30,7 @@ https://github.com/user-attachments/assets/98d46192-5469-430f-ad9e-5c042adbb10d
|
|||
|
||||
## Features
|
||||
- 💻 **One-command deployment**: Get started instantly using Docker on your own machine.
|
||||
- 🔍 **Multi-repo search**: Effortlessly index and search through multiple public and private repositories in GitHub or GitLab.
|
||||
- 🔍 **Multi-repo search**: Effortlessly index and search through multiple public and private repositories in GitHub, GitLab, or Gitea.
|
||||
- ⚡**Lightning fast performance**: Built on top of the powerful [Zoekt](https://github.com/sourcegraph/zoekt) search engine.
|
||||
- 📂 **Full file visualization**: Instantly view the entire file when selecting any search result.
|
||||
- 🎨 **Modern web app**: Enjoy a sleek interface with features like syntax highlighting, light/dark mode, and vim-style navigation
|
||||
|
|
@ -62,7 +62,7 @@ Sourcebot supports indexing and searching through public and private repositorie
|
|||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset=".github/images/github-favicon-inverted.png">
|
||||
<img src="https://github.com/favicon.ico" width="16" height="16" alt="GitHub icon">
|
||||
</picture> GitHub and <img src="https://gitlab.com/favicon.ico" width="16" height="16" /> GitLab. This section will guide you through configuring the repositories that Sourcebot indexes.
|
||||
</picture> GitHub, <img src="https://gitlab.com/favicon.ico" width="16" height="16" /> GitLab and <img src="https://gitea.com/favicon.ico" width="16" height="16"> Gitea. This section will guide you through configuring the repositories that Sourcebot indexes.
|
||||
|
||||
1. Create a new folder on your machine that stores your configs and `.sourcebot` cache, and navigate into it:
|
||||
```sh
|
||||
|
|
@ -214,6 +214,53 @@ docker run -e <b>GITLAB_TOKEN=glpat-mytoken</b> /* additional args */ ghcr.io/so
|
|||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><img src="https://gitea.com/favicon.ico" width="16" height="16"> Gitea</summary>
|
||||
|
||||
Generate a Gitea access token [here](http://gitea.com/user/settings/applications). At minimum, you'll need to select the `read:repository` scope, but `read:user` and `read:organization` are required for the `user` and `org` fields of your config file:
|
||||
|
||||

|
||||
|
||||
Next, update your configuration with the `token` field:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v2/index.json",
|
||||
"repos": [
|
||||
{
|
||||
"type": "gitea",
|
||||
"token": "my-secret-token",
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass tokens as environment variables:
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v2/index.json",
|
||||
"repos": [
|
||||
{
|
||||
"type": "gitea",
|
||||
"token": {
|
||||
// note: this env var can be named anything. It
|
||||
// doesn't need to be `GITEA_TOKEN`.
|
||||
"env": "GITEA_TOKEN"
|
||||
},
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You'll need to pass this environment variable each time you run Sourcebot:
|
||||
|
||||
<pre>
|
||||
docker run -e <b>GITEA_TOKEN=my-secret-token</b> /* additional args */ ghcr.io/sourcebot-dev/sourcebot:latest
|
||||
</pre>
|
||||
|
||||
</details>
|
||||
|
||||
</div>
|
||||
|
||||
## Using a self-hosted GitLab / GitHub instance
|
||||
|
|
@ -226,7 +273,7 @@ If you're using a self-hosted GitLab or GitHub instance with a custom domain, yo
|
|||
|
||||
1. Install <a href="https://go.dev/doc/install"><img src="https://go.dev/favicon.ico" width="16" height="16"> go</a> and <a href="https://nodejs.org/"><img src="https://nodejs.org/favicon.ico" width="16" height="16"> NodeJS</a>. Note that a NodeJS version of at least `21.1.0` is required.
|
||||
|
||||
2. Install [ctags](https://github.com/universal-ctags/ctags) (required by zoekt-indexserver)
|
||||
2. Install [ctags](https://github.com/universal-ctags/ctags) (required by zoekt)
|
||||
```sh
|
||||
// macOS:
|
||||
brew install universal-ctags
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
"@gitbeaker/rest": "^40.5.1",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
"argparse": "^2.0.1",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"gitea-js": "^1.22.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"simple-git": "^3.27.0",
|
||||
"strip-json-comments": "^5.0.1",
|
||||
|
|
|
|||
150
packages/backend/src/gitea.ts
Normal file
150
packages/backend/src/gitea.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { Api, giteaApi, HttpResponse, Repository as GiteaRepository } from 'gitea-js';
|
||||
import { GiteaConfig } from './schemas/v2.js';
|
||||
import { excludeArchivedRepos, excludeForkedRepos, excludeReposByName, getTokenFromConfig, marshalBool, measure } from './utils.js';
|
||||
import { AppContext, Repository } from './types.js';
|
||||
import fetch from 'cross-fetch';
|
||||
import { createLogger } from './logger.js';
|
||||
import path from 'path';
|
||||
|
||||
const logger = createLogger('Gitea');
|
||||
|
||||
export const getGiteaReposFromConfig = async (config: GiteaConfig, ctx: AppContext) => {
|
||||
const token = config.token ? getTokenFromConfig(config.token, ctx) : undefined;
|
||||
|
||||
const api = giteaApi(config.url ?? 'https://gitea.com', {
|
||||
token,
|
||||
customFetch: fetch,
|
||||
});
|
||||
|
||||
let allRepos: GiteaRepository[] = [];
|
||||
|
||||
if (config.orgs) {
|
||||
const _repos = await getReposForOrgs(config.orgs, api);
|
||||
allRepos = allRepos.concat(_repos);
|
||||
}
|
||||
|
||||
if (config.repos) {
|
||||
const _repos = await getRepos(config.repos, api);
|
||||
allRepos = allRepos.concat(_repos);
|
||||
}
|
||||
|
||||
if (config.users) {
|
||||
const _repos = await getReposOwnedByUsers(config.users, api);
|
||||
allRepos = allRepos.concat(_repos);
|
||||
}
|
||||
|
||||
let repos: Repository[] = allRepos
|
||||
.map((repo) => {
|
||||
const hostname = config.url ? new URL(config.url).hostname : 'gitea.com';
|
||||
const repoId = `${hostname}/${repo.full_name!}`;
|
||||
const repoPath = path.resolve(path.join(ctx.reposPath, `${repoId}.git`));
|
||||
|
||||
const cloneUrl = new URL(repo.clone_url!);
|
||||
if (token) {
|
||||
cloneUrl.username = token;
|
||||
}
|
||||
|
||||
return {
|
||||
name: repo.full_name!,
|
||||
id: repoId,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
path: repoPath,
|
||||
isStale: false,
|
||||
isFork: repo.fork!,
|
||||
isArchived: !!repo.archived,
|
||||
gitConfigMetadata: {
|
||||
'zoekt.web-url-type': 'gitea',
|
||||
'zoekt.web-url': repo.html_url!,
|
||||
'zoekt.name': repoId,
|
||||
'zoekt.archived': marshalBool(repo.archived),
|
||||
'zoekt.fork': marshalBool(repo.fork!),
|
||||
'zoekt.public': marshalBool(repo.internal === false && repo.private === false),
|
||||
}
|
||||
} satisfies Repository;
|
||||
});
|
||||
|
||||
if (config.exclude) {
|
||||
if (!!config.exclude.forks) {
|
||||
repos = excludeForkedRepos(repos, logger);
|
||||
}
|
||||
|
||||
if (!!config.exclude.archived) {
|
||||
repos = excludeArchivedRepos(repos, logger);
|
||||
}
|
||||
|
||||
if (config.exclude.repos) {
|
||||
repos = excludeReposByName(repos, config.exclude.repos, logger);
|
||||
}
|
||||
}
|
||||
|
||||
return repos;
|
||||
}
|
||||
|
||||
const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
|
||||
const repos = (await Promise.all(users.map(async (user) => {
|
||||
logger.debug(`Fetching repos for user ${user}...`);
|
||||
|
||||
const { durationMs, data } = await measure(() =>
|
||||
paginate((page) => api.users.userListRepos(user, {
|
||||
page,
|
||||
}))
|
||||
);
|
||||
|
||||
logger.debug(`Found ${data.length} repos owned by user ${user} in ${durationMs}ms.`);
|
||||
return data;
|
||||
}))).flat();
|
||||
|
||||
return repos;
|
||||
}
|
||||
|
||||
const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
|
||||
return (await Promise.all(orgs.map(async (org) => {
|
||||
logger.debug(`Fetching repos for org ${org}...`);
|
||||
|
||||
const { durationMs, data } = await measure(() =>
|
||||
paginate((page) => api.orgs.orgListRepos(org, {
|
||||
limit: 100,
|
||||
page,
|
||||
}))
|
||||
);
|
||||
|
||||
logger.debug(`Found ${data.length} repos for org ${org} in ${durationMs}ms.`);
|
||||
return data;
|
||||
}))).flat();
|
||||
}
|
||||
|
||||
const getRepos = async <T>(repos: string[], api: Api<T>) => {
|
||||
return Promise.all(repos.map(async (repo) => {
|
||||
logger.debug(`Fetching repository info for ${repo}...`);
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const { durationMs, data: response } = await measure(() =>
|
||||
api.repos.repoGet(owner, repoName),
|
||||
);
|
||||
|
||||
logger.debug(`Found repo ${repo} in ${durationMs}ms.`);
|
||||
|
||||
return response.data;
|
||||
}));
|
||||
}
|
||||
|
||||
// @see : https://docs.gitea.com/development/api-usage#pagination
|
||||
const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[], any>>) => {
|
||||
let page = 1;
|
||||
const result = await request(page);
|
||||
const output: T[] = result.data;
|
||||
|
||||
const totalCountString = result.headers.get('x-total-count');
|
||||
if (!totalCountString) {
|
||||
throw new Error("Header 'x-total-count' not found");
|
||||
}
|
||||
const totalCount = parseInt(totalCountString);
|
||||
|
||||
while (output.length < totalCount) {
|
||||
page++;
|
||||
const result = await request(page);
|
||||
output.push(...result.data);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import path from 'path';
|
|||
import { SourcebotConfigurationSchema } from "./schemas/v2.js";
|
||||
import { getGitHubReposFromConfig } from "./github.js";
|
||||
import { getGitLabReposFromConfig } from "./gitlab.js";
|
||||
import { getGiteaReposFromConfig } from "./gitea.js";
|
||||
import { AppContext, Repository } from "./types.js";
|
||||
import { cloneRepository, fetchRepository } from "./git.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
|
|
@ -75,6 +76,11 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal,
|
|||
configRepos.push(...gitLabRepos);
|
||||
break;
|
||||
}
|
||||
case 'gitea': {
|
||||
const giteaRepos = await getGiteaReposFromConfig(repoConfig, ctx);
|
||||
configRepos.push(...giteaRepos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +186,8 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal,
|
|||
// since it implies another sync is in progress.
|
||||
} else {
|
||||
isSyncing = false;
|
||||
logger.error(`Failed to sync configuration file ${args.configPath} with error:\n`, err);
|
||||
logger.error(`Failed to sync configuration file ${args.configPath} with error:`);
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export type Repos = GitHubConfig | GitLabConfig;
|
||||
export type Repos = GitHubConfig | GitLabConfig | GiteaConfig;
|
||||
|
||||
/**
|
||||
* A Sourcebot configuration file outlines which repositories Sourcebot should sync and index.
|
||||
|
|
@ -106,3 +106,50 @@ export interface GitLabConfig {
|
|||
projects?: string[];
|
||||
};
|
||||
}
|
||||
export interface GiteaConfig {
|
||||
/**
|
||||
* Gitea Configuration
|
||||
*/
|
||||
type: "gitea";
|
||||
/**
|
||||
* An access token.
|
||||
*/
|
||||
token?:
|
||||
| string
|
||||
| {
|
||||
/**
|
||||
* The name of the environment variable that contains the token.
|
||||
*/
|
||||
env: string;
|
||||
};
|
||||
/**
|
||||
* The URL of the Gitea host. Defaults to https://gitea.com
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* List of organizations to sync with. All repositories in the organization visible to the provided `token` (if any) will be synced, unless explicitly defined in the `exclude` property. If a `token` is provided, it must have the read:organization scope.
|
||||
*/
|
||||
orgs?: string[];
|
||||
/**
|
||||
* List of individual repositories to sync with. Expected to be formatted as '{orgName}/{repoName}' or '{userName}/{repoName}'.
|
||||
*/
|
||||
repos?: string[];
|
||||
/**
|
||||
* List of users to sync with. All repositories that the user owns will be synced, unless explicitly defined in the `exclude` property. If a `token` is provided, it must have the read:user scope.
|
||||
*/
|
||||
users?: string[];
|
||||
exclude?: {
|
||||
/**
|
||||
* Exlcude forked repositories from syncing.
|
||||
*/
|
||||
forks?: boolean;
|
||||
/**
|
||||
* Exlcude archived repositories from syncing.
|
||||
*/
|
||||
archived?: boolean;
|
||||
/**
|
||||
* List of individual repositories to exclude from syncing. Expected to be formatted as '{orgName}/{repoName}' or '{userName}/{repoName}'.
|
||||
*/
|
||||
repos?: string[];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
1
packages/web/public/gitea.svg
Normal file
1
packages/web/public/gitea.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="enable-background:new 0 0 640 640;" xml:space="preserve" viewBox="5.67 143.05 628.65 387.55"> <g> <path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"></path> <g> <g> <path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"></path> <path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"></path> </g> </g> </g> </svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1 +1 @@
|
|||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitLab</title><path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/></svg>
|
||||
<svg width="2500" height="2305" viewBox="0 0 256 236" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M128.075 236.075l47.104-144.97H80.97l47.104 144.97z" fill="#E24329"/><path d="M128.075 236.074L80.97 91.104H14.956l113.119 144.97z" fill="#FC6D26"/><path d="M14.956 91.104L.642 135.16a9.752 9.752 0 0 0 3.542 10.903l123.891 90.012-113.12-144.97z" fill="#FCA326"/><path d="M14.956 91.105H80.97L52.601 3.79c-1.46-4.493-7.816-4.492-9.275 0l-28.37 87.315z" fill="#E24329"/><path d="M128.075 236.074l47.104-144.97h66.015l-113.12 144.97z" fill="#FC6D26"/><path d="M241.194 91.104l14.314 44.056a9.752 9.752 0 0 1-3.543 10.903l-123.89 90.012 113.119-144.97z" fill="#FCA326"/><path d="M241.194 91.105h-66.015l28.37-87.315c1.46-4.493 7.816-4.492 9.275 0l28.37 87.315z" fill="#E24329"/></svg>
|
||||
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 814 B |
|
|
@ -64,7 +64,7 @@ const RepositoryBadge = ({
|
|||
repoIcon: <Image
|
||||
src={info.icon}
|
||||
alt={info.costHostName}
|
||||
className="w-4 h-4 dark:invert"
|
||||
className={`w-4 h-4 ${info.iconClassname}`}
|
||||
/>,
|
||||
repoName: info.repoName,
|
||||
repoLink: info.repoLink,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const FileMatchContainer = ({
|
|||
repoIcon: <Image
|
||||
src={info.icon}
|
||||
alt={info.costHostName}
|
||||
className="w-4 h-4 dark:invert"
|
||||
className={`w-4 h-4 ${info.iconClassname}`}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { type ClassValue, clsx } from "clsx"
|
|||
import { twMerge } from "tailwind-merge"
|
||||
import githubLogo from "../../public/github.svg";
|
||||
import gitlabLogo from "../../public/gitlab.svg";
|
||||
import giteaLogo from "../../public/gitea.svg";
|
||||
import { ServiceError } from "./serviceError";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
|
|
@ -29,11 +30,12 @@ export const createPathWithQueryParams = (path: string, ...queryParams: [string,
|
|||
}
|
||||
|
||||
type CodeHostInfo = {
|
||||
type: "github" | "gitlab";
|
||||
type: "github" | "gitlab" | "gitea";
|
||||
repoName: string;
|
||||
costHostName: string;
|
||||
repoLink: string;
|
||||
icon: string;
|
||||
iconClassname?: string;
|
||||
}
|
||||
|
||||
export const getRepoCodeHostInfo = (repoName: string): CodeHostInfo | undefined => {
|
||||
|
|
@ -44,6 +46,7 @@ export const getRepoCodeHostInfo = (repoName: string): CodeHostInfo | undefined
|
|||
costHostName: "GitHub",
|
||||
repoLink: `https://${repoName}`,
|
||||
icon: githubLogo,
|
||||
iconClassname: "dark:invert",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +60,16 @@ export const getRepoCodeHostInfo = (repoName: string): CodeHostInfo | undefined
|
|||
}
|
||||
}
|
||||
|
||||
if (repoName.startsWith("gitea.com")) {
|
||||
return {
|
||||
type: "gitea",
|
||||
repoName: repoName.substring("gitea.com/".length),
|
||||
costHostName: "Gitea",
|
||||
repoLink: `https://${repoName}`,
|
||||
icon: giteaLogo,
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +84,10 @@ export const getCodeHostFilePreviewLink = (repoName: string, filePath: string):
|
|||
return `${info.repoLink}/-/blob/HEAD/${filePath}`;
|
||||
}
|
||||
|
||||
if (info?.type === "gitea") {
|
||||
return `${info.repoLink}/src/branch/HEAD/${filePath}`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,96 @@
|
|||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"GiteaConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitea",
|
||||
"description": "Gitea Configuration"
|
||||
},
|
||||
"token": {
|
||||
"$ref": "#/definitions/Token",
|
||||
"description": "An access token.",
|
||||
"examples": [
|
||||
"secret-token",
|
||||
{ "env": "ENV_VAR_CONTAINING_TOKEN" }
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"default": "https://gitea.com",
|
||||
"description": "The URL of the Gitea host. Defaults to https://gitea.com",
|
||||
"examples": [
|
||||
"https://gitea.com",
|
||||
"https://gitea.example.com"
|
||||
],
|
||||
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||
},
|
||||
"orgs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"my-org-name"
|
||||
]
|
||||
],
|
||||
"description": "List of organizations to sync with. All repositories in the organization visible to the provided `token` (if any) will be synced, unless explicitly defined in the `exclude` property. If a `token` is provided, it must have the read:organization scope."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.-]+\\/[\\w.-]+$"
|
||||
},
|
||||
"description": "List of individual repositories to sync with. Expected to be formatted as '{orgName}/{repoName}' or '{userName}/{repoName}'."
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"username-1",
|
||||
"username-2"
|
||||
]
|
||||
],
|
||||
"description": "List of users to sync with. All repositories that the user owns will be synced, unless explicitly defined in the `exclude` property. If a `token` is provided, it must have the read:user scope."
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"forks": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exlcude forked repositories from syncing."
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exlcude archived repositories from syncing."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.-]+\\/[\\w.-]+$"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of individual repositories to exclude from syncing. Expected to be formatted as '{orgName}/{repoName}' or '{userName}/{repoName}'."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Repos": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
|
@ -221,6 +311,9 @@
|
|||
},
|
||||
{
|
||||
"$ref": "#/definitions/GitLabConfig"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/GiteaConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
37
yarn.lock
37
yarn.lock
|
|
@ -1961,6 +1961,13 @@ crelt@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
|
||||
integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==
|
||||
|
||||
cross-fetch@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983"
|
||||
integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==
|
||||
dependencies:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
|
|
@ -2764,6 +2771,11 @@ get-tsconfig@^4.7.5:
|
|||
dependencies:
|
||||
resolve-pkg-maps "^1.0.0"
|
||||
|
||||
gitea-js@^1.22.0:
|
||||
version "1.22.0"
|
||||
resolved "https://registry.yarnpkg.com/gitea-js/-/gitea-js-1.22.0.tgz#bf081fd69eff102d5a00660b6d5f5e8f8fd34f3a"
|
||||
integrity sha512-vG3yNU2NKX7vbsqHH5U3q0u3OmWWh3c4nvyWtx022jQEDJDZP47EoGurXCmOhzvD5AwgUV6r+lVAz+Fa1dazgg==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
|
|
@ -3516,6 +3528,13 @@ node-cleanup@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
|
||||
integrity sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==
|
||||
|
||||
node-fetch@^2.6.12:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
|
||||
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
normalize-package-data@^2.3.2:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||
|
|
@ -4582,6 +4601,11 @@ to-regex-range@^5.0.1:
|
|||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
triple-beam@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984"
|
||||
|
|
@ -4765,6 +4789,19 @@ web-vitals@^4.0.1:
|
|||
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7"
|
||||
integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
|
|
|
|||
Loading…
Reference in a new issue