diff --git a/.github/workflows/sync-to-frontend.yml b/.github/workflows/sync-to-frontend.yml index 3d8061bb..ff4b171c 100644 --- a/.github/workflows/sync-to-frontend.yml +++ b/.github/workflows/sync-to-frontend.yml @@ -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 @@ -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: | diff --git a/apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx b/apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx index 86719f28..4650eb63 100644 --- a/apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx +++ b/apps/web/src/app/university/score/submit/language-test/LanguageTestSubmitForm.tsx @@ -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 = () => { @@ -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: "어학증명서", diff --git a/apps/web/src/components/ui/BottomSheet/hooks/useHandleModal.ts b/apps/web/src/components/ui/BottomSheet/hooks/useHandleModal.ts index 9ebb4154..3eaf36bd 100644 --- a/apps/web/src/components/ui/BottomSheet/hooks/useHandleModal.ts +++ b/apps/web/src/components/ui/BottomSheet/hooks/useHandleModal.ts @@ -1,4 +1,4 @@ -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 => { @@ -6,7 +6,6 @@ const isInteractiveElement = (el: EventTarget | null): boolean => { }; interface UseHandleModalReturn { - elementRef: RefObject; isVisible: boolean; translateY: number; isDraggingRef: MutableRefObject; @@ -23,7 +22,6 @@ const useHandleModal = (onClose: () => void, snap: number[] = [0]): UseHandleMod const startYRef = useRef(0); // 시작 Y좌표 const currentYRef = useRef(0); // 현재 Y좌표 const isDraggingRef = useRef(false); // 드래그 상태 - const elementRef = useRef(null); const snapPoints = useMemo((): number[] => { if (typeof window === "undefined") return [0]; // SSR 대응 @@ -115,7 +113,6 @@ const useHandleModal = (onClose: () => void, snap: number[] = [0]): UseHandleMod }, [isVisible, translateY, snapPoints, handleClose]); return { - elementRef, isVisible, translateY, isDraggingRef, diff --git a/apps/web/src/components/ui/BottomSheet/index.tsx b/apps/web/src/components/ui/BottomSheet/index.tsx index 6e6a6f94..893ab808 100644 --- a/apps/web/src/components/ui/BottomSheet/index.tsx +++ b/apps/web/src/components/ui/BottomSheet/index.tsx @@ -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; @@ -45,7 +37,6 @@ const BottomSheet = ({ isOpen, onClose, children, titleChild, snap = DEFAULT_SNA {/* 바텀 시트 */}
/.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 @@ -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 이름을 생성합니다. diff --git a/docs/skills/univ-extends-bruno-sync-skill.md b/docs/skills/univ-extends-bruno-sync-skill.md index d983228e..a84e28e2 100644 --- a/docs/skills/univ-extends-bruno-sync-skill.md +++ b/docs/skills/univ-extends-bruno-sync-skill.md @@ -21,10 +21,10 @@ 1. 기본 실행 - `pnpm run sync:bruno` -2. 원격 명세 강제 동기화 - - `BRUNO_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`) ## 환경 변수 규칙 @@ -32,6 +32,7 @@ - `BRUNO_REPO_URL`: 원격 Bruno 저장소 URL - `BRUNO_REPO_REF`: 원격 브랜치/태그 (기본 `main`) - `BRUNO_COLLECTION_PATH`: 저장소 내부 명세 폴더 (기본 `Solid Connection`) +- 권장 파일: `packages/api-schema/.env` ## 검증 체크리스트 @@ -41,5 +42,5 @@ ## 실패 대응 -- 로컬 명세 폴더 미존재 + `BRUNO_REPO_URL` 미설정이면 즉시 실패한다. +- `BRUNO_SOURCE_MODE=remote`에서 `BRUNO_REPO_URL` 미설정이면 즉시 실패한다. - 원격 clone 성공 후 `BRUNO_COLLECTION_PATH` 경로가 없으면 즉시 실패한다. diff --git a/packages/api-schema/.env b/packages/api-schema/.env new file mode 100644 index 00000000..59cbf044 --- /dev/null +++ b/packages/api-schema/.env @@ -0,0 +1,3 @@ +BRUNO_REPO_URL=https://github.com/solid-connection/api-docs.git +BRUNO_REPO_REF=main +BRUNO_COLLECTION_PATH="Solid Connection" diff --git a/packages/api-schema/scripts/sync-bruno.mjs b/packages/api-schema/scripts/sync-bruno.mjs index a66ccf9f..e1280deb 100644 --- a/packages/api-schema/scripts/sync-bruno.mjs +++ b/packages/api-schema/scripts/sync-bruno.mjs @@ -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, @@ -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 });