Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .air.cli.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ root = "."
tmp_dir = "tmp"

[build]
cmd = "make build-cli-dev"
cmd = "mage buildCLIDev"
bin = "/home/hersi/.nextdeploy/bin/nextdeploy"
full_bin = ""
include_ext = ["go", "toml"]
Expand Down
2 changes: 1 addition & 1 deletion .air.daemon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ root = "."
tmp_dir = "tmp"

[build]
cmd = "make build-daemon-dev"
cmd = "mage buildDaemonDev"
bin = "~/.nextdeploy/bin/nextdeployd"
full_bin = "~/.nextdeploy/bin/nextdeployd --foreground=true"
include_ext = ["go", "toml", "yml"]
Expand Down
111 changes: 111 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env bash
#
# NextDeploy pre-commit hook
#
# Blocks commits that contain:
# - Compiled binaries (Mach-O, ELF, PE)
# - Files larger than MAX_FILE_KB
# - Obvious secrets (.env, AWS keys, private keys)
# - Files matching ignored patterns
#
# Install with: mage preCommitInstall
# Bypass (use sparingly, with explicit reason): git commit --no-verify

set -euo pipefail

MAX_FILE_KB=512
RED='\033[0;31m'
YELLOW='\033[0;33m'
GREEN='\033[0;32m'
NC='\033[0m'

fail=0

# Collect staged files (added/copied/modified, not deleted)
staged=$(git diff --cached --name-only --diff-filter=ACM)
if [ -z "$staged" ]; then
exit 0
fi

err() { echo -e "${RED}✘ $*${NC}" >&2; fail=1; }
warn() { echo -e "${YELLOW}⚠ $*${NC}" >&2; }
ok() { echo -e "${GREEN}✓ $*${NC}"; }

# 1. Block compiled binaries by content sniffing
for f in $staged; do
[ -f "$f" ] || continue
mime=$(file --mime-type -b "$f" 2>/dev/null || echo "unknown")
case "$mime" in
application/x-mach-binary|application/x-executable|application/x-sharedlib|application/x-pie-executable|application/x-dosexec)
err "Binary file blocked: $f ($mime)"
;;
esac
done

# 2. Block oversized files
for f in $staged; do
[ -f "$f" ] || continue
size_kb=$(du -k "$f" | cut -f1)
if [ "$size_kb" -gt "$MAX_FILE_KB" ]; then
err "File too large: $f (${size_kb}KB > ${MAX_FILE_KB}KB). Use Git LFS or .gitignore."
fi
done

# 3. Block secrets by filename
for f in $staged; do
case "$f" in
.env|.env.*|*.pem|*.key|.nextdeploy/.env|.secrets|credentials.json|service-account*.json)
# allow examples and testdata
case "$f" in
.env.example|*/testdata/*) continue ;;
esac
err "Secret file blocked: $f"
;;
esac
done

# 4. Block secret PATTERNS in staged content (text files only)
secret_pattern='(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*=\s*[A-Za-z0-9/+=]{40}|-----BEGIN [A-Z ]*PRIVATE KEY-----|ghp_[A-Za-z0-9]{36}|xox[baprs]-[A-Za-z0-9-]+)'
for f in $staged; do
[ -f "$f" ] || continue
mime=$(file --mime-type -b "$f" 2>/dev/null || echo "unknown")
case "$mime" in text/*|application/json|application/xml|application/yaml) ;; *) continue ;; esac
if grep -EH "$secret_pattern" "$f" >/dev/null 2>&1; then
err "Possible secret detected in $f"
grep -EHn "$secret_pattern" "$f" | sed 's/^/ /' >&2 || true
fi
done

# 5. gofmt check on staged Go files
go_files=$(echo "$staged" | grep '\.go$' || true)
if [ -n "$go_files" ]; then
if ! command -v gofmt >/dev/null 2>&1; then
warn "gofmt not found — skipping format check"
else
bad=$(gofmt -s -l $go_files || true)
if [ -n "$bad" ]; then
err "Go files need gofmt:"
echo "$bad" | sed 's/^/ /' >&2
echo " Fix with: mage fmt" >&2
fi
fi
fi

# 6. go vet on changed packages (cheap, catches obvious bugs)
if [ -n "$go_files" ] && command -v go >/dev/null 2>&1; then
pkgs=$(echo "$go_files" | xargs -n1 dirname | sort -u | sed 's|^|./|')
if ! go vet $pkgs >/dev/null 2>&1; then
err "go vet failed on changed packages:"
go vet $pkgs 2>&1 | sed 's/^/ /' >&2 || true
fi
fi

if [ "$fail" -ne 0 ]; then
echo
echo -e "${RED}Pre-commit hook blocked the commit.${NC}" >&2
echo " - Fix the issues above, OR" >&2
echo " - Bypass with --no-verify ONLY if you understand the risk and document why." >&2
exit 1
fi

ok "Pre-commit checks passed"
113 changes: 99 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,64 +16,149 @@ permissions:
env:
GO_VERSION: "1.25.x"
GOTOOLCHAIN: local
GOLANGCI_LINT_VERSION: "v2.5.0"

jobs:
build:
name: Build & Test
modules:
name: Modules
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum

- name: Verify modules
run: |
go mod verify
go mod tidy
git diff --exit-code go.mod go.sum

- name: Build CLI
run: go build -o /dev/null ./cli
fmt:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Install mage
run: go install github.com/magefile/mage@latest
- name: gofmt
run: mage fmtCheck

build:
name: Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Install mage
run: go install github.com/magefile/mage@latest
- name: Build CLI
run: mage buildCLI
- name: Build Daemon
run: go build -o /dev/null ./daemon/cmd/nextdeployd
if: runner.os == 'Linux'
run: mage buildDaemon

- name: Test
run: go test -race -timeout 5m ./...
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: [modules]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Install mage
run: go install github.com/magefile/mage@latest
- name: Run unit tests
run: mage testUnit
- name: Coverage summary
run: go tool cover -func=coverage.out | tail -1
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.out
retention-days: 7

lint:
name: Lint
runs-on: ubuntu-latest
needs: [modules]
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
# v7 is required for golangci-lint v2.x (v6 only supports v1.x and
# errors with `invalid version string 'v2.5.0'`).
uses: golangci/golangci-lint-action@v7
with:
version: latest
version: ${{ env.GOLANGCI_LINT_VERSION }}
args: --timeout 10m

vuln:
name: Vulnerability Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: ${{ env.GO_VERSION }}
check-latest: true

hook-smoke:
name: Pre-commit Hook Smoke Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate hook syntax
run: bash -n .githooks/pre-commit
- name: Run hook against HEAD
run: |
# Stage everything from the last commit so the hook has something
# to inspect, then run it. Catches accidental binaries on main.
git -c user.email=ci@example.com -c user.name=ci \
reset --soft HEAD~1 || true
./.githooks/pre-commit || (echo "Hook failed on HEAD"; exit 1)

quality:
name: Code Quality (informational)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Install scc
run: go install github.com/boyter/scc/v3@latest
- name: Lines of code
run: scc --format wide --exclude-dir vendor,test-serverless-app,.next
- name: Benchmarks (best-effort)
run: go test -bench=. -benchmem -run='^$' ./... 2>/dev/null || echo "No benchmarks found"
77 changes: 63 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,25 +1,74 @@
/bin/
/.env
./.keys
.secrets
# ── Build artifacts ──────────────────────────────────────────────
/bin/
/dist/
/nextdeploy
/nextdeployd
*.exe
*.test
*.out
*.prof
coverage.out
coverage.html

/daemon.md
# ── Secrets and credentials ──────────────────────────────────────
.env
.env.*
!.env.example
.secrets
.keys
.nextdeploy/.env
.nextdeploy/secrets/
*.pem
*.key
!**/testdata/**/*.pem
!**/testdata/**/*.key
credentials.json
service-account*.json

# Documentation
*.md
!README.md
!TECH_DOCS.md
# ── Generated / packaged ─────────────────────────────────────────
*.tar.gz
*.tgz
*.zip
!assets/**/*.zip
node_modules/
.next/
out/

# Root-level binaries
/nextdeploy
/nextdeployd
# ── Tooling / IDE / OS ───────────────────────────────────────────
.idea/
.vscode/
*.swp
*.swo
*~
.DS_Store
Thumbs.db
.air.toml
.air.*.toml.bak

# Test artifacts
test*.go
# ── Test scratch ─────────────────────────────────────────────────
testfile*
testdir*/
testsym*
testwalk/
test_src_dir/

# ── Documentation ────────────────────────────────────────────────
# Track only the docs we curate. Anything else is scratch/draft.
*.md
!README.md
!TECH_DOCS.md
!CODE_QUALITY.md
!CHANGELOG.md
!CONTRIBUTING.md
!cli/internal/serverless/REVIEW.md
!**/README.md
!CLOUDFLARE_PARITY.md

# ── Misc ─────────────────────────────────────────────────────────
/daemon.md
vendor/

# ── Dev-local ────────────────────────────────────────────────────
tmp/
NextDeploy/
graphify-out/
Loading
Loading