쉘 스크립트 하나로 GAS 프로젝트, GitHub 저장소, 자동 배포 환경을 한 번에 구축할 수 있다. 이 환경만 갖추면 바이브 코딩의 준비는 끝이다.

나 혼자만의 2가지 선물이 있다. 하나는 템플릿 레포지토리이고, 하나는 쉘 프로그램이다.

템플릿 레포지토리는 앞서 아티클에서 설명했던 rules, workflows 뿐만 아니라 Google Apps Script 기본 설정들이 포함되어 있다.

쉘 프로그램은 수행을 하면 위의 레포지토리를 활용해서 본인의 Github 레포지토리와 구글 드라이브에 Google Apps Script 프로젝트를 자동으로 생성하게 도와주는 프로그램이다. 그리고 배포를 쉽게 하기 위해서 자동으로 Github과 clasp 를 연동시켜주기도 한다.

"명령어 하나로 GAS 프로젝트 + GitHub 저장소 + 자동 배포까지 다 만들어줌"

앞서서 우리는 모든 프로그램 및 환경 설정을 마무리 했다. 이제는 사용하면 된다.

./setup.sh my-awesome-gas

작업 디렉토리에 해당 쉘 프로그램을 위치시킨 후 실행 시키면 다음과 같이 사전 환경을 체크한 후 프로젝트 타입을 사용자에게 입력을 받는다.

프로젝트 선택

프로젝트를 선택하면 다음의 행동이 수행 된다. 위에서 말한 GAS 프로젝트 + GitHub 저장소 + 자동 배포까지 다 만들어주는 것이다.

GitHub 저장소 생성

템플릿에서 새 저장소 복사

자동으로 clone

GAS 프로젝트 생성

clasp create 실행

선택한 타입에 맞는 Code.js 생성

Script ID 추출

GitHub Secrets 자동 등록

SCRIPT_ID

CLASP_TOKEN_BASE64 (인증 토큰)

PROJECT_TYPE

프로젝트 생성 완료

결과는 구글 드라이브, Github 에 가면 확인할 수 있다.

참고로 프로젝트 선택 시 2번 스프레드시트를 선택했기 때문에 스프레드시트가 생긴다.

즉 Container-bounded 프로젝트가 생긴 것이고, 1번 독립형으로 선택을 하면 Apps Script 프로젝트가 드라이브에 생성된다.

구글 드라이브

Github

아래 스크립트에 대한 설명은 Antigravity 에 질문하면 더 쉽게 설명을 해 줄 것이다.

이 환경에서 바이브 코딩을 시작하려면?

이제 진짜 바이브코딩으로 Google Apps Script 프로젝트를 할 수 있는 모든 준비가 되었다.

다음 아티클은 실습 중심으로 프롬프트를 어떻게 작성해야 하는지 등에 대해서 정리할 예정이다. 스프레드시트에서 메일 발송하기가 첫 번째 실습이다.

#!/bin/bash

# ============================================
# GAS Unified Project Setup Script
# ============================================
# 사전 조건:
#   - clasp login 완료
#   - gh auth login 완료
# 사용법: ./setup.sh <project-name>
# ============================================

set -e  # 에러 발생 시 즉시 중단

# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 아이콘 정의
CHECK="✅"
CROSS="❌"
ROCKET="🚀"
GEAR="⚙️"

# 로그 함수
log_info() { echo -e "${BLUE}ℹ️  $1${NC}"; }
log_success() { echo -e "${GREEN}${CHECK} $1${NC}"; }
log_warning() { echo -e "${YELLOW}⚠️  $1${NC}"; }
log_error() { echo -e "${RED}${CROSS} $1${NC}"; }
log_step() { echo -e "\n${GEAR} ${YELLOW}$1${NC}"; }

# 헤더 출력
print_header() {
    echo -e "${BLUE}"
    echo "╔════════════════════════════════════════════╗"
    echo "║     ${ROCKET} GAS Unified Project Setup ${ROCKET}       ║"
    echo "╚════════════════════════════════════════════╝"
    echo -e "${NC}"
}

# 사용법 출력
print_usage() {
    echo "사용법: $0 <project-name>"
    echo ""
    echo "예시:"
    echo "  $0 my-gas-project"
    echo ""
    echo "사전 조건:"
    echo "  - clasp login 완료 (clasp login)"
    echo "  - gh auth login 완료 (gh auth login)"
    exit 1
}

# 사전 조건 확인
check_prerequisites() {
    log_step "사전 조건 확인 중..."

    local missing_deps=0

    # gh CLI 확인
    if ! command -v gh &> /dev/null; then
        log_error "gh CLI가 없습니다."
        missing_deps=1
    else
        # gh 인증 확인
        if ! gh auth status &> /dev/null; then
            log_error "gh 로그인이 필요합니다. (gh auth login)"
            exit 1
        fi
    fi

    # clasp 확인
    if ! command -v clasp &> /dev/null; then
        log_error "clasp이 없습니다."
        missing_deps=1
    else
        # clasp 인증 확인
        if [ ! -f ~/.clasprc.json ]; then
            log_error "clasp 로그인이 필요합니다. (clasp login)"
            exit 1
        fi
    fi

    # jq 확인
    if ! command -v jq &> /dev/null; then
        log_error "jq가 없습니다."
        missing_deps=1
    fi

    if [ $missing_deps -eq 1 ]; then
        echo ""
        log_warning "필수 도구가 설치되어 있지 않습니다."
        echo "다음 명령어를 실행하여 자동으로 설치하세요:"
        echo -e "${YELLOW}  ./install-deps.sh${NC}"
        exit 1
    fi

    log_success "모든 사전 조건이 충족되었습니다."
}

# 프로젝트 타입 선택
select_project_type() {
    echo ""
    echo -e "${BLUE}📦 프로젝트 타입 선택${NC}"
    echo "────────────────────────────────────"
    echo "  1) Standalone   (독립형 - 웹앱, API, 라이브러리)"
    echo "  2) Google Sheets (스프레드시트)"
    echo "  3) Google Docs   (문서)"
    echo "  4) Google Forms  (폼)"
    echo "  5) Google Slides (프레젠테이션)"
    echo ""

    while true; do
        read -p "선택 (1-5): " choice
        case $choice in
            1) PROJECT_TYPE="standalone"; break;;
            2) PROJECT_TYPE="sheets"; break;;
            3) PROJECT_TYPE="docs"; break;;
            4) PROJECT_TYPE="forms"; break;;
            5) PROJECT_TYPE="slides"; break;;
            *) log_error "1-5 사이의 숫자를 입력하세요.";;
        esac
    done

    log_success "선택된 타입: ${PROJECT_TYPE}"
}

# 타입별 Code.js 생성
create_code_template() {
    local target_file="$1"

    if [ "${PROJECT_TYPE}" == "standalone" ]; then
        # Standalone 템플릿
        cat > "${target_file}" << 'EOF'
/**
 * @fileoverview Google Apps Script 메인 엔트리 포인트
 * @description 이 템플릿은 clasp + GitHub Actions 자동 배포를 지원합니다.
 */

/**
 * 스크립트 실행 시 호출되는 메인 함수
 * GAS 에디터에서 직접 실행하거나 트리거로 호출할 수 있습니다.
 */
function main() {
  Logger.log('🚀 GAS Template - Hello World!');
  Logger.log('현재 시간: ' + new Date().toLocaleString('ko-KR'));
  Logger.log('배포가 성공적으로 완료되었습니다.');
}

/**
 * 웹 앱으로 배포 시 GET 요청 핸들러
 * @param {Object} e - 이벤트 객체
 * @returns {TextOutput} JSON 응답
 */
function doGet(e) {
  const response = {
    status: 'success',
    message: 'GAS Template is running!',
    timestamp: new Date().toISOString()
  };

  return ContentService
    .createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * 웹 앱으로 배포 시 POST 요청 핸들러
 * @param {Object} e - 이벤트 객체
 * @returns {TextOutput} JSON 응답
 */
function doPost(e) {
  const response = {
    status: 'success',
    message: 'POST request received',
    timestamp: new Date().toISOString()
  };

  return ContentService
    .createTextOutput(JSON.stringify(response))
    .setMimeType(ContentService.MimeType.JSON);
}
EOF
    else
        # Bounded 템플릿 (Sheets/Docs/Forms/Slides)
        local ui_app=""
        case ${PROJECT_TYPE} in
            sheets) ui_app="SpreadsheetApp";;
            docs) ui_app="DocumentApp";;
            forms) ui_app="FormApp";;
            slides) ui_app="SlidesApp";;
        esac

        cat > "${target_file}" << EOF
/**
 * @fileoverview Google Apps Script Bounded Project 메인 엔트리 포인트
 * @description Container-Bound 프로젝트용 템플릿 (${PROJECT_TYPE})
 */

/**
 * 컨테이너 문서가 열릴 때 자동 실행되는 함수
 * 커스텀 메뉴를 추가합니다.
 */
function onOpen() {
  const ui = ${ui_app}.getUi();

  ui.createMenu('📋 내 메뉴')
    .addItem('기능 실행', 'myFunction')
    .addSeparator()
    .addIte

불러오는 중...