minimimi

[Shell Script] 01. boilerplate 폴더/파일 생성하기 with. ChatGPT 본문

프로그래밍 공부/Unix

[Shell Script] 01. boilerplate 폴더/파일 생성하기 with. ChatGPT

99mini 2024. 3. 24. 14:00
반응형

Intro

react를 이용한 atomic design 시스템 개발을 하던 중 atom을 만들 때 마다 동일한 패턴의 파일을 생성하고, atom/index.ts에 export { default as Button } from "./Button";과 같이 export 구문을 수동으로 추가해줘야 했습니다.

보일러 플레이트 폴더 구조와 파일을 스크립트를 이용하여 생성할 수 있을 것으로 기대하여 스크립트를 개발해보기로 하였습니다.

하지만 스크립트를 작성해본 적이 없었다. chatGPT와 함께

스크립트를 작성해본 적이 없기 때문에 GPT의 도움으로 작성을 시작하였습니다.

chatgpt와 나눈 대화

스크립트 문법에 대해서 잘 알지 못하였지만 gpt가 작성해준 코드를 보며 이해해보며 작성할 수 있었습니다.

모든 스크립트의 파일 명 앞에 _ prefix를 이용하여 npm에 올릴 때 포함되지 않도록 하였습니다.

_constants.sh

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

터미널의 색상을 바꾸기 위한 색깔 상수를 저장합니다.

_generate_boilerplate.sh

# Check if correct number of arguments provided
if [ "$#" -ne 2 ]; then
	echo "Usage: $0 <directory> <filename>"
	exit 1
fi

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/_constants.sh"

directory=$1
filename=$2

# Create directory if it doesn't exist
if [ ! -d "src/$directory/$filename" ]; then
	mkdir -p "src/$directory/$filename"
	echo -e "${GREEN}+ src/$directory/$filename${NC}"
else
	echo -e "src/$directory/$filename"
fi

# Call touch_file.sh script to create the file

scripts/_touch_file.sh "$directory" "$filename" "tsx"
scripts/_touch_file.sh "$directory" "$filename" "scss"
scripts/_touch_file.sh "$directory" "$filename" "stories.tsx"
scripts/_touch_file.sh "$directory" "$filename" "ts"
scripts/_update_index.sh "$directory" "$filename"

Line by Line

# Check if correct number of arguments provided
if [ "$#" -ne 2 ]; then
	echo "Usage: $0 <directory> <filename>"
	exit 1
fi

argument에 <directory> <filename> 가 필수이기에 없다면 종료해줍니다.

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$SCRIPT_DIR/_constants.sh"

상수 파일을 import하기 위해 source 키워드를 이용하여 파일에 접근 가능하게 합니다.

# Create directory if it doesn't exist
if [ ! -d "src/$directory/$filename" ]; then
	mkdir -p "src/$directory/$filename"
	echo -e "${GREEN}+ src/$directory/$filename${NC}"
else
	echo -e "src/$directory/$filename"
fi

src/$directory/$filename 디렉토리가 존재하지 않는다면 디렉토리를 생성합니다.

# Call touch_file.sh script to create the file

scripts/_touch_file.sh "$directory" "$filename" "tsx"
scripts/_touch_file.sh "$directory" "$filename" "scss"
scripts/_touch_file.sh "$directory" "$filename" "stories.tsx"
scripts/_touch_file.sh "$directory" "$filename" "ts"
scripts/_update_index.sh "$directory" "$filename"

_touch_file.sh와 _update_index.sh를 호출하여 보일러 플레이트 파일을 생성하고, index.ts에 export문을 업데이트해줍니다.

_touch_file.sh

# Check if correct number of arguments provided
if [ "$#" -ne 3 ]; then
  echo "Usage: $0 <directory> <filename> <extension>"
  exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/_constants.sh"

directory="$1"
filename="$2"
extension="$3"

# Create index.ts file
if [ "$extension" = "ts" ]; then

  index_file_path="src/$directory/$filename/index.ts"
  # Check if file already exists
  if [ -e "$index_file_path" ]; then
    echo -e "[$0]\\tSkip: File $index_file_path already exists in src/$directory/$filename."
    exit 1
  fi

  cat <<EOF >"$index_file_path"
export { default } from "./${filename}";
EOF
  echo -e "${GREEN}  + index.ts${NC}"
  exit 0
fi

# Set file path
file_path="src/$directory/$filename/$filename.$extension"

# Check if file already exists
if [ -e "$file_path" ]; then
  echo -e "[$0]\\tSkip: File $filename.$extension already exists in src/$directory/$filename."
  exit 1
fi

# Create the file with proper indentation
if [ "$extension" = "scss" ]; then
  cat <<EOF >"$file_path"
.Mini-${filename} {

}
EOF

# Create tsx file
elif [ "$extension" = "tsx" ]; then

  cat <<EOF >"$file_path"
import React from 'react';
import classNames from "classnames";
import './$filename.scss'

export type ${filename}Props = React.DetailedHTMLProps<
  React.HTMLAttributes<HTMLDivElement>,
  HTMLDivElement
> & {};

const $filename = ({...props}: ${filename}Props) => {
  return (
    <div {...props} className={classNames("Mini-${filename}", props.className)}>
      {props.children ?? "$filename"}
    </div>
  );
}

export default $filename;
EOF

# Create stories.tsx file
elif [ "$extension" = "stories.tsx" ]; then
  cat <<EOF >"$file_path"

import React from "react";

import $filename, { ${filename}Props } from "./$filename";
import styles from "./$filename.scss";
import classNames from "classnames";

export default {
  component: $filename,
  title: "$filename",
};

const cx = classNames.bind(styles);

const Template = (args: ${filename}Props) => <$filename {...args} />;

export const Default = Template.bind({});
Default.args = {
  $filename: {
    title: "Default $filename",
    state: "$filename",
  },
};
EOF
fi

echo -e "${GREEN}  + $filename.$extension${NC}"
exit 0

Line by Line

# Create index.ts file
if [ "$extension" = "ts" ]; then

  index_file_path="src/$directory/$filename/index.ts"
  # Check if file already exists
  if [ -e "$index_file_path" ]; then
    echo -e "[$0]\\tSkip: File $index_file_path already exists in src/$directory/$filename."
    exit 1
  fi

  cat <<EOF >"$index_file_path"
export { default } from "./${filename}";
EOF
  echo -e "${GREEN}  + index.ts${NC}"
  exit 0
fi

확장자가 ts일 경우 index_file_path파일을 생성하고, export { default } from "./${filename}";를 컨텐츠를 채워줍니다.

그 외 확장자가 tsx, scss, stories.tsx 인 경우 각각에 맞는 파일을 생성하고 컨텐츠를 채워줍니다. 이 확장자의 경우는 $filename.$extenstion 으로 파일 명이 정해지기에 if…elif…fi 구문으로 묶어주었습니다.

# Set file path
file_path="src/$directory/$filename/$filename.$extension"

# Check if file already exists
if [ -e "$file_path" ]; then
  echo -e "[$0]\\tSkip: File $filename.$extension already exists in src/$directory/$filename."
  exit 1
fi

# Create the file with proper indentation
if [ "$extension" = "scss" ]; then
  cat <<EOF >"$file_path"
.Mini-${filename} {

}
EOF

# Create tsx file
elif [ "$extension" = "tsx" ]; then
# ... 이하 생략
fi

echo -e "${GREEN}  + $filename.$extension${NC}"
exit 0

_update_index.sh

# Check if correct number of arguments provided
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <directory> <component_name>"
    exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/_constants.sh"

directory="$1"
component_name="$2"
index_file="src/$directory/index.ts"

# Check if index.ts file exists
if [ -f "$index_file" ]; then
    # Check if the export line already exists
    if grep -q "export { default as $component_name } from \\"./$component_name\\";" "$index_file"; then
        echo -e "[$0]\\tSkip: Export line already exists in $index_file"
        exit 0
    fi

    # Append export line to the end of the file
    echo "export { default as $component_name } from \\"./$component_name\\";" >>"$index_file"
    echo -e "src/$directory"
    echo -e "${YELLOW}  + $index_file${NC}"
else
    echo -e "[$0]\\tError: $index_file does not exist."
    exit 1
fi

Line by Line

# Check if the export line already exists
if grep -q "export { default as $component_name } from \\"./$component_name\\";" "$index_file"; then
    echo -e "[$0]\\tSkip: Export line already exists in $index_file"
    exit 0
fi

index.ts에 export 구문이 있는 지 확인합니다. 프로젝트에서 .eslintrc.js를 사용하고 있기에 포멧을 맞출 수 있고 export { default as $component_name } from \\"./$component_name\\"; 라인만 확인하면 됩니다.

해당 export 구문이 없다면 추가해줍니다.

실행 결과

실행결과

atom 디렉토리에 Textarea 를 추가하는 스크립트를 실행합니다.

Textarea 보일러 플레이트를 생성하고 atom/index.ts 를 업데이트하였습니다.

개선 사항

개선사항

CheckBox 폴더에 CheckBox.stories.tsx 폴더만 존재하지 않아 스크립트를 실행하니 터미널 로그가 이쁘게 나오지 않네요. echo 구문을 수정하여 생성 및 업데이트를 먼저 echo하고 그 외 스킵한 구문은 가장 아래로 내리면 더 보기 좋은 echo문을 만들 수 있을 것 같습니다.

다음 포스트

[1] Shell Script - boilerplate 폴더/파일 생성하기 with. ChatGPT

반응형