Content is user-generated and unverified.

local-cicd.el - 로컬 CI/CD 관리 패키지

Docker.el의 tabulated-list-mode 구조를 참고하여 만든 Emacs에서 로컬 프로젝트의 CI/CD를 관리하는 패키지입니다.

설치 및 설정

1. 패키지 설치

elisp
;; ~/.emacs.d/init.el 또는 ~/.emacs
;; local-cicd.el 파일을 load-path에 추가
(add-to-list 'load-path "~/.emacs.d/lisp/")

;; use-package를 사용하는 경우
(use-package local-cicd
  :ensure nil  ; 로컬 패키지이므로
  :bind ("C-c c" . local-cicd)
  :config
  (setq local-cicd-default-build-command "bun run tauri build --no-bundle")
  (setq local-cicd-auto-refresh-interval 60)
  ;; 자동 새로고침 시작
  (local-cicd-start-auto-refresh))

2. 프로젝트 설정 파일

프로젝트 정보는 ~/.config/local-cicd/projects.json에 저장됩니다:

json
[
  {
    "name": "claudia",
    "path": "/home/user/projects/claudia",
    "build-cmd": "bun run tauri build --no-bundle",
    "git-url": "https://github.com/getAsterisk/claudia.git",
    "status": "clean",
    "last-build": "14:30:25",
    "last-commit": "a7b8c9d"
  },
  {
    "name": "my-rust-project",
    "path": "/home/user/projects/rust-app",
    "build-cmd": "cargo build --release",
    "git-url": "git@github.com:user/rust-app.git",
    "status": "success",
    "last-build": "15:45:12",
    "last-commit": "e1f2g3h"
  }
]

사용법

기본 사용

  1. 패키지 실행: M-x local-cicd 또는 C-c c
  2. 프로젝트 목록 확인: tabulated-list 형태로 프로젝트들이 표시됩니다

키바인딩

명령어설명
?local-cicd-menu메뉴 표시 (transient)
plocal-cicd-pull-current현재 프로젝트 git pull
blocal-cicd-build-current현재 프로젝트 빌드
Plocal-cicd-pull-and-build-currentPull 후 빌드
klocal-cicd-kill-build실행 중인 빌드 중단
alocal-cicd-add-project새 프로젝트 추가
dlocal-cicd-remove-project프로젝트 제거
olocal-cicd-open-project-dir프로젝트 디렉토리 열기
glocal-cicd-refresh-buffer새로고침
llocal-cicd-show-logs빌드 로그 보기
RETlocal-cicd-open-project-dir프로젝트 디렉토리 열기

Docker.el과 유사한 tabulated-list 기능

  • m: 항목 마크
  • u: 마크 해제
  • t: 마크 토글
  • U: 모든 마크 해제
  • s: 정렬
  • </>: 컬럼 너비 조정

프로젝트 관리

프로젝트 추가

elisp
;; 대화형으로 추가
M-x local-cicd-add-project

;; 코드로 추가
(local-cicd-add-project 
  "claudia"
  "~/projects/claudia" 
  "bun run tauri build --no-bundle"
  "https://github.com/getAsterisk/claudia.git")

상태 표시

  • clean: Git 상태가 깨끗함
  • dirty: 커밋되지 않은 변경사항 존재
  • building: 현재 빌드 중
  • success: 마지막 빌드 성공
  • failed: 마지막 빌드 실패
  • no-git: Git 저장소가 아님

워크플로우 예시

1. 데일리 업데이트 워크플로우

elisp
;; 1. local-cicd 실행
M-x local-cicd

;; 2. Claudia 프로젝트에서 Pull & Build
;; (Claudia 행에서) P

;; 3. 빌드 로그 확인
;; l

;; 4. 빌드 완료 후 바이너리 실행
;; o (dired에서 target/release/claudia 실행)

2. 여러 프로젝트 일괄 관리

elisp
;; 여러 프로젝트를 마크한 후 일괄 처리 (향후 구현 예정)
;; m m m  (여러 프로젝트 마크)
;; P      (마크된 모든 프로젝트 pull & build)

고급 설정

커스터마이징

elisp
(use-package local-cicd
  :custom
  ;; 프로젝트 설정 파일 위치
  (local-cicd-projects-file "~/my-projects.json")
  
  ;; 기본 빌드 명령
  (local-cicd-default-build-command "make build")
  
  ;; 로그 버퍼 크기
  (local-cicd-log-buffer-size 2000)
  
  ;; 자동 새로고침 간격 (초)
  (local-cicd-auto-refresh-interval 45)
  
  :config
  ;; 자동 새로고침 시작
  (local-cicd-start-auto-refresh)
  
  ;; 특정 프로젝트에 대한 후크
  (add-hook 'local-cicd-build-success-hook 
            (lambda (project-name)
              (when (string= project-name "claudia")
                (message "Claudia build completed! Ready to test.")))))

빌드 명령 커스터마이징

elisp
;; 프로젝트별 빌드 명령 설정
(defun my-custom-build-command (project-name)
  "Return custom build command based on project name."
  (pcase project-name
    ("claudia" "bun run tauri build --no-bundle")
    ("rust-project" "cargo build --release")
    ("node-app" "npm run build")
    (_ local-cicd-default-build-command)))

;; 빌드 전 후크
(add-hook 'local-cicd-before-build-hook
          (lambda (project)
            (message "Starting build for %s..." 
                     (cdr (assoc 'name project)))))

통합 예시

Orgmode와 연동

elisp
;; 빌드 완료 시 orgmode 태스크 업데이트
(defun my-update-org-task (project-name status)
  "Update org task based on build status."
  (when (string= status "success")
    (org-clock-out)  ; 작업 시간 기록 종료
    (save-excursion
      (org-todo "DONE"))))

(add-hook 'local-cicd-build-complete-hook #'my-update-org-task)

알림 시스템

elisp
;; 빌드 완료 시 알림
(defun my-cicd-notification (project-name status)
  "Send notification for build completion."
  (let ((title (format "Build %s" (upcase status)))
        (body (format "Project: %s" project-name)))
    (cond
     ;; Linux 알림
     ((executable-find "notify-send")
      (start-process "notify" nil "notify-send" title body))
     ;; macOS 알림
     ((executable-find "osascript")
      (start-process "notify" nil "osascript" "-e"
                     (format "display notification \"%s\" with title \"%s\"" 
                             body title)))
     ;; Emacs 내부 알림
     (t (message "%s: %s" title body)))))

(add-hook 'local-cicd-build-complete-hook #'my-cicd-notification)

Denote 연동 (개인지식관리)

elisp
;; 빌드 로그를 Denote 노트로 저장
(defun my-cicd-create-denote-log (project-name status log-content)
  "Create Denote note for build log."
  (when (and (featurep 'denote) (string= status "failed"))
    (let* ((title (format "Build Failed: %s" project-name))
           (keywords '("cicd" "build" "error"))
           (note-content (format "* Build Log for %s

** Status: %s
** Time: %s
** Log:

%s" project-name status (current-time-string) log-content)))
      (denote title keywords)
      (insert note-content)
      (save-buffer))))

확장 기능

1. 배치 작업 (Batch Operations)

elisp
;; 마크된 프로젝트들에 대한 일괄 작업
(defun local-cicd-pull-marked ()
  "Pull all marked projects."
  (interactive)
  (let ((marked (tablist-get-marked-items)))
    (dolist (item marked)
      (let ((project-name (car item)))
        (when-let ((project (cl-find project-name local-cicd-projects
                                    :test #'string=
                                    :key (lambda (p) (cdr (assoc 'name p))))))
          (local-cicd-git-pull project))))))

(defun local-cicd-build-marked ()
  "Build all marked projects."
  (interactive)
  (let ((marked (tablist-get-marked-items)))
    (dolist (item marked)
      (let ((project-name (car item)))
        (when-let ((project (cl-find project-name local-cicd-projects
                                    :test #'string=
                                    :key (lambda (p) (cdr (assoc 'name p))))))
          (local-cicd-build-project project))))))

2. 프로젝트 템플릿

elisp
;; 프로젝트 타입별 템플릿
(defvar local-cicd-project-templates
  '((tauri . "bun run tauri build --no-bundle")
    (rust . "cargo build --release")
    (node . "npm run build")
    (python . "python setup.py build")
    (go . "go build -o bin/app")
    (make . "make")))

(defun local-cicd-add-project-with-template ()
  "Add project using predefined templates."
  (interactive)
  (let* ((project-type (intern (completing-read "Project type: "
                                               (mapcar #'car local-cicd-project-templates))))
         (build-cmd (cdr (assoc project-type local-cicd-project-templates)))
         (name (read-string "Project name: "))
         (path (read-directory-name "Project path: "))
         (git-url (read-string "Git URL (optional): ")))
    (local-cicd-add-project name path build-cmd git-url)))

3. 빌드 파이프라인

elisp
;; 다단계 빌드 파이프라인 정의
(defvar local-cicd-pipelines
  '((claudia . (("Test" . "bun test")
                ("Build" . "bun run tauri build --no-bundle")
                ("Package" . "bun run tauri build --bundles deb")))
    (rust-app . (("Lint" . "cargo clippy")
                 ("Test" . "cargo test")
                 ("Build" . "cargo build --release")))))

(defun local-cicd-run-pipeline (project-name)
  "Run complete pipeline for project."
  (interactive 
   (list (completing-read "Project: "
                          (mapcar (lambda (p) (cdr (assoc 'name p)))
                                  local-cicd-projects))))
  (when-let ((pipeline (cdr (assoc (intern project-name) local-cicd-pipelines))))
    (local-cicd-run-pipeline-steps project-name pipeline)))

(defun local-cicd-run-pipeline-steps (project-name steps)
  "Run pipeline steps sequentially."
  (if (null steps)
      (message "Pipeline completed for %s" project-name)
    (let* ((step (car steps))
           (step-name (car step))
           (step-cmd (cdr step))
           (project (cl-find project-name local-cicd-projects
                            :test #'string=
                            :key (lambda (p) (cdr (assoc 'name p))))))
      (when project
        (message "Running %s for %s..." step-name project-name)
        (local-cicd-run-command project step-cmd
                               (lambda (success)
                                 (if success
                                     (local-cicd-run-pipeline-steps project-name (cdr steps))
                                   (message "Pipeline failed at %s for %s" step-name project-name))))))))

4. 실시간 모니터링

elisp
;; 프로젝트 디렉토리 변경 감지
(defun local-cicd-setup-file-watching ()
  "Setup file watching for project directories."
  (when (featurep 'filenotify)
    (dolist (project local-cicd-projects)
      (let* ((project-path (cdr (assoc 'path project)))
             (project-name (cdr (assoc 'name project))))
        (file-notify-add-watch
         project-path
         '(change)
         (lambda (event)
           (when (string-match "\\.rs$\\|\\.js$\\|\\.ts$" (nth 2 event))
             (local-cicd-update-project-status project-name)
             (local-cicd-refresh-buffer))))))))

;; 자동 빌드 (옵션)
(defcustom local-cicd-auto-build-on-change nil
  "Automatically build when files change."
  :type 'boolean
  :group 'local-cicd)

(defun local-cicd-auto-build-handler (project-name)
  "Handle automatic build trigger."
  (when local-cicd-auto-build-on-change
    (run-with-timer 2 nil  ; 2초 지연 후 빌드
                    (lambda ()
                      (when-let ((project (cl-find project-name local-cicd-projects
                                                  :test #'string=
                                                  :key (lambda (p) (cdr (assoc 'name p))))))
                        (local-cicd-build-project project))))))

i3wm 통합

워크스페이스 관리

elisp
;; i3wm과 연동하여 프로젝트별 워크스페이스 관리
(defun local-cicd-open-in-i3-workspace (project)
  "Open project in dedicated i3 workspace."
  (let* ((project-name (cdr (assoc 'name project)))
         (workspace-name (format "dev:%s" project-name))
         (project-path (cdr (assoc 'path project))))
    
    ;; i3에서 워크스페이스 생성/이동
    (start-process "i3-workspace" nil "i3-msg" 
                   (format "workspace %s" workspace-name))
    
    ;; 터미널에서 프로젝트 디렉토리 열기
    (start-process "terminal" nil "alacritty" 
                   "--working-directory" project-path)
    
    ;; Emacs에서 프로젝트 열기
    (dired project-path)))

;; 키바인딩 추가
(define-key local-cicd-mode-map (kbd "w") #'local-cicd-open-in-i3-workspace)

상태 표시 (i3status 연동)

elisp
;; i3status-rust에 빌드 상태 표시를 위한 JSON 출력
(defun local-cicd-export-status-for-i3 ()
  "Export build status for i3status-rust."
  (let* ((building-count (length (cl-remove-if-not 
                                 (lambda (p) (string= (cdr (assoc 'status p)) "building"))
                                 local-cicd-projects)))
         (failed-count (length (cl-remove-if-not
                               (lambda (p) (string= (cdr (assoc 'status p)) "failed"))
                               local-cicd-projects)))
         (status-text (cond
                       ((> failed-count 0) (format "❌ %d" failed-count))
                       ((> building-count 0) (format "🔨 %d" building-count))
                       (t "✅")))
         (status-data `((text . ,status-text)
                        (tooltip . ,(format "Building: %d, Failed: %d" 
                                           building-count failed-count))
                        (class . ,(cond ((> failed-count 0) "critical")
                                       ((> building-count 0) "warning")
                                       (t "good"))))))
    
    ;; 상태 파일에 쓰기
    (with-temp-file "/tmp/local-cicd-status.json"
      (insert (json-encode status-data)))))

;; 주기적으로 상태 업데이트
(run-with-timer 0 10 #'local-cicd-export-status-for-i3)

Doomemacs 통합

Doom 모듈 설정

elisp
;; ~/.doom.d/config.el

(use-package! local-cicd
  :bind (:leader
         (:prefix ("c" . "cicd")
          :desc "Open CI/CD manager" "c" #'local-cicd
          :desc "Quick build current" "b" #'local-cicd-quick-build-current-project
          :desc "Add project" "a" #'local-cicd-add-project))
  :config
  (setq local-cicd-default-build-command "bun run tauri build --no-bundle")
  (local-cicd-start-auto-refresh))

;; 현재 프로젝트를 빠르게 빌드하는 함수
(defun local-cicd-quick-build-current-project ()
  "Quick build current project if it's in local-cicd."
  (interactive)
  (when-let* ((current-dir (or (projectile-project-root) default-directory))
              (project (cl-find-if (lambda (p)
                                    (string-prefix-p (cdr (assoc 'path p)) current-dir))
                                  local-cicd-projects)))
    (local-cicd-build-project project)))

Evil 키바인딩

elisp
;; Evil 모드 전용 키바인딩
(evil-define-key 'normal local-cicd-mode-map
  "p" #'local-cicd-pull-current
  "b" #'local-cicd-build-current
  "P" #'local-cicd-pull-and-build-current
  "k" #'local-cicd-kill-build
  "gr" #'local-cicd-refresh-buffer
  "a" #'local-cicd-add-project
  "d" #'local-cicd-remove-project
  "o" #'local-cicd-open-project-dir
  "l" #'local-cicd-show-logs
  "w" #'local-cicd-open-in-i3-workspace
  "?" #'local-cicd-menu)

이제 완전한 로컬 CI/CD 관리 시스템이 준비되었습니다! 이 시스템을 통해 Emacs에서 모든 프로젝트를 중앙 집중식으로 관리하고, git pull → build → test 워크플로우를 손쉽게 실행할 수 있습니다.

Content is user-generated and unverified.
    local-cicd.el 설정 및 사용법 | Claude