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
13 changes: 12 additions & 1 deletion .github/workflows/sync-to-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
repository_dispatch:
types: [bruno_updated]

env:
BRUNO_REPO_URL: ${{ vars.BRUNO_REPO_URL || format('https://github.com/{0}/api-docs.git', github.repository_owner) }}

jobs:
sync:
runs-on: ubuntu-latest
Expand All @@ -22,8 +25,16 @@ jobs:
run: |
rm -rf /tmp/bruno
BRANCH="${{ github.event.client_payload.branch || 'main' }}"
REPO_PATH="${BRUNO_REPO_URL#https://github.com/}"

if [ "$REPO_PATH" = "$BRUNO_REPO_URL" ]; then
echo "BRUNO_REPO_URL must start with https://github.com/: $BRUNO_REPO_URL"
exit 1
fi

echo "Cloning Bruno repo from branch: $BRANCH"
git clone -b "$BRANCH" https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository_owner }}/api-docs.git /tmp/bruno
echo "Bruno source repo: $BRUNO_REPO_URL"
git clone -b "$BRANCH" "https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${REPO_PATH}" /tmp/bruno

- name: Clone bruno-api-typescript
run: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SubmitLinkTab from "@/components/score/SubmitLinkTab";
import SubmitResult, { type InfoRowProps } from "@/components/score/SubmitResult";
import CloudSpinnerPage from "@/components/ui/CloudSpinnerPage";
import { toast } from "@/lib/zustand/useToastStore";
import { LanguageTestEnum } from "@/types/score";
import { LanguageTestEnum, languageTestScoreInfo } from "@/types/score";
import { type LanguageTestFormData, languageTestSchema } from "./_lib/schema";

const LanguageTestSubmitForm = () => {
Expand Down Expand Up @@ -61,11 +61,13 @@ const LanguageTestSubmitForm = () => {
};

if (showResult && submittedData) {
const submittedTestInfo = languageTestScoreInfo[submittedData.testType];

const infoRows: InfoRowProps[] = [
{
label: "공인어학",
status: "TOEIC",
details: `${submittedData.score}/500`,
status: submittedTestInfo.label,
details: `${submittedData.score}/${submittedTestInfo.max}`,
},
{
label: "어학증명서",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { type MutableRefObject, type RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { type MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";

// 드래그 핸들에서 제외해야 하는 인터랙티브 엘리먼트 판별
const isInteractiveElement = (el: EventTarget | null): boolean => {
return el instanceof HTMLElement && ["INPUT", "TEXTAREA", "SELECT", "BUTTON", "A", "LABEL"].includes(el.tagName);
};

interface UseHandleModalReturn {
elementRef: RefObject<HTMLDivElement | null>;
isVisible: boolean;
translateY: number;
isDraggingRef: MutableRefObject<boolean>;
Expand All @@ -23,7 +22,6 @@ const useHandleModal = (onClose: () => void, snap: number[] = [0]): UseHandleMod
const startYRef = useRef<number>(0); // 시작 Y좌표
const currentYRef = useRef<number>(0); // 현재 Y좌표
const isDraggingRef = useRef<boolean>(false); // 드래그 상태
const elementRef = useRef<HTMLDivElement>(null);

const snapPoints = useMemo((): number[] => {
if (typeof window === "undefined") return [0]; // SSR 대응
Expand Down Expand Up @@ -115,7 +113,6 @@ const useHandleModal = (onClose: () => void, snap: number[] = [0]): UseHandleMod
}, [isVisible, translateY, snapPoints, handleClose]);

return {
elementRef,
isVisible,
translateY,
isDraggingRef,
Expand Down
13 changes: 2 additions & 11 deletions apps/web/src/components/ui/BottomSheet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,8 @@ interface BottomSheetProps {
const DEFAULT_SNAP = [0];

const BottomSheet = ({ isOpen, onClose, children, titleChild, snap = DEFAULT_SNAP }: BottomSheetProps) => {
const {
elementRef,
isVisible,
translateY,
isDraggingRef,
handleClose,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
} = useHandleModal(onClose, snap);
const { isVisible, translateY, isDraggingRef, handleClose, handleTouchStart, handleTouchMove, handleTouchEnd } =
useHandleModal(onClose, snap);

if (!isOpen) return null;

Expand All @@ -45,7 +37,6 @@ const BottomSheet = ({ isOpen, onClose, children, titleChild, snap = DEFAULT_SNA

{/* 바텀 시트 */}
<div
ref={elementRef}
className={clsx(
"fixed bottom-0 left-0 z-50 flex h-[90vh] max-h-screen w-full flex-col rounded-t-2xl bg-white shadow-2xl",
!isDraggingRef.current && "transition-transform duration-300 ease-out",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/tsconfig.ci.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "../../node_modules/@types"],
"typeRoots": ["./node_modules/@types"],
"types": ["react", "react-dom"],
"skipLibCheck": true
}
Expand Down
25 changes: 19 additions & 6 deletions docs/bruno-typescript-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
## 2) 주요 명령어

```bash
# 스키마 동기화 (필요할 때 수동 실행)
# 스키마 동기화 (기본: 원격 api-docs 기준)
pnpm run sync:bruno

# 항상 원격 명세 저장소에서 동기화
BRUNO_REPO_URL=https://github.com/<org>/<repo>.git pnpm --filter @solid-connect/api-schema run sync:bruno:remote
# 원격 강제 모드 (동일 동작을 명시적으로 실행)
pnpm --filter @solid-connect/api-schema run sync:bruno:remote

# 로컬 명세 폴더 강제 모드
BRUNO_SOURCE_MODE=local pnpm --filter @solid-connect/api-schema run sync:bruno

# 전체 빌드 시에도 자동으로 실행되도록 turbo에 연동
pnpm run build
Expand All @@ -24,19 +27,29 @@ pnpm run build

1. Bruno 정의 위치(우선순위)
- `BRUNO_COLLECTION_DIR` 환경 변수 지정 경로
- 로컬 기본 경로: `api-docs/Solid Connection/**/*.bru`
- 로컬 경로가 없을 때 원격 저장소(`BRUNO_REPO_URL`)를 clone 후 `BRUNO_COLLECTION_PATH` 하위 경로
- 원격 저장소(`BRUNO_REPO_URL`) clone 후 `BRUNO_COLLECTION_PATH` 하위 경로 (기본)
- `BRUNO_SOURCE_MODE=auto`일 때만 로컬 기본 경로(`api-docs/Solid Connection/**/*.bru`) fallback
2. 생성 명령: `bruno-api generate-hooks`
3. 생성 결과: `packages/api-schema/src/apis/**`

### 원격 동기화용 환경 변수

- `BRUNO_SOURCE_MODE`: `auto` | `local` | `remote` (기본값: `auto`)
- `BRUNO_SOURCE_MODE`: `auto` | `local` | `remote` (기본값: `remote`)
- `BRUNO_COLLECTION_DIR`: 명세 폴더 절대/상대 경로
- `BRUNO_REPO_URL`: 원격 Bruno 저장소 URL (`remote` 모드에서 필수)
- `BRUNO_REPO_REF`: 원격 브랜치/태그 (기본값: `main`)
- `BRUNO_COLLECTION_PATH`: 저장소 내부 컬렉션 경로 (기본값: `Solid Connection`)

### 권장 환경 변수 파일

`packages/api-schema/.env`

```env
BRUNO_REPO_URL=https://github.com/solid-connection/api-docs.git
BRUNO_REPO_REF=main
BRUNO_COLLECTION_PATH="Solid Connection"
```

## 4) 파일/폴더 네이밍 규칙

`bruno-api-typescript`는 폴더/파일명 규칙에 따라 API 이름을 생성합니다.
Expand Down
9 changes: 5 additions & 4 deletions docs/skills/univ-extends-bruno-sync-skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@

1. 기본 실행
- `pnpm run sync:bruno`
2. 원격 명세 강제 동기화
- `BRUNO_REPO_URL=<repo-url> pnpm --filter @solid-connect/api-schema run sync:bruno:remote`
2. 원격 명세 강제 동기화 (기본과 동일 동작 명시)
- `pnpm --filter @solid-connect/api-schema run sync:bruno:remote`
3. 모드 제어
- `BRUNO_SOURCE_MODE=local|remote|auto`
- `BRUNO_SOURCE_MODE=local|remote|auto` (기본: `remote`)

## 환경 변수 규칙

- `BRUNO_COLLECTION_DIR`: 로컬 명세 폴더를 직접 지정
- `BRUNO_REPO_URL`: 원격 Bruno 저장소 URL
- `BRUNO_REPO_REF`: 원격 브랜치/태그 (기본 `main`)
- `BRUNO_COLLECTION_PATH`: 저장소 내부 명세 폴더 (기본 `Solid Connection`)
- 권장 파일: `packages/api-schema/.env`

## 검증 체크리스트

Expand All @@ -41,5 +42,5 @@

## 실패 대응

- 로컬 명세 폴더 미존재 + `BRUNO_REPO_URL` 미설정이면 즉시 실패한다.
- `BRUNO_SOURCE_MODE=remote`에서 `BRUNO_REPO_URL` 미설정이면 즉시 실패한다.
- 원격 clone 성공 후 `BRUNO_COLLECTION_PATH` 경로가 없으면 즉시 실패한다.
3 changes: 3 additions & 0 deletions packages/api-schema/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BRUNO_REPO_URL=https://github.com/solid-connection/api-docs.git
BRUNO_REPO_REF=main
BRUNO_COLLECTION_PATH="Solid Connection"
46 changes: 44 additions & 2 deletions packages/api-schema/scripts/sync-bruno.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,55 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
import { resolve } from "node:path";

const rootDir = resolve(process.cwd());
const monorepoRootDir = resolve(rootDir, "../..");

loadEnvFiles([
resolve(rootDir, ".env.local"),
resolve(rootDir, ".env"),
resolve(monorepoRootDir, ".env.local"),
resolve(monorepoRootDir, ".env"),
]);

const defaultLocalCollectionDir = resolve(rootDir, "../../../api-docs/Solid Connection");
const cacheRoot = resolve(rootDir, ".cache");
const checkoutDir = resolve(cacheRoot, "bruno-source");
const sourceMode = process.env.BRUNO_SOURCE_MODE ?? "auto";
const sourceMode = normalizeSourceMode(process.env.BRUNO_SOURCE_MODE ?? "remote");
const remoteRepoUrl = process.env.BRUNO_REPO_URL;
const remoteRepoRef = process.env.BRUNO_REPO_REF ?? "main";
const remoteCollectionPath = process.env.BRUNO_COLLECTION_PATH ?? "Solid Connection";
const explicitCollectionDir = process.env.BRUNO_COLLECTION_DIR;

function loadEnvFiles(filePaths) {
for (const filePath of filePaths) {
loadEnvFile(filePath);
}
}

function loadEnvFile(filePath) {
if (typeof process.loadEnvFile !== "function") {
return;
}

try {
process.loadEnvFile(filePath);
} catch (error) {
if (error?.code === "ENOENT") {
return;
}

throw error;
}
}

function normalizeSourceMode(mode) {
const allowedModes = new Set(["auto", "local", "remote"]);
if (!allowedModes.has(mode)) {
throw new Error(`Invalid BRUNO_SOURCE_MODE: ${mode}. Expected one of auto, local, remote.`);
}

return mode;
}

function run(command, args, cwd = rootDir) {
const result = spawnSync(command, args, {
cwd,
Expand All @@ -26,7 +66,9 @@ function run(command, args, cwd = rootDir) {

function ensureRemoteCollectionDir() {
if (!remoteRepoUrl) {
throw new Error("BRUNO_REPO_URL is required when BRUNO source is remote.");
throw new Error(
"BRUNO_REPO_URL is required for remote sync. Set it in packages/api-schema/.env, repo root .env, or shell environment.",
);
}

mkdirSync(cacheRoot, { recursive: true });
Expand Down