mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-06 08:16:02 +00:00
* feat(core): add cache-first npm update-check service Shared fail-open checker: validated 24h cache under GITNEXUS_HOME, acquireFileLock-guarded refresh, monotonic publication, hardened registry fetch (no credentials, private-address redirects refused, body-capped), strict x.y.z comparator, install-eligibility classification, and an unref'd refresh scheduler for long-lived processes. Extracts getGlobalDir into storage/global-dir.ts with a repo-manager re-export (no caller changes). Co-authored-by: Cursor <cursoragent@cursor.com> * feat(cli): notify on available updates via stderr and doctor One i18n'd stderr line on interactive invocations when the validated cache holds a newer version (TTY-gated, CI/opt-out/eligibility-gated, hook and help/version command identities excluded). Stale cache spawns a detached hidden __update-check refresh child so command exit latency is unchanged. doctor prints the cached latest version when known. Dockerfile.cli sets GITNEXUS_NO_UPDATE_NOTIFIER=1. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(mcp): emit one stderr update notice per process per version Process-scoped adapter in mcpCommand (stdio and --http), dynamically imported after the stdout sentinel, started only after connect, fully catch-isolated. Arms the shared refresh scheduler with cleanup on process exit. Protocol payloads stay free of update state (R15). Co-authored-by: Cursor <cursoragent@cursor.com> * feat(serve): expose update state on /api/info Serve-scoped controller owns an in-memory update snapshot: one staleness evaluation after listen, then the shared unref'd scheduler, stopped on close/shutdown. /api/info reads only the snapshot and gains optional latestVersion/updateAvailable fields for eligible installs; the existing three fields are byte-compatible. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): dismissible update-available banner from /api/info Fetches server info after backend connect and on reconnect, renders a fixed banner in the exploring view only when updateAvailable is true and the version is undismissed, hides while the reconnect banner is active, and fails open on fetch errors. role=status + aria-live with a keyboard-focusable dismiss; dismissal persists per version in localStorage. Copy in en/zh-CN common.json with version interpolation. Co-authored-by: Cursor <cursoragent@cursor.com> * docs(cli): document update notifications and opt-outs Co-authored-by: Cursor <cursoragent@cursor.com> * fix(review): apply review findings and simplify pass Review: gate the detached refresh spawn on a live lock-owner probe so parallel CLI invocations coalesce to one refresh child (validated P2, three-reviewer agreement); poll /api/info on a slow cadence while exploring so post-load server-side discoveries surface (validated P1); add a monotonic sequence guard so overlapping server-info fetches commit in order. Simplify (behavior-preserving): shared truthy-env/opt-out/freshness helpers in update-cache.ts, shared cachedUpdateNoticeLine for CLI and doctor, extracted install-eligibility core with per-process memo, memoized registry parsing, single evaluation per scheduler cycle, cache-only startup evaluate in serve, flattened MCP exit handler, shared bottom-banner shell, storage keys in ui-constants. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(update-notifier): address residual review tickets on this PR Stop the lock-busy 1ms scheduler spin, replace clock-skewed cache entries, move the outbound URL guard into core, and extract the serve update controller. Pin the startup/guard/single-flight/MCP/CLI contracts those tickets called out. Fixes #3167 #3168 #3169 #3170 #3171 #3172 #3173 #3174 Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3175) Fetch the npm /latest document instead of the full packument so the 64KiB cap can succeed, and treat reused lock PIDs as stale so refresh is not suppressed. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3175) Register the CLI spawn suite on the OS matrix, pin MCP opt-out env, and compare versions without IEEE-754 rounding. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(cli): add gitnexus update install and versioned command banners Give an explicit Claude/Codex-style upgrade (`npm i -g gitnexus@version`) and print `GitNexus <Name> (version)` on every command so the running build is obvious without silent self-update. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3175) - Document the pinned install as npm i -g gitnexus@<x.y.z>, not a copyable @version tag - Wait for wall-clock-future cache repair to publish before asserting - Restore the stdout spy if the TTY notice assertions fail Co-authored-by: Cursor <cursoragent@cursor.com> * fix(update-notifier): keep last known latestVersion on a failed refresh A later offline check was wiping the pin and hiding a known update for 24h. gitnexus update still treats a failed live fetch as checkFailed. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3175) - Word update.current so a newer-than-latest install is not called the latest stable version. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): hide the detached update-check spawn on Windows The refresh child was spawned without windowsHide, so Windows CI could stall before writing the cache and then fail cleanup with EBUSY. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
7.3 KiB
Text
133 lines
7.3 KiB
Text
ARG BUILDPLATFORM
|
|
ARG TARGETPLATFORM
|
|
# Pinned npm version used to replace the bundled npm in the upstream Node
|
|
# image. Bumping requires a coordinated update in Dockerfile.web and
|
|
# gitnexus/Dockerfile.test so all images bootstrap the same npm.
|
|
ARG NPM_VERSION=11.14.1
|
|
|
|
# -- Builder -----------------------------------------------------------
|
|
# Native modules (tree-sitter-*, onnxruntime-node, node-gyp builds for
|
|
# tree-sitter-proto / tree-sitter-swift) require python3 + a C/C++ toolchain.
|
|
# node:22-bookworm-slim
|
|
FROM node:22-bookworm-slim@sha256:9f6d5975c7dca860947d3915877f85607946403fc55349f39b4bc3688448bb6e AS builder
|
|
ARG NPM_VERSION
|
|
|
|
WORKDIR /app
|
|
|
|
RUN npx --yes npm@${NPM_VERSION} install -g npm@${NPM_VERSION}
|
|
|
|
# Toolchain for node-gyp / native builds.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ git && rm -rf /var/lib/apt/lists/*
|
|
|
|
# Build gitnexus-shared first - gitnexus depends on it as a workspace.
|
|
COPY gitnexus-shared/package.json gitnexus-shared/package-lock.json ./gitnexus-shared/
|
|
RUN npm ci --prefix gitnexus-shared
|
|
COPY gitnexus-shared ./gitnexus-shared
|
|
RUN rm -f gitnexus-shared/tsconfig.tsbuildinfo
|
|
RUN npm run build --prefix gitnexus-shared
|
|
|
|
# Copy the full gitnexus package before installing - `npm ci` triggers
|
|
# `postinstall` (patches tree-sitter-swift, builds the vendored
|
|
# tree-sitter-proto) and `prepare` (compiles TypeScript via scripts/build.js),
|
|
# both of which need the source tree.
|
|
COPY gitnexus ./gitnexus
|
|
RUN npm ci --prefix gitnexus
|
|
|
|
# Drop dev dependencies for a smaller runtime layer.
|
|
RUN npm prune --omit=dev --prefix gitnexus
|
|
|
|
# `npm prune` removes anything not in package.json's dependency tree — which
|
|
# includes the VENDORED tree-sitter grammars (materialized into node_modules/ by
|
|
# postinstall, but not declared as deps) and their freshly-built native bindings.
|
|
# The `serve` image analyzes/parses uploaded repos at runtime, so those grammars
|
|
# must survive into the runtime layer. Re-run the grammar postinstall here in the
|
|
# builder (which still has python3/make/g++ and the hoisted node-addon-api /
|
|
# node-gyp-build) to re-materialize + rebuild them after the prune. This is
|
|
# load-bearing for tree-sitter-c (a core, REQUIRED grammar now vendored, #2116):
|
|
# as a former `dependency` it used to survive prune; vendored, it would not.
|
|
RUN npm run postinstall --prefix gitnexus
|
|
|
|
# -- Runtime -----------------------------------------------------------
|
|
# node:22-bookworm-slim
|
|
FROM node:22-bookworm-slim@sha256:9f6d5975c7dca860947d3915877f85607946403fc55349f39b4bc3688448bb6e AS runtime
|
|
|
|
# curl for the healthcheck; git for cloning; procps for watch process identity;
|
|
# ca-certificates for TLS verification.
|
|
RUN apt-get update && apt-get install -y --no-install-recommends curl git procps ca-certificates && rm -rf /var/lib/apt/lists/* \
|
|
&& rm -rf /usr/local/lib/node_modules/npm \
|
|
&& rm -rf /usr/local/lib/node_modules/corepack \
|
|
&& rm -f /usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack
|
|
|
|
WORKDIR /app
|
|
|
|
# Pre-create the data directory and hand it to the unprivileged `node` user
|
|
# so the bind-mounted volume is writable without root.
|
|
RUN mkdir -p /data/gitnexus && chown -R node:node /data
|
|
|
|
COPY --from=builder --chown=node:node /app/gitnexus/dist ./gitnexus/dist
|
|
COPY --from=builder --chown=node:node /app/gitnexus/node_modules ./gitnexus/node_modules
|
|
COPY --from=builder --chown=node:node /app/gitnexus/package.json ./gitnexus/package.json
|
|
COPY --from=builder --chown=node:node /app/gitnexus/scripts/install-duckdb-extension.mjs ./gitnexus/scripts/install-duckdb-extension.mjs
|
|
COPY --from=builder --chown=node:node /app/gitnexus/vendor ./gitnexus/vendor
|
|
|
|
# Expose the `gitnexus` binary on PATH so the documented Docker workflow
|
|
# (`docker compose exec gitnexus-server gitnexus index /workspace/<repo>`)
|
|
# works without users having to invoke `node /app/gitnexus/dist/cli/index.js`.
|
|
# `npm prune --omit=dev` in the builder stage strips `node_modules/.bin/`
|
|
# entries, so the `gitnexus` bin declared in package.json (`dist/cli/index.js`,
|
|
# which already carries `#!/usr/bin/env node` and 755 perms) is otherwise
|
|
# unreachable from $PATH.
|
|
RUN ln -s /app/gitnexus/dist/cli/index.js /usr/local/bin/gitnexus
|
|
|
|
# Bake the LadybugDB FTS extension into the image so BM25 keyword search works
|
|
# at runtime. The server runs the default `load-only` extension policy (the read
|
|
# pool pins `{ policy: 'load-only' }`), so a runtime `LOAD EXTENSION fts` never
|
|
# INSTALLs — the extension must already exist in the runtime user's HOME
|
|
# extension dir, or every keyword search silently degrades (no FTS indexes are
|
|
# written and ranking falls back to vector-only with only a `warning` field).
|
|
# Run the installer as the `node` user with the SAME HOME the server runs under,
|
|
# so `INSTALL fts` materializes the extension under `$HOME/.lbdb/extension` where
|
|
# the runtime `LOAD` resolves it offline. `ENV HOME` is pinned because Docker
|
|
# does not derive HOME from `USER`, so without it build-install and runtime-load
|
|
# would resolve different paths. Requires network egress for the one-time
|
|
# INSTALL; the build fails loudly if it cannot fetch the extension. The DB-size
|
|
# default comes from GITNEXUS_LBUG_MAX_DB_SIZE (single source of truth, matches
|
|
# the runtime) — it only sizes the throwaway scratch DB used to run INSTALL.
|
|
# The second `--verify-only` step re-LOADs the extension in a FRESH process
|
|
# under the same HOME, so a HOME/extension-dir mismatch fails the build here
|
|
# rather than silently degrading keyword search to vector-only at runtime.
|
|
ENV HOME=/home/node \
|
|
GITNEXUS_LBUG_MAX_DB_SIZE=17179869184
|
|
RUN su node -s /bin/sh -c "HOME=/home/node node /app/gitnexus/scripts/install-duckdb-extension.mjs fts" \
|
|
&& su node -s /bin/sh -c "HOME=/home/node node /app/gitnexus/scripts/install-duckdb-extension.mjs fts --verify-only"
|
|
|
|
# Published runtime assets (in package.json `files`). Placed AFTER the DuckDB
|
|
# FTS-extension RUN above so editing hook/skill content does not invalidate that
|
|
# network-fetching cache layer; they have no input dependency on it.
|
|
# `hooks/`: dist/cli/resolve-invocation.js does
|
|
# `require('../../hooks/claude/resolve-analyze-cmd.cjs')` at module load — the
|
|
# single source of truth for the npm-11 npx-crash invocation decision (#1939).
|
|
# Without it, `gitnexus analyze` inside the image crashes with MODULE_NOT_FOUND
|
|
# before it does any work (#2130). `skills/`: the CLI reads the bundled SKILL.md
|
|
# templates from `<pkg>/skills/` for `gitnexus analyze --skills` and `gitnexus
|
|
# setup`/`uninstall`; absent, those degrade silently (placeholder content / zero
|
|
# skills installed). (The web UI bundle `web/`, also in `files`, is deliberately
|
|
# NOT shipped: this builder never builds gitnexus-web, so the image is API-only;
|
|
# the UI is the separate Dockerfile.web image / hosted app.)
|
|
COPY --from=builder --chown=node:node /app/gitnexus/hooks ./gitnexus/hooks
|
|
COPY --from=builder --chown=node:node /app/gitnexus/skills ./gitnexus/skills
|
|
|
|
USER node
|
|
|
|
# The web UI defaults to http://localhost:4747 - keep that contract.
|
|
ENV GITNEXUS_HOME=/data/gitnexus \
|
|
GITNEXUS_NO_UPDATE_NOTIFIER=1 \
|
|
NODE_ENV=production \
|
|
PORT=4747
|
|
|
|
EXPOSE 4747
|
|
|
|
# Bind 0.0.0.0 for the host's mapped port, honoring an injected $PORT (Render
|
|
# sets one). `sh -c` expands it; `exec` keeps the server PID 1 so SIGTERM still
|
|
# reaches it. Platforms can rely on this instead of a dockerCommand override.
|
|
CMD ["sh", "-c", "exec gitnexus serve --host 0.0.0.0 --port \"${PORT:-4747}\""]
|