mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks are pending
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(server): resolve clone/upload/mapping roots from GITNEXUS_HOME CLONE_ROOT (git-clone.ts), UPLOAD_ROOT (upload-paths.ts), and the server-mapping file (embeddings/server-mapping.ts) computed their root from os.homedir() directly, ignoring GITNEXUS_HOME. The Docker image sets GITNEXUS_HOME=/data/gitnexus (the persistent volume that also holds the registry and indexes), so cloned/uploaded repos and their .gitnexus indexes instead landed in the container's ephemeral ~/.gitnexus and were lost on container recreation, while registry.json (which honors GITNEXUS_HOME) kept pointing at the now-dead path. That also defeated incremental re-analysis: a recreated container re-clones from scratch and full-rebuilds instead of git pull + incremental update. Source all three from the existing getGlobalDir() helper — the same GITNEXUS_HOME-aware primitive the registry and groups already use. Behavior is unchanged when GITNEXUS_HOME is unset (CLI / local installs): it falls back to ~/.gitnexus exactly as before. No signatures change and no UPLOAD_ROOT consumers are touched. Adds test/unit/gitnexus-home-roots.test.ts covering both the GITNEXUS_HOME-set and unset paths for all three roots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): make clone-root assertions GITNEXUS_HOME-aware; cover server-mapping fallback Addresses PR review (#2229): - git-clone.test.ts hardcoded path.join(os.homedir(), '.gitnexus', 'repos') for the clone root, so the "direct child of the clone root" and containment assertions failed when GITNEXUS_HOME was set in the ambient env (e.g. a CI runner). CLONE_ROOT now derives from getGlobalDir(); mirror that derivation via EXPECTED_CLONE_ROOT (GITNEXUS_HOME || ~/.gitnexus), computed at module load — the same point CLONE_ROOT is frozen — so the two always agree. - The GITNEXUS_HOME-unset fallback test covered clone + upload roots but not server-mapping (one of the three changed modules). Add a fallback case for readServerMapping. It redirects HOME/USERPROFILE to a tmp dir (os.homedir() honors them) so it exercises the real ~/.gitnexus fallback without writing into the developer's actual ~/.gitnexus/server-mapping.json. Both files pass with and without GITNEXUS_HOME set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): use mkdtemp for GITNEXUS_HOME path-root test temp dirs CodeQL js/insecure-temporary-file flagged the two writes that used a fixed os.tmpdir() name (gitnexus-home-roots-test, gitnexus-fallback-home): a co-located process could pre-create or symlink the predictable path before the test write lands. Switch both to fs.mkdtemp(), which atomically creates a uniquely-named directory — the canonical sanitizer this repo already uses (see core/group/storage.ts). Behavior is unchanged; tests still pass with and without GITNEXUS_HOME set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(embeddings): resolve server-mapping path for parity with clone/upload roots MAPPING_FILE now wraps path.resolve() like CLONE_ROOT (git-clone.ts) and UPLOAD_ROOT (upload-paths.ts), so a relative GITNEXUS_HOME yields an absolute path. No-op for the supported absolute-GITNEXUS_HOME config (Docker) and for readServerMapping's only caller (run-analyze.ts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): derive EXPECTED_CLONE_ROOT from getGlobalDir() to avoid drift The test mirror now imports getGlobalDir() and computes the expected clone root the same way production CLONE_ROOT does, instead of re-deriving the GITNEXUS_HOME || ~/.gitnexus fallback by hand. Byte-identical today; future-proof if getGlobalDir() grows a branch. (os import retained — still used by os.tmpdir().) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): rename shadowed savedHome in server-mapping fallback test The inner savedHome (saves process.env.HOME) shadowed the describe-scope savedHome (saves process.env.GITNEXUS_HOME). Renamed the inner one to savedProcessHome at its declaration and both restore sites in the finally block. Pure rename, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(server): scope customHome temp dir to the GITNEXUS_HOME-set cases beforeEach created a customHome temp dir for all five tests, but the two fallback cases never use it (one manages its own fakeHome, the other needs none). Moved the mkdtemp/rm into a nested describe('with GITNEXUS_HOME set') wrapping the three set-cases; the shared outer afterEach still restores GITNEXUS_HOME and resets modules for all five. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import path from 'node:path';
|
|
import os from 'node:os';
|
|
import fs from 'node:fs/promises';
|
|
|
|
/**
|
|
* Regression guard: the clone root (git-clone.ts), upload root (upload-paths.ts),
|
|
* and server-mapping file (embeddings/server-mapping.ts) must follow
|
|
* GITNEXUS_HOME, not the bare home directory.
|
|
*
|
|
* The Docker image sets GITNEXUS_HOME=/data/gitnexus — the persistent volume
|
|
* that also holds the registry and indexes. Before this fix these three roots
|
|
* used os.homedir() directly, so clones/uploads landed in the container's
|
|
* ephemeral ~/.gitnexus and were lost on container recreation while the
|
|
* registry (which honors GITNEXUS_HOME) still pointed at the dead path.
|
|
*
|
|
* The roots are module-level constants resolved at import time from
|
|
* getGlobalDir(), so each case sets the env var, resets the module registry,
|
|
* and re-imports under the new value.
|
|
*/
|
|
describe('GITNEXUS_HOME path roots', () => {
|
|
const savedHome = process.env.GITNEXUS_HOME;
|
|
|
|
afterEach(() => {
|
|
if (savedHome === undefined) delete process.env.GITNEXUS_HOME;
|
|
else process.env.GITNEXUS_HOME = savedHome;
|
|
vi.resetModules();
|
|
});
|
|
|
|
// customHome is only needed by the GITNEXUS_HOME-set cases below; the two
|
|
// fallback cases manage their own (or no) temp dir, so its lifecycle lives in
|
|
// this nested block rather than a shared beforeEach.
|
|
describe('with GITNEXUS_HOME set', () => {
|
|
// mkdtemp (not a fixed os.tmpdir() name) so a co-located process cannot
|
|
// pre-create or symlink the path before our writes land
|
|
// (CodeQL js/insecure-temporary-file).
|
|
let customHome: string;
|
|
|
|
beforeEach(async () => {
|
|
customHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-home-roots-'));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(customHome, { recursive: true, force: true });
|
|
});
|
|
|
|
it('clone root follows GITNEXUS_HOME', async () => {
|
|
process.env.GITNEXUS_HOME = customHome;
|
|
vi.resetModules();
|
|
const { getCloneDir } = await import('../../src/server/git-clone.js');
|
|
expect(getCloneDir('my-repo')).toBe(path.resolve(customHome, 'repos', 'my-repo'));
|
|
});
|
|
|
|
it('upload root follows GITNEXUS_HOME', async () => {
|
|
process.env.GITNEXUS_HOME = customHome;
|
|
vi.resetModules();
|
|
const { UPLOAD_ROOT, getUploadDir } = await import('../../src/server/upload-paths.js');
|
|
expect(UPLOAD_ROOT).toBe(path.resolve(customHome, 'uploads'));
|
|
expect(getUploadDir('my-repo')).toBe(path.resolve(customHome, 'uploads', 'my-repo'));
|
|
});
|
|
|
|
it('server-mapping file is read from GITNEXUS_HOME', async () => {
|
|
process.env.GITNEXUS_HOME = customHome;
|
|
await fs.writeFile(
|
|
path.join(customHome, 'server-mapping.json'),
|
|
JSON.stringify({ 'my-repo': 'payments-service' }),
|
|
'utf-8',
|
|
);
|
|
vi.resetModules();
|
|
const { readServerMapping } = await import('../../src/core/embeddings/server-mapping.js');
|
|
expect(await readServerMapping('my-repo')).toBe('payments-service');
|
|
});
|
|
});
|
|
|
|
it('falls back to ~/.gitnexus when GITNEXUS_HOME is unset', async () => {
|
|
delete process.env.GITNEXUS_HOME;
|
|
vi.resetModules();
|
|
const { getCloneDir } = await import('../../src/server/git-clone.js');
|
|
const { UPLOAD_ROOT } = await import('../../src/server/upload-paths.js');
|
|
expect(getCloneDir('my-repo')).toBe(
|
|
path.resolve(os.homedir(), '.gitnexus', 'repos', 'my-repo'),
|
|
);
|
|
expect(UPLOAD_ROOT).toBe(path.resolve(os.homedir(), '.gitnexus', 'uploads'));
|
|
});
|
|
|
|
it('server-mapping falls back to ~/.gitnexus when GITNEXUS_HOME is unset', async () => {
|
|
// os.homedir() honors $HOME (and $USERPROFILE on Windows), so redirect the
|
|
// home dir to a tmp path to exercise the real fallback (getGlobalDir() ->
|
|
// ~/.gitnexus) without writing into the developer's actual
|
|
// ~/.gitnexus/server-mapping.json.
|
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-fallback-home-'));
|
|
const savedProcessHome = process.env.HOME;
|
|
const savedUserProfile = process.env.USERPROFILE;
|
|
delete process.env.GITNEXUS_HOME;
|
|
process.env.HOME = fakeHome;
|
|
process.env.USERPROFILE = fakeHome;
|
|
try {
|
|
await fs.mkdir(path.join(fakeHome, '.gitnexus'), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(fakeHome, '.gitnexus', 'server-mapping.json'),
|
|
JSON.stringify({ 'my-repo': 'fallback-service' }),
|
|
'utf-8',
|
|
);
|
|
vi.resetModules();
|
|
const { readServerMapping } = await import('../../src/core/embeddings/server-mapping.js');
|
|
expect(await readServerMapping('my-repo')).toBe('fallback-service');
|
|
} finally {
|
|
if (savedProcessHome === undefined) delete process.env.HOME;
|
|
else process.env.HOME = savedProcessHome;
|
|
if (savedUserProfile === undefined) delete process.env.USERPROFILE;
|
|
else process.env.USERPROFILE = savedUserProfile;
|
|
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|