Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Check

# PR마다 배포와 같은 빌드를 돌려 합치기 전에 오류를 잡는다. 배포는 하지 않는다.
# 빌드에는 타입 검사(astro check), 글 검사(src/utils/checkPosts.ts),
# 작성자 조회(src/utils/postAuthors.ts)가 들어 있다.
on:
pull_request:
branches: [main]

permissions:
contents: read

# 같은 PR에 새로 push하면 앞선 검사는 취소한다.
concurrency:
group: check-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
# 글의 작성자를 찾을 때 커밋이 들어온 PR을 읽는다(src/utils/postAuthors.ts)
pull-requests: read
steps:
- uses: actions/checkout@v7
# deploy.yml과 같은 액션으로 빌드해서 여기서 통과하면 배포 빌드도 통과하게 한다.
# 이 액션은 결과물을 Pages용 아티팩트로 올리기까지 하지만, 배포 잡이 없어 쓰이지 않는다.
- uses: withastro/action@v6
with:
package-manager: pnpm@latest
# 소유확인 토큰과 분석 ID(vars.*)는 넘기지 않는다. 비어 있으면 태그만 빠지고 빌드는 같다.
env:
GITHUB_TOKEN: ${{ github.token }}
53 changes: 45 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ velog `@nomadamas`와 dev.to는 재게시 채널이다. 정본이 아니다.
## 발행

```bash
# 글 하나 추가하고 push하면 끝이다. 별도 업로드 단계가 없다.
git add src/content/posts/<슬러그>.md src/assets/images/
# 글을 브랜치에 올리고 PR을 연다. PR마다 배포와 같은 빌드가 돌고(check.yml), 합치면 배포된다(deploy.yml).
git switch -c post/<슬러그>
git add src/content/posts/<슬러그>.md src/assets/images/<슬러그>/
git commit -m "post: <제목>"
git push
git push -u origin post/<슬러그>
gh pr create
```

합치기 전에 PR의 검사(Check)가 통과했는지 본다. 빌드는 프런트매터 형식 오류, 없는 이미지 경로,
아래 "빌드가 막는 것", 작성자 조회 실패에서 멈춘다. 합친 뒤 배포 빌드가 멈추면 사이트는 이전 배포에
머물고 새 글만 올라가지 않는다.

frontmatter는 AstroPaper 스키마를 따른다. `pubDate`가 아니라 `pubDatetime`이다.

```yaml
Expand All @@ -37,6 +43,14 @@ draft: false # true면 빌드에서 빠진다

`modDatetime`을 발행 뒤 다른 날짜로 적으면 날짜 옆에 수정일이 함께 나온다. 같은 날 고친 것은 표시하지 않는다.

`pubDatetime`은 발행하는 시각을 시간대(`+09:00`)까지 적고, 합치기 직전에 합치는 시각으로 고친다.

- **시간대를 빼면 UTC로 읽힌다.** 한국 시각으로 9시간 늦게 발행된다
- **예약 발행은 없다.** 빌드 시각보다 15분 넘게 미래인 글은 빌드에서 빠지는데(`src/utils/postFilter.ts`),
정해진 시각에 다시 빌드하는 장치가 없어서 그 시각이 지나도 다음 배포 전까지 보이지 않는다

둘 다 빌드가 막는다(아래 "빌드가 막는 것").

### 본문 관례

글마다 켤 필요 없이 붙는 것은 하나다. 절(h2)과 소절(h3)을 합쳐 3개 이상이면 마우스가 있는 1024px 이상
Expand All @@ -47,16 +61,31 @@ draft: false # true면 빌드에서 빠진다
- **목차 상자**: 요약 뒤, 첫 절 앞에 `## 목차` 한 줄을 두고 바로 다음에 `##` 절을 쓴다. remark-toc가 그 뒤의 절과 소절로
목록을 채우고 `src/utils/remarkTocBox.ts`가 늘 펼친 상자로 감싼다. 떠 있는 목차가 보이는 화면에서는 상자가 숨는다
- `## 목차`부터 다음 `##` 제목 전까지는 전부 목록으로 바뀌며 지워진다. 그 사이에 쓴 문장은 물론
`###` 소절과 그 본문도 빌드 경고 없이 사라진다
`###` 소절과 그 본문도 사라지므로 빌드가 막는다
- `## 목차`보다 앞에 있는 제목은 상자에 들어가지 않는다. 떠 있는 목차에는 들어간다
- 테마 기본의 접는 목차(remark-collapse)는 뺐다. `## Table of contents`, `## Contents`도 같은 상자가 된다
- **접는 내용**(자주 묻는 질문, 개발자용 예시): rehype-callouts 문법으로 `> [!faq]- 질문`처럼 종류 뒤에 `-`를 붙인다.
`+`를 붙이면 펼친 채로 시작한다. 접는 방식은 이것 하나로 통일한다. raw `<details>`를 쓰면 테마
`typography.css`의 `details:not(.callout)` 규칙이 안의 문단을 숨긴다(remark-collapse 목차용으로 남은 업스트림 규칙이다)
`typography.css`의 `details:not(.callout)` 규칙이 안의 문단을 숨긴다(remark-collapse 목차용으로 남은 업스트림 규칙이다).
그래서 빌드가 막는다
- **강조 상자**: `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`
- **줄바꿈되는 코드 블록**: 독자가 복사해 쓰는 긴 문장은 ```` ```text wrap ````처럼 코드 블록 메타에 `wrap`을 적는다.
나머지 코드 블록은 가로 스크롤이다. 줄바꿈이 뜻을 바꿀 수 있어서다

### 빌드가 막는 것

아래는 빌드가 통과하는데 발행된 글이 조용히 깨지는 경우다. `src/utils/checkPosts.ts`가 빌드 시작 전에
초안을 뺀 모든 글을 보고, 하나라도 걸리면 파일과 줄을 알려 주며 빌드를 멈춘다.
로컬 `pnpm build`, PR 검사, 배포 빌드가 모두 거친다.

- `## 목차` 뒤에 지워질 내용(목차 판정은 remark-toc와 같은 `src/utils/toc.ts` 규칙을 쓴다)
- raw `<details>`
- `/`로 시작하는 이미지 주소(`public/` 이미지). 이유는 "이미지" 절
- GIF 참조와 `src/assets/`, `public/` 아래의 GIF 파일
- 시간대가 없는 `pubDatetime`, `modDatetime`과 빌드 시각보다 미래인 `pubDatetime`

코드 블록과 인라인 코드 안은 예시로 보고 검사하지 않는다.

## 웹에서 글쓰기

`app.pagescms.org`에 GitHub으로 로그인하면 브라우저에서 글을 쓰고 이미지를 드래그해 올릴 수 있다.
Expand All @@ -74,6 +103,9 @@ draft: false # true면 빌드에서 빠진다
에이전트가 push하는 경로와 충돌하지 않는다. 둘 다 같은 저장소 파일을 고칠 뿐이다.
다만 같은 글을 동시에 건드리지 않는다.

웹 편집기는 PR 없이 `main`에 바로 커밋하므로 PR 검사를 거치지 않는다. "빌드가 막는 것"에 걸리면
배포 빌드가 멈추고 사이트는 이전 배포에 머문다. 저장한 뒤 Actions 탭에서 배포가 성공했는지 본다.

## 작성자 표시

글 제목 아래 "작성"은 그 글 파일을 처음 추가한 커밋, "최근 수정"은 가장 최근 커밋을 한 GitHub 계정이다.
Expand All @@ -89,16 +121,21 @@ draft: false # true면 빌드에서 빠진다
- **대신 올리는 글은 커밋의 author를 실제로 쓴 사람으로 적는다.** git은 쓴 사람(author)과 올린 사람(committer)을
따로 기록한다. `git commit --author="이름 <이메일>"`로 올리면 그 사람이 작성자로 고정된다. 이메일은 그 사람의
GitHub 계정에 등록된 주소여야 계정이 잡힌다. 확실한 것은 `<id>+<아이디>@users.noreply.github.com`이고,
id는 `gh api users/<아이디> --jq .id`로 본다
id는 `gh api users/<아이디> --jq .id`로 본다.
자동 발행(Actions, 클라우드 세션)도 같다. 봇 계정이 커밋하고 PR까지 열면 작성자가 봇으로 나오므로
커밋 author를 사람으로 적고, 첫 글에서 작성자 줄을 확인한다
- **"최근 수정"은 그 파일을 건드린 마지막 커밋이다.** 여러 글을 한꺼번에 고치는 커밋(경로 일괄 변경,
서식 정리)은 그 글들의 "최근 수정"을 모두 그 커밋을 한 사람으로 바꾼다. 글 내용과 무관한 일괄 수정은 꼭 필요할 때만 한다
- **글쓴이 표기는 전부 이 작성자 하나에서 나온다.** 구조화 데이터의 author, `<meta name="author">`,
자동 생성 OG 이미지의 "by" 줄이다. 봇이 처음 올린 글과 GitHub 기록을 못 받은 로컬 빌드만
프런트매터 `author`(기본값 조직)로 돌아간다
- **커밋 이메일이 GitHub 계정에 없으면 PR을 연 계정으로 대신한다.** GitHub은 계정에 등록된 이메일로만
커밋을 계정에 잇는다. 로컬 git 이메일이 계정에 없으면 커밋의 author가 비어 오므로 그 커밋이 들어온 PR을 본다.
PR 없이 `main`에 바로 올린 커밋이면 커밋에 적힌 이름만 링크 없이 나온다.
봇(Pages CMS, Actions) 커밋도 PR을 먼저 보고, PR을 연 쪽도 봇이면 봇 계정이 나온다
- **배포 빌드는 GitHub 요청이 실패하면 멈춘다.** `deploy.yml`이 빌드에 `GITHUB_TOKEN`을 넘기고
빌드 잡에 `pull-requests: read`를 준다. 로컬 빌드는 실패해도 이 줄만 빼고 통과한다.
- **배포 빌드는 GitHub 요청이 실패하면 멈춘다.** `deploy.yml`과 `check.yml`이 빌드에 `GITHUB_TOKEN`을 넘기고
빌드 잡에 `pull-requests: read`를 준다. 일시 오류(5xx, 10초 시간 초과, 1분 이하의 `retry-after`)는
2초, 5초 뒤에 두 번까지 다시 요청하고, 그래도 실패하면 멈춘다. 로컬 빌드는 다시 요청하지 않고 이 줄만 빼고 통과한다.
토큰 없는 요청은 시간당 60회라 글이 많으면 로컬에서 줄이 빠진다. 제대로 보려면 `GITHUB_TOKEN=$(gh auth token) pnpm build`
- **GitHub에 올라간 기록만 반영된다.** push하지 않은 글과 커밋은 빠진다. 로컬 빌드는 기본 브랜치의 기록을 쓴다
- **Pages CMS는 로그인한 사람 명의로 커밋한다.** `.pages.yml`의 `settings.commit.identity: user`.
Expand Down
7 changes: 7 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { transformerFileName } from "./src/utils/transformers/fileName";
import { transformerWrap } from "./src/utils/transformers/wrap";
import { postLastmod } from "./src/utils/postLastmod";
import checkPosts from "./src/utils/checkPosts";
import config from "./astro-paper.config";

const lastmod = postLastmod(config.site.url);
Expand Down Expand Up @@ -47,6 +48,12 @@ export default defineConfig({
return date ? { ...item, lastmod: date } : item;
},
}),
// 빌드는 통과하는데 발행된 글이 조용히 깨지는 경우(목차에 지워지는 문장, 미래 발행 시각 등)를
// 빌드 시작 전에 잡아 멈춘다. 규칙은 src/utils/checkPosts.ts
checkPosts({
// src/config.ts와 같은 기본값
scheduledPostMargin: config.posts?.scheduledPostMargin ?? 15 * 60 * 1000,
}),
],
i18n: {
locales: ["ko"],
Expand Down
Loading
Loading