feat(web,ui): per-project git provider API base URL overrides

Per provider (github|gitlab|gitea) a project override stored in
~/.config/openchamber/projects/<projectId>.json under gitProviders wins
over the global settings.json value (precedence: project override > global
> built-in default). Server forge routes resolve the override per request
directory (worktree-aware via git-common-dir + containment + path fallback,
60s TTL cache); the override host is also accepted for remote parsing and
client detection. New GET/PUT /api/projects/:projectId/git-providers route;
client openchamberConfig preserves the server-owned gitProviders key;
Projects page gains a Git provider API base URLs section; detection store
hydrates per-project overrides (memory-only, server-authoritative).
This commit is contained in:
2026-08-17 09:57:54 +00:00
parent 697925ee0d
commit 66edc74fac
42 changed files with 1765 additions and 87 deletions
@@ -47,6 +47,7 @@
- Auth storage: `~/.config/openchamber/gitlab-auth.json` (override with `OPENCHAMBER_DATA_DIR`).
- Writes are atomic (tmp file + rename) and file mode is `0o600`.
- Base URL resolution: caller-supplied `baseUrl` (normalized) -> effective default via `getGitLabDefaultBaseUrl()` (configured `settings.json` `gitProviders.gitlab.apiBaseUrl`, else `https://gitlab.com`).
- Per-project overrides: data routes resolve a directory-scoped API base via `getEffectiveProviderApiBaseUrl('gitlab', directory)` (in `packages/web/server/lib/git-providers/project-config.js`). A per-project `gitProviders.gitlab.apiBaseUrl` override (stored under `projects/<projectId>.json`) replaces the global default for that project's routes, and its host is accepted for directory-to-repo resolution (`resolveGitLabRepoFromDirectory`); a connected account whose host matches the remote keeps its own base URL. Global routes (`auth/connect`, `auth/status`, `auth/activate`, `me`, `repo/branches`) stay global.
- Account id: `` `${host}:${username}` `` (e.g. `gitlab.com:alice`), falling back to `token:<first8>` when the username is missing.
- Auth header on every request: `PRIVATE-TOKEN: <pat>`.