Docker.el의 tabulated-list-mode 구조를 참고하여 만든 Emacs에서 로컬 프로젝트의 CI/CD를 관리하는 패키지입니다.
;; ~/.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))프로젝트 정보는 ~/.config/local-cicd/projects.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"
}
]M-x local-cicd 또는 C-c c| 키 | 명령어 | 설명 |
|---|---|---|
? | local-cicd-menu | 메뉴 표시 (transient) |
p | local-cicd-pull-current | 현재 프로젝트 git pull |
b | local-cicd-build-current | 현재 프로젝트 빌드 |
P | local-cicd-pull-and-build-current | Pull 후 빌드 |
k | local-cicd-kill-build | 실행 중인 빌드 중단 |
a | local-cicd-add-project | 새 프로젝트 추가 |
d | local-cicd-remove-project | 프로젝트 제거 |
o | local-cicd-open-project-dir | 프로젝트 디렉토리 열기 |
g | local-cicd-refresh-buffer | 새로고침 |
l | local-cicd-show-logs | 빌드 로그 보기 |
RET | local-cicd-open-project-dir | 프로젝트 디렉토리 열기 |
m: 항목 마크u: 마크 해제t: 마크 토글U: 모든 마크 해제s: 정렬</>: 컬럼 너비 조정;; 대화형으로 추가
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");; 1. local-cicd 실행
M-x local-cicd
;; 2. Claudia 프로젝트에서 Pull & Build
;; (Claudia 행에서) P
;; 3. 빌드 로그 확인
;; l
;; 4. 빌드 완료 후 바이너리 실행
;; o (dired에서 target/release/claudia 실행);; 여러 프로젝트를 마크한 후 일괄 처리 (향후 구현 예정)
;; m m m (여러 프로젝트 마크)
;; P (마크된 모든 프로젝트 pull & build)(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.")))));; 프로젝트별 빌드 명령 설정
(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 태스크 업데이트
(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);; 빌드 완료 시 알림
(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 노트로 저장
(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))));; 마크된 프로젝트들에 대한 일괄 작업
(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))))));; 프로젝트 타입별 템플릿
(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)));; 다단계 빌드 파이프라인 정의
(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))))))));; 프로젝트 디렉토리 변경 감지
(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과 연동하여 프로젝트별 워크스페이스 관리
(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-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);; ~/.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 모드 전용 키바인딩
(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 워크플로우를 손쉽게 실행할 수 있습니다.