mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-20 00:11:37 +00:00
Some checks are pending
Gitleaks / gitleaks (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Publish / Classify release event (push) Waiting to run
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(embeddings): isolate local ONNX inference in a child_process sidecar The analyze parent must not load onnxruntime-node. Fork a sidecar for vectors only and reap it on worker exit; keep Ladybug writes in-process. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): share the sidecar client across MCP, serve, and sync Query hosts now use the core façade instead of a second in-process ONNX embedder. Search skips an empty table, sync reaps beside closeLbug, and ready means the stack is resolvable rather than a warm singleton. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): refuse Intel Mac and unloadable prefix before npm heal Analyze, sync, install, and the sidecar client now consult the platform blocker before forking or downloading the optional stack. HTTP stays the escape hatch; wasm is not treated as a rescue. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(embeddings): take the ONNX stack off default npm install Pins live in gitnexusEmbeddingStack. embeddings install writes prefix overrides before npm spawn. Leftover 1.6.12 package-first trees are residual. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(publish): drop grammar source from the published tarball Every vendored grammar has 6/6 prebuilds, so files ships those plus Leiden and FTS instead of parser.c. First ship stays above 80 MiB. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): match MCP missing-stack warn to the R20 copy Default install no longer calls the stack optional, so the once-per-backend stderr assertion must look for the new lead line. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(review): bound sidecar death, cancel writes, and publish-file guards Init-time native crashes no longer respawn a child on every query. Local embedBatch honors AbortSignal after sidecar return, MCP query() surfaces vector-lane degradation, disconnect always reaps, and the grammar prepack guard checks files globs instead of on-disk prebuilds. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(embeddings): share runtime preflight and sidecar reap helpers Analyze and embeddings-sync used the same blocker/prefix/install gate with different error routing. One assessment keeps those paths aligned without changing CLI vs thrown-error behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Address PR review feedback (#3287) Keep a reaped sidecar from resetting its replacement, wait for dispose, tighten the publish-files guard, and stop assuming a leftover ONNX tree in CI. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Clear the sidecar reap timeout, add init IPC slack, and isolate embeddings-sync tests from HTTP-mode env. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): unstub globals after sidecar HTTP-mode tests Keep a leaked fetch stub from failing assertions out of later tests in the same file. Co-authored-by: Cursor <cursoragent@cursor.com> * test(embeddings): pin sidecar success cases off darwin/x64 The runtime blocker reads the real process platform before the fork mock, so local-success tests must not inherit an Intel Mac host. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Keep vector degradation per query, treat leftover Intel-Mac stacks as not ready, and document that the CLI image no longer ships ONNX. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify embedding sidecar shutdown and search hot paths Drop redundant sidecar reaps and unused child helpers, and run FTS alongside semantic search. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Share HF attempt parsing with the sidecar init deadline, abort embed waits without killing the child, and restore last init options on recreate. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Treat sub-1 HF attempt env values as invalid, and drop leaked sidecar waiters when IPC send throws. Co-authored-by: Cursor <cursoragent@cursor.com> * Address remaining PR review feedback (#3287) Keep sidecar init on a shared chain; each waiter can abort only its own wait. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): declare embedding-table existence probe as unordered LIMIT The empty-table skip in semanticSearch is existence-only; declare it so the #2787 determinism guard stops failing coverage shard 3/3. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
273 lines
14 KiB
YAML
273 lines
14 KiB
YAML
name: Docker Build & Push
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
pull_request:
|
|
# workflow_dispatch is allowed for dry-run testing only. Publishing is still
|
|
# exclusively tag-driven so that every signed image corresponds 1:1 to a
|
|
# published `gitnexus@X.Y.Z` on npm. dry_run:true (the default) skips all
|
|
# push, sign, and attestation steps — the build runs but nothing is published.
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: 'Build only — skip push, signing, and attestations'
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
workflow_call:
|
|
inputs:
|
|
tag:
|
|
description: >-
|
|
The full v-prefixed tag to build (e.g. v1.2.3-rc.1).
|
|
The tag must already exist in the repo and its tree must contain
|
|
a gitnexus/package.json whose version matches the tag.
|
|
required: true
|
|
type: string
|
|
# Explicit secret contract — callers pass these by name. Replaces the
|
|
# blanket `secrets: inherit` pattern (zizmor `secrets-inherit` audit).
|
|
# GHCR auth uses the implicit GITHUB_TOKEN; only Docker Hub credentials
|
|
# need to be passed through.
|
|
secrets:
|
|
DOCKERHUB_USERNAME:
|
|
required: true
|
|
DOCKERHUB_TOKEN:
|
|
required: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Concurrency convention: see CONTRIBUTING.md → "GitHub Actions — Concurrency Convention".
|
|
# Tag refs are unique per release, so distinct tags run in parallel.
|
|
# Re-pushes of the same tag serialize. cancel-in-progress: false — never cancel a publish mid-flight.
|
|
# Hardcoded `docker-build-push-` prefix (not `${{ github.workflow }}`) when invoked as a reusable
|
|
# workflow: in called-workflow context `github.workflow` is ambiguous and could resolve to the
|
|
# caller's name, sharing a concurrency group with the caller → deadlock.
|
|
# Direct tag-push invocations use `docker-build-push-<ref>`; workflow_call invocations get a
|
|
# per-run-unique group (they are already serialized by the caller's own concurrency group).
|
|
concurrency:
|
|
group: ${{ (github.event_name == 'push') && format('docker-build-push-{0}', github.ref) || format('docker-build-push-nested-{0}', github.run_id) }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build-push:
|
|
name: Build & Push ${{ matrix.image.name }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
# Required for Cosign keyless signing via the OIDC token exchange,
|
|
# and for build provenance / SBOM attestations.
|
|
id-token: write
|
|
attestations: write
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image:
|
|
# Static UI bundle. Small, fast image. Drop-in replacement for the
|
|
# legacy single-image setup at the same `gitnexus` repository slug
|
|
# is intentionally avoided — the UI now lives at `gitnexus-web` and
|
|
# the CLI/server takes the canonical `gitnexus` slug below.
|
|
- name: gitnexus-web
|
|
dockerfile: Dockerfile.web
|
|
slug: gitnexus-web
|
|
# CLI / `gitnexus serve` backend. Tree-sitter natives live in this
|
|
# image. onnxruntime-node is opt-in (`gitnexus embeddings install` or
|
|
# a bind-mounted prefix / GITNEXUS_EMBEDDING_URL); npm is stripped
|
|
# at runtime so the image cannot auto-heal the embedding stack.
|
|
- name: gitnexus
|
|
dockerfile: Dockerfile.cli
|
|
slug: gitnexus
|
|
|
|
steps:
|
|
# Only the workflow_call path requires a non-empty `inputs.tag` — callers
|
|
# (publish.yml in RC mode) must pass the RC tag explicitly. On direct
|
|
# tag pushes the tag comes from `github.ref`, so `inputs.tag` is always
|
|
# empty and validating it here would break every real release (#1064).
|
|
# The downstream "Verify tag matches gitnexus/package.json version" step
|
|
# handles both event types by falling back to GITHUB_REF.
|
|
- name: Validate tag input
|
|
if: github.event_name == 'workflow_call'
|
|
shell: bash
|
|
env:
|
|
TAG_INPUT: ${{ inputs.tag }}
|
|
run: |
|
|
if [ -z "${TAG_INPUT}" ]; then
|
|
echo "::error::No tag provided to docker.yml — refusing to build/push."
|
|
exit 1
|
|
fi
|
|
|
|
# When triggered by workflow_call the caller passes the RC tag as an input;
|
|
# we check out that tag so the Dockerfile and package.json match the built image.
|
|
# For tag-push events github.ref is already the tag ref — no override needed.
|
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
ref: ${{ inputs.tag || github.ref }}
|
|
|
|
# ── Lock the docker image version to the npm package version ──────────
|
|
# Mirrors the check in publish.yml: refuse to build unless the git tag
|
|
# exactly matches `gitnexus/package.json`'s version. This guarantees
|
|
# `ghcr.io/<owner>/gitnexus:X.Y.Z` always corresponds to the same
|
|
# `gitnexus@X.Y.Z` published to npm — no drift, no surprises.
|
|
- name: Verify tag matches gitnexus/package.json version
|
|
id: version
|
|
if: github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request'
|
|
shell: bash
|
|
env:
|
|
# For workflow_call the tag comes from the caller input; for push events
|
|
# it is derived from GITHUB_REF (set to empty so the else-branch fires).
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
if [ -n "$INPUT_TAG" ]; then
|
|
TAG_VERSION="${INPUT_TAG#v}"
|
|
else
|
|
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
|
|
fi
|
|
if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
echo "::error::Tag does not follow semver: v$TAG_VERSION"
|
|
exit 1
|
|
fi
|
|
PKG_VERSION=$(node -p "require('./gitnexus/package.json').version")
|
|
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
|
echo "::error::Tag version (v$TAG_VERSION) does not match gitnexus/package.json version ($PKG_VERSION)"
|
|
exit 1
|
|
fi
|
|
echo "version=$PKG_VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "Version verified: $PKG_VERSION"
|
|
|
|
# Required for multi-platform (linux/arm64) emulation.
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
|
|
- name: Install Cosign
|
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Docker Hub is a mirror of GHCR: same tags, same digests, same Cosign
|
|
# signatures. GHCR remains authoritative (it is the registry the
|
|
# ClusterImagePolicy globs against by default), but Docker Hub is the
|
|
# registry most users reach for first, so we publish there too.
|
|
# Requires repo secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN (a scoped
|
|
# access token, NOT the account password) with write access to the
|
|
# `akonlabs/gitnexus` and `akonlabs/gitnexus-web` repos.
|
|
- name: Log in to Docker Hub
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
# Computes image tags and labels from the verified semver tag:
|
|
# v1.2.3 → :1.2.3, :1.2, :1, :latest (auto, only for non-prerelease)
|
|
# v1.2.3-rc.1 → :1.2.3-rc.1 only (prereleases never become :latest)
|
|
# `:latest` is only emitted for tag pushes thanks to `flavor: latest=auto`,
|
|
# ensuring it always points at a real npm-published version.
|
|
#
|
|
# For workflow_call invocations github.ref is the caller's branch ref, so
|
|
# the type=semver patterns would not match. In that case we add an explicit
|
|
# type=raw tag using the version already verified above, so the same
|
|
# image-naming rules apply regardless of how the workflow was triggered.
|
|
# NOTE: We check `inputs.tag` rather than `github.event_name` because in a
|
|
# reusable workflow the github context is inherited from the caller —
|
|
# `github.event_name` would still be "push", not "workflow_call".
|
|
- name: Extract Docker metadata
|
|
id: meta
|
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
with:
|
|
# Dual-registry publish. metadata-action expands the same tag set
|
|
# against every image ref listed here, and build-push-action pushes
|
|
# one build to all of them, so the GHCR and Docker Hub images share
|
|
# a digest and are byte-identical. The Docker Hub namespace
|
|
# (`akonlabs`) is hardcoded because it differs from the GitHub org
|
|
# (`abhigyanpatwari`) — `github.repository_owner` would produce the
|
|
# wrong ref.
|
|
images: |
|
|
ghcr.io/${{ github.repository_owner }}/${{ matrix.image.slug }}
|
|
docker.io/akonlabs/${{ matrix.image.slug }}
|
|
flavor: latest=auto
|
|
tags: |
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=raw,value=${{ steps.version.outputs.version }},enable=${{ inputs.tag != '' }}
|
|
|
|
# Transient 502s from GHCR / Docker Hub / GHA cache during multi-platform
|
|
# exports are retried inside `.github/actions/docker-build-push-retry`
|
|
# (see docker/build-push-action#1422 — retry policy stays out of the
|
|
# upstream action). `ignore-error=true` on cache-to avoids cache export
|
|
# flakes failing an otherwise successful push.
|
|
- name: Build and push
|
|
id: build
|
|
uses: ./.github/actions/docker-build-push-retry
|
|
with:
|
|
context: .
|
|
file: ${{ matrix.image.dockerfile }}
|
|
platforms: linux/amd64,linux/arm64
|
|
push: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=${{ matrix.image.slug }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.image.slug }},ignore-error=true
|
|
|
|
# Cosign keyless signing. Each pushed tag is signed by the workflow's
|
|
# OIDC identity, so consumers can verify the image with the strict,
|
|
# fully-anchored identity regex (kept in sync with README.md and
|
|
# deploy/kubernetes/cluster-image-policy.yaml — update all three together).
|
|
# NOTE: `${...}` expression syntax is NOT evaluated inside YAML comments, so
|
|
# the example below uses literal `<owner>/<repo>` placeholders that consumers
|
|
# substitute themselves; the canonical, fully-rendered command lives in README.md.
|
|
# cosign verify ghcr.io/<owner>/<slug>:<tag> \
|
|
# --certificate-identity-regexp '^https://github\.com/<owner>/<repo>/\.github/workflows/docker\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$' \
|
|
# --certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
# Do NOT relax to `@.*` — that accepts signatures from any ref, including
|
|
# unprotected branches and PRs, and defeats the supply-chain guarantee.
|
|
- name: Sign image with Cosign (keyless)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
env:
|
|
# Cosign v2 (installed by sigstore/cosign-installer above) makes
|
|
# keyless the default. COSIGN_EXPERIMENTAL is a v1-only opt-in flag
|
|
# that is now deprecated/no-op, so it is intentionally omitted.
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
TAGS: ${{ steps.meta.outputs.tags }}
|
|
run: |
|
|
# Sign every tag at the same digest so consumers can verify by tag or by digest.
|
|
# Use `while read` instead of `for $TAGS` to be robust against tags that
|
|
# could ever contain whitespace (the metadata-action output is newline-
|
|
# separated, not space-separated).
|
|
while IFS= read -r tag; do
|
|
[[ -n "$tag" ]] && cosign sign --yes "${tag}@${DIGEST}"
|
|
done <<< "$TAGS"
|
|
|
|
# Attach the SBOM produced by buildx as a verifiable attestation on the
|
|
# digest. Attestations are pushed as OCI referrers to the registry named
|
|
# in `subject-name`, so we call the action once per registry. The digest
|
|
# is identical across registries (same build, same push), so consumers
|
|
# pulling from either GHCR or Docker Hub see the same provenance.
|
|
- name: Generate build provenance attestation (GHCR)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-name: ghcr.io/${{ github.repository_owner }}/${{ matrix.image.slug }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
- name: Generate build provenance attestation (Docker Hub)
|
|
if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }}
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
with:
|
|
subject-name: docker.io/akonlabs/${{ matrix.image.slug }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|