feat(multi-repo): support multiple repositories per project (backend + UI)#183
Open
eipasteur wants to merge 9 commits into
Open
feat(multi-repo): support multiple repositories per project (backend + UI)#183eipasteur wants to merge 9 commits into
eipasteur wants to merge 9 commits into
Conversation
Add ability to link multiple repositories to a project with automatic
tech stack detection via GitHub API. Each repository is stored as a
Neptune vertex connected via HAS_REPO edges.
- Add CRUD endpoints for /projects/{projectId}/repos
- Detect languages and frameworks from repo root files
- Lazy migration: existing git_repo field auto-creates Repository vertex
- Support both legacy single-repo and new multi-repo project creation
- Add DynamoDB access for reading user GitHub tokens
- Add frontend API client types and service methods
# Conflicts: # frontend/src/services/projects.ts # lambda/projects/index.js
PR #176 migrated lambda/github from CommonJS (require('./shared/...')) to ESM (import '../shared/response.js'), but the Terraform packaging copied shared/ at the same level as index.js in the zip. At runtime, '../shared/response.js' resolved to /var/shared/response.js (one level above /var/task/), which does not exist, causing ERR_MODULE_NOT_FOUND on every invocation. Apply the same pattern as PR #180 (github-issues): build the lambda with esbuild via the npm workspace and zip the .build output. esbuild inlines the shared/ modules at build time, eliminating runtime path resolution. - terraform/modules/api/lambda/main.tf: - Replace npm_requirements + prefix_in_zip='shared' with the build/:zip pattern, mirroring github_issues_lambda - Bump runtime to nodejs24.x for consistency with the build target (esbuild --target=node24, package.json already targets node24) Tests (153 unit tests across 7 files) continue to pass since they import '../index.js' which resolves to lambda/github/index.js, whose original '../shared/response.js' import resolves correctly inside the source tree at lambda/shared/response.js (the same path esbuild uses to bundle). Closes the runtime regression introduced in #176.
Two layered runtime regressions blocked /api/github/* on staging: 1. PR #176 migrated lambda/github from CommonJS to ESM but the Terraform packaging used npm_requirements + prefix_in_zip='shared'. The ESM resolver dereferenced '../shared/response.js' to /var/shared/... (one level above /var/task/), which doesn't exist, throwing ERR_MODULE_NOT_FOUND on every cold start. 2. shared/git-token.js is CommonJS and does require('@aws-sdk/client-ssm') at the module top level. esbuild bundles a CJS shim around it, but the Node.js ESM runtime rejects the resulting dynamic require: 'Dynamic require of @aws-sdk/client-ssm is not supported'. Fix: - terraform/modules/api/lambda/main.tf: switch github_lambda from npm_requirements to the build/:zip pattern (mirroring github_issues per PR #180), bumping runtime to nodejs24.x to match the esbuild --target=node24 the workspace already uses. - lambda/github/index.js: inline resolveGitToken locally, mirroring the exact pattern adopted by lambda/github-issues. shared/response.js remains imported because esbuild can statically bundle it without hitting a dynamic require (it has no top-level require of an external package). Validated end-to-end on staging: - terraform apply succeeded (no drift) - aws lambda invoke returns statusCode 200 with valid JSON body ({connected: false}) for /api/github/status - 153 unit tests across the repo continue to pass Closes the runtime regression introduced in #176.
Complete the multi-repo feature by adding the frontend UI to
list/add/remove repositories on a project. Backend was already
shipped (CRUD endpoints on /projects/{projectId}/repos, Repository
vertices in Neptune via HAS_REPO edges).
- GitHubRepoSelect: support multi-select mode with checkbox UI,
search filter, and exclude list (to hide repos already linked).
Single-select mode is preserved for backward compatibility via
a discriminated union on the multiple prop.
- ProjectSettings: replace the single gitRepo input with a
Repositories section that lists all linked repos (URL, role,
detected stack), supports bulk add via the multi-select picker
with parallel addRepo calls (Promise.allSettled), and per-repo
remove with confirmation.
CreateProjectModal stays single-repo for now: users create a
project with one repo and add additional repos from settings.
This keeps the create flow simple and matches the GitLab-side
phasing (Phase 3 of multi-repo rollout).
Server-side validation guards against malformed input that could land in URL templates downstream (api.github.com/repos/<url>/...). Even though detectRepoStack only ever calls api.github.com (no SSRF to arbitrary hosts), accepting non-conforming inputs leaks 400-class errors as 5xx and pollutes the audit log. Pattern follows GitHub's published constraints: - owner: 1-39 chars, alphanumeric + hyphens (no leading hyphen) - repo: 1-100 chars, alphanumeric + hyphen/underscore/dot Returns 400 with explicit message on mismatch.
This was referenced May 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add support for multiple repositories per project end-to-end (backend + frontend). A project can now link several GitHub repositories — each one becomes a
Repositoryvertex in Neptune connected to theProjectvertex via aHAS_REPOedge — with role detection (frontend/backend/infra/shared/docs) and tech-stack inference (languages, frameworks).Backend (
lambda/projects/)/projects/{projectId}/repos:GET— list linked repos for a projectPOST— add a repo (membership required, owner/admin role for write)DELETE— remove a repo (role-gated)git_repofield auto-creates aRepositoryvertex on first read.package.json,requirements.txt,Cargo.toml, etc. Fails non-fatally — repo is added even if detection fails.owner/repoformat (regex matches GitHub's published constraints — owner ≤39 chars, repo ≤100 chars).gitRepofield on the project still reflects the primary repo.Frontend (
frontend/src/)GitHubRepoSelect: support multi-select mode (checkboxes + search filter +excludelist to hide repos already linked). Discriminated union onmultipleprop preserves the single-select API.ProjectSettings: new Repositories section listing linked repos (URL, role badge, detected stack), bulk-add via the multi-select picker (paralleladdRepocalls withPromise.allSettled), per-repo remove with confirmation. Permissions enforced UI-side.services/projects.ts:ProjectRepotype +listRepos/addRepo/removeRepoclient methods.CreateProjectModalstays single-repo for now — users create with one repo and add more from settings. Mirrors the GitLab-side phasing (Phase 3 of multi-repo rollout).Stacked on
This PR includes #182 (
fix/lambda-github-esbuild-runtime) because the multi-repo POST flow calls into the github lambda, which is broken onmainuntil the fix lands. Merge #182 first and this PR will rebase cleanly to drop the merged commits.Validation
npm run buildcleanTest plan
E2E manual flow on staging:
Repositoryvertex auto-created