gate-stack-cli 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate +427 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gate-stack-cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "private": false,
5
5
  "description": "Gate CLI wrapper for git (gate clone/init/push/pull/status)",
6
6
  "bin": {
package/scripts/gate CHANGED
@@ -1,81 +1,474 @@
1
1
  #!/usr/bin/env bash
2
2
  # Gate CLI — wrapper sobre git com convenções da GateStack
3
- # Suporta:
4
- # gate clone <slug|url> [dir]
5
- # gate init [dir]
6
- # gate pull [args...]
7
- # gate push [args...]
8
- # gate status [args...]
9
- # gate <qualquer-comando-git> [...]
3
+ #
4
+ # Comandos:
5
+ # clone init pull push status fetch checkout branch remote
6
+ # log commit diff stash stash-sub config login open pr
7
+ # reset completion
8
+ # Aliases: st br co ci f lg df rm
9
+ # Pass-through: qualquer outro comando vai pro git
10
10
 
11
11
  set -euo pipefail
12
12
 
13
13
  GATE_HOST="${GATE_HOST:-http://localhost:5000}"
14
14
  GIT_BIN="${GIT_BIN:-git}"
15
+ VERSION="1.0.6"
15
16
 
17
+ # ── Config dir ────────────────────────────────────────────────────────
18
+ GATE_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gate"
19
+ GATE_CONFIG="$GATE_CONFIG_DIR/config"
20
+
21
+ # ── Cores ─────────────────────────────────────────────────────────────
22
+ if [ -t 1 ]; then
23
+ R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'
24
+ C='\033[0;36m'; M='\033[0;35m'; W='\033[0;37m'
25
+ K='\033[0;90m'; N='\033[0m'
26
+ BOLD='\033[1m'; DIM='\033[2m'
27
+ else
28
+ R=''; G=''; Y=''; B=''; C=''; M=''; W=''; K=''; N=''
29
+ BOLD=''; DIM=''
30
+ fi
31
+
32
+ info() { printf "${G}▸${N} %s\n" "$*"; }
33
+ warn() { printf "${Y}⚠${N} %s\n" "$*" >&2; }
34
+ error() { printf "${R}✖${N} %s\n" "$*" >&2; }
35
+ header() { printf "\n${BOLD}${B}❯❯${N} %s\n\n" "$*"; }
36
+ dim() { printf "${DIM}%s${N}" "$*"; }
37
+ success() { printf "${G}✓${N} %s\n" "$*"; }
38
+
39
+ # ── Versão ────────────────────────────────────────────────────────────
40
+ version() { printf "gate-stack-cli %s\n" "$VERSION"; }
41
+
42
+ # ── Usage ─────────────────────────────────────────────────────────────
16
43
  usage() {
17
- cat <<'EOF'
18
- Gate CLI (wrapper para git)
19
- Usage:
20
- gate clone <slug|url> [dir]
21
- gate init [dir]
22
- gate pull [args...]
23
- gate push [args...]
24
- gate status [args...]
25
- gate <comando-git> [...]
26
-
27
- Env vars:
28
- GATE_HOST Base URL para o Git HTTP (default: http://localhost:5000)
29
- GIT_BIN Caminho do binário git (default: git)
44
+ cat <<EOF
45
+ ${BOLD}${C}Gate CLI${N} wrapper sobre git v${VERSION}
46
+
47
+ ${BOLD}${B}Comandos principais:${N}
48
+ init [dir] Inicializa repositório
49
+ clone <slug|url> [dir] Clona por slug ou URL
50
+ pull [args...] git pull
51
+ push [args...] git push
52
+ st [args...] git status (alias: status)
53
+ ci [args...] git commit (alias: commit)
54
+ fetch [args...] git remote fetch (alias: f)
55
+ df [args...] git diff --stat (alias: diff)
56
+ lg [args...] git log bonito (alias: log)
57
+
58
+ ${BOLD}${B}Branch & checkout:${N}
59
+ br [args...] git branch (alias: branch)
60
+ co <ref> [args...] git checkout (alias: checkout)
61
+
62
+ ${BOLD}${B}Stash:${N}
63
+ stash Stash rápido (salva mudanças)
64
+ stash [save|pop|list|drop|apply|clear]
65
+
66
+ ${BOLD}${B}Operações avançadas:${N}
67
+ open Abre repo no browser
68
+ reset [soft|hard|mixed] Reset com confirmação de segurança
69
+ config [key] [value] Ler/setar configurações
70
+ login [token] Salvar token de API
71
+ pr [--open] Criar/listar PR
72
+ remote [args...] Gerencia remotes (alias: rm)
73
+
74
+ ${BOLD}${B}Shell completion:${N}
75
+ completion Gera script bash/zsh para autocomplete
76
+
77
+ ${BOLD}${B}Opções:${N}
78
+ -h --help help Esta ajuda
79
+ -v --version Versão
80
+
81
+ ${BOLD}${B}Env vars:${N}
82
+ GATE_HOST Base URL GateStack (default: ${GATE_HOST})
83
+ GIT_BIN Caminho do git (default: git)
30
84
  EOF
31
85
  }
32
86
 
87
+ # ── Helpers ───────────────────────────────────────────────────────────
33
88
  ensure_args() {
34
- local count=$1
35
- shift
36
- if [ $# -lt "$count" ]; then
37
- usage
38
- exit 1
89
+ local count=$1; shift
90
+ if [ $# -lt "$count" ]; then usage; exit 1; fi
91
+ }
92
+
93
+ read_config() {
94
+ local key=$1
95
+ if [ -f "$GATE_CONFIG" ]; then
96
+ grep "^${key}=" "$GATE_CONFIG" 2>/dev/null | tail -1 | cut -d= -f2-
97
+ fi
98
+ }
99
+
100
+ write_config() {
101
+ local key=$1 val=$2
102
+ mkdir -p "$GATE_CONFIG_DIR"
103
+ if [ -f "$GATE_CONFIG" ]; then
104
+ if grep -q "^${key}=" "$GATE_CONFIG" 2>/dev/null; then
105
+ sed -i "s|^${key}=.*|${key}=${val}|" "$GATE_CONFIG"
106
+ else
107
+ printf '%s=%s\n' "$key" "$val" >> "$GATE_CONFIG"
108
+ fi
109
+ else
110
+ printf '%s=%s\n' "$key" "$val" > "$GATE_CONFIG"
111
+ fi
112
+ }
113
+
114
+ get_gate_host() {
115
+ local configured
116
+ configured=$(read_config "host")
117
+ printf '%s' "${configured:-$GATE_HOST}"
118
+ }
119
+
120
+ check_host() {
121
+ local url=$1
122
+ if command -v curl &>/dev/null; then
123
+ curl -sf --max-time 5 "$url" &>/dev/null || return 1
124
+ elif command -v wget &>/dev/null; then
125
+ wget -q --spider --timeout=5 "$url" 2>/dev/null || return 1
39
126
  fi
40
127
  }
41
128
 
129
+ is_git_repo() {
130
+ "$GIT_BIN" rev-parse --git-dir &>/dev/null
131
+ }
132
+
42
133
  normalize_clone_url() {
43
134
  local target=$1
44
135
  if [[ "$target" =~ ^https?:// ]] || [[ "$target" =~ ^git@ ]] || [[ "$target" =~ ^ssh:// ]]; then
45
136
  echo "$target"
46
137
  else
47
- echo "${GATE_HOST%/}/git/${target}"
138
+ echo "$(get_gate_host)/git/${target}"
139
+ fi
140
+ }
141
+
142
+ # Spinner: roda comando no fundo enquanto mostra ...
143
+ with_spinner() {
144
+ local msg=$1; shift
145
+ if [ -t 2 ]; then
146
+ printf "${DIM}%s${N}" "$msg " >&2
147
+ local pid=$!
148
+ "$@" 2>&1 &
149
+ local cmd_pid=$!
150
+ trap "kill $cmd_pid 2>/dev/null" EXIT
151
+ local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
152
+ while kill -0 "$cmd_pid" 2>/dev/null; do
153
+ for ((i=0; i<${#spin}; i+=3)); do
154
+ if kill -0 "$cmd_pid" 2>/dev/null; then
155
+ printf "\r${DIM}%s %s${N}" "$msg" "${spin:$i:3}" >&2
156
+ sleep 0.1
157
+ else
158
+ break
159
+ fi
160
+ done
161
+ done
162
+ wait "$cmd_pid"
163
+ local ret=$?
164
+ printf "\r${DIM}%s${N}\n" "$msg" >&2
165
+ return $ret
166
+ else
167
+ "$@"
48
168
  fi
49
169
  }
50
170
 
171
+ # Detecta remote URL pra abrir no browser
172
+ detect_repo_url() {
173
+ local remote_url
174
+ remote_url=$("$GIT_BIN" remote get-url origin 2>/dev/null) || return 1
175
+ # Converte SSH → HTTPS
176
+ if [[ "$remote_url" =~ ^git@([^:]+):(.+)$\.git$?$ ]]; then
177
+ printf "https://%s/%s" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
178
+ else
179
+ # Remove .git suffix
180
+ printf '%s' "${remote_url%.git}"
181
+ fi
182
+ }
183
+
184
+ # ── Main ──────────────────────────────────────────────────────────────
51
185
  cmd="${1:-}"
52
- if [ -z "$cmd" ]; then
53
- usage
54
- exit 1
55
- fi
186
+ if [ -z "$cmd" ]; then usage; exit 1; fi
56
187
  shift || true
57
188
 
189
+ # Aliases curtos
190
+ case "$cmd" in
191
+ st) cmd="status" ;;
192
+ ci) cmd="commit" ;;
193
+ br) cmd="branch" ;;
194
+ co) cmd="checkout" ;;
195
+ f) cmd="fetch" ;;
196
+ rm) if [ -z "${1:-}" ] || [[ "${1:-}" != ==* ]]; then cmd="remote"; fi ;;
197
+ lg) cmd="log" ;;
198
+ df) cmd="diff" ;;
199
+ esac
200
+
201
+ # --version
202
+ case "$cmd" in
203
+ -v|--version) version; exit 0 ;;
204
+ esac
205
+
58
206
  case "$cmd" in
59
207
  clone)
60
208
  ensure_args 1 "$@"
61
209
  url=$(normalize_clone_url "$1"); shift
62
- exec "$GIT_BIN" clone "$url" "$@"
210
+ base_url="${url%%/*}"
211
+ if check_host "$base_url"; then
212
+ info "Clonando ${C}${url}${N} ..."
213
+ exec "$GIT_BIN" clone "$url" "$@"
214
+ else
215
+ error "Não conecta a ${R}${base_url}${N}"
216
+ error "Verifique: GATE_HOST=${GATE_HOST}"
217
+ error "Ou use URL completa: gate clone https://..."
218
+ exit 1
219
+ fi
63
220
  ;;
221
+
64
222
  init)
223
+ info "Inicializando repositório"
65
224
  exec "$GIT_BIN" init "$@"
66
225
  ;;
226
+
67
227
  pull)
68
- exec "$GIT_BIN" pull "$@"
228
+ with_spinner "⇣ Pull" "$GIT_BIN" pull "$@"
69
229
  ;;
230
+
70
231
  push)
71
- exec "$GIT_BIN" push "$@"
232
+ with_spinner "⇡ Push" "$GIT_BIN" push "$@"
72
233
  ;;
234
+
73
235
  status)
74
- exec "$GIT_BIN" status "$@"
236
+ exec "$GIT_BIN" status -sb "$@" 2>/dev/null || exec "$GIT_BIN" status "$@"
237
+ ;;
238
+
239
+ fetch)
240
+ with_spinner "↻ Fetch" "$GIT_BIN" fetch "$@"
241
+ ;;
242
+
243
+ checkout)
244
+ if [ $# -eq 1 ] && ! [[ "$1" == -* ]]; then
245
+ info "Checkout ${C}${1}${N}"
246
+ fi
247
+ exec "$GIT_BIN" checkout "$@"
248
+ ;;
249
+
250
+ branch)
251
+ if [ $# -eq 0 ]; then
252
+ exec "$GIT_BIN" branch --sort=-committerdate --format='%(HEAD) %(color:cyan)%(refname:short)%(color:reset) %(color:green)(%(committerdate:relative))%(color:reset)'
253
+ else
254
+ exec "$GIT_BIN" branch "$@"
255
+ fi
256
+ ;;
257
+
258
+ remote)
259
+ exec "$GIT_BIN" remote "$@"
260
+ ;;
261
+
262
+ log)
263
+ # Se sem args, log bonitão padrão
264
+ if [ $# -eq 0 ]; then
265
+ exec "$GIT_BIN" log --graph --pretty=format:"${C}%h${N} ${BOLD}%s${N} ${DIM}%cr${N} ${M}<%an>${N}" --abbrev-commit --date=relative "$@"
266
+ else
267
+ exec "$GIT_BIN" log "$@"
268
+ fi
269
+ ;;
270
+
271
+ commit)
272
+ exec "$GIT_BIN" commit "$@"
273
+ ;;
274
+
275
+ diff)
276
+ if [ $# -eq 0 ]; then
277
+ exec "$GIT_BIN" diff --stat --color
278
+ else
279
+ # Se passar --, usa diff normal
280
+ exec "$GIT_BIN" diff "$@"
281
+ fi
282
+ ;;
283
+
284
+ stash)
285
+ sub="${1:-save}"
286
+ case "$sub" in
287
+ save|pop|list|drop|apply|clear)
288
+ if [ "$sub" = "list" ]; then
289
+ exec "$GIT_BIN" stash list --format="${C}%gd${N} ${BOLD}%gs${N} ${DIM}%cr${N}"
290
+ elif [ "$sub" = "save" ]; then
291
+ if [ $# -le 1 ]; then
292
+ info "Salvando mudanças locais"
293
+ exec "$GIT_BIN" stash push -m "gate$(date +%H%M)"
294
+ else
295
+ shift
296
+ exec "$GIT_BIN" stash push -m "gate: $*" "$@"
297
+ fi
298
+ else
299
+ shift
300
+ exec "$GIT_BIN" stash "$sub" "$@"
301
+ fi
302
+ ;;
303
+ *)
304
+ # Sem args → save por default
305
+ info "Salvando mudanças locais"
306
+ exec "$GIT_BIN" stash push -m "gate$(date +%H%M)"
307
+ ;;
308
+ esac
309
+ ;;
310
+
311
+ config)
312
+ if [ $# -eq 0 ]; then
313
+ header "Configuração atual"
314
+ if [ -f "$GATE_CONFIG" ]; then
315
+ while IFS='=' read -r k v; do
316
+ printf " ${C}%s${N} = %s\n" "$k" "$v"
317
+ done < "$GATE_CONFIG"
318
+ else
319
+ warn "Nenhuma configuração definida"
320
+ info "Use: gate config <key> <value>"
321
+ fi
322
+ elif [ $# -eq 1 ]; then
323
+ val=$(read_config "$1")
324
+ if [ -n "$val" ]; then
325
+ printf '%s\n' "$val"
326
+ else
327
+ error "Chave '${1}' não encontrada"
328
+ fi
329
+ else
330
+ write_config "$1" "$2"
331
+ success "Configurado: ${C}${1}${N} = ${2}"
332
+ fi
333
+ ;;
334
+
335
+ login)
336
+ ensure_args 1 "$@"
337
+ write_config "token" "$1"
338
+ success "Token salvo em ${GATE_CONFIG}"
339
+ info "Use este token para autenticação via API GateStack"
340
+ ;;
341
+
342
+ open)
343
+ if ! is_git_repo; then
344
+ error "Não está em um repositório git"
345
+ exit 1
346
+ fi
347
+ repo_url=$(detect_repo_url) || { error "Nenhum remote 'origin' encontrado"; exit 1; }
348
+ info "Abrindo ${C}${repo_url}${N} ..."
349
+ if command -v xdg-open &>/dev/null; then
350
+ exec xdg-open "$repo_url"
351
+ elif command -v open &>/dev/null; then
352
+ exec open "$repo_url"
353
+ elif command -v cmd.exe &>/dev/null; then
354
+ exec cmd.exe /c start "$repo_url"
355
+ else
356
+ error "Nenhum browser detectado"
357
+ printf " ${C}%s${N}\n" "$repo_url"
358
+ fi
359
+ ;;
360
+
361
+ reset)
362
+ mode="${1:-soft}"
363
+ case "$mode" in
364
+ soft|mixed|hard) ;;
365
+ *) error "Modo inválido: '${mode}'. Use: soft, mixed, hard"; exit 1 ;;
366
+ esac
367
+ if [ "$mode" = "hard" ]; then
368
+ warn "reset --hard descarta alterações não-committadas"
369
+ fi
370
+ shift
371
+ target="${1:-HEAD}"
372
+ with_spinner "Reset ${mode}" "$GIT_BIN" reset --"$mode" "$target"
373
+ success "Reset ${mode} → ${C}${target}${N}"
75
374
  ;;
375
+
376
+ pr)
377
+ # Integra com o backend GateStack
378
+ if ! is_git_repo; then
379
+ error "Não está em um repositório git"
380
+ exit 1
381
+ fi
382
+ host=$(get_gate_host)
383
+ branch=$("$GIT_BIN" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch="unknown"
384
+ remote_url=$(detect_repo_url) || remote_url=""
385
+ info "Branch atual: ${C}${branch}${N}"
386
+ if [ "${1:-}" = "--open" ] || [ "${1:-}" = "-o" ]; then
387
+ if [ -z "$remote_url" ]; then
388
+ error "Nenhum remote origin detectado"
389
+ exit 1
390
+ fi
391
+ if [[ "$remote_url" =~ github\.com ]] || [[ "$remote_url" =~ gitlab\.com ]]; then
392
+ info "Abrindo para criar PR ..."
393
+ if command -v gh &>/dev/null; then
394
+ exec gh pr create --web 2>/dev/null || exec xdg-open "${remote_url}/compare/main...${branch}"
395
+ else
396
+ exec xdg-open "${remote_url}/compare/main...${branch}"
397
+ fi
398
+ else
399
+ exec xdg-open "${host}/projects"
400
+ fi
401
+ else
402
+ info "Use ${C}gate pr --open${N} para criar/abrir no browser"
403
+ if command -v gh &>/dev/null; then
404
+ echo
405
+ gh pr list --limit 5 2>/dev/null || warn "Não foi possível listar PRs via gh"
406
+ fi
407
+ fi
408
+ ;;
409
+
410
+ completion)
411
+ shell="${1:-bash}"
412
+ case "$shell" in
413
+ bash)
414
+ cat <<'COMP'
415
+ _gate_completion() {
416
+ local cur="${COMP_WORDS[COMP_CWORD]}"
417
+ local cmds="clone init pull push status fetch checkout branch remote log commit diff stash config login open pr reset completion st ci br co f rm lg df -h --help help -v --version"
418
+ if [ "$COMP_CWORD" -eq 1 ]; then
419
+ COMPREPLY=( $(compgen -W "$cmds" -- "$cur") )
420
+ else
421
+ COMPREPLY=( $(compgen -f -- "$cur") )
422
+ fi
423
+ }
424
+ complete -F _gate_completion gate
425
+ COMP
426
+ ;;
427
+ zsh)
428
+ cat <<'COMP'
429
+ #compdef gate
430
+ _gate() {
431
+ local -a cmds
432
+ cmds=(
433
+ 'clone:Clone by slug or URL'
434
+ 'init:Initialize repository'
435
+ 'pull:git pull'
436
+ 'push:git push'
437
+ 'status:git status short-branch'
438
+ 'fetch:git remote fetch'
439
+ 'checkout:git checkout'
440
+ 'branch:List branches'
441
+ 'remote:Manage remotes'
442
+ 'log:Pretty git log'
443
+ 'commit:git commit'
444
+ 'diff:git diff --stat'
445
+ 'stash:Quick stash save'
446
+ 'stash-sub:Stash subcommands'
447
+ 'config:Read/set config'
448
+ 'login:Save API token'
449
+ 'open:Open repo in browser'
450
+ 'pr:Pull requests'
451
+ 'reset:Git reset with safety'
452
+ 'completion:Generate shell completion'
453
+ )
454
+ _arguments '*: :->cmds'
455
+ case $state in
456
+ cmds) _describe 'command' cmds ;;
457
+ esac
458
+ }
459
+ COMP
460
+ ;;
461
+ *)
462
+ error "Shell '${shell}' não suportado. Use: bash ou zsh"
463
+ exit 1
464
+ ;;
465
+ esac
466
+ ;;
467
+
76
468
  -h|--help|help)
77
469
  usage
78
470
  ;;
471
+
79
472
  *)
80
473
  exec "$GIT_BIN" "$cmd" "$@"
81
474
  ;;