diff --git a/.cursor/.gitignore b/.cursor/.gitignore new file mode 100644 index 0000000000..8bf7cc27a1 --- /dev/null +++ b/.cursor/.gitignore @@ -0,0 +1 @@ +plans/ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..e09631a0aa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.gitignore +.DS_Store + +node_modules +**/node_modules + +dist +**/dist +coverage +**/coverage + +.env +.env.local +.env.*.local + +.gitnexus +gitnexus-web/playwright-report +gitnexus-web/test-results diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..b52d05ae19 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +IMAGE_NAME=ghcr.io/abhigyanpatwari/gitnexus:latest +CONTAINER_NAME=gitnexus +HOST_PORT=4173 diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 27eb753839..7010ee6f33 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -41,6 +41,9 @@ jobs: --outputFile=web-test-results.json working-directory: gitnexus-web + - name: Run docker-server integration tests + run: node --test docker-server.test.mjs + - name: Upload test reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..40a1a6eef9 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,71 @@ +name: Docker Build & Push + +on: + push: + tags: + - 'v*' + branches: + - main + paths-ignore: ['**.md', 'docs/**', 'LICENSE'] + workflow_dispatch: + +# Concurrency convention: see CONTRIBUTING.md → "GitHub Actions — Concurrency Convention". +# Tag refs are unique per release — distinct tags run in parallel. +# Pushes to main serialize; cancel superseded runs. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + +jobs: + build-push: + name: Build & Push image + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Required for multi-platform (linux/arm64) emulation. + - name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Computes image tags and labels from Git metadata: + # v* tag → ghcr.io//: (e.g. 1.2.3, 1.2, 1) + # main push → ghcr.io//:latest + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest,enable={{is_default_branch}} + type=sha,prefix=sha-,format=short + + - name: Build and push + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILDPLATFORM=${{ runner.os == 'Linux' && 'linux/amd64' || 'linux/amd64' }} diff --git a/.gitignore b/.gitignore index e8d4077ec7..95c9164e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ Thumbs.db .env .env.local .env.*.local +docker/.env # Logs *.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..347888c23f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +ARG BUILDPLATFORM +ARG TARGETPLATFORM + +FROM --platform=$BUILDPLATFORM node:22-alpine AS builder + +WORKDIR /app + +COPY gitnexus-shared/package.json gitnexus-shared/package-lock.json ./gitnexus-shared/ +RUN npm ci --prefix gitnexus-shared + +COPY gitnexus-shared ./gitnexus-shared +RUN npm run build --prefix gitnexus-shared + +COPY gitnexus/package.json ./gitnexus/package.json +COPY gitnexus-web/package.json gitnexus-web/package-lock.json ./gitnexus-web/ +RUN npm ci --prefix gitnexus-web + +COPY gitnexus-web ./gitnexus-web +RUN npm run build --prefix gitnexus-web + +FROM node:22-alpine AS runtime + +RUN apk add --no-cache curl + +WORKDIR /app + +COPY --from=builder /app/gitnexus-web/dist ./dist +COPY docker-server.mjs ./docker-server.mjs + +RUN chown -R node:node /app + +USER node + +EXPOSE 4173 + +CMD ["node", "docker-server.mjs"] diff --git a/README.md b/README.md index 65ac7d3329..d61fc54d5d 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,42 @@ cd ../gitnexus-web && npm install npm run dev ``` +## Docker + +```bash +docker run --rm \ + --name gitnexus \ + -p 4173:4173 \ + ghcr.io/abhigyanpatwari/gitnexus:latest +``` + +Or with Docker Compose: + +```bash +docker compose up -d +``` + +Optional env file: + +```bash +cp .env.example .env +set -a +source .env +set +a +``` + +Docker files: + +- [Dockerfile](Dockerfile) is the source for the published `gitnexus` image. It builds `gitnexus-shared` and `gitnexus-web`, then serves the production frontend. +- [docker-compose.yaml](docker-compose.yaml) starts the published image with Docker Compose. +- [.env.example](.env.example) sets the image name, container name, and exposed port for the example commands. + +Notes: + +- The published image serves the production frontend only. It does not start `gitnexus serve`. +- In backend mode, the app still defaults to `http://localhost:4747` unless you change the server URL in the UI. +- If you do not want an env file, the defaults are `ghcr.io/abhigyanpatwari/gitnexus:latest`, container name `gitnexus`, and port `4173`. + The web UI uses the same indexing pipeline as the CLI but runs entirely in WebAssembly (Tree-sitter WASM, LadybugDB WASM, in-browser embeddings). It's great for quick exploration but limited by browser memory for larger repos. **Local Backend Mode:** Run `gitnexus serve` and open the web UI locally — it auto-detects the server and shows all your indexed repos, with full AI chat support. No need to re-upload or re-index. The agent's tools (Cypher queries, search, code navigation) route through the backend HTTP API automatically. diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000..849a3ae14b --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +services: + gitnexus: + image: ${IMAGE_NAME:-ghcr.io/brainifii/gitnexus:latest} + container_name: ${CONTAINER_NAME:-gitnexus} + ports: + - '${HOST_PORT:-4173}:4173' + restart: unless-stopped + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:4173/'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s diff --git a/docker-server.mjs b/docker-server.mjs new file mode 100644 index 0000000000..adf84a07bf --- /dev/null +++ b/docker-server.mjs @@ -0,0 +1,81 @@ +import { createReadStream } from 'node:fs'; +import { stat } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import { extname, join, normalize, sep } from 'node:path'; + +const host = '0.0.0.0'; +const port = Number(process.env.PORT || '4173'); +const root = join(process.cwd(), 'dist'); + +const contentTypes = { + '.css': 'text/css; charset=utf-8', + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.map': 'application/json; charset=utf-8', + '.png': 'image/png', + '.svg': 'image/svg+xml', + '.txt': 'text/plain; charset=utf-8', + '.woff': 'font/woff', + '.woff2': 'font/woff2', +}; + +function resolvePath(urlPath) { + let decoded; + try { + decoded = decodeURIComponent(urlPath); + } catch { + return null; + } + if (decoded.includes('\0')) return null; + const cleanPath = normalize(decoded.replace(/^\/+/, '')); + const candidate = join(root, cleanPath); + if (candidate !== root && !candidate.startsWith(root + sep)) return null; + return candidate; +} + +const server = createServer(async (req, res) => { + const requestPath = req.url?.split('?')[0] || '/'; + let filePath = resolvePath(requestPath); + + if (!filePath) { + res.writeHead(400); + res.end('Bad request'); + return; + } + + try { + const fileStat = await stat(filePath).catch(() => null); + if (fileStat?.isDirectory()) { + filePath = join(filePath, 'index.html'); + } else if (!fileStat?.isFile()) { + filePath = join(root, 'index.html'); + } + + const finalStat = await stat(filePath).catch(() => null); + if (!finalStat?.isFile()) { + res.writeHead(404); + res.end('Not found'); + return; + } + + res.writeHead(200, { + 'Cache-Control': filePath.includes('/assets/') + ? 'public, max-age=31536000, immutable' + : 'no-cache', + 'Content-Type': contentTypes[extname(filePath)] || 'application/octet-stream', + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }); + const stream = createReadStream(filePath); + stream.on('error', () => res.destroy()); + stream.pipe(res); + } catch (error) { + res.writeHead(500); + res.end(error instanceof Error ? error.message : 'Internal server error'); + } +}); + +server.listen(port, host, () => { + console.log(`gitnexus-web listening on http://${host}:${port}`); +}); diff --git a/docker-server.test.mjs b/docker-server.test.mjs new file mode 100644 index 0000000000..a1005b0e49 --- /dev/null +++ b/docker-server.test.mjs @@ -0,0 +1,107 @@ +import { mkdir, mkdtemp, rm, unlink, writeFile } from 'node:fs/promises'; +import http, { createServer } from 'node:http'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { after, before, it } from 'node:test'; +import assert from 'node:assert/strict'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const serverScript = join(__dirname, 'docker-server.mjs'); + +function getFreePort() { + return new Promise((resolve) => { + const s = createServer(); + s.listen(0, '127.0.0.1', () => { + const { port } = s.address(); + s.close(() => resolve(port)); + }); + }); +} + +function rawGet(port, path) { + return new Promise((resolve, reject) => { + const req = http.request({ host: '127.0.0.1', port, path }, (res) => { + let body = ''; + res.setEncoding('utf8'); + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body })); + }); + req.on('error', reject); + req.end(); + }); +} + +async function waitForServer(port, retries = 30) { + for (let i = 0; i < retries; i++) { + try { + await rawGet(port, '/'); + return; + } catch { + await new Promise((r) => setTimeout(r, 100)); + } + } + throw new Error('Server did not start in time'); +} + +let tmpDir, serverPort, child; + +before(async () => { + tmpDir = await mkdtemp(join(tmpdir(), 'gitnexus-docker-test-')); + const distDir = join(tmpDir, 'dist'); + const assetsDir = join(distDir, 'assets'); + await mkdir(assetsDir, { recursive: true }); + await writeFile(join(distDir, 'index.html'), 'spa'); + await writeFile(join(assetsDir, 'app.abc123.js'), 'console.log("app")'); + + serverPort = await getFreePort(); + child = spawn(process.execPath, [serverScript], { + cwd: tmpDir, + env: { ...process.env, PORT: String(serverPort) }, + stdio: 'pipe', + }); + child.on('error', (err) => { + throw err; + }); + + await waitForServer(serverPort); +}); + +after(async () => { + child?.kill(); + if (tmpDir) await rm(tmpDir, { recursive: true, force: true }); +}); + +it('serves a valid asset with immutable cache header', async () => { + const res = await rawGet(serverPort, '/assets/app.abc123.js'); + assert.equal(res.status, 200); + assert.match(res.headers['cache-control'], /immutable/); + assert.equal(res.headers['cross-origin-opener-policy'], 'same-origin'); + assert.equal(res.headers['cross-origin-embedder-policy'], 'require-corp'); +}); + +it('serves SPA fallback for unknown routes', async () => { + const res = await rawGet(serverPort, '/some/unknown/route'); + assert.equal(res.status, 200); + assert.match(res.body, /spa/); + assert.match(res.headers['cache-control'], /no-cache/); +}); + +it('rejects path traversal with 400', async () => { + const res = await rawGet(serverPort, '/../../../etc/passwd'); + assert.equal(res.status, 400); +}); + +it('rejects percent-encoded null bytes with 400', async () => { + const res = await rawGet(serverPort, '/foo%00bar'); + assert.equal(res.status, 400); +}); + +it('returns 404 when dist/index.html is missing', async () => { + await unlink(join(tmpDir, 'dist', 'index.html')); + const res = await rawGet(serverPort, '/nonexistent-page'); + assert.equal(res.status, 404); +}); diff --git a/gitnexus-web/package-lock.json b/gitnexus-web/package-lock.json index 2190e27a61..845fe19986 100644 --- a/gitnexus-web/package-lock.json +++ b/gitnexus-web/package-lock.json @@ -2224,257 +2224,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@swc/core": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz", - "integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.8", - "@swc/core-darwin-x64": "1.15.8", - "@swc/core-linux-arm-gnueabihf": "1.15.8", - "@swc/core-linux-arm64-gnu": "1.15.8", - "@swc/core-linux-arm64-musl": "1.15.8", - "@swc/core-linux-x64-gnu": "1.15.8", - "@swc/core-linux-x64-musl": "1.15.8", - "@swc/core-win32-arm64-msvc": "1.15.8", - "@swc/core-win32-ia32-msvc": "1.15.8", - "@swc/core-win32-x64-msvc": "1.15.8" - }, - "peerDependencies": { - "@swc/helpers": ">=0.5.17" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz", - "integrity": "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz", - "integrity": "sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz", - "integrity": "sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz", - "integrity": "sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz", - "integrity": "sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz", - "integrity": "sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz", - "integrity": "sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz", - "integrity": "sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz", - "integrity": "sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz", - "integrity": "sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true - }, - "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@swc/wasm": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.15.8.tgz", - "integrity": "sha512-RG2BxGbbsjtddFCo1ghKH6A/BMXbY1eMBfpysV0lJMCpI4DZOjW1BNBnxvBt7YsYmlJtmy5UXIg9/4ekBTFFaQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true - }, "node_modules/@tailwindcss/node": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", diff --git a/gitnexus-web/vite.config.ts b/gitnexus-web/vite.config.ts index b177f68044..a62b9e5862 100644 --- a/gitnexus-web/vite.config.ts +++ b/gitnexus-web/vite.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ alias: { '@': path.resolve(__dirname, './src'), '@shared': path.resolve(__dirname, '../shared'), + 'gitnexus-shared': path.resolve(__dirname, '../gitnexus-shared/src/index.ts'), // Fix for Rollup failing to resolve this deep import from @langchain/anthropic '@anthropic-ai/sdk/lib/transform-json-schema': path.resolve( __dirname, diff --git a/gitnexus/Dockerfile.test b/gitnexus/Dockerfile.test index b2d22384f2..7cafbe2c19 100644 --- a/gitnexus/Dockerfile.test +++ b/gitnexus/Dockerfile.test @@ -1,6 +1,6 @@ FROM node:20-bookworm WORKDIR /app -RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* +RUN apt-get -o Acquire::Check-Valid-Until=false -o Acquire::Check-Date=false update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/* COPY . . RUN npm ci --ignore-scripts \ && node scripts/patch-tree-sitter-swift.cjs \