mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 20:35:24 +00:00
add back gitlab, gitea, and gerrit support (#184)
* add non github config definitions * refactor github config compilation to seperate file * add gitlab config compilation * Connection management (#183) * wip gitlab repo sync support * fix gitlab zoekt metadata * add gitea support * add gerrit support * Connection management (#183) * add gerrit config compilation * Connection management (#183) --------- Co-authored-by: Brendan Kellam <bshizzle1234@gmail.com>
This commit is contained in:
parent
e2e5433d20
commit
da33220289
30 changed files with 1576 additions and 213 deletions
|
|
@ -5,8 +5,7 @@ import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
|||
import { createLogger } from "./logger.js";
|
||||
import os from 'os';
|
||||
import { Redis } from 'ioredis';
|
||||
import { marshalBool } from "./utils.js";
|
||||
import { getGitHubReposFromConfig } from "./github.js";
|
||||
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
|
||||
|
||||
interface IConnectionManager {
|
||||
scheduleConnectionSync: (connection: Connection) => Promise<void>;
|
||||
|
|
@ -79,64 +78,28 @@ export class ConnectionManager implements IConnectionManager {
|
|||
// @note: We aren't actually doing anything with this atm.
|
||||
const abortController = new AbortController();
|
||||
|
||||
type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>;
|
||||
const repoData: RepoData[] = (
|
||||
await (async () => {
|
||||
switch (config.type) {
|
||||
case 'github': {
|
||||
const gitHubRepos = await getGitHubReposFromConfig(config, orgId, this.db, abortController.signal);
|
||||
const hostUrl = config.url ?? 'https://github.com';
|
||||
const hostname = config.url ? new URL(config.url).hostname : 'github.com';
|
||||
|
||||
return gitHubRepos.map((repo) => {
|
||||
const repoName = `${hostname}/${repo.full_name}`;
|
||||
const cloneUrl = new URL(repo.clone_url!);
|
||||
|
||||
const record: RepoData = {
|
||||
external_id: repo.id.toString(),
|
||||
external_codeHostType: 'github',
|
||||
external_codeHostUrl: hostUrl,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
imageUrl: repo.owner.avatar_url,
|
||||
name: repoName,
|
||||
isFork: repo.fork,
|
||||
isArchived: !!repo.archived,
|
||||
org: {
|
||||
connect: {
|
||||
id: orgId,
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
create: {
|
||||
connectionId: job.data.connectionId,
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
'zoekt.web-url-type': 'github',
|
||||
'zoekt.web-url': repo.html_url,
|
||||
'zoekt.name': repoName,
|
||||
'zoekt.github-stars': (repo.stargazers_count ?? 0).toString(),
|
||||
'zoekt.github-watchers': (repo.watchers_count ?? 0).toString(),
|
||||
'zoekt.github-subscribers': (repo.subscribers_count ?? 0).toString(),
|
||||
'zoekt.github-forks': (repo.forks_count ?? 0).toString(),
|
||||
'zoekt.archived': marshalBool(repo.archived),
|
||||
'zoekt.fork': marshalBool(repo.fork),
|
||||
'zoekt.public': marshalBool(repo.private === false)
|
||||
},
|
||||
};
|
||||
|
||||
return record;
|
||||
})
|
||||
}
|
||||
case 'gitlab': {
|
||||
// @todo
|
||||
return [];
|
||||
}
|
||||
const repoData: RepoData[] = await (async () => {
|
||||
switch (config.type) {
|
||||
case 'github': {
|
||||
return await compileGithubConfig(config, job.data.connectionId, orgId, this.db, abortController);
|
||||
}
|
||||
})()
|
||||
)
|
||||
case 'gitlab': {
|
||||
return await compileGitlabConfig(config, job.data.connectionId, orgId, this.db);
|
||||
}
|
||||
case 'gitea': {
|
||||
return await compileGiteaConfig(config, job.data.connectionId, orgId, this.db);
|
||||
}
|
||||
case 'gerrit': {
|
||||
return await compileGerritConfig(config, job.data.connectionId, orgId);
|
||||
}
|
||||
default: {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Filter out any duplicates by external_id and external_codeHostUrl.
|
||||
.filter((repo, index, self) => {
|
||||
repoData.filter((repo, index, self) => {
|
||||
return index === self.findIndex(r =>
|
||||
r.external_id === repo.external_id &&
|
||||
r.external_codeHostUrl === repo.external_codeHostUrl
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import fetch from 'cross-fetch';
|
||||
import { GerritConfig } from "@sourcebot/schemas/v2/index.type"
|
||||
import { AppContext, GitRepository } from './types.js';
|
||||
import { createLogger } from './logger.js';
|
||||
import path from 'path';
|
||||
import micromatch from "micromatch";
|
||||
import { measure, marshalBool, excludeReposByName, includeReposByName } from './utils.js';
|
||||
|
||||
// https://gerrit-review.googlesource.com/Documentation/rest-api.html
|
||||
|
|
@ -16,6 +15,13 @@ interface GerritProjectInfo {
|
|||
web_links?: GerritWebLink[];
|
||||
}
|
||||
|
||||
interface GerritProject {
|
||||
name: string;
|
||||
id: string;
|
||||
state?: string;
|
||||
web_links?: GerritWebLink[];
|
||||
}
|
||||
|
||||
interface GerritWebLink {
|
||||
name: string;
|
||||
url: string;
|
||||
|
|
@ -23,12 +29,12 @@ interface GerritWebLink {
|
|||
|
||||
const logger = createLogger('Gerrit');
|
||||
|
||||
export const getGerritReposFromConfig = async (config: GerritConfig, ctx: AppContext): Promise<GitRepository[]> => {
|
||||
export const getGerritReposFromConfig = async (config: GerritConfig): Promise<GerritProject[]> => {
|
||||
|
||||
const url = config.url.endsWith('/') ? config.url : `${config.url}/`;
|
||||
const hostname = new URL(config.url).hostname;
|
||||
|
||||
const { durationMs, data: projects } = await measure(async () => {
|
||||
let { durationMs, data: projects } = await measure(async () => {
|
||||
try {
|
||||
return fetchAllProjects(url)
|
||||
} catch (err) {
|
||||
|
|
@ -42,67 +48,29 @@ export const getGerritReposFromConfig = async (config: GerritConfig, ctx: AppCon
|
|||
}
|
||||
|
||||
// exclude "All-Projects" and "All-Users" projects
|
||||
delete projects['All-Projects'];
|
||||
delete projects['All-Users'];
|
||||
delete projects['All-Avatars']
|
||||
delete projects['All-Archived-Projects']
|
||||
|
||||
logger.debug(`Fetched ${Object.keys(projects).length} projects in ${durationMs}ms.`);
|
||||
|
||||
let repos: GitRepository[] = Object.keys(projects).map((projectName) => {
|
||||
const project = projects[projectName];
|
||||
let webUrl = "https://www.gerritcodereview.com/";
|
||||
// Gerrit projects can have multiple web links; use the first one
|
||||
if (project.web_links) {
|
||||
const webLink = project.web_links[0];
|
||||
if (webLink) {
|
||||
webUrl = webLink.url;
|
||||
}
|
||||
}
|
||||
const repoId = `${hostname}/${projectName}`;
|
||||
const repoPath = path.resolve(path.join(ctx.reposPath, `${repoId}.git`));
|
||||
|
||||
const cloneUrl = `${url}${encodeURIComponent(projectName)}`;
|
||||
|
||||
return {
|
||||
vcs: 'git',
|
||||
codeHost: 'gerrit',
|
||||
name: projectName,
|
||||
id: repoId,
|
||||
cloneUrl: cloneUrl,
|
||||
path: repoPath,
|
||||
isStale: false, // Gerrit projects are typically not stale
|
||||
isFork: false, // Gerrit doesn't have forks in the same way as GitHub
|
||||
isArchived: false,
|
||||
gitConfigMetadata: {
|
||||
// Gerrit uses Gitiles for web UI. This can sometimes be "browse" type in zoekt
|
||||
'zoekt.web-url-type': 'gitiles',
|
||||
'zoekt.web-url': webUrl,
|
||||
'zoekt.name': repoId,
|
||||
'zoekt.archived': marshalBool(false),
|
||||
'zoekt.fork': marshalBool(false),
|
||||
'zoekt.public': marshalBool(true), // Assuming projects are public; adjust as needed
|
||||
},
|
||||
branches: [],
|
||||
tags: []
|
||||
} satisfies GitRepository;
|
||||
});
|
||||
|
||||
const excludedProjects = ['All-Projects', 'All-Users', 'All-Avatars', 'All-Archived-Projects'];
|
||||
projects = projects.filter(project => !excludedProjects.includes(project.name));
|
||||
|
||||
// include repos by glob if specified in config
|
||||
if (config.projects) {
|
||||
repos = includeReposByName(repos, config.projects);
|
||||
projects = projects.filter((project) => {
|
||||
return micromatch.isMatch(project.name, config.projects!);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (config.exclude && config.exclude.projects) {
|
||||
repos = excludeReposByName(repos, config.exclude.projects);
|
||||
projects = projects.filter((project) => {
|
||||
return !micromatch.isMatch(project.name, config.exclude!.projects!);
|
||||
});
|
||||
}
|
||||
|
||||
return repos;
|
||||
logger.debug(`Fetched ${Object.keys(projects).length} projects in ${durationMs}ms.`);
|
||||
return projects;
|
||||
};
|
||||
|
||||
const fetchAllProjects = async (url: string): Promise<GerritProjects> => {
|
||||
const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
|
||||
const projectsEndpoint = `${url}projects/`;
|
||||
let allProjects: GerritProjects = {};
|
||||
let allProjects: GerritProject[] = [];
|
||||
let start = 0; // Start offset for pagination
|
||||
let hasMoreProjects = true;
|
||||
|
||||
|
|
@ -119,8 +87,15 @@ const fetchAllProjects = async (url: string): Promise<GerritProjects> => {
|
|||
const jsonText = text.replace(")]}'\n", ''); // Remove XSSI protection prefix
|
||||
const data: GerritProjects = JSON.parse(jsonText);
|
||||
|
||||
// Merge the current batch of projects with allProjects
|
||||
Object.assign(allProjects, data);
|
||||
// Add fetched projects to allProjects
|
||||
for (const [projectName, projectInfo] of Object.entries(data)) {
|
||||
allProjects.push({
|
||||
name: projectName,
|
||||
id: projectInfo.id,
|
||||
state: projectInfo.state,
|
||||
web_links: projectInfo.web_links
|
||||
})
|
||||
}
|
||||
|
||||
// Check if there are more projects to fetch
|
||||
hasMoreProjects = Object.values(data).some(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import { Api, giteaApi, HttpResponse, Repository as GiteaRepository } from 'gitea-js';
|
||||
import { GiteaConfig } from "@sourcebot/schemas/v2/index.type"
|
||||
import { excludeArchivedRepos, excludeForkedRepos, excludeReposByName, getTokenFromConfig, marshalBool, measure } from './utils.js';
|
||||
import { AppContext, GitRepository } from './types.js';
|
||||
import { GiteaConnectionConfig } from '@sourcebot/schemas/v3/gitea.type';
|
||||
import { getTokenFromConfig, measure } from './utils.js';
|
||||
import fetch from 'cross-fetch';
|
||||
import { createLogger } from './logger.js';
|
||||
import path from 'path';
|
||||
import micromatch from 'micromatch';
|
||||
import { PrismaClient } from '@sourcebot/db';
|
||||
|
||||
const logger = createLogger('Gitea');
|
||||
|
||||
export const getGiteaReposFromConfig = async (config: GiteaConfig, orgId: number, ctx: AppContext) => {
|
||||
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||
// TODO: pass in DB here to fetch secret properly
|
||||
const token = config.token ? await getTokenFromConfig(config.token, orgId) : undefined;
|
||||
const token = config.token ? await getTokenFromConfig(config.token, orgId, db) : undefined;
|
||||
|
||||
const api = giteaApi(config.url ?? 'https://gitea.com', {
|
||||
token,
|
||||
|
|
@ -34,66 +33,26 @@ export const getGiteaReposFromConfig = async (config: GiteaConfig, orgId: number
|
|||
const _repos = await getReposOwnedByUsers(config.users, api);
|
||||
allRepos = allRepos.concat(_repos);
|
||||
}
|
||||
|
||||
let repos: GitRepository[] = 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 {
|
||||
vcs: 'git',
|
||||
codeHost: 'gitea',
|
||||
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),
|
||||
},
|
||||
branches: [],
|
||||
tags: []
|
||||
} satisfies GitRepository;
|
||||
});
|
||||
|
||||
if (config.exclude) {
|
||||
if (!!config.exclude.forks) {
|
||||
repos = excludeForkedRepos(repos, logger);
|
||||
allRepos = allRepos.filter(repo => repo.full_name !== undefined);
|
||||
allRepos = allRepos.filter(repo => {
|
||||
if (repo.full_name === undefined) {
|
||||
logger.warn(`Repository with undefined full_name found: orgId=${orgId}, repoId=${repo.id}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!!config.exclude.archived) {
|
||||
repos = excludeArchivedRepos(repos, logger);
|
||||
}
|
||||
|
||||
if (config.exclude.repos) {
|
||||
repos = excludeReposByName(repos, config.exclude.repos, logger);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Found ${repos.length} total repositories.`);
|
||||
|
||||
|
||||
if (config.revisions) {
|
||||
if (config.revisions.branches) {
|
||||
const branchGlobs = config.revisions.branches;
|
||||
repos = await Promise.all(
|
||||
repos.map(async (repo) => {
|
||||
const [owner, name] = repo.name.split('/');
|
||||
allRepos = await Promise.all(
|
||||
allRepos.map(async (repo) => {
|
||||
const [owner, name] = repo.full_name!.split('/');
|
||||
let branches = (await getBranchesForRepo(owner, name, api)).map(branch => branch.name!);
|
||||
branches = micromatch.match(branches, branchGlobs);
|
||||
|
||||
|
||||
return {
|
||||
...repo,
|
||||
branches,
|
||||
|
|
@ -101,27 +60,80 @@ export const getGiteaReposFromConfig = async (config: GiteaConfig, orgId: number
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if (config.revisions.tags) {
|
||||
const tagGlobs = config.revisions.tags;
|
||||
repos = await Promise.all(
|
||||
repos.map(async (repo) => {
|
||||
const [owner, name] = repo.name.split('/');
|
||||
allRepos = await Promise.all(
|
||||
allRepos.map(async (allRepos) => {
|
||||
const [owner, name] = allRepos.name!.split('/');
|
||||
let tags = (await getTagsForRepo(owner, name, api)).map(tag => tag.name!);
|
||||
tags = micromatch.match(tags, tagGlobs);
|
||||
|
||||
|
||||
return {
|
||||
...repo,
|
||||
...allRepos,
|
||||
tags,
|
||||
};
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let repos = allRepos
|
||||
.filter((repo) => {
|
||||
const isExcluded = shouldExcludeRepo({
|
||||
repo,
|
||||
exclude: config.exclude,
|
||||
});
|
||||
|
||||
return !isExcluded;
|
||||
});
|
||||
|
||||
logger.debug(`Found ${repos.length} total repositories.`);
|
||||
return repos;
|
||||
}
|
||||
|
||||
const shouldExcludeRepo = ({
|
||||
repo,
|
||||
exclude
|
||||
} : {
|
||||
repo: GiteaRepository,
|
||||
exclude?: {
|
||||
forks?: boolean,
|
||||
archived?: boolean,
|
||||
repos?: string[],
|
||||
}
|
||||
}) => {
|
||||
let reason = '';
|
||||
const repoName = repo.full_name!;
|
||||
|
||||
const shouldExclude = (() => {
|
||||
if (!!exclude?.forks && repo.fork) {
|
||||
reason = `\`exclude.forks\` is true`;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!!exclude?.archived && !!repo.archived) {
|
||||
reason = `\`exclude.archived\` is true`;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exclude?.repos) {
|
||||
if (micromatch.isMatch(repoName, exclude.repos)) {
|
||||
reason = `\`exclude.repos\` contains ${repoName}`;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
if (shouldExclude) {
|
||||
logger.debug(`Excluding repo ${repoName}. Reason: ${reason}`);
|
||||
}
|
||||
|
||||
return shouldExclude;
|
||||
}
|
||||
|
||||
const getTagsForRepo = async <T>(owner: string, repo: string, api: Api<T>) => {
|
||||
try {
|
||||
logger.debug(`Fetching tags for repo ${owner}/${repo}...`);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { Gitlab, ProjectSchema } from "@gitbeaker/rest";
|
||||
import micromatch from "micromatch";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { GitLabConfig } from "@sourcebot/schemas/v2/index.type"
|
||||
import { AppContext } from "./types.js";
|
||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"
|
||||
import { getTokenFromConfig, measure } from "./utils.js";
|
||||
import { PrismaClient } from "@sourcebot/db";
|
||||
|
||||
const logger = createLogger("GitLab");
|
||||
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";
|
||||
|
||||
export const getGitLabReposFromConfig = async (config: GitLabConfig, orgId: number, ctx: AppContext) => {
|
||||
// TODO: pass in DB here to fetch secret properly
|
||||
const token = config.token ? await getTokenFromConfig(config.token, orgId) : undefined;
|
||||
export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||
const token = config.token ? await getTokenFromConfig(config.token, orgId, db) : undefined;
|
||||
const api = new Gitlab({
|
||||
...(config.token ? {
|
||||
token,
|
||||
|
|
@ -37,7 +36,7 @@ export const getGitLabReposFromConfig = async (config: GitLabConfig, orgId: numb
|
|||
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Ignoring option all:true in ${ctx.configPath} : host is ${GITLAB_CLOUD_HOSTNAME}`);
|
||||
logger.warn(`Ignoring option all:true in config : host is ${GITLAB_CLOUD_HOSTNAME}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,9 +118,9 @@ export const shouldExcludeProject = ({
|
|||
}: {
|
||||
project: ProjectSchema,
|
||||
include?: {
|
||||
topics?: GitLabConfig['topics'],
|
||||
topics?: GitlabConnectionConfig['topics'],
|
||||
},
|
||||
exclude?: GitLabConfig['exclude'],
|
||||
exclude?: GitlabConnectionConfig['exclude'],
|
||||
}) => {
|
||||
const projectName = project.path_with_namespace;
|
||||
let reason = '';
|
||||
|
|
|
|||
208
packages/backend/src/repoCompileUtils.ts
Normal file
208
packages/backend/src/repoCompileUtils.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
import { GithubConnectionConfig } from '@sourcebot/schemas/v3/github.type';
|
||||
import { getGitHubReposFromConfig } from "./github.js";
|
||||
import { getGitLabReposFromConfig } from "./gitlab.js";
|
||||
import { getGiteaReposFromConfig } from "./gitea.js";
|
||||
import { getGerritReposFromConfig } from "./gerrit.js";
|
||||
import { Prisma, PrismaClient } from '@sourcebot/db';
|
||||
import { WithRequired } from "./types.js"
|
||||
import { marshalBool } from "./utils.js";
|
||||
import { GerritConnectionConfig, GiteaConnectionConfig, GitlabConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
||||
|
||||
export type RepoData = WithRequired<Prisma.RepoCreateInput, 'connections'>;
|
||||
|
||||
export const compileGithubConfig = async (
|
||||
config: GithubConnectionConfig,
|
||||
connectionId: number,
|
||||
orgId: number,
|
||||
db: PrismaClient,
|
||||
abortController: AbortController): Promise<RepoData[]> => {
|
||||
const gitHubRepos = await getGitHubReposFromConfig(config, orgId, db, abortController.signal);
|
||||
const hostUrl = config.url ?? 'https://github.com';
|
||||
const hostname = config.url ? new URL(config.url).hostname : 'github.com';
|
||||
|
||||
return gitHubRepos.map((repo) => {
|
||||
const repoName = `${hostname}/${repo.full_name}`;
|
||||
const cloneUrl = new URL(repo.clone_url!);
|
||||
|
||||
const record: RepoData = {
|
||||
external_id: repo.id.toString(),
|
||||
external_codeHostType: 'github',
|
||||
external_codeHostUrl: hostUrl,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
name: repoName,
|
||||
isFork: repo.fork,
|
||||
isArchived: !!repo.archived,
|
||||
org: {
|
||||
connect: {
|
||||
id: orgId,
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
create: {
|
||||
connectionId: connectionId,
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
'zoekt.web-url-type': 'github',
|
||||
'zoekt.web-url': repo.html_url,
|
||||
'zoekt.name': repoName,
|
||||
'zoekt.github-stars': (repo.stargazers_count ?? 0).toString(),
|
||||
'zoekt.github-watchers': (repo.watchers_count ?? 0).toString(),
|
||||
'zoekt.github-subscribers': (repo.subscribers_count ?? 0).toString(),
|
||||
'zoekt.github-forks': (repo.forks_count ?? 0).toString(),
|
||||
'zoekt.archived': marshalBool(repo.archived),
|
||||
'zoekt.fork': marshalBool(repo.fork),
|
||||
'zoekt.public': marshalBool(repo.private === false)
|
||||
},
|
||||
};
|
||||
|
||||
return record;
|
||||
})
|
||||
}
|
||||
|
||||
export const compileGitlabConfig = async (
|
||||
config: GitlabConnectionConfig,
|
||||
connectionId: number,
|
||||
orgId: number,
|
||||
db: PrismaClient) => {
|
||||
|
||||
const gitlabRepos = await getGitLabReposFromConfig(config, orgId, db);
|
||||
const hostUrl = config.url ?? 'https://gitlab.com';
|
||||
|
||||
return gitlabRepos.map((project) => {
|
||||
const projectUrl = `${hostUrl}/${project.path_with_namespace}`;
|
||||
const cloneUrl = new URL(project.http_url_to_repo);
|
||||
const isFork = project.forked_from_project !== undefined;
|
||||
|
||||
const record: RepoData = {
|
||||
external_id: project.id.toString(),
|
||||
external_codeHostType: 'gitlab',
|
||||
external_codeHostUrl: hostUrl,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
name: project.path_with_namespace,
|
||||
isFork: isFork,
|
||||
isArchived: !!project.archived,
|
||||
org: {
|
||||
connect: {
|
||||
id: orgId,
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
create: {
|
||||
connectionId: connectionId,
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
'zoekt.web-url-type': 'gitlab',
|
||||
'zoekt.web-url': projectUrl,
|
||||
'zoekt.name': project.path_with_namespace,
|
||||
'zoekt.gitlab-stars': (project.stargazers_count ?? 0).toString(),
|
||||
'zoekt.gitlab-forks': (project.forks_count ?? 0).toString(),
|
||||
'zoekt.archived': marshalBool(project.archived),
|
||||
'zoekt.fork': marshalBool(isFork),
|
||||
'zoekt.public': marshalBool(project.private === false)
|
||||
},
|
||||
};
|
||||
|
||||
return record;
|
||||
})
|
||||
}
|
||||
|
||||
export const compileGiteaConfig = async (
|
||||
config: GiteaConnectionConfig,
|
||||
connectionId: number,
|
||||
orgId: number,
|
||||
db: PrismaClient) => {
|
||||
|
||||
const giteaRepos = await getGiteaReposFromConfig(config, orgId, db);
|
||||
const hostUrl = config.url ?? 'https://gitea.com';
|
||||
|
||||
return giteaRepos.map((repo) => {
|
||||
const repoUrl = `${hostUrl}/${repo.full_name}`;
|
||||
const cloneUrl = new URL(repo.clone_url!);
|
||||
|
||||
const record: RepoData = {
|
||||
external_id: repo.id!.toString(),
|
||||
external_codeHostType: 'gitea',
|
||||
external_codeHostUrl: hostUrl,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
name: repo.full_name!,
|
||||
isFork: repo.fork!,
|
||||
isArchived: !!repo.archived,
|
||||
org: {
|
||||
connect: {
|
||||
id: orgId,
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
create: {
|
||||
connectionId: connectionId,
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
'zoekt.web-url-type': 'gitea',
|
||||
'zoekt.web-url': repo.html_url!,
|
||||
'zoekt.name': repo.full_name!,
|
||||
'zoekt.archived': marshalBool(repo.archived),
|
||||
'zoekt.fork': marshalBool(repo.fork!),
|
||||
'zoekt.public': marshalBool(repo.internal === false && repo.private === false),
|
||||
},
|
||||
};
|
||||
|
||||
return record;
|
||||
})
|
||||
}
|
||||
|
||||
export const compileGerritConfig = async (
|
||||
config: GerritConnectionConfig,
|
||||
connectionId: number,
|
||||
orgId: number) => {
|
||||
|
||||
const gerritRepos = await getGerritReposFromConfig(config);
|
||||
const hostUrl = config.url ?? 'https://gerritcodereview.com';
|
||||
const hostname = new URL(hostUrl).hostname;
|
||||
|
||||
return gerritRepos.map((project) => {
|
||||
const repoId = `${hostname}/${project.name}`;
|
||||
const cloneUrl = new URL(`${config.url}/${encodeURIComponent(project.name)}`);
|
||||
|
||||
let webUrl = "https://www.gerritcodereview.com/";
|
||||
// Gerrit projects can have multiple web links; use the first one
|
||||
if (project.web_links) {
|
||||
const webLink = project.web_links[0];
|
||||
if (webLink) {
|
||||
webUrl = webLink.url;
|
||||
}
|
||||
}
|
||||
|
||||
const record: RepoData = {
|
||||
external_id: project.id.toString(),
|
||||
external_codeHostType: 'gerrit',
|
||||
external_codeHostUrl: hostUrl,
|
||||
cloneUrl: cloneUrl.toString(),
|
||||
name: project.name,
|
||||
isFork: false,
|
||||
isArchived: false,
|
||||
org: {
|
||||
connect: {
|
||||
id: orgId,
|
||||
},
|
||||
},
|
||||
connections: {
|
||||
create: {
|
||||
connectionId: connectionId,
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
'zoekt.web-url-type': 'gitiles',
|
||||
'zoekt.web-url': webUrl,
|
||||
'zoekt.name': repoId,
|
||||
'zoekt.archived': marshalBool(false),
|
||||
'zoekt.fork': marshalBool(false),
|
||||
'zoekt.public': marshalBool(true),
|
||||
},
|
||||
};
|
||||
|
||||
return record;
|
||||
})
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { Job, Queue, Worker } from 'bullmq';
|
|||
import { Redis } from 'ioredis';
|
||||
import { createLogger } from "./logger.js";
|
||||
import { Connection, PrismaClient, Repo, RepoToConnection, RepoIndexingStatus } from "@sourcebot/db";
|
||||
import { ConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
||||
import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig } from '@sourcebot/schemas/v3/connection.type';
|
||||
import { AppContext, Settings } from "./types.js";
|
||||
import { captureEvent } from "./posthog.js";
|
||||
import { getRepoPath, getTokenFromConfig, measure, getShardPrefix } from "./utils.js";
|
||||
|
|
@ -74,7 +74,7 @@ export class RepoManager implements IRepoManager {
|
|||
const repos = await this.db.repo.findMany({
|
||||
where: {
|
||||
repoIndexingStatus: {
|
||||
notIn: [RepoIndexingStatus.IN_INDEX_QUEUE, RepoIndexingStatus.FAILED]
|
||||
notIn: [RepoIndexingStatus.IN_INDEX_QUEUE, RepoIndexingStatus.INDEXING, RepoIndexingStatus.FAILED]
|
||||
},
|
||||
OR: [
|
||||
{ indexedAt: null },
|
||||
|
|
@ -147,10 +147,15 @@ export class RepoManager implements IRepoManager {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
let token: string | undefined;
|
||||
for (const repoConnection of repoConnections) {
|
||||
const connection = repoConnection.connection;
|
||||
const config = connection.config as unknown as ConnectionConfig;
|
||||
if (connection.connectionType !== 'github' && connection.connectionType !== 'gitlab' && connection.connectionType !== 'gitea') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const config = connection.config as unknown as GithubConnectionConfig | GitlabConnectionConfig | GiteaConnectionConfig;
|
||||
if (config.token) {
|
||||
token = await getTokenFromConfig(config.token, connection.orgId, db);
|
||||
if (token) {
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ const schema = {
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitLabConnectionConfig",
|
||||
"title": "GitlabConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitlab",
|
||||
|
|
@ -352,6 +352,233 @@ const schema = {
|
|||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GiteaConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitea",
|
||||
"description": "Gitea Configuration"
|
||||
},
|
||||
"token": {
|
||||
"$ref": "#/oneOf/0/properties/token",
|
||||
"description": "A Personal Access Token (PAT).",
|
||||
"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": "Exclude forked repositories from syncing."
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exclude archived repositories from syncing."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of individual repositories to exclude from syncing. Glob patterns are supported."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"revisions": {
|
||||
"$ref": "#/oneOf/0/properties/revisions"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gerrit",
|
||||
"description": "Gerrit Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL of the Gerrit host.",
|
||||
"examples": [
|
||||
"https://gerrit.example.com"
|
||||
],
|
||||
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of specific projects to sync. If not specified, all projects will be synced. Glob patterns are supported",
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
]
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
],
|
||||
"description": "List of specific projects to exclude from syncing."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "git",
|
||||
"description": "Git Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL to the git repository."
|
||||
},
|
||||
"revisions": {
|
||||
"$ref": "#/oneOf/0/properties/revisions"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "local",
|
||||
"description": "Local Configuration"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the local directory to sync with. Relative paths are relative to the configuration file's directory.",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables a file watcher that will automatically re-sync when changes are made within `path` (recursively). Defaults to true."
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"description": "List of paths relative to the provided `path` to exclude from the index. .git, .hg, and .svn are always exluded.",
|
||||
"default": [],
|
||||
"examples": [
|
||||
[
|
||||
"node_modules",
|
||||
"bin",
|
||||
"dist",
|
||||
"build",
|
||||
"out"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"path"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export type ConnectionConfig = GithubConnectionConfig | GitLabConnectionConfig;
|
||||
export type ConnectionConfig =
|
||||
| GithubConnectionConfig
|
||||
| GitlabConnectionConfig
|
||||
| GiteaConnectionConfig
|
||||
| GerritConnectionConfig
|
||||
| GitConnectionConfig
|
||||
| GerritConnectionConfig1;
|
||||
|
||||
export interface GithubConnectionConfig {
|
||||
/**
|
||||
|
|
@ -92,7 +98,7 @@ export interface GitRevisions {
|
|||
*/
|
||||
tags?: string[];
|
||||
}
|
||||
export interface GitLabConnectionConfig {
|
||||
export interface GitlabConnectionConfig {
|
||||
/**
|
||||
* GitLab Configuration
|
||||
*/
|
||||
|
|
@ -160,3 +166,108 @@ export interface GitLabConnectionConfig {
|
|||
};
|
||||
revisions?: GitRevisions;
|
||||
}
|
||||
export interface GiteaConnectionConfig {
|
||||
/**
|
||||
* Gitea Configuration
|
||||
*/
|
||||
type: "gitea";
|
||||
/**
|
||||
* A Personal Access Token (PAT).
|
||||
*/
|
||||
token?:
|
||||
| string
|
||||
| {
|
||||
/**
|
||||
* The name of the environment variable that contains the token.
|
||||
*/
|
||||
env: string;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* The name of the secret that contains the token.
|
||||
*/
|
||||
secret: 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?: {
|
||||
/**
|
||||
* Exclude forked repositories from syncing.
|
||||
*/
|
||||
forks?: boolean;
|
||||
/**
|
||||
* Exclude archived repositories from syncing.
|
||||
*/
|
||||
archived?: boolean;
|
||||
/**
|
||||
* List of individual repositories to exclude from syncing. Glob patterns are supported.
|
||||
*/
|
||||
repos?: string[];
|
||||
};
|
||||
revisions?: GitRevisions;
|
||||
}
|
||||
export interface GerritConnectionConfig {
|
||||
/**
|
||||
* Gerrit Configuration
|
||||
*/
|
||||
type: "gerrit";
|
||||
/**
|
||||
* The URL of the Gerrit host.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* List of specific projects to sync. If not specified, all projects will be synced. Glob patterns are supported
|
||||
*/
|
||||
projects?: string[];
|
||||
exclude?: {
|
||||
/**
|
||||
* List of specific projects to exclude from syncing.
|
||||
*/
|
||||
projects?: string[];
|
||||
};
|
||||
}
|
||||
export interface GitConnectionConfig {
|
||||
/**
|
||||
* Git Configuration
|
||||
*/
|
||||
type: "git";
|
||||
/**
|
||||
* The URL to the git repository.
|
||||
*/
|
||||
url: string;
|
||||
revisions?: GitRevisions;
|
||||
}
|
||||
export interface GerritConnectionConfig1 {
|
||||
/**
|
||||
* Local Configuration
|
||||
*/
|
||||
type: "local";
|
||||
/**
|
||||
* Path to the local directory to sync with. Relative paths are relative to the configuration file's directory.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Enables a file watcher that will automatically re-sync when changes are made within `path` (recursively). Defaults to true.
|
||||
*/
|
||||
watch?: boolean;
|
||||
exclude?: {
|
||||
/**
|
||||
* List of paths relative to the provided `path` to exclude from the index. .git, .hg, and .svn are always exluded.
|
||||
*/
|
||||
paths?: string[];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
59
packages/schemas/src/v3/gerrit.schema.ts
Normal file
59
packages/schemas/src/v3/gerrit.schema.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
const schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gerrit",
|
||||
"description": "Gerrit Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL of the Gerrit host.",
|
||||
"examples": [
|
||||
"https://gerrit.example.com"
|
||||
],
|
||||
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of specific projects to sync. If not specified, all projects will be synced. Glob patterns are supported",
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
]
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
],
|
||||
"description": "List of specific projects to exclude from syncing."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
export { schema as gerritSchema };
|
||||
22
packages/schemas/src/v3/gerrit.type.ts
Normal file
22
packages/schemas/src/v3/gerrit.type.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export interface GerritConnectionConfig {
|
||||
/**
|
||||
* Gerrit Configuration
|
||||
*/
|
||||
type: "gerrit";
|
||||
/**
|
||||
* The URL of the Gerrit host.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* List of specific projects to sync. If not specified, all projects will be synced. Glob patterns are supported
|
||||
*/
|
||||
projects?: string[];
|
||||
exclude?: {
|
||||
/**
|
||||
* List of specific projects to exclude from syncing.
|
||||
*/
|
||||
projects?: string[];
|
||||
};
|
||||
}
|
||||
64
packages/schemas/src/v3/git.schema.ts
Normal file
64
packages/schemas/src/v3/git.schema.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
const schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "git",
|
||||
"description": "Git Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL to the git repository."
|
||||
},
|
||||
"revisions": {
|
||||
"type": "object",
|
||||
"description": "The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed.",
|
||||
"properties": {
|
||||
"branches": {
|
||||
"type": "array",
|
||||
"description": "List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"main",
|
||||
"release/*"
|
||||
],
|
||||
[
|
||||
"**"
|
||||
]
|
||||
],
|
||||
"default": []
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"description": "List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"latest",
|
||||
"v2.*.*"
|
||||
],
|
||||
[
|
||||
"**"
|
||||
]
|
||||
],
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
export { schema as gitSchema };
|
||||
26
packages/schemas/src/v3/git.type.ts
Normal file
26
packages/schemas/src/v3/git.type.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export interface GitConnectionConfig {
|
||||
/**
|
||||
* Git Configuration
|
||||
*/
|
||||
type: "git";
|
||||
/**
|
||||
* The URL to the git repository.
|
||||
*/
|
||||
url: string;
|
||||
revisions?: GitRevisions;
|
||||
}
|
||||
/**
|
||||
* The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed.
|
||||
*/
|
||||
export interface GitRevisions {
|
||||
/**
|
||||
* List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported.
|
||||
*/
|
||||
branches?: string[];
|
||||
/**
|
||||
* List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported.
|
||||
*/
|
||||
tags?: string[];
|
||||
}
|
||||
166
packages/schemas/src/v3/gitea.schema.ts
Normal file
166
packages/schemas/src/v3/gitea.schema.ts
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
const schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GiteaConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitea",
|
||||
"description": "Gitea Configuration"
|
||||
},
|
||||
"token": {
|
||||
"description": "A Personal Access Token (PAT).",
|
||||
"examples": [
|
||||
"secret-token",
|
||||
{
|
||||
"env": "ENV_VAR_CONTAINING_TOKEN"
|
||||
}
|
||||
],
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"env": {
|
||||
"type": "string",
|
||||
"description": "The name of the environment variable that contains the token."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"env"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"description": "The name of the secret that contains the token."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"secret"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": "Exclude forked repositories from syncing."
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exclude archived repositories from syncing."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of individual repositories to exclude from syncing. Glob patterns are supported."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"revisions": {
|
||||
"type": "object",
|
||||
"description": "The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed.",
|
||||
"properties": {
|
||||
"branches": {
|
||||
"type": "array",
|
||||
"description": "List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"main",
|
||||
"release/*"
|
||||
],
|
||||
[
|
||||
"**"
|
||||
]
|
||||
],
|
||||
"default": []
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"description": "List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"latest",
|
||||
"v2.*.*"
|
||||
],
|
||||
[
|
||||
"**"
|
||||
]
|
||||
],
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
export { schema as giteaSchema };
|
||||
69
packages/schemas/src/v3/gitea.type.ts
Normal file
69
packages/schemas/src/v3/gitea.type.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export interface GiteaConnectionConfig {
|
||||
/**
|
||||
* Gitea Configuration
|
||||
*/
|
||||
type: "gitea";
|
||||
/**
|
||||
* A Personal Access Token (PAT).
|
||||
*/
|
||||
token?:
|
||||
| string
|
||||
| {
|
||||
/**
|
||||
* The name of the environment variable that contains the token.
|
||||
*/
|
||||
env: string;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* The name of the secret that contains the token.
|
||||
*/
|
||||
secret: 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?: {
|
||||
/**
|
||||
* Exclude forked repositories from syncing.
|
||||
*/
|
||||
forks?: boolean;
|
||||
/**
|
||||
* Exclude archived repositories from syncing.
|
||||
*/
|
||||
archived?: boolean;
|
||||
/**
|
||||
* List of individual repositories to exclude from syncing. Glob patterns are supported.
|
||||
*/
|
||||
repos?: string[];
|
||||
};
|
||||
revisions?: GitRevisions;
|
||||
}
|
||||
/**
|
||||
* The revisions (branches, tags) that should be included when indexing. The default branch (HEAD) is always indexed.
|
||||
*/
|
||||
export interface GitRevisions {
|
||||
/**
|
||||
* List of branches to include when indexing. For a given repo, only the branches that exist on the repo's remote *and* match at least one of the provided `branches` will be indexed. The default branch (HEAD) is always indexed. Glob patterns are supported.
|
||||
*/
|
||||
branches?: string[];
|
||||
/**
|
||||
* List of tags to include when indexing. For a given repo, only the tags that exist on the repo's remote *and* match at least one of the provided `tags` will be indexed. Glob patterns are supported.
|
||||
*/
|
||||
tags?: string[];
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
const schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitLabConnectionConfig",
|
||||
"title": "GitlabConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitlab",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export interface GitLabConnectionConfig {
|
||||
export interface GitlabConnectionConfig {
|
||||
/**
|
||||
* GitLab Configuration
|
||||
*/
|
||||
|
|
|
|||
52
packages/schemas/src/v3/local.schema.ts
Normal file
52
packages/schemas/src/v3/local.schema.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
const schema = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "local",
|
||||
"description": "Local Configuration"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the local directory to sync with. Relative paths are relative to the configuration file's directory.",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables a file watcher that will automatically re-sync when changes are made within `path` (recursively). Defaults to true."
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"description": "List of paths relative to the provided `path` to exclude from the index. .git, .hg, and .svn are always exluded.",
|
||||
"default": [],
|
||||
"examples": [
|
||||
[
|
||||
"node_modules",
|
||||
"bin",
|
||||
"dist",
|
||||
"build",
|
||||
"out"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"path"
|
||||
],
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
export { schema as localSchema };
|
||||
22
packages/schemas/src/v3/local.type.ts
Normal file
22
packages/schemas/src/v3/local.type.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY!
|
||||
|
||||
export interface GerritConnectionConfig {
|
||||
/**
|
||||
* Local Configuration
|
||||
*/
|
||||
type: "local";
|
||||
/**
|
||||
* Path to the local directory to sync with. Relative paths are relative to the configuration file's directory.
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Enables a file watcher that will automatically re-sync when changes are made within `path` (recursively). Defaults to true.
|
||||
*/
|
||||
watch?: boolean;
|
||||
exclude?: {
|
||||
/**
|
||||
* List of paths relative to the provided `path` to exclude from the index. .git, .hg, and .svn are always exluded.
|
||||
*/
|
||||
paths?: string[];
|
||||
};
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ import { ErrorCode } from "@/lib/errorCodes";
|
|||
import { isServiceError } from "@/lib/utils";
|
||||
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
|
||||
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
|
||||
import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema";
|
||||
import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema";
|
||||
import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
||||
import { encrypt } from "@sourcebot/crypto"
|
||||
import { getConnection } from "./data/connection";
|
||||
|
|
@ -515,6 +517,10 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
|
|||
return githubSchema;
|
||||
case "gitlab":
|
||||
return gitlabSchema;
|
||||
case 'gitea':
|
||||
return giteaSchema;
|
||||
case 'gerrit':
|
||||
return gerritSchema;
|
||||
}
|
||||
})();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,18 @@ import { z } from "zod";
|
|||
import { ConfigEditor, QuickAction } from "../../components/configEditor";
|
||||
import { createZodConnectionConfigValidator } from "../../utils";
|
||||
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
|
||||
import { githubQuickActions, gitlabQuickActions } from "../../quickActions";
|
||||
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
|
||||
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
||||
import { githubQuickActions, gitlabQuickActions, giteaQuickActions, gerritQuickActions } from "../../quickActions";
|
||||
import { Schema } from "ajv";
|
||||
import { GitLabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
|
||||
import { updateConnectionConfigAndScheduleSync } from "@/actions";
|
||||
import { useToast } from "@/components/hooks/use-toast";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema";
|
||||
import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
|
||||
|
||||
|
|
@ -40,13 +44,29 @@ export const ConfigSetting = (props: ConfigSettingProps) => {
|
|||
}
|
||||
|
||||
if (type === 'gitlab') {
|
||||
return <ConfigSettingInternal<GitLabConnectionConfig>
|
||||
return <ConfigSettingInternal<GitlabConnectionConfig>
|
||||
{...props}
|
||||
quickActions={gitlabQuickActions}
|
||||
schema={gitlabSchema}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (type === 'gitea') {
|
||||
return <ConfigSettingInternal<GiteaConnectionConfig>
|
||||
{...props}
|
||||
quickActions={giteaQuickActions}
|
||||
schema={giteaSchema}
|
||||
/>;
|
||||
}
|
||||
|
||||
if (type === 'gerrit') {
|
||||
return <ConfigSettingInternal<GerritConnectionConfig>
|
||||
{...props}
|
||||
quickActions={gerritQuickActions}
|
||||
schema={gerritSchema}
|
||||
/>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { ConfigEditor, QuickActionFn } from "../../../components/configEditor";
|
|||
import { useDomain } from "@/hooks/useDomain";
|
||||
|
||||
interface ConnectionCreationForm<T> {
|
||||
type: 'github' | 'gitlab';
|
||||
type: 'github' | 'gitlab' | 'gitea' | 'gerrit';
|
||||
defaultValues: {
|
||||
name: string;
|
||||
config: string;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
'use client';
|
||||
|
||||
import { githubQuickActions, gitlabQuickActions } from "../../quickActions";
|
||||
import { gerritQuickActions, giteaQuickActions, githubQuickActions, gitlabQuickActions } from "../../quickActions";
|
||||
import ConnectionCreationForm from "./components/connectionCreationForm";
|
||||
import { GitLabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
|
||||
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
||||
import { gitlabSchema } from "@sourcebot/schemas/v3/gitlab.schema";
|
||||
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
|
||||
import { giteaSchema } from "@sourcebot/schemas/v3/gitea.schema";
|
||||
import { gerritSchema } from "@sourcebot/schemas/v3/gerrit.schema";
|
||||
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
|
|
@ -22,16 +26,24 @@ export default function NewConnectionPage({
|
|||
return <GitLabCreationForm />;
|
||||
}
|
||||
|
||||
if (type === 'gitea') {
|
||||
return <GiteaCreationForm />;
|
||||
}
|
||||
|
||||
if (type === 'gerrit') {
|
||||
return <GerritCreationForm />;
|
||||
}
|
||||
|
||||
router.push('/connections');
|
||||
}
|
||||
|
||||
const GitLabCreationForm = () => {
|
||||
const defaultConfig: GitLabConnectionConfig = {
|
||||
const defaultConfig: GitlabConnectionConfig = {
|
||||
type: 'gitlab',
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectionCreationForm<GitLabConnectionConfig>
|
||||
<ConnectionCreationForm<GitlabConnectionConfig>
|
||||
type="gitlab"
|
||||
title="Create a GitLab connection"
|
||||
defaultValues={{
|
||||
|
|
@ -61,4 +73,43 @@ const GitHubCreationForm = () => {
|
|||
quickActions={githubQuickActions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const GiteaCreationForm = () => {
|
||||
const defaultConfig: GiteaConnectionConfig = {
|
||||
type: 'gitea',
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectionCreationForm<GiteaConnectionConfig>
|
||||
type="gitea"
|
||||
title="Create a Gitea connection"
|
||||
defaultValues={{
|
||||
config: JSON.stringify(defaultConfig, null, 2),
|
||||
name: 'my-gitea-connection',
|
||||
}}
|
||||
schema={giteaSchema}
|
||||
quickActions={giteaQuickActions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const GerritCreationForm = () => {
|
||||
const defaultConfig: GerritConnectionConfig = {
|
||||
type: 'gerrit',
|
||||
url: "https://gerrit.example.com"
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectionCreationForm<GerritConnectionConfig>
|
||||
type="gerrit"
|
||||
title="Create a Gerrit connection"
|
||||
defaultValues={{
|
||||
config: JSON.stringify(defaultConfig, null, 2),
|
||||
name: 'my-gerrit-connection',
|
||||
}}
|
||||
schema={gerritSchema}
|
||||
quickActions={gerritQuickActions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"
|
||||
import { GitLabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
|
||||
import { QuickAction } from "./components/configEditor";
|
||||
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/connection.type";
|
||||
import { GerritConnectionConfig } from "@sourcebot/schemas/v3/gerrit.type";
|
||||
|
||||
export const githubQuickActions: QuickAction<GithubConnectionConfig>[] = [
|
||||
{
|
||||
|
|
@ -41,9 +43,9 @@ export const githubQuickActions: QuickAction<GithubConnectionConfig>[] = [
|
|||
}
|
||||
];
|
||||
|
||||
export const gitlabQuickActions: QuickAction<GitLabConnectionConfig>[] = [
|
||||
export const gitlabQuickActions: QuickAction<GitlabConnectionConfig>[] = [
|
||||
{
|
||||
fn: (previous: GitLabConnectionConfig) => ({
|
||||
fn: (previous: GitlabConnectionConfig) => ({
|
||||
...previous,
|
||||
groups: [
|
||||
...previous.groups ?? [],
|
||||
|
|
@ -53,14 +55,14 @@ export const gitlabQuickActions: QuickAction<GitLabConnectionConfig>[] = [
|
|||
name: "Add a group",
|
||||
},
|
||||
{
|
||||
fn: (previous: GitLabConnectionConfig) => ({
|
||||
fn: (previous: GitlabConnectionConfig) => ({
|
||||
...previous,
|
||||
url: previous.url ?? "",
|
||||
}),
|
||||
name: "Set a custom url",
|
||||
},
|
||||
{
|
||||
fn: (previous: GitLabConnectionConfig) => ({
|
||||
fn: (previous: GitlabConnectionConfig) => ({
|
||||
...previous,
|
||||
token: previous.token ?? {
|
||||
secret: "",
|
||||
|
|
@ -69,7 +71,7 @@ export const gitlabQuickActions: QuickAction<GitLabConnectionConfig>[] = [
|
|||
name: "Add a secret",
|
||||
},
|
||||
{
|
||||
fn: (previous: GitLabConnectionConfig) => ({
|
||||
fn: (previous: GitlabConnectionConfig) => ({
|
||||
...previous,
|
||||
projects: [
|
||||
...previous.projects ?? [],
|
||||
|
|
@ -80,3 +82,68 @@ export const gitlabQuickActions: QuickAction<GitLabConnectionConfig>[] = [
|
|||
}
|
||||
]
|
||||
|
||||
export const giteaQuickActions: QuickAction<GiteaConnectionConfig>[] = [
|
||||
{
|
||||
fn: (previous: GiteaConnectionConfig) => ({
|
||||
...previous,
|
||||
orgs: [
|
||||
...(previous.orgs ?? []),
|
||||
""
|
||||
]
|
||||
}),
|
||||
name: "Add an organization",
|
||||
},
|
||||
{
|
||||
fn: (previous: GiteaConnectionConfig) => ({
|
||||
...previous,
|
||||
url: previous.url ?? "",
|
||||
}),
|
||||
name: "Set a custom url",
|
||||
},
|
||||
{
|
||||
fn: (previous: GiteaConnectionConfig) => ({
|
||||
...previous,
|
||||
repos: [
|
||||
...(previous.repos ?? []),
|
||||
""
|
||||
]
|
||||
}),
|
||||
name: "Add a repo",
|
||||
},
|
||||
{
|
||||
fn: (previous: GiteaConnectionConfig) => ({
|
||||
...previous,
|
||||
token: previous.token ?? {
|
||||
secret: "",
|
||||
},
|
||||
}),
|
||||
name: "Add a secret",
|
||||
}
|
||||
]
|
||||
|
||||
export const gerritQuickActions: QuickAction<GerritConnectionConfig>[] = [
|
||||
{
|
||||
fn: (previous: GerritConnectionConfig) => ({
|
||||
...previous,
|
||||
projects: [
|
||||
...(previous.projects ?? []),
|
||||
""
|
||||
]
|
||||
}),
|
||||
name: "Add a project",
|
||||
},
|
||||
{
|
||||
fn: (previous: GerritConnectionConfig) => ({
|
||||
...previous,
|
||||
exclude: {
|
||||
...previous.exclude,
|
||||
projects: [
|
||||
...(previous.exclude?.projects ?? []),
|
||||
""
|
||||
]
|
||||
}
|
||||
}),
|
||||
name: "Exclude a project",
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export function OrgCreateForm({ setOrgCreateData }: OrgCreateFormProps) {
|
|||
<FormControl>
|
||||
<div className="flex items-center">
|
||||
<Input placeholder="aperature-labs" {...field} className="w-1/2" />
|
||||
<span className="ml-2">.sourcebot.dev</span>
|
||||
<span className="ml-2">.sourcebot.app</span>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,18 @@
|
|||
},
|
||||
{
|
||||
"$ref": "./gitlab.json"
|
||||
},
|
||||
{
|
||||
"$ref": "./gitea.json"
|
||||
},
|
||||
{
|
||||
"$ref": "./gerrit.json"
|
||||
},
|
||||
{
|
||||
"$ref": "./git.json"
|
||||
},
|
||||
{
|
||||
"$ref": "./local.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
57
schemas/v3/gerrit.json
Normal file
57
schemas/v3/gerrit.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gerrit",
|
||||
"description": "Gerrit Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL of the Gerrit host.",
|
||||
"examples": [
|
||||
"https://gerrit.example.com"
|
||||
],
|
||||
"pattern": "^https?:\\/\\/[^\\s/$.?#].[^\\s]*$"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of specific projects to sync. If not specified, all projects will be synced. Glob patterns are supported",
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
]
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"project1/repo1",
|
||||
"project2/**"
|
||||
]
|
||||
],
|
||||
"description": "List of specific projects to exclude from syncing."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
24
schemas/v3/git.json
Normal file
24
schemas/v3/git.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "git",
|
||||
"description": "Git Configuration"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "url",
|
||||
"description": "The URL to the git repository."
|
||||
},
|
||||
"revisions": {
|
||||
"$ref": "./shared.json#/definitions/GitRevisions"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
96
schemas/v3/gitea.json
Normal file
96
schemas/v3/gitea.json
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GiteaConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitea",
|
||||
"description": "Gitea Configuration"
|
||||
},
|
||||
"token": {
|
||||
"$ref": "./shared.json#/definitions/Token",
|
||||
"description": "A Personal Access Token (PAT).",
|
||||
"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": "Exclude forked repositories from syncing."
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Exclude archived repositories from syncing."
|
||||
},
|
||||
"repos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of individual repositories to exclude from syncing. Glob patterns are supported."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"revisions": {
|
||||
"$ref": "./shared.json#/definitions/GitRevisions"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GitLabConnectionConfig",
|
||||
"title": "GitlabConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "gitlab",
|
||||
|
|
|
|||
50
schemas/v3/local.json
Normal file
50
schemas/v3/local.json
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "GerritConnectionConfig",
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "local",
|
||||
"description": "Local Configuration"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to the local directory to sync with. Relative paths are relative to the configuration file's directory.",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables a file watcher that will automatically re-sync when changes are made within `path` (recursively). Defaults to true."
|
||||
},
|
||||
"exclude": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": ".+"
|
||||
},
|
||||
"description": "List of paths relative to the provided `path` to exclude from the index. .git, .hg, and .svn are always exluded.",
|
||||
"default": [],
|
||||
"examples": [
|
||||
[
|
||||
"node_modules",
|
||||
"bin",
|
||||
"dist",
|
||||
"build",
|
||||
"out"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"path"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
Loading…
Reference in a new issue