Jaypore CI

> Jaypore CI: Minimal, Offline, Local CI system.
Log | Files | Refs | README

commit b91970d6142590aaaecdd47ff2e8c09f79189f5c
parent f4b3d7d66922b7567167e5c48e8b4feb3be87967
Author: arjoonn <arjoonn@midpathsoftware.com>
Date:   Thu, 26 Feb 2026 07:30:29 +0000

Website (!6)

Reviewed-on: https://gitea.midpathsoftware.com/midpath/jayporeci/pulls/6
Co-authored-by: arjoonn <arjoonn@midpathsoftware.com>
Co-committed-by: arjoonn <arjoonn@midpathsoftware.com>

Diffstat:
M.gitignore | 2++
M.jci/run.sh | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
ACHANGELOG.md | 0
MREADME.md | 4+---
Ascripts/build_binary.sh | 47+++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/build_image.sh | 20++++++++++++++++++++
Ascripts/build_site.sh | 42++++++++++++++++++++++++++++++++++++++++++
Ascripts/build_website.sh | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Awww_jci/Dockerfile | 12++++++++++++
Awww_jci/public/.gitignore | 2++
Awww_jci/public/assets/git.style.css | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Awww_jci/public/binaries.html | 31+++++++++++++++++++++++++++++++
Mwww_jci/public/index.html | 297+++++++++++++++++++------------------------------------------------------------
13 files changed, 649 insertions(+), 243 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -6,3 +6,5 @@ dist/ .coverage prof/ ./git-jci +./description +bin/* diff --git a/.jci/run.sh b/.jci/run.sh @@ -15,22 +15,101 @@ echo "" cd "$JCI_REPO_ROOT" -echo "Running tests..." -go test ./... +# --------------------------------------------------------------------------- +# Helper: build one binary inside its own Docker container. +# +# Usage: build_target <GOOS> <GOARCH> <OUTPUT_BINARY_NAME> [GOARM] +# +# Each invocation is fire-and-forget (&); collect PIDs for later wait. +# --------------------------------------------------------------------------- +PIDS=() +TARGETS=() # human-readable label per job, index-matched to PIDS + +build_target() { + local goos="$1" + local goarch="$2" + local output_name="$3" + local goarm="${4:-7}" # only meaningful when goarch=arm + + local label="${goos}/${goarch}" + [[ "${goarch}" == "arm" ]] && label="${label}v${goarm}" + + echo "[build] Spawning container for ${label} → bin/${output_name}" + + docker run --rm \ + -v "$PWD:/tmp/repo" \ + -e GOOS="${goos}" \ + -e GOARCH="${goarch}" \ + -e GOARM="${goarm}" \ + -e OUTPUT_BINARY_NAME="${output_name}" \ + golang:1.25 \ + "/tmp/repo/scripts/build_binary.sh" \ + & + + PIDS+=($!) + TARGETS+=("${label} → bin/${output_name}") +} + +# --------------------------------------------------------------------------- +# Step 1: Build Docker image (sequential — other steps depend on it) +# --------------------------------------------------------------------------- +echo "--- Step 1: Building Docker image via scripts/build_image.sh ---" +scripts/build_image.sh echo "" -echo "Building static binary (CGO_ENABLED=0)..." -CGO_ENABLED=0 go build -ldflags='-s -w -extldflags "-static"' -o "$JCI_OUTPUT_DIR/git-jci" ./cmd/git-jci +# --------------------------------------------------------------------------- +# Step 2: Cross-compile binaries in parallel +# +# Targets: +# Linux amd64 git-jci-linux-amd64 +# Linux arm64 git-jci-linux-arm64 +# Linux arm (v7) git-jci-linux-armv7 +# Windows amd64 git-jci-windows-amd64.exe +# Windows arm64 git-jci-windows-arm64.exe +# --------------------------------------------------------------------------- +echo "--- Step 2: Building binaries in parallel ---" +mkdir -p bin -# Verify it's static +build_target linux amd64 git-jci-linux-amd64 +build_target linux arm64 git-jci-linux-arm64 +build_target linux arm git-jci-linux-armv7 7 +build_target windows amd64 git-jci-windows-amd64.exe +build_target windows arm64 git-jci-windows-arm64.exe + +echo "" +echo "Waiting for all build containers to finish..." echo "" -echo "Binary info:" -file "$JCI_OUTPUT_DIR/git-jci" -ls -lh "$JCI_OUTPUT_DIR/git-jci" + +# --------------------------------------------------------------------------- +# Collect results — report each job individually so failures are obvious +# --------------------------------------------------------------------------- +FAILED=0 +for i in "${!PIDS[@]}"; do + pid="${PIDS[$i]}" + label="${TARGETS[$i]}" + if wait "${pid}"; then + echo "[OK] ${label}" + else + echo "[FAIL] ${label}" + FAILED=$((FAILED + 1)) + fi +done echo "" -echo "All steps completed successfully!" +if [[ "${FAILED}" -gt 0 ]]; then + echo "${FAILED} build(s) failed. See output above for details." + exit 1 +fi + +echo "All binaries built successfully:" +ls -lh bin/git-jci-* echo "" -echo "=== Installation ===" -echo "Download and install with:" -echo " curl -fsSL \$(git jci web --url)/git-jci -o /tmp/git-jci && sudo install /tmp/git-jci /usr/local/bin/" + +# --------------------------------------------------------------------------- +# Step 3: Build site (sequential — needs the binaries to be present) +# --------------------------------------------------------------------------- +echo "--- Step 3: Building site inside jci container ---" +docker run --rm -it -v "$PWD:/tmp/Jaypore CI" jci "/tmp/Jaypore CI/scripts/build_site.sh" +echo "" + +echo "All steps completed successfully!" diff --git a/CHANGELOG.md b/CHANGELOG.md diff --git a/README.md b/README.md @@ -1,11 +1,9 @@ # Jaypore CI -A local-first CI system that stores results in git's custom refs. +> Minimal, Offline, Local CI system. ## Installation -### From source - ```bash go build -o git-jci ./cmd/git-jci sudo mv git-jci /usr/local/bin/ diff --git a/scripts/build_binary.sh b/scripts/build_binary.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail + +# --------------------------------------------------------------------------- +# Build a static git-jci binary for the requested platform. +# +# Required environment variables (set by the calling docker run command): +# GOOS - Target OS (linux | windows | darwin) +# GOARCH - Target arch (amd64 | arm64 | arm) +# OUTPUT_BINARY_NAME - Final filename placed in ${OUTPUT_DIR} +# e.g. git-jci-linux-amd64 / git-jci-windows-amd64.exe +# +# Optional: +# GOARM - ARM version for GOARCH=arm (default: 7) +# --------------------------------------------------------------------------- + +REPO_DIR="/tmp/repo" +OUTPUT_DIR="${REPO_DIR}/bin" +ENTRY_POINT="./cmd/git-jci" + +# Ensure required vars are present +: "${GOOS:?GOOS must be set}" +: "${GOARCH:?GOARCH must be set}" +: "${OUTPUT_BINARY_NAME:?OUTPUT_BINARY_NAME must be set}" + +GOARM="${GOARM:-7}" + +cd "${REPO_DIR}" + +echo "[${OUTPUT_BINARY_NAME}] Downloading dependencies..." +go mod download + +echo "[${OUTPUT_BINARY_NAME}] Building for GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM}..." +mkdir -p "${OUTPUT_DIR}" + +CGO_ENABLED=0 \ +GOOS="${GOOS}" \ +GOARCH="${GOARCH}" \ +GOARM="${GOARM}" \ +go build \ + -buildvcs=false \ + -tags "netgo osusergo" \ + -ldflags "-s -w -extldflags '-static'" \ + -o "${OUTPUT_DIR}/${OUTPUT_BINARY_NAME}" \ + "${ENTRY_POINT}" + +echo "[${OUTPUT_BINARY_NAME}] Binary built successfully: ${OUTPUT_DIR}/${OUTPUT_BINARY_NAME}" diff --git a/scripts/build_image.sh b/scripts/build_image.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +IMAGE_NAME="jci" + +echo "Building Docker image '${IMAGE_NAME}'..." + +# Write a minimal Dockerfile that installs the required tools. +# The repo is NOT baked into the image; it is mounted at runtime via -v. +docker build -t "${IMAGE_NAME}" - <<'DOCKERFILE' +FROM alpine:latest +RUN apk add --no-cache git ca-certificates stagit bash +WORKDIR /work +CMD ["/bin/sh"] +DOCKERFILE + +echo "Image '${IMAGE_NAME}' built successfully." diff --git a/scripts/build_site.sh b/scripts/build_site.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# Runs INSIDE the container. +# The host repo is mounted at /tmp/repo (read-write). +# Generated site is written to /tmp/repo/www_jci/public/releases. +set -euo pipefail + +export REPO_DIR="/tmp/Jaypore CI" +export PUBLIC_DIR="${REPO_DIR}/www_jci/public" +export BUILD_DIR="/build" +export RELEASES_DIR="${PUBLIC_DIR}/releases" +export ASSETS_DIR="${PUBLIC_DIR}/assets" +cd "$REPO_DIR" + +echo "===============" +env +echo "===============" + +# Ensure output directory exists +mkdir -p "${RELEASES_DIR}" +mkdir -p "${BUILD_DIR}" + +# Build a description line from the repo README for stagit +( + cd "${REPO_DIR}" + head README.md | grep '>' > description 2>/dev/null || echo "jci" > description +) + +cp -r "${REPO_DIR}/bin" "${REPO_DIR}/binaries" +cp -r "${REPO_DIR}/bin" "${PUBLIC_DIR}/binaries" +(cd "${BUILD_DIR}" && pwd && stagit -u /releases/git "${REPO_DIR}") +cp -r "${BUILD_DIR}/." "${RELEASES_DIR}" + +# Clean up the temporary description file +rm -f "${REPO_DIR}/description" +rm -rf "${REPO_DIR}/binaries" + +cp "${ASSETS_DIR}/git.style.css" "${RELEASES_DIR}/style.css" +cp "${ASSETS_DIR}/logo.png" "${RELEASES_DIR}/logo.png" +cp "${ASSETS_DIR}/logo.png" "${RELEASES_DIR}/favicon.png" + +echo "Site generated at ${RELEASES_DIR}" diff --git a/scripts/build_website.sh b/scripts/build_website.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${DOCKER_BUILDKIT:=1}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +PUBLIC_DIR="${REPO_ROOT}/www_jci/public" +RELEASES_DIR="${PUBLIC_DIR}/releases" + +IMAGE_NAME="jci-stagit" +CONTAINER_NAME="jci-stagit-builder" + +# Ensure releases directory exists +mkdir -p "${RELEASES_DIR}" + +# Build docker image with stagit (using inline Dockerfile via here-doc) +if ! docker image inspect "${IMAGE_NAME}">/dev/null 2>&1; then + tmp_dir="$(mktemp -d)" + mkdir -p "${tmp_dir}/repo" "${tmp_dir}/public_assets" + cp -R "${REPO_ROOT}"/.[!.]* "${tmp_dir}/repo" 2>/dev/null || true + cp -R "${REPO_ROOT}"/* "${tmp_dir}/repo" + cp "${PUBLIC_DIR}/assets/logo.png" "${tmp_dir}/public_assets/logo.png" + cp "${REPO_ROOT}/www_jci/Dockerfile" "${tmp_dir}/Dockerfile" + docker build -t "${IMAGE_NAME}" "${tmp_dir}" + rm -rf "${tmp_dir}" +fi + +# Prepare context for stagit run +BUILD_CONTEXT="$(mktemp -d)" +mkdir -p "${BUILD_CONTEXT}/repo" "${BUILD_CONTEXT}/public_assets" +cp -a "${REPO_ROOT}/." "${BUILD_CONTEXT}/repo" +cp "${PUBLIC_DIR}/assets/logo.png" "${BUILD_CONTEXT}/public_assets/logo.png" +if [ -f "${REPO_ROOT}/ui/src/libs/git.style.css" ]; then + cp "${REPO_ROOT}/ui/src/libs/git.style.css" "${BUILD_CONTEXT}/stagit_style.css" +else + cat <<'EOF' > "${BUILD_CONTEXT}/stagit_style.css" +:root {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;} +EOF +fi + +docker run --rm \ + -v "${BUILD_CONTEXT}/repo:/tmp/repo" \ + -v "${PUBLIC_DIR}/assets/logo.png:/data/www_jci/public/assets/logo.png" \ + -v "${RELEASES_DIR}:/gitrepo" \ + "${IMAGE_NAME}" + +rm -rf "${BUILD_CONTEXT}" + +echo "Releases site generated at ${RELEASES_DIR}" diff --git a/www_jci/Dockerfile b/www_jci/Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:latest +RUN apk add --no-cache git ca-certificates stagit +WORKDIR /work +ADD ./ /tmp +RUN mkdir /gitrepo \ + && (cd /tmp/repo && ls -alR /tmp && head README.md | grep '>' > description) \ + && stagit -u /git /tmp/repo \ + && rm /tmp/repo/description \ + && cp /tmp/repo/www_jci/public/assets/git.style.css /gitrepo/style.css \ + && cp /tmp/repo/www_jci/public/assets/logo.png /gitrepo/logo.png \ + && cp /gitrepo/logo.png /gitrepo/favicon.png +CMD ["/bin/sh"] diff --git a/www_jci/public/.gitignore b/www_jci/public/.gitignore @@ -0,0 +1,2 @@ +releases/ +binaries/ diff --git a/www_jci/public/assets/git.style.css b/www_jci/public/assets/git.style.css @@ -0,0 +1,282 @@ +/* ── Jaypore CI · shared theme ── used by index.html, binaries.html, and stagit pages ── */ + +:root { + --bg: #fbf9f4; + --ink: #14110f; + --muted: #6f6a65; + --line: #e6dfd2; + --accent: #14110f; +} + +*, *::before, *::after { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 2rem clamp(1rem, 4vw, 3rem) 4rem; + font-family: "iA Writer Quattro", "Inter", "SF Pro", system-ui, + -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: var(--bg); + color: var(--ink); + line-height: 1.6; +} + +/* ── centring wrapper (used by stagit pages directly on body, by index.html via <main>) ── */ +body, main { + max-width: 1080px; + margin-inline: auto; +} + +/* ── headings ── */ +h1, h2, h3, h4, h5, h6 { + margin: 0 0 0.5rem; + line-height: 1.25; +} + +h1 { font-size: clamp(2rem, 5vw, 2.8rem); margin-bottom: 0.75rem; } +h2 { font-size: 1.6rem; } +h3, h4, h5, h6 { font-size: 1rem; } + +img, h1, h2 { + vertical-align: middle; +} + +img { + border: 0; +} +[src="logo.png"], [src="../logo.png"] { + width: 80px !important; + height: 80px !important; +} + +p { + margin: 0 0 1rem; +} + +ul { + margin: 0 0 1rem; + padding-left: 1.1rem; +} + +li + li { + margin-top: 0.3rem; +} + +/* ── links ── */ +a { + color: var(--ink); + text-decoration: underline; + text-underline-offset: 2px; +} + +a:hover, +a:focus-visible { + color: var(--muted); +} + +a.d, +a.h, +a.i, +a.line { + text-decoration: none; +} + +#blob a { + color: var(--muted); +} + +#blob a:hover { + color: var(--ink); + text-decoration: none; +} + +/* ── tables ── */ +table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; +} + +table thead td { + font-weight: 600; + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.18em; + color: var(--muted); + padding-bottom: 0.5rem; +} + +table td { + padding: 0.35em 0.5em; +} + +#content table td { + vertical-align: top; + white-space: nowrap; +} + +#branches tr:hover td, +#tags tr:hover td, +#index tr:hover td, +#log tr:hover td, +#files tr:hover td { + background-color: var(--line); +} + +#index tr td:nth-child(2), +#tags tr td:nth-child(3), +#branches tr td:nth-child(3), +#log tr td:nth-child(2) { + white-space: normal; +} + +td.num { + text-align: right; + font-variant-numeric: tabular-nums; +} + +/* ── misc ── */ +.desc { + color: var(--muted); +} + +.muted { + color: var(--muted); +} + +hr { + border: 0; + border-top: 1px solid var(--line); + height: 1px; + margin: 2rem 0; +} + +a:target { + background-color: var(--line); +} + +/* ── code / pre ── */ +code, pre { + font-family: "JetBrains Mono", "SFMono-Regular", Consolas, monospace; + font-size: 0.92em; + background: var(--line); + padding: 2px 4px; + border-radius: 2px; +} + +pre { + padding: 1rem 0px; + border-radius: 6px; + overflow-x: auto; + line-height: 1.55; +} + +/* ── diff colours ── */ +pre a.h { color: var(--ink); font-weight: 600; } + +.A, +span.i, +pre a.i { color: #2d7a3a; } /* addition – green, readable on --bg */ + +.D, +span.d, +pre a.d { color: #b93a3a; } /* deletion – red, readable on --bg */ + +pre a.h:hover, +pre a.i:hover, +pre a.d:hover { + text-decoration: none; +} + +/* ── index.html landing-page components ── */ +header { + margin-bottom: 3rem; +} + +nav { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.95rem; + margin-bottom: 2rem; +} + +nav a { + color: var(--muted); + text-decoration: none; + margin-left: 1.25rem; +} + +nav a:hover, +nav a:focus-visible { + color: var(--ink); +} + +.lede { + font-size: 1.1rem; + color: var(--muted); +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1.5rem; + border: 1px solid var(--ink); + background: transparent; + font-weight: 600; + border-radius: 999px; + cursor: pointer; +} + +.button:focus-visible, +.button:hover { + background: var(--ink); + color: var(--bg); +} + +section + section { + margin-top: 3rem; + padding-top: 3rem; + border-top: 1px solid var(--line); +} + +.label { + display: block; + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.18em; + color: var(--muted); + margin-bottom: 0.4rem; +} + +.note { + font-size: 0.88rem; + color: var(--muted); +} + +footer { + margin-top: 4rem; + padding-top: 2rem; + border-top: 1px solid var(--line); + font-size: 0.9rem; + color: var(--muted); +} + +@keyframes rzp-shimmer { + 0% { background-position: -340px 0; } + 100% { background-position: 340px 0; } +} + +.rzp-skeleton { + border-radius: 10px; + background: linear-gradient( + 90deg, + var(--line) 25%, + var(--bg) 50%, + var(--line) 75% + ); + background-size: 340px 100%; + animation: rzp-shimmer 1.4s ease-in-out infinite; +} diff --git a/www_jci/public/binaries.html b/www_jci/public/binaries.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<title>Downloads - Jaypore CI - &gt; Minimal, Offline, Local CI system. +</title> +<link rel="icon" type="image/png" href="releases/favicon.png" /> +<link rel="alternate" type="application/atom+xml" title="Jaypore CI Atom Feed" href="releases/atom.xml" /> +<link rel="alternate" type="application/atom+xml" title="Jaypore CI Atom Feed (tags)" href="releases/tags.xml" /> +<link rel="stylesheet" type="text/css" href="releases/style.css" /> +</head> +<body> +<table><tr><td><a href="index.html"><img src="assets/logo.png" alt="" width="32" height="32" /></a></td><td><h1>Jaypore CI</h1><span class="desc">&gt; Minimal, Offline, Local CI system. +</span></td></tr><tr><td></td><td> +<a href="releases/log.html">Log</a> | <a href="releases/files.html">Files</a> | <a href="releases/refs.html">Refs</a> | <a href="releases/file/README.md.html">README</a></td></tr></table> +<hr/> +<div id="content"> +<h2>Downloads</h2> +<p>Pre-built binaries for the latest release of <strong>git-jci</strong>. Pick the one that matches your platform:</p> +<table id="files"><thead> +<tr><td><b>Mode</b></td><td><b>Name</b></td><td class="num" align="right"><b>Size</b></td></tr> +</thead><tbody> +<tr><td>-rwxr-xr-x</td><td><a href="binaries/git-jci-linux-amd64">git-jci-linux-amd64</a></td><td class="num" align="right">6.0M</td></tr> +<tr><td>-rwxr-xr-x</td><td><a href="binaries/git-jci-linux-arm64">git-jci-linux-arm64</a></td><td class="num" align="right">5.6M</td></tr> +<tr><td>-rwxr-xr-x</td><td><a href="binaries/git-jci-linux-armv7">git-jci-linux-armv7</a></td><td class="num" align="right">5.9M</td></tr> +<tr><td>-rwxr-xr-x</td><td><a href="binaries/git-jci-windows-amd64.exe">git-jci-windows-amd64.exe</a></td><td class="num" align="right">6.2M</td></tr> +<tr><td>-rwxr-xr-x</td><td><a href="binaries/git-jci-windows-arm64.exe">git-jci-windows-arm64.exe</a></td><td class="num" align="right">5.6M</td></tr> +</tbody></table></div> +</body> +</html> diff --git a/www_jci/public/index.html b/www_jci/public/index.html @@ -1,240 +1,81 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>JCI Project Overview</title> - <style> - :root { - color-scheme: light dark; - --bg: #f8f9fb; - --surface: #ffffff; - --text: #111217; - --muted: #5b5f6d; - --border: rgba(15, 19, 39, 0.08); - --accent: #111827; - } - - * { - box-sizing: border-box; - } - - body { - margin: 0; - font-family: "Inter", "SF Pro Display", "Segoe UI", system-ui, -apple-system, - BlinkMacSystemFont, sans-serif; - background: var(--bg); - color: var(--text); - line-height: 1.6; - -webkit-font-smoothing: antialiased; - } - - header { - padding: 2rem clamp(1.5rem, 5vw, 5rem) 0; - } - - nav { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 4rem; - font-size: 0.92rem; - } - - nav .brand { - font-weight: 600; - letter-spacing: 0.02em; - } - - nav ul { - list-style: none; - display: flex; - gap: 1.5rem; - margin: 0; - padding: 0; - } - - nav a { - text-decoration: none; - color: var(--muted); - transition: color 0.2s ease; - } - - nav a:hover, - nav a:focus-visible { - color: var(--text); - } - - .hero { - max-width: 720px; - } - - .hero h1 { - font-size: clamp(2.5rem, 4vw, 3.6rem); - line-height: 1.1; - margin-bottom: 1rem; - } - - .hero p { - font-size: 1.08rem; - color: var(--muted); - margin-bottom: 2.5rem; - } - - .cta-row { - display: flex; - flex-wrap: wrap; - gap: 1rem; - align-items: center; - } - - .cta-primary { - padding: 0.95rem 1.75rem; - border-radius: 999px; - border: none; - cursor: pointer; - background: var(--accent); - color: white; - font-size: 1rem; - font-weight: 600; - letter-spacing: 0.01em; - } - - .cta-note { - font-size: 0.9rem; - color: var(--muted); - } - - section { - padding: 4rem clamp(1.5rem, 5vw, 5rem); - } - - .grid { - display: grid; - gap: 1.5rem; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - } - - .card { - background: var(--surface); - border: 1px solid var(--border); - border-radius: 20px; - padding: 1.75rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - min-height: 220px; - } - - .card h3 { - margin: 0; - font-size: 1.2rem; - } - - .card p { - margin: 0; - color: var(--muted); - } - - .meta { - padding-top: 0; - } - - .meta-list { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - margin: 2rem 0 0; - padding: 0; - list-style: none; - color: var(--muted); - font-size: 0.95rem; - } - - footer { - padding: 2rem clamp(1.5rem, 5vw, 5rem) 3rem; - color: var(--muted); - font-size: 0.88rem; - } - - @media (max-width: 600px) { - nav { - flex-direction: column; - align-items: flex-start; - gap: 1rem; - } - - .card { - min-height: auto; - } - } - </style> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <title>Jaypore CI</title> + <link rel="stylesheet" type="text/css" href="assets/git.style.css" /> + <link rel="icon" type="image/png" href="assets/logo.png" /> </head> <body> - <header> - <nav> - <div class="brand">JCI</div> + <main> + <header> + <nav> + <strong>Jaypore&nbsp;CI</strong> + <div> + <a href="/binaries.html">Binaries</a> + <a href="/releases/file/README.md.html">Docs</a> + </div> + </nav> + <h1><img src="assets/logo.png" alt="JCI logo" style="height:1em;width:auto;vertical-align:middle;margin-right:0.4em;">Minimal. Offline. Local.</h1> + <p class="lede"> + Jaypore CI is a minimal single-binary CI system, that can run offline on your own laptop. + </p> + <p class="lede"> + It supports complex workflows, has a web interface, and has collaboration via git. + </p> + </header> + + <section id="releases"> + <span class="label">Binaries</span> + <h2>Download binaries</h2> + <p> + Download a <a href='binaries.html'>binary</a> of your choice and move it to <code>/usr/bin/git-jci</code>. Git will automatically start picking up the command and you can start running as <code>git jci web</code>. + </p> + <p> + </p> + <form id="rzp-form" class="rzp-skeleton" style="display:inline-block;min-width:172px;min-height:48px;"><script src="https://checkout.razorpay.com/v1/payment-button.js" data-payment_button_id="pl_SKcbmqJhCoHh1L" async> </script> </form> + <script> + (function () { + var form = document.getElementById('rzp-form'); + var observer = new MutationObserver(function () { + if (form.querySelector('button,iframe,[class*="razorpay"]')) { + form.classList.remove('rzp-skeleton'); + observer.disconnect(); + } + }); + observer.observe(form, { childList: true, subtree: true }); + })(); + </script> + </section> + + <section id="docs"> + <span class="label">Documentation</span> + <h2><a href='/releases/file/README.md.html'>README.md</a> for docs</h2> + <p> + Go through the <a href='/releases/file/README.md.html'>README.md</a> file to see how things work. A simple workflow is: + </p> <ul> - <li><a href="#source">Source code</a></li> - <li><a href="#docs">Docs</a></li> + <li><code>git jci run</code> executes your pipeline. Run this mannually, or via pre-commit, or cron as shown below.</li> + <li><code>git jci web</code> runs a web server, browse the CI results locally.</li> + <li><code>git jci push/pull</code> syncs refs with your remotes.</li> + <li><code>git jci cron sync</code> syncs your cron spec with system cron so that you can run midnight builds for example.</li> </ul> - </nav> - <div class="hero"> - <h1>Jaypore CI</h1> + </section> + + <section id="discussions"> + <span class="label">Discussions</span> + <h2>Bugs / Ideas / Support / Discussions</h2> <p> - Minimal. Offline. Local. + Please email us at <a href='mailto:support@midpathsoftware.com'>support@midpathsoftware.com</a> for anything you need. </p> - <div class="cta-row"> - <button class="cta-primary" aria-label="Buy this version for INR 100"> - Buy for $5 - </button> - <span class="cta-note">Razorpay integration placeholder</span> - </div> - </div> - </header> - - <section id="source"> - <div class="grid"> - <article class="card"> - <h3>Source code</h3> - <p> - Explore a modular Go + Node toolchain designed for command-line and - web surfaces. Every component is documented and version-controlled to - make auditing and contributions straightforward. - </p> - <p> - <strong>Highlights:</strong><br /> - Clear folder layout, reproducible builds, thoughtful CLI ergonomics. - </p> - </article> - <article class="card" id="docs"> - <h3>Docs</h3> - <p> - Lightweight documentation outlines project concepts, build targets, - and extension hooks. Follow concise guides inspired by UV and Exe to - move from setup to contribution in minutes. - </p> - <p> - <strong>Includes:</strong><br /> - Architecture notes, extension patterns, release checklists. - </p> - </article> - </div> - </section> - - <section class="meta"> - <h2>Project focus</h2> - <ul class="meta-list"> - <li>Minimal surface area, maximum clarity.</li> - <li>Composable CLI experiences with clean UX.</li> - <li>Self-hosted assets ready for static deployment.</li> - </ul> - </section> + </section> - <footer> - © <span id="year"></span> JCI. Built for teams who prefer fewer tabs and - faster ships. - </footer> + <footer> + © <span id="year"></span> Jaypore CI. Built for laptops, not data + centers. + </footer> + </main> <script> document.getElementById("year").textContent = new Date().getFullYear(); </script>