260706_0_회사싱크 후 커밋
This commit is contained in:
@@ -12,7 +12,8 @@
|
||||
- Stage 3 B05 (경로 설계): ✅ 백엔드 완료 (Dijkstra + ridge-valley + skeleton)
|
||||
- Stage 4 B06 (종횡단 생성): ✅ 백엔드 완료 (sampler + 종횡단 + Repository/Router)
|
||||
- 공통: 전 페이지 DB 접근을 aiomysql Raw SQL로 통일, 순수 계산부는 실데이터로 검증
|
||||
- 남은 작업: B04~B06 프론트엔드(Vanilla TS/WebCAD), 실제 aislo_db 연동·구형 수치 비교
|
||||
- Stage 2~4 프론트엔드 폼/결과 UI: ✅ 완료 (B04~B06 UI_Page + Api_Fetch + CSS + locale, tsc 타입체크 통과)
|
||||
- 남은 작업: B04~B06 **WebCAD 3D/차트 뷰어**, 실제 aislo_db 연동·구형 수치 비교
|
||||
|
||||
---
|
||||
|
||||
@@ -178,7 +179,8 @@ B04로 넘길 기능:
|
||||
- [x] 등고선 생성 함수 이전
|
||||
- [x] 미리보기·메타데이터·모델 확정 라우트 추가 (analyze/models 라우트 + 파이프라인 manifest)
|
||||
- [x] `processed_point_cloud`, `surface_models`, `terrain_layers` Raw SQL 저장 함수 추가 (aiomysql)
|
||||
- [ ] B04 폼·WebCAD 뷰어를 Vanilla TypeScript로 재작성
|
||||
- [x] B04 폼·모델 목록 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B04 WebCAD 3D 뷰어(포인트클라우드/메시 렌더) 추가
|
||||
- [ ] 샘플 LAS 결과를 구형 출력과 비교 검증
|
||||
|
||||
### Stage 3 — B05_wf2_Route
|
||||
@@ -193,7 +195,8 @@ B04로 넘길 기능:
|
||||
- [ ] 입력 서명·stale 판정 함수 이전
|
||||
- [x] 경로 확정 함수 이전 (Repository confirm_route + confirm 라우트)
|
||||
- [x] `routes`, `route_points`, `route_statistics` Raw SQL 저장 함수 추가 (aiomysql)
|
||||
- [ ] B05 경로 편집·제약조건·결과 뷰어를 Vanilla TypeScript로 재작성
|
||||
- [x] B05 경로점 입력·제약조건·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B05 경로 WebCAD 뷰어(노선 폴리라인 렌더/편집) 추가
|
||||
- [x] 기존 route solver 테스트를 신규 구조로 이관·통과
|
||||
|
||||
### Stage 4 — B06_wf3_ProfileCross
|
||||
@@ -207,7 +210,8 @@ B04로 넘길 기능:
|
||||
- [ ] 입력 서명·중복 실행 방지 함수 이전 (delete_sections_for_route로 멱등 재실행만 구현)
|
||||
- [x] 결과 조회·확정 라우트 추가 (sections/generate·get·confirm 라우트)
|
||||
- [x] `longitudinal_sections`, `cross_sections` Raw SQL 저장 함수 추가 (aiomysql Repository)
|
||||
- [ ] B06 종단도·횡단 카드 UI를 Vanilla TypeScript로 재작성
|
||||
- [x] B06 측점·횡단 옵션·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B06 종단도·횡단 카드 WebCAD 뷰어(프로필 차트 렌더) 추가
|
||||
- [ ] 구형 section 결과와 수치 비교 검증
|
||||
|
||||
**B06 백엔드 구현 파일:**
|
||||
|
||||
+40
-7
@@ -96,14 +96,47 @@ my-project/
|
||||
│ ├── B03_FileInput_Engine_Analyze.py # 원본 파일 메타데이터 분석
|
||||
│ ├── B03_FileInput_Repository.py # input_files asyncpg Raw SQL
|
||||
│ └── B03_FileInput_Router.py # 다중 파일 업로드 API
|
||||
├── B04_wf1_Surface/ # 로그인 후 04: 1차 workflow (지표면 모델 분석) — 워크플로우 셸
|
||||
│ ├── B04_wf1_Surface_UI_Page.ts
|
||||
├── B04_wf1_Surface/ # 로그인 후 04: 1차 workflow (지표면 모델 분석)
|
||||
│ ├── B04_wf1_Surface_UI_Page.ts # 3단 레이아웃 (필터/표현 선택 폼 + 모델 목록)
|
||||
│ ├── B04_wf1_Surface_UI_Style.css # B04 고유 폼 그룹/모델 카드 스타일
|
||||
│ ├── B04_wf1_Surface_Api_Fetch.ts # surface/analyze·models API 클라이언트
|
||||
│ ├── B04_wf1_Surface_Schema.py # 분석 요청/응답 Pydantic 검증
|
||||
│ ├── B04_wf1_Surface_Engine.py # 지표면 분석 오케스트레이터
|
||||
│ ├── B04_wf1_Surface_Engine_Structurize.py # LAS/LAZ 구조화
|
||||
│ └── B04_wf1_Surface_Engine_Filter_Grid.py # grid minimum-Z 필터
|
||||
├── B05_wf2_Route/ # 로그인 후 05: 2차 workflow (경로설계) — 워크플로우 셸
|
||||
│ └── B05_wf2_Route_UI_Page.ts
|
||||
├── B06_wf3_ProfileCross/ # 로그인 후 06: 3차 workflow (종횡단 생성) — 워크플로우 셸
|
||||
│ └── B06_wf3_ProfileCross_UI_Page.ts
|
||||
│ ├── B04_wf1_Surface_Engine_Ground.py # 지면 분석 진입
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_Grid.py # grid minimum-Z 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_CSF.py # CSF 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_PMF.py # PMF 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_RANSAC.py # RANSAC 필터
|
||||
│ ├── B04_wf1_Surface_Engine_ModelContext.py # 모델 공통 컨텍스트/메시 유틸
|
||||
│ ├── B04_wf1_Surface_Engine_ModelBuild.py # TIN/DTM/NURBS/implicit/meshfree 생성
|
||||
│ ├── B04_wf1_Surface_Engine_Smooth.py # DTM/TIN 스무딩
|
||||
│ ├── B04_wf1_Surface_Engine_Contour.py # 등고선 생성
|
||||
│ ├── B04_wf1_Surface_Engine_Pipeline.py # 파이프라인 manifest
|
||||
│ ├── B04_wf1_Surface_Repository.py # processed_point_cloud/surface_models aiomysql Raw SQL
|
||||
│ └── B04_wf1_Surface_Router.py # surface/analyze·models API
|
||||
├── B05_wf2_Route/ # 로그인 후 05: 2차 workflow (경로설계)
|
||||
│ ├── B05_wf2_Route_UI_Page.ts # 3단 레이아웃 (제어점/제약 폼 + 결과 메트릭)
|
||||
│ ├── B05_wf2_Route_UI_Style.css # B05 고유 제어점 행/제약 그룹 스타일
|
||||
│ ├── B05_wf2_Route_Api_Fetch.ts # route/solve·confirm API 클라이언트
|
||||
│ ├── B05_wf2_Route_Schema.py # 경로 탐색 요청/응답 Pydantic 검증
|
||||
│ ├── B05_wf2_Route_Engine.py # 경로 설계 오케스트레이터
|
||||
│ ├── B05_wf2_Route_Engine_Geometry.py # Dijkstra 단일 구간 탐색
|
||||
│ ├── B05_wf2_Route_Engine_Solver.py # 비용면 생성 + 최적 경로 조립
|
||||
│ ├── B05_wf2_Route_Engine_Skeleton.py # 지형 skeleton (D8 흐름누적)
|
||||
│ ├── B05_wf2_Route_Engine_RidgeValley.py # ridge-valley 탐색
|
||||
│ ├── B05_wf2_Route_Repository.py # routes/route_points/route_statistics aiomysql Raw SQL
|
||||
│ └── B05_wf2_Route_Router.py # route/solve·confirm API
|
||||
├── B06_wf3_ProfileCross/ # 로그인 후 06: 3차 workflow (종횡단 생성)
|
||||
│ ├── B06_wf3_ProfileCross_UI_Page.ts # 3단 레이아웃 (측점/횡단 옵션 폼 + 결과 메트릭)
|
||||
│ ├── B06_wf3_ProfileCross_UI_Style.css # B06 고유 옵션 그룹/결과 메트릭 스타일
|
||||
│ ├── B06_wf3_ProfileCross_Api_Fetch.ts # sections/generate·get·confirm API 클라이언트
|
||||
│ ├── B06_wf3_ProfileCross_Schema.py # 종횡단 생성 요청/응답 Pydantic 검증
|
||||
│ ├── B06_wf3_ProfileCross_Engine.py # 종횡단 생성 오케스트레이터
|
||||
│ ├── B06_wf3_ProfileCross_Engine_Sampler.py # 표고 sampler (Protocol + DTM/보간 5종)
|
||||
│ ├── B06_wf3_ProfileCross_Engine_Section.py # 종횡단 생성 (측점/접선/횡단 샘플)
|
||||
│ ├── B06_wf3_ProfileCross_Repository.py # longitudinal/cross_sections aiomysql Raw SQL
|
||||
│ └── B06_wf3_ProfileCross_Router.py # sections/generate·get·confirm API
|
||||
├── B07_wf4_DesignDetail/ # 로그인 후 07: 4차 workflow (상세설계) — 워크플로우 셸
|
||||
│ └── B07_wf4_DesignDetail_UI_Page.ts
|
||||
├── B08_wf5_Quantity/ # 로그인 후 08: 5차 workflow (수량 산출) — 워크플로우 셸
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
document_type: entrypoint
|
||||
priority: CRITICAL
|
||||
version: 2.0
|
||||
---
|
||||
# Aislo (아이슬로) 프로젝트 지식 베이스 진입점
|
||||
|
||||
이 문서는 AI 코딩 에이전트(Codex, Claude, Antigravity 등)가 프로젝트의 방대한 정보 중 **현재 필요한 컨텍스트를 정확하게 로드**하기 위한 인덱스입니다. 임의로 구조를 추론하지 말고, 작업할 영역에 맞춰 아래 연결된 상세 명세서를 반드시 읽고 코딩을 진행하십시오.
|
||||
|
||||
## 🧭 컨텍스트 라우팅 (필요한 문서를 로드하는 방법)
|
||||
|
||||
작업 성격에 따라 다음 번호의 문서를 확인하십시오.
|
||||
모든 문서는 이 폴더(`.agent/test/`) 하위에 존재합니다.
|
||||
|
||||
1. **[구조/아키텍처 파악]** `01_Architecture_and_Structure.md`
|
||||
- **언제 읽는가:** 새로운 페이지를 추가하거나, 파일(프론트/백엔드)을 생성/수정해야 할 때.
|
||||
- **내용:** A00~B11까지의 전체 디렉토리 트리, 파일 명명 규칙(수직 통합).
|
||||
2. **[DB 연동 및 쿼리 작성]** `02_Database_Schema.md`
|
||||
- **언제 읽는가:** DB 테이블 조회, 쿼리문 작성, 관계(FK) 및 데이터 저장이 필요할 때.
|
||||
- **내용:** 18개 전체 테이블 스키마, 핵심 쿼리 예제, 공간 데이터(JSON) 설계 원칙.
|
||||
3. **[백엔드 로직/저장소 제어]** `03_Backend_Logic_and_Storage.md`
|
||||
- **언제 읽는가:** FastAPI 라우터, 비즈니스 엔진 로직, 영구저장소(물리 파일) 입출력을 다룰 때.
|
||||
- **내용:** Raw SQL 제약, 워크플로우 6단계 폴더 매핑, `workflow.json` 기반의 Stale 상태 동기화 기법.
|
||||
4. **[프론트엔드 UI/디자인 구현]** `04_Frontend_and_Design.md`
|
||||
- **언제 읽는가:** TypeScript 이벤트 핸들러, 화면 레이아웃, 다국어 처리, CSS 스타일링을 수행할 때.
|
||||
- **내용:** 3단 레이아웃 제약, 다국어(`ui_locales`) 제약, Wiza 테마(Britti Sans, 색상 토큰) 및 프롬프트 가이드.
|
||||
|
||||
---
|
||||
|
||||
## 🛑 범용 제약 사항 (절대 준수)
|
||||
1. **700줄 제한:** 단일 파일 코드 작성/수정 시 700줄이 넘으면 기능별 분할 필수.
|
||||
2. **코드 포맷팅:** 작성 후 반드시 포맷터 적용 (Python: `ruff`, TS/CSS: `prettier`).
|
||||
3. **인코딩:** 모든 파일은 UTF-8.
|
||||
4. **DB 원칙:** `aiomysql` 기반 비동기 통신. **ORM 사용은 절대 금지**하며 무조건 Raw SQL 사용.
|
||||
5. **다국어 규칙:** 프론트엔드 내 UI 텍스트 하드코딩 금지. 반드시 다국어 인덱스 배열 방식 준수.
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
document_type: architecture
|
||||
priority: HIGH
|
||||
---
|
||||
# 프로젝트 구조 및 아키텍처 명세
|
||||
|
||||
## 1. 아키텍처 기본 원칙
|
||||
* **수직 통합:** 백엔드 로직과 프론트엔드 코드는 페이지 폴더(`A01_`, `B01_` 등)별로 격리. 거대 단일 모듈을 금지하고 기능별로 파일을 분할한다.
|
||||
* **루트 main.py:** FastAPI 진입점으로, 라우터 등록(`include_router`)만 수행. 비즈니스 로직 작성 금지.
|
||||
* **명명 규칙:**
|
||||
- 백엔드: `[폴더명]_Router.py`, `[폴더명]_Engine.py`, `[폴더명]_Schema.py`, `[폴더명]_Repository.py`
|
||||
- 프론트엔드: `[폴더명]_UI_Page.ts`, `[폴더명]_UI_Style.css`, `[폴더명]_Api_Fetch.ts`
|
||||
- 로그인 전은 `A01_`, 로그인 후는 `B01_`, 워크플로우는 `B04_wf1_` 형태 적용.
|
||||
- 공통 유틸 코드는 `common_util_[기능].확장자`
|
||||
|
||||
## 2. 전체 디렉토리 트리 상세
|
||||
|
||||
```text
|
||||
my-project/
|
||||
├── A00_Common/ # 프론트엔드 공통 인프라
|
||||
│ ├── main.ts # 앱 진입점
|
||||
│ ├── router.ts # 해시 기반 SPA 라우터
|
||||
│ └── app_shell.ts # 공통 헤더/푸터
|
||||
├── common_util/ # 공통 유틸리티
|
||||
│ ├── common_util_validate.ts # 프론트 유효성 검사
|
||||
│ ├── common_util_storage.py # 저장 경로 로직
|
||||
│ └── common_util_workflow.py # 상태 동기화 로직
|
||||
├── config/ # 환경 설정
|
||||
│ ├── config_system.py # 시스템 파라미터 (제어 변수 중앙화)
|
||||
│ └── config_db.py # DB 설정
|
||||
├── db_management/ # 스키마 및 마이그레이션
|
||||
├── resources/ # 정적 리소스 (에셋)
|
||||
├── storage/ # 물리 파일 영구 저장소 (DB와 매핑)
|
||||
├── templates/ # 보고서/도면 양식
|
||||
├── ui_template/ # 공통 UI 컴포넌트, 테마, 다국어
|
||||
│ ├── ui_template_theme.css
|
||||
│ ├── ui_template_locale.ts
|
||||
│ └── ui_template_elements.ts
|
||||
|
||||
# --- 로그인 전 페이지 (A 그룹) ---
|
||||
├── A01_Home/ # 홈 (소개, 소식)
|
||||
├── A02_ProgDetail/ # 프로그램 상세 (6단계 워크플로우 설명)
|
||||
├── A03_CompDetail/ # 회사 상세 (미션 등)
|
||||
├── A04_NewsHistory/ # 최신소식 및 개선 이력
|
||||
├── A05_EduDetail/ # 교육 상세
|
||||
├── A06_Login/ # 로그인 (이메일/비밀번호)
|
||||
├── A07_Register/ # 회원가입
|
||||
├── A08_Support/ # 기술지원 요청
|
||||
|
||||
# --- 로그인 후 페이지 (B 그룹) ---
|
||||
├── B01_AccountDetail/ # 계정 관리
|
||||
├── B02_ProjRegister/ # 신규 프로젝트 등록
|
||||
├── B03_FileInput/ # [WF0] 원본 파일 입력 (다중 파일 업로드)
|
||||
├── B04_wf1_Surface/ # [WF1] 지표면 모델 분석 (LAS 처리)
|
||||
├── B05_wf2_Route/ # [WF2] 경로 설계 (최적 경로 계산)
|
||||
├── B06_wf3_ProfileCross/ # [WF3] 종횡단 생성 (단면 추출)
|
||||
├── B07_wf4_DesignDetail/ # [WF4] 상세 설계 (구조물 배치)
|
||||
├── B08_wf5_Quantity/ # [WF5] 수량 산출 (물량 집계)
|
||||
├── B09_wf6_Estimation/ # [WF6] 견적/최종 산출물 생성
|
||||
├── B10_Payment/ # 결제 관리
|
||||
└── B11_Status/ # 상태 출력 페이지
|
||||
|
||||
├── main.py # FastAPI 루트
|
||||
└── package.json, requirements.txt 등
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
document_type: rules
|
||||
priority: CRITICAL
|
||||
---
|
||||
# 프로젝트 기본 규칙 및 제약 (Rules & Constraints)
|
||||
|
||||
## 🚨 필수 행동 지침 (Do's)
|
||||
- **[MUST]** 코드 작성 전 반드시 `02_Architecture_and_Structure.md`의 구조를 확인하고 적절한 위치에 작성한다.
|
||||
- **[MUST]** 700줄 제한: 단일 파일이 700줄을 초과할 경우 사전 공지하고 기능별로 파일을 분할한다.
|
||||
- **[MUST]** 인코딩은 반드시 **UTF-8** 표준으로 통일한다.
|
||||
- **[MUST]** 코드 작성이 완료되면 다음 포맷터를 실행한다.
|
||||
- Python: `ruff format [파일명]`, `ruff check --fix [파일명]`
|
||||
- TypeScript/CSS: `npx prettier --write [파일명]`
|
||||
- **[MUST]** 외부 라이브러리 추가 시 프로젝트 호환성을 고려하고 무분별한 사용을 지양한다.
|
||||
|
||||
## 🛑 금지 행동 지침 (Don'ts)
|
||||
- **[DON'T]** 하드코딩 금지: 제어 변수/파라미터는 `config/config_system.py`에서, UI 문자열은 `ui_template/ui_template_locale.ts`에서 가져와야 한다.
|
||||
- **[DON'T]** 임의 파일 경로 생성 금지: 모든 파일 입출력 및 저장은 `05_Workflow_and_Storage.md`에 정의된 6단계 워크플로우 폴더 경로를 준수해야 한다.
|
||||
- **[DON'T]** 백엔드에서 중간 산출물이나 임시 파일을 MariaDB에 직접 바이너리로 저장하지 않는다. 물리 파일로 저장 후 DB에는 경로(프로젝트 상대 경로)만 기록한다.
|
||||
- **[DON'T]** 삭제 및 덮어쓰기 금지: 다른 단계의 산출물을 직접 삭제하거나 덮어쓰지 않고 stale 상태를 통해 재계산 필요성을 전파한다.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
document_type: architecture
|
||||
priority: HIGH
|
||||
---
|
||||
# 파일 구조 및 명명 규칙
|
||||
|
||||
## 📂 아키텍처 원칙
|
||||
- **수직 통합:** 프론트엔드(TypeScript)와 백엔드(Python) 파일을 기능/페이지별로 같은 폴더(`A01_`, `B01_` 등)에 격리하여 관리한다. (단일 거대 모듈 금지)
|
||||
- **루트 `main.py`:** FastAPI의 진입점. 라우터 등록(`include_router`)만 수행하고 비즈니스 로직은 절대 작성하지 않는다.
|
||||
|
||||
## 📝 명명 규칙
|
||||
- **폴더명:**
|
||||
- 로그인 전 페이지: `A01_`, `A02_` ...
|
||||
- 로그인 후 페이지: `B01_`, `B02_` ...
|
||||
- 워크플로우 페이지: `B04_wf1_`, `B05_wf2_` ...
|
||||
- 공통/시스템 폴더: `A00_Common`, `common_util`, `config` 등 접두사 없이 영문명 사용.
|
||||
- **파일명:** `[폴더명(페이지명)]_[기능]_[기타사양].[확장자]` 형태 필수.
|
||||
- 예시: `B04_wf1_Surface_UI_Page.ts`, `B04_wf1_Surface_Router.py`
|
||||
- **공통 모듈:** `common_util` 폴더 내에 `common_util_[기능].[확장자]` 형태로 보관.
|
||||
|
||||
## 🏗️ 주요 폴더 구조 요약
|
||||
- `A00_Common/`: 프론트엔드 공통 인프라 (SPA 라우터, 앱 셸 등)
|
||||
- `common_util/`: 공통 유틸리티 코드 (검증, 스토리지 제어 등)
|
||||
- `config/`: 시스템 설정, DB 설정, TS/Vite 환경 설정
|
||||
- `ui_template/`: 공통 UI 테마, 컴포넌트, 다국어 파일 배열
|
||||
- `storage/`: 파일 영구 저장소 (DB 경로와 연동됨)
|
||||
- `B03_FileInput/` ~ `B09_wf6_Estimation/`: 핵심 6단계 워크플로우 페이지 및 API
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
document_type: database
|
||||
priority: CRITICAL
|
||||
---
|
||||
# 데이터베이스 스키마 명세 (MariaDB)
|
||||
|
||||
MariaDB v10.6+를 사용하며, 총 18개의 테이블로 구성됩니다. ORM 없이 Raw SQL과 `aiomysql` 드라이버를 사용합니다. **공간 데이터(GEOMETRY)는 지원 한계로 인해 JSONB 또는 TEXT(GeoJSON)로 저장합니다.**
|
||||
|
||||
## 테이블 목록 및 컬럼 구조
|
||||
|
||||
### 1. 사용자 & 프로젝트 (Core)
|
||||
* **`users`**: `id`(PK), `email`, `password_hash`, `name`, `position`, `department`, `phone`, `company_id`(FK)
|
||||
* **`companies`**: `id`(PK), `name`, `business_registration_number`, `business_address`, `business_owner`, `business_status`
|
||||
* **`projects`**: `id`(UUID, PK), `user_id`(FK), `company_id`(FK), `name`, `region`, `road_type`, `status`, `crs_epsg`, `storage_path`
|
||||
* **`project_versions`**: `id`, `project_id`, `version_num`, `data`(JSONB)
|
||||
|
||||
### 2. 파일 & 지표면 데이터 (WF0, WF1)
|
||||
* **`input_files`**: `id`(PK), `project_id`(FK), `file_type`(las, tif 등), `original_filename`, `raw_file_path`, `crs_epsg`, `metadata`(JSONB)
|
||||
* **`processed_point_cloud`**: `id`, `input_file_id`, `project_id`, `processed_file_path`, `converted_file_path`, `min_z`, `max_z` 등 통계치
|
||||
* **`surface_models`**: `id`, `project_id`, `model_type`(dem_grid, tin 등), `model_file_path`, `generation_params`(JSONB)
|
||||
* **`terrain_layers`**: `id`, `surface_model_id`, `layer_name`, `layer_file_path`(geojson 등)
|
||||
|
||||
### 3. 경로 설계 (WF2)
|
||||
* **`routes`**: `id`(PK), `project_id`, `surface_model_id`, `status`, `geometry`(LineString JSON), `route_data_path`, `constraints`(JSONB)
|
||||
* **`route_points`**: `id`, `route_id`, `chainage_m`, `geometry`(Point JSON), `elevation_m`
|
||||
* **`route_statistics`**: `id`, `route_id`, `mean_slope`, `cut_volume_m3`, `fill_volume_m3`
|
||||
|
||||
### 4. 종횡단 및 구조물 설계 (WF3, WF4)
|
||||
* **`longitudinal_sections`**: `id`, `project_id`, `route_id`, `longitudinal_file_path`, `data`(JSONB - chainages, elevations 등 배열)
|
||||
* **`cross_sections`**: `id`, `route_id`, `chainage_m`, `cross_section_file_path`, `data`(JSONB - left_slope, cut_volume_m3 등)
|
||||
* **`structures`**: `id`, `cross_section_id`, `structure_type`, `location`(LEFT/RIGHT/CENTER), `length_m`, `geometry`(Polygon JSON), `structure_data_path`
|
||||
|
||||
### 5. 수량 산출 및 산출물 (WF5, WF6)
|
||||
* **`quantity_items`**: `id`, `project_id`, `category`, `item_name`, `quantity_design`, `quantity_actual`, `unit_price`, `quantity_data_path`
|
||||
* **`outputs`**: `id`, `project_id`, `output_type`, `outputs_directory_path`
|
||||
* **`output_files`**: `id`, `output_id`, `file_type`, `output_file_path`
|
||||
|
||||
### 6. 로그 (Audit)
|
||||
* **`audit_logs`**, **`change_logs`**
|
||||
|
||||
---
|
||||
|
||||
## 💡 주요 원칙 (DB vs File System)
|
||||
- **DB에는 메타데이터와 파일 시스템의 "상대 경로"만 기록합니다.** (예: `input_files.raw_file_path = "B03_FileInput/input/las/cloud.las"`)
|
||||
- 대용량 파일(LAS, TIF, GeoJSON)은 MariaDB에 Blob으로 저장하지 않습니다.
|
||||
|
||||
## 📝 쿼리 작성 예제 (Raw SQL & aiomysql)
|
||||
```python
|
||||
# 쿼리에는 ORM이 아닌 %s 플레이스홀더를 사용해야 함
|
||||
async def get_project_route(pool, project_id: str):
|
||||
async with pool.acquire() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
await cur.execute("""
|
||||
SELECT r.id, r.total_length_m, r.route_data_path
|
||||
FROM routes r
|
||||
WHERE r.project_id = %s
|
||||
ORDER BY r.computed_at DESC LIMIT 1
|
||||
""", (project_id,))
|
||||
return await cur.fetchone()
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
document_type: backend
|
||||
priority: HIGH
|
||||
---
|
||||
# 백엔드 제어 및 저장소/워크플로우 명세
|
||||
|
||||
## 1. 물리 파일 및 저장소 구조 (Workflow Mapping)
|
||||
프로젝트 저장소 내부는 워크플로우 단계와 1:1로 매핑됩니다. 백엔드에서 파일을 다룰 때는 DB에 직접 Blob을 저장하지 않고 이 구조를 따라 저장 후 DB에 상대 경로만 기록합니다.
|
||||
|
||||
```text
|
||||
storage/{company_slug}/{user_slug}/{project_id}/
|
||||
├── B03_FileInput/
|
||||
│ └── input/ (원본 업로드 파일들: .las, .tif)
|
||||
├── B04_wf1_Surface/
|
||||
│ ├── processed/ (변환된 데이터: cloud_filtered.las 등)
|
||||
│ └── models/ (DEM, TIN 결과물: dem_2m.tif, layer.geojson)
|
||||
├── B05_wf2_Route/
|
||||
│ └── route/ (최적 경로: route_main.geojson)
|
||||
├── B06_wf3_ProfileCross/
|
||||
│ ├── longitudinal/ (종단면: longitudinal.json)
|
||||
│ └── cross_sections/ (횡단면 배열: cross_0020m.json 등)
|
||||
├── B07_wf4_DesignDetail/
|
||||
│ └── structures/ (구조물 객체: struct_0020m_001.json)
|
||||
├── B08_wf5_Quantity/
|
||||
│ └── quantities/ (수량 집계: items.json)
|
||||
└── B09_wf6_Estimation/
|
||||
└── v1/, v2/ (버전별 최종 엑셀, PDF, DXF)
|
||||
```
|
||||
**규칙:** 페이지별 백엔드 모듈은 자기 단계의 폴더만 제어합니다. 하위 단계의 파일을 강제로 지우거나 덮어쓰지 않고 `stale_from` 상태를 통해 제어합니다.
|
||||
|
||||
## 2. 워크플로우 상태 동기화 (workflow.json)
|
||||
다중 브라우저 동시 작업을 지원하기 위해 서버 측 상태 파일(`workflow.json`)을 원자적(Atomic)으로 관리합니다.
|
||||
|
||||
* **Stale (상태 무효화) 발생:**
|
||||
상위 단계(예: 지표면 분석 B04)가 재계산되면, 그에 의존하는 하위 단계(경로 B05, 횡단 B06 등) 결과는 신뢰할 수 없으므로 `workflow.json`에 `stale_from: "route"` 형태로 마킹됩니다.
|
||||
* **상태 업데이트 함수 (예시):**
|
||||
```python
|
||||
def _patch_workflow_stale(project_id: str, stale_from: str | None) -> None:
|
||||
# workflow.json의 stale_from 필드만 원자적 업데이트
|
||||
```
|
||||
|
||||
## 3. 클라이언트 폴링 (Polling)
|
||||
- 프론트는 3초 주기로 `GET /api/projects/{project_id}/workflow` 를 폴링합니다.
|
||||
- 서버가 반환하는 `stale_from` 값을 확인하여, 만약 현재 단계와 다르다면 UI에서 사용자에게 "결과가 변경되었습니다. 재계산하시겠습니까?"를 경고합니다.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
document_type: backend
|
||||
priority: HIGH
|
||||
---
|
||||
# 백엔드 & 데이터베이스 제어 명세
|
||||
|
||||
## 🐍 백엔드 (FastAPI / Python)
|
||||
- **파일 분할:** 라우터(`_Router.py`), 비즈니스 로직(`_Engine.py`), 스키마(`_Schema.py`), DB 접근(`_Repository.py`) 형태로 세분화.
|
||||
- **데이터 검증:** 모든 JSON 요청은 Pydantic 모델을 통해 라우터 진입 전 타입/범위 필수 검증.
|
||||
- **예외 처리:** 지형/메쉬 연산 등 복잡한 로직 수행 시 `try-except` 필수 처리. 에러 발생 시 `{"status": "error", "message": "원인"}` 반환 표준 포맷 유지.
|
||||
|
||||
## 🗄️ 데이터베이스 (MariaDB)
|
||||
- **[MUST]** 비동기 통신: `aiomysql` 드라이버 사용 강제. (`asyncmy` 사용 불가)
|
||||
- **[DON'T]** ORM 사용 금지: 오직 **Raw SQL**만 작성한다.
|
||||
- **[MUST]** 파라미터 바인딩: `%s` 플레이스홀더를 사용하여 SQL Injection 방지.
|
||||
- **트랜잭션:** `pool.acquire()`로 커넥션 확보 후 다건 쓰기는 `connection.begin()` → `commit()` / 예외 시 `rollback()` 필수.
|
||||
- **자동 증가 ID:** `INSERT` 후 `cursor.lastrowid`로 조회.
|
||||
- **공간 데이터 처리:** MariaDB의 GEOMETRY 타입 한계로 인해, 공간 기하 데이터(좌표, 다각형, 경로 등)는 JSON (GeoJSON 형식)으로 저장 후 애플리케이션(Python Shapely/Geopandas)에서 파싱하여 기하 연산을 처리한다.
|
||||
|
||||
## 🔄 워크플로우 상태 동기화 (Sync)
|
||||
- **공유 상태:** 다중 브라우저 대응을 위해 각 프로젝트는 `workflow.json`으로 현재 진행 단계와 'stale(무효화)' 상태를 관리한다.
|
||||
- **Stale 전파:** 상위 단계(예: 지표면 분석)가 재실행되면 하위 단계들은 자동으로 `stale_from`으로 표기되며, 클라이언트는 3초 주기의 폴링(Polling)을 통해 상태를 확인하고 경고/재계산을 유도한다.
|
||||
- **원자적 쓰기:** `workflow.json` 상태 업데이트는 `common_util_workflow.py`를 통해 원자적 업데이트로 보호되어야 한다.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
document_type: frontend
|
||||
priority: HIGH
|
||||
---
|
||||
# 프론트엔드 제어 및 Wiza 디자인 시스템
|
||||
|
||||
## 1. Wiza 디자인 시스템 (Theme Constraints)
|
||||
프론트엔드 UI는 Wiza 브랜드 가이드라인에 따라 구현해야 합니다. 하드코딩된 색상이나 수치를 금지하며, 제공되는 `ui_template_theme.css` 토큰을 사용합니다.
|
||||
|
||||
* **색상 토큰 (Monochromatic Violet):**
|
||||
- `--color-deep-iris` (#26114a): 헤드라인, 메인 버튼 배경 (강조)
|
||||
- `--color-plum-velvet` (#312749): 네비게이션, 2차 헤딩
|
||||
- `--color-royal-amethyst` (#3e0079): 링크, 포커스 링, 강조 아이콘
|
||||
- `--color-lavender-wash`: 영웅 영역 및 섹션의 부드러운 그라데이션 배경
|
||||
- `--color-canvas` (#ffffff): 카드, 입력 폼 등 캔버스 표면
|
||||
* **타이포그래피:**
|
||||
- **Display/Heading:** `Britti Sans` (대체제: Plus Jakarta Sans), 500 Weight, **Line-height 1.0 (절대 준수)**.
|
||||
- **Body/UI:** `Inter`, 400/500/700 Weight, 크기는 컴팩트하게(12~16px) 유지.
|
||||
* **형태 및 간격 (Spacing & Radius):**
|
||||
- 카드, 버튼, 인풋 폼 등 모든 사각형 컨테이너는 **8px Radius** 강제.
|
||||
- 뱃지(Badge), 필(Pill), 네비게이션 아이템 등은 **1440px Radius** 강제.
|
||||
- 요소 간 기본 간격 유닛은 **8px**.
|
||||
|
||||
## 2. 레이아웃 제약
|
||||
워크플로우 페이지(B04~B09)는 반드시 **3단 기본 레이아웃**을 따릅니다.
|
||||
1. 상단: 페이지 타이틀 및 현재 진행 단계
|
||||
2. 좌측: 데이터 입력 폼 및 설정 제어 패널 (너비 고정)
|
||||
3. 우측: WebCAD 도면 뷰어 (Canvas/WebGL) 또는 결과 데이터 그리드 (나머지 화면 너비 전체)
|
||||
|
||||
## 3. 다국어 제어 원칙 (i18n)
|
||||
모든 텍스트는 컴포넌트 내에 직접 작성할 수 없습니다.
|
||||
|
||||
```typescript
|
||||
// 선언부: ui_template/ui_template_locale.ts
|
||||
export const ui_locales = {
|
||||
A01_Home_Title: ["반갑습니다", "Welcome"],
|
||||
B04_wf1_Surface_Btn: ["지표면 분석 실행", "Run Surface Analysis"]
|
||||
};
|
||||
|
||||
// 구현부 호출 예시
|
||||
<button>{ui_locales.B04_wf1_Surface_Btn[currentLanguageIndex]}</button>
|
||||
```
|
||||
|
||||
## 4. 로직 컨벤션 (TypeScript)
|
||||
* **이벤트 핸들러:** `on[페이지명]_[기능명]_[액션]` 형태 강제. (예: `onB04_Surface_Calculate_Click`)
|
||||
* **프론트 유효성 검사:** 백엔드 API 전송 전, 1차적으로 빈 값 및 타입 유효성 검사를 수행하여 피드백합니다.
|
||||
* **로딩 스피너:** API 요청 시작 전 Overlay 활성화, 통신 성공/실패 시 즉시 해제 로직 필수.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
document_type: frontend
|
||||
priority: HIGH
|
||||
---
|
||||
# 프론트엔드 및 UI 제어 명세
|
||||
|
||||
## 🎨 테마 및 디자인 규약
|
||||
- **[DON'T]** 하드코딩된 Hex 색상 코드나 임의의 스타일 직접 입력 금지.
|
||||
- **[MUST]** 테마(라이트/다크) 전환 호환을 위해 `ui_template/ui_template_theme.css`에 선언된 CSS 변수(`var(--color-...)`)만 참조.
|
||||
- **공통 레이아웃 (3단 분리 준수):**
|
||||
1. 상단 영역: 페이지 타이틀 및 진행 상태 (헤더)
|
||||
2. 좌측 패널: 데이터 입력 폼 및 제어 설정 창 (고정 너비)
|
||||
3. 우측 영역: WebCAD 도면 뷰어 (Canvas/WebGL) 또는 데이터 그리드 (가변 너비)
|
||||
|
||||
## 🌐 다국어 지원 (i18n)
|
||||
- **[DON'T]** UI 문자열(라벨, 버튼 명, 툴팁, 안내 메시지 등)의 코드 내 하드코딩 절대 금지.
|
||||
- **[MUST]** 동시 수정 프로토콜:
|
||||
1. 신규 텍스트 발생 시 `ui_template/ui_template_locale.ts` 최하단에 키(Key)와 번역 배열(`[한국어, 영어]`) 선 등록.
|
||||
2. 컴포넌트 내에서는 `ui_locales.키값[현재언어인덱스]` 형태로만 호출한다.
|
||||
3. UI 수정 PR시 다국어 업데이트 내역 누락은 심각한 에러로 취급한다.
|
||||
|
||||
## ⚙️ TypeScript 및 로직 제어
|
||||
- **이벤트 핸들러 명명법:** `on[페이지명]_[기능명]_[액션]` 규칙 준수.
|
||||
- 예: `onB04_Surface_Calculate_Click`
|
||||
- **상태 제어 피드백:** API 비동기 호출 시작 시 전체 로딩 스피너(Overlay) 활성화, 통신 완료(성공/에러) 시 반드시 해제 보장.
|
||||
- **1차 검증:** 백엔드 전송 전 빈 값, 최대 길이, 숫자 범위 검증은 프론트엔드(`common_util_validate.ts`)에서 우선 처리하며, 에러 시 입력창 주변에 명시한다.
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
document_type: workflow
|
||||
priority: HIGH
|
||||
---
|
||||
# 워크플로우 및 데이터 저장소 명세
|
||||
|
||||
## 🔄 6단계 워크플로우 (6-Stage Workflow)
|
||||
설계의 순차적 흐름을 정의하며, DB 및 폴더 구조와 1:1 맵핑됩니다.
|
||||
|
||||
- **WF0 (B03): 파일 입력** - 사용자 원본 파일 (LAS, TIF, DXF 등) 업로드
|
||||
- **WF1 (B04): 지표면 분석** - 포인트클라우드 포맷 변환 및 지표면 모델(DEM/TIN) 생성
|
||||
- **WF2 (B05): 경로 설계** - 제약조건 반영 최적 선형 도출 (GeoJSON)
|
||||
- **WF3 (B06): 종횡단 생성** - 종단면도, 횡단면도 지형/설계 데이터 생성
|
||||
- **WF4 (B07): 상세 설계** - 횡단면별 구조물(옹벽, 수로 등) 배치
|
||||
- **WF5 (B08): 수량 산출** - 구조물 수량 및 토공량 집계
|
||||
- **WF6 (B09): 견적·문서** - Excel, PDF, DXF 등 최종 산출물 패키지 생성
|
||||
|
||||
## 📁 영구 저장소 구조 (파일시스템)
|
||||
중간 산출물(LAS, TIF, GeoJSON 등) 및 대용량 파일은 DB가 아닌 파일시스템에 저장되며, DB(MariaDB)에는 저장된 파일의 **상대 경로**만 기록합니다. 임의의 위치에 파일을 쓰거나 사용자 입력을 경로에 그대로 결합하지 않습니다.
|
||||
|
||||
**저장 규칙 기반 디렉토리 구조:**
|
||||
`storage/{company_slug}/{user_slug}/{project_id}/` 내부
|
||||
- `B03_FileInput/input/` : 원본 업로드 파일
|
||||
- `B04_wf1_Surface/processed/` : 변환 완료된 파일 (ply, 필터링 las 등)
|
||||
- `B04_wf1_Surface/models/` : 생성된 DEM, TIN 모델
|
||||
- `B05_wf2_Route/route/` : 주요 경로선 (GeoJSON) 및 제약 정보
|
||||
- `B06_wf3_ProfileCross/longitudinal/` : 종단면 데이터셋
|
||||
- `B06_wf3_ProfileCross/cross_sections/` : 구간별 횡단면 데이터셋
|
||||
- `B07_wf4_DesignDetail/structures/` : 배치된 구조물 메타데이터
|
||||
- `B08_wf5_Quantity/quantities/` : 산출 물량표 내역
|
||||
- `B09_wf6_Estimation/v1/` : 완료 버전별 패키징 결과물 (엑셀, 도면)
|
||||
@@ -1 +0,0 @@
|
||||
../.agent/design.md
|
||||
@@ -7,7 +7,8 @@
|
||||
"Bash(ruff format *)",
|
||||
"Bash(ruff check *)",
|
||||
"Bash(./venv/Scripts/python.exe -m unittest discover -s tests -p \"test_b04_surface_filter_pmf.py\" -v)",
|
||||
"Bash(./venv/Scripts/python.exe -m unittest discover -s tests -p \"test_b04_surface_filter_ransac.py\" -v)"
|
||||
"Bash(./venv/Scripts/python.exe -m unittest discover -s tests -p \"test_b04_surface_filter_ransac.py\" -v)",
|
||||
"Bash(curl -s http://localhost:5175/ui_template/ui_template_theme.css)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
SERVER_HOST=0.0.0.0
|
||||
SERVER_PORT=8000
|
||||
DEBUG=True
|
||||
ENVIRONMENT=development
|
||||
|
||||
DB_HOST=dsm.chemifactory.com
|
||||
DB_PORT=53306
|
||||
DB_NAME=aislo_db
|
||||
DB_USER=ctnt_root
|
||||
DB_PASSWORD=Umsang6595!!
|
||||
DB_POOL_MIN=5
|
||||
DB_POOL_MAX=20
|
||||
|
||||
LOG_LEVEL=INFO
|
||||
UPLOAD_MAX_MB=500
|
||||
MESH_GRID_SIZE=1.0
|
||||
MESH_SMOOTHING_ITERATIONS=0
|
||||
SPATIAL_INDEX_ENABLED=True
|
||||
JWT_SECRET_KEY=your-secret-key-change-in-production
|
||||
JWT_EXPIRE_MINUTES=1440
|
||||
CORS_ORIGINS=http://localhost:5173,http://localhost:8000
|
||||
@@ -1,40 +0,0 @@
|
||||
# 서버 설정
|
||||
SERVER_HOST=0.0.0.0
|
||||
SERVER_PORT=8000
|
||||
DEBUG=False
|
||||
ENVIRONMENT=development
|
||||
|
||||
# 데이터베이스 (MariaDB)
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=aislo_db
|
||||
DB_USER=aislo
|
||||
DB_PASSWORD=aislo
|
||||
DB_POOL_MIN=5
|
||||
DB_POOL_MAX=20
|
||||
|
||||
# 로깅
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# 파일 업로드
|
||||
UPLOAD_MAX_MB=500
|
||||
UPLOAD_MAX_FILES=20
|
||||
UPLOAD_CHUNK_SIZE_BYTES=1048576
|
||||
|
||||
# 지형 분석 파라미터
|
||||
MESH_GRID_SIZE=1.0
|
||||
MESH_SMOOTHING_ITERATIONS=0
|
||||
SURFACE_LAS_CHUNK_SIZE=500000
|
||||
SURFACE_DEFAULT_RGB_VALUE=128
|
||||
SURFACE_GRID_CELL_SIZE_M=2.0
|
||||
SURFACE_GRID_HEIGHT_THRESHOLD_M=1.5
|
||||
|
||||
# 인증
|
||||
JWT_SECRET_KEY=your-secret-key-change-in-production
|
||||
JWT_EXPIRE_MINUTES=1440
|
||||
|
||||
# CORS (프론트엔드 도메인)
|
||||
CORS_ORIGINS=http://localhost:5173,http://localhost:8000
|
||||
|
||||
# 프론트엔드 빌드 결과 폴더
|
||||
STATIC_DIR=node_modules/.build
|
||||
+13
-9
@@ -1,22 +1,26 @@
|
||||
# Python
|
||||
venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
**/__pycache__/
|
||||
**/*.pyc
|
||||
.pytest_cache/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Node / Frontend (루트 기반 구조)
|
||||
node_modules/
|
||||
dist/
|
||||
*.tsbuildinfo
|
||||
|
||||
# 구형 코드 백업 (참고용, 빌드 제외)
|
||||
0_old/node_modules/
|
||||
0_old/dist/
|
||||
config\node_modules
|
||||
|
||||
# Storage (물리 산출물)
|
||||
storage/projects/*/processed/*.json
|
||||
storage/projects/*/processed/*.png
|
||||
storage/projects/*/exports/*.json
|
||||
|
||||
#.claude
|
||||
.dependencygraph/
|
||||
.ruff_cache/
|
||||
|
||||
# 구형 코드 백업 (참고용, 빌드 제외)
|
||||
0_old/
|
||||
|
||||
# 신규 백업
|
||||
storage
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"hash": "c4f37c34",
|
||||
"configHash": "ac5d08a1",
|
||||
"lockfileHash": "35998b1e",
|
||||
"browserHash": "7f5b0cf9",
|
||||
"hash": "43705b0a",
|
||||
"configHash": "f5412da4",
|
||||
"lockfileHash": "0cc0e656",
|
||||
"browserHash": "ae6c1db5",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>임도 설계 및 견적 자동화</title>
|
||||
<link rel="stylesheet" href="/ui_template/ui_template_theme.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* - hashchange 1회 구독: 셸 chrome 갱신 + 현재 라우트 렌더
|
||||
* ========================================================================== */
|
||||
|
||||
import "@ui/ui_template_theme.css";
|
||||
import { injectBaseStyles } from "@ui/ui_template_elements";
|
||||
import { initThemeAndLang, injectShellStyles, renderShell } from "./app_shell";
|
||||
import { renderCurrentRoute, ensureInitialHash } from "./router";
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, createCard, createTag } from "@ui/ui_template_elements";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { ROUTES, AUTH_TOKEN_KEY } from "@config/config_frontend";
|
||||
import "./A01_Home_UI_Style.css";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
@@ -46,6 +46,11 @@ function onA01_Home_Explore_Click(): void {
|
||||
navigateTo(ROUTES.A02_PROG_DETAIL);
|
||||
}
|
||||
|
||||
function navigateToDevPage(route: string): void {
|
||||
localStorage.setItem(AUTH_TOKEN_KEY, "dev-temp-token");
|
||||
navigateTo(route as never);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 섹션 빌더
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -145,12 +150,56 @@ function buildFeatures(): HTMLElement {
|
||||
return section;
|
||||
}
|
||||
|
||||
function buildDevMenu(): HTMLElement {
|
||||
const section = document.createElement("section");
|
||||
section.className = "a01-section a01-dev-menu";
|
||||
section.style.opacity = "0.6";
|
||||
section.style.borderTop = "2px dashed var(--color-slate)";
|
||||
section.style.marginTop = "var(--spacing-48)";
|
||||
section.style.paddingTop = "var(--spacing-48)";
|
||||
|
||||
const heading = document.createElement("h2");
|
||||
heading.className = "a01-section__title";
|
||||
heading.textContent = "🔧 개발 모드 - B 그룹 테스트";
|
||||
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "a01-feature-grid";
|
||||
grid.style.gridTemplateColumns = "repeat(auto-fit, minmax(200px, 1fr))";
|
||||
|
||||
const bPages: [string, string][] = [
|
||||
[ROUTES.B01_ACCOUNT, "B01 · 계정 상세"],
|
||||
[ROUTES.B02_PROJ_REGISTER, "B02 · 프로젝트 등록"],
|
||||
[ROUTES.B03_FILE_INPUT, "B03 · 파일 입력"],
|
||||
[ROUTES.B04_WF1_SURFACE, "B04 · 지표면 분석"],
|
||||
[ROUTES.B05_WF2_ROUTE, "B05 · 경로 설계"],
|
||||
[ROUTES.B06_WF3_PROFILE_CROSS, "B06 · 종횡단 생성"],
|
||||
[ROUTES.B07_WF4_DESIGN_DETAIL, "B07 · 상세 설계"],
|
||||
[ROUTES.B08_WF5_QUANTITY, "B08 · 수량 산출"],
|
||||
[ROUTES.B09_WF6_ESTIMATION, "B09 · 견적/문서"],
|
||||
[ROUTES.B10_PAYMENT, "B10 · 결재"],
|
||||
[ROUTES.B11_STATUS, "B11 · 상태 출력"],
|
||||
];
|
||||
|
||||
for (const [route, label] of bPages) {
|
||||
const btn = createButton({
|
||||
label,
|
||||
variant: "ghost",
|
||||
onClick: () => navigateToDevPage(route),
|
||||
});
|
||||
btn.style.width = "100%";
|
||||
grid.append(btn);
|
||||
}
|
||||
|
||||
section.append(heading, grid);
|
||||
return section;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 페이지 진입점
|
||||
* -------------------------------------------------------------------------- */
|
||||
export function renderA01Home(root: HTMLElement): void {
|
||||
const page = document.createElement("div");
|
||||
page.className = "a01-home";
|
||||
page.append(buildHero(), buildNews(), buildFeatures());
|
||||
page.append(buildHero(), buildNews(), buildFeatures(), buildDevMenu());
|
||||
root.append(page);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# 프로젝트 행동지침 및 기술 스택 명세 (agent.md)
|
||||
|
||||
## 프로젝트 & DB 정보
|
||||
|
||||
### 📌 프로그램명
|
||||
**Aislo (아이슬로)**
|
||||
- **의미**: AI (인공지능) + Slotti (핀란드어: 임도/산림 도로)
|
||||
- **콘셉트**: 산림의 미래를 열어가는 인공지능 경로 설계 솔루션
|
||||
|
||||
---
|
||||
|
||||
## 1. 기술 환경 및 기본 스택 (Technical Stack Baseline)
|
||||
프로젝트의 코드를 작성, 테스트 또는 검증할 환경 명확화 및 호환 기준 제시.
|
||||
|
||||
### A. 백엔드 프레임워크 (Python)
|
||||
* **Runtime:** Python v3.12 or v3.13.7
|
||||
* **Framework:** FastAPI / Pydantic
|
||||
* **Geometry/GIS Engine:** Trimesh, Whitebox, Geopandas, Shapely, Rasterio, Laspy
|
||||
|
||||
### B. 데이터베이스 (MariaDB)
|
||||
* **DBMS:** MariaDB v10.6+
|
||||
* **Character Set:** utf8mb4 (한글 완벽 지원)
|
||||
* **Collation:** utf8mb4_unicode_ci
|
||||
* **DB Driver (비동기):** `aiomysql` (순수 Python, Windows 호환. asyncmy는 Cython 빌드 필요로 미채택)
|
||||
* **쿼리 방식:** Raw SQL (ORM 사용 금지)
|
||||
* **공간 데이터:** JSON 기반 저장 (MariaDB는 PostGIS 미지원)
|
||||
|
||||
### C. 프론트엔드 & WebCAD (TypeScript & WebGL)
|
||||
* **Language/Runtime:** TypeScript / Node.js
|
||||
* **Rendering:** HTML5 Canvas 및 WebGL 기반 WebCAD 시스템 구현
|
||||
|
||||
### D. 인코딩 및 다국어 표준
|
||||
* **인코딩:** 텍스트 파일은 UTF-8 표준으로 통일
|
||||
|
||||
---
|
||||
|
||||
## 2. 문맥 라우팅 및 문서 구조 (Context Routing)
|
||||
코드 작성 및 기술 검증 시 문서 우선순위에 따른 계층적 레퍼런스 필수 읽기.
|
||||
|
||||
* **1단계 (필수):** `.agent/structure.md` 읽기 후 신규 폴더 생성 기준 분석.
|
||||
* **2단계 (선택 읽기):** 프로젝트 구현에 필요한 세부 기술 명세 읽기.
|
||||
* **그룹 A (UI, 스타일, 컴포넌트, WebCAD):** `.agent/frontend.md` 필수 추가 읽기.
|
||||
* **그룹 B (알고리즘, 저장, DB, MariaDB 값 제어):** `.agent/backend.md` 필수 추가 읽기.
|
||||
* **그룹 C (DB 구조 변경):** DB 구조 변경 시 `.agent/db_schema.md` 선택적 읽기.
|
||||
|
||||
---
|
||||
|
||||
## 3. 코드 작성 기본 제약 (Constraints)
|
||||
* **구조 준수:** `.agent/structure.md` 트리 구조 엄격 준수.
|
||||
* **700줄 제한:** 단일 파일 코드 작성/수정 시 700줄 이상이 될 경우 사전 공지. 기능별 파일 분할 후 `structure.md` 갱신 필수.
|
||||
* **안전 보관:** 백엔드 작업 시 보안 저장소 경로 명시 및 임의 삭제 방지 필수.
|
||||
* **경로 활용:** 단계별 페이지 기반 폴더 구조(B03~B09)와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
|
||||
* **일관성 검증:** 설계 단계에서부터 DB 설계, 파일 경로, API 라우팅이 모두 동일한 워크플로우 기준으로 통일되어야 한다. (불일치 발생 시 설계 단계에서 재논의 필수)
|
||||
* **코드 포맷팅 (자동화 도구):** 프로젝트의 코드 작성이 완료되는 시, 반드시 프로젝트 루트에서 각 언어별 포맷터를 재실행하여 스타일을 통일해야 한다.
|
||||
* *Python 포맷팅 명령어:* `ruff format [파일명]` 및 `ruff check --fix [파일명]` 실행
|
||||
* *TypeScript/CSS 포맷팅 명령어:* `npx prettier --write [파일명]` 실행
|
||||
* **외부 라이브러리 활용:** 외부 라이브러리 활용 시 프로젝트에 맞춰 호환 라이브러리 선정하고, 불필요한 함수는 작성하지 않도록 최소화.
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터베이스 상세 명세 (Database Specification)
|
||||
|
||||
### 4.1 스키마 기본 정보
|
||||
- **DB 명:** `aislo_db`
|
||||
- **DBMS:** MariaDB v10.6+
|
||||
- **인코딩:** utf8mb4_unicode_ci
|
||||
- **테이블 수:** 18개
|
||||
- **드라이버:** aiomysql (비동기)
|
||||
|
||||
### 4.2 파일 경로 추적 (Path Tracking)
|
||||
**모든 중간 산출물의 파일 경로를 DB에 기록:**
|
||||
- `input_files.raw_file_path` — 원본 입력 파일
|
||||
- `processed_point_cloud.converted_file_path` — 변환된 포인트클라우드
|
||||
- `surface_models.model_file_path` — 지표면 모델
|
||||
- `routes.route_data_path` — 경로 데이터
|
||||
- `longitudinal_sections.longitudinal_file_path` — 종단면
|
||||
- `cross_sections.cross_section_file_path` — 횡단면
|
||||
- `structures.structure_data_path` — 구조물 배치
|
||||
- `quantity_items.quantity_data_path` — 수량 항목
|
||||
- `outputs.outputs_directory_path` — 산출물 폴더
|
||||
- `output_files.output_file_path` — 개별 산출 파일
|
||||
|
||||
### 4.3 저장소 구조 (Workflow-based Folder Structure)
|
||||
```
|
||||
storage/{company_slug}/{user_slug}/{project_id}/
|
||||
├── B03_FileInput/input/ (WF0: 원본 입력)
|
||||
├── B04_wf1_Surface/processed/ (WF1: 변환된 포인트클라우드 & 모델)
|
||||
├── B05_wf2_Route/route/ (WF2: 경로 설계)
|
||||
├── B06_wf3_ProfileCross/ (WF3: 종단면 & 횡단면)
|
||||
├── B07_wf4_DesignDetail/structures/ (WF4: 구조물 배치)
|
||||
├── B08_wf5_Quantity/quantities/ (WF5: 수량 산출)
|
||||
└── B09_wf6_Estimation/v1,v2,.../ (WF6: 최종 산출물)
|
||||
```
|
||||
|
||||
### 4.4 공간 데이터 처리 (MariaDB 특성)
|
||||
- **지하형 기하 데이터:** GEOMETRY 타입 미지원 → JSON으로 저장
|
||||
- **좌표 저장 예:** `{"type": "Point", "coordinates": [127.5, 37.5]}`
|
||||
- **경로 저장 예:** `{"type": "LineString", "coordinates": [[127.5, 37.5], [127.6, 37.6]]}`
|
||||
- **애플리케이션 처리:** Python의 Shapely, Geopandas에서 JSON 파싱 후 기하 연산
|
||||
|
||||
---
|
||||
|
||||
## 5. 워크플로우 6단계 (6-Stage Workflow)
|
||||
|
||||
```
|
||||
WF0: B03_FileInput (파일 입력)
|
||||
↓ (LAS, TIF, TFW, PRJ, DXF 업로드)
|
||||
WF1: B04_wf1_Surface (지표면 분석)
|
||||
↓ (DEM, TIN, 포인트클라우드 변환)
|
||||
WF2: B05_wf2_Route (경로 설계)
|
||||
↓ (최적 경로 계산)
|
||||
WF3: B06_wf3_ProfileCross (종횡단 생성)
|
||||
↓ (종단면, 횡단면 생성)
|
||||
WF4: B07_wf4_DesignDetail (상세 설계)
|
||||
↓ (구조물 배치)
|
||||
WF5: B08_wf5_Quantity (수량 산출)
|
||||
↓ (수량 항목 계산)
|
||||
WF6: B09_wf6_Estimation (견적·문서)
|
||||
↓ (Excel, PDF, DXF 생성)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 주요 문서 참고 순서
|
||||
|
||||
1. **구조 설계:** `.agent/structure.md` 읽기
|
||||
2. **프론트 구현:** `.agent/frontend.md` 읽기
|
||||
3. **백엔드 구현:** `.agent/backend.md` 읽기
|
||||
4. **마이그레이션:** `.agent/migration_plan.md` 읽기
|
||||
5. **DB 스키마:** `.agent/db_schema_simple.md` 읽기
|
||||
6. **프로젝트 정보:** `.agent/project_info.md` 읽기
|
||||
|
||||
---
|
||||
|
||||
## 7. 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| **프로그램명** | Aislo (아이슬로) |
|
||||
| **DB 명** | aislo_db |
|
||||
| **DBMS** | MariaDB v10.6+ |
|
||||
| **인코딩** | utf8mb4_unicode_ci |
|
||||
| **Backend** | Python 3.12+ / FastAPI |
|
||||
| **Frontend** | TypeScript / Node.js |
|
||||
| **드라이버** | aiomysql |
|
||||
| **워크플로우** | 6단계 (WF0~WF6) |
|
||||
| **테이블 수** | 18개 |
|
||||
| **폴더 구조** | 워크플로우 기반 (B03~B09) |
|
||||
@@ -0,0 +1,109 @@
|
||||
/* =============================================================================
|
||||
* B04_wf1_Surface_UI_Style.css
|
||||
* 1차 워크플로우(지표면 모델 분석) 페이지 전용 스타일.
|
||||
*
|
||||
* 원칙(frontend.md §1): 하드코딩 색상 금지. theme.css 변수(var(--...))만 참조.
|
||||
* 공통 컴포넌트(ui-wf, ui-btn 등) 스타일은 ui_template_elements.ts가 주입하므로
|
||||
* 여기서는 B04 고유 레이아웃(폼 그룹/모델 카드)만 정의한다.
|
||||
* ========================================================================== */
|
||||
|
||||
/* --- 좌측 입력 폼 --- */
|
||||
.b04-surface__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b04-surface__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
margin: 0;
|
||||
padding: var(--spacing-16);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.b04-surface__group-legend {
|
||||
padding: 0 var(--spacing-8);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.b04-surface__check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.b04-surface__check input {
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* --- 우측 결과 영역 --- */
|
||||
.b04-surface__result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-24);
|
||||
}
|
||||
|
||||
.b04-surface__result-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.b04-surface__result-head h3 {
|
||||
font-size: var(--text-subheading);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.b04-surface__models {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b04-surface__empty {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.b04-surface__model-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--spacing-16);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface-raised);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.b04-surface__model-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b04-surface__model-head strong {
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-plum-velvet);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.b04-surface__model-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/* =============================================================================
|
||||
* B05_wf2_Route_Api_Fetch.ts
|
||||
* 2차 워크플로우(경로 설계) API 클라이언트
|
||||
*
|
||||
* 백엔드 계약 (B05_wf2_Route_Router.py):
|
||||
* POST /api/projects/{project_id}/route/solve → 경로 탐색 + DB 기록
|
||||
* POST /api/projects/{project_id}/route/confirm → 최신 경로 확정
|
||||
*
|
||||
* 규칙:
|
||||
* - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지).
|
||||
* - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환.
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL, API_TIMEOUT_MS, AUTH_TOKEN_KEY } from "@config/config_frontend";
|
||||
|
||||
/** 경로 제어점 (BP/EP/CP) */
|
||||
export interface RoutePoint {
|
||||
x: number;
|
||||
y: number;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
/** 경로 탐색 실행 요청 (RouteSolveRequest) */
|
||||
export interface RouteSolveRequest {
|
||||
filter_key: string;
|
||||
method?: string;
|
||||
smooth?: boolean;
|
||||
surface_model_id?: number | null;
|
||||
algorithm?: string;
|
||||
bp: RoutePoint;
|
||||
ep: RoutePoint;
|
||||
cp?: RoutePoint[];
|
||||
grade_class?: string;
|
||||
min_curve_radius_m?: number | null;
|
||||
max_uphill_grade?: number | null;
|
||||
max_downhill_grade?: number | null;
|
||||
}
|
||||
|
||||
/** 경로 탐색 실행 결과 (RouteSolveResponse) */
|
||||
export interface RouteSolveResponse {
|
||||
status: string;
|
||||
project_id: string;
|
||||
route_id: number;
|
||||
total_length_m: number;
|
||||
metrics: Record<string, unknown>;
|
||||
required_points_ok: boolean;
|
||||
route_data_path: string;
|
||||
}
|
||||
|
||||
/** 경로 확정 결과 (RouteConfirmResponse) */
|
||||
export interface RouteConfirmResponse {
|
||||
status: string;
|
||||
project_id: string;
|
||||
route_id: number;
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
/** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환. */
|
||||
async function requestJson<T>(path: string, init: RequestInit): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
||||
const token = localStorage.getItem(AUTH_TOKEN_KEY);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(init.headers ?? {}),
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
const payload = (await response.json()) as T & { message?: string };
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.message ?? `HTTP ${response.status}`);
|
||||
}
|
||||
return payload;
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 경로 탐색을 실행한다 (비용면 생성 → Dijkstra/ridge-valley → GeoJSON 저장). */
|
||||
export async function solveRoute(
|
||||
projectId: string,
|
||||
request: RouteSolveRequest,
|
||||
): Promise<RouteSolveResponse> {
|
||||
return requestJson<RouteSolveResponse>(`/projects/${projectId}/route/solve`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
/** 프로젝트의 최신 경로를 확정한다. */
|
||||
export async function confirmRoute(projectId: string): Promise<RouteConfirmResponse> {
|
||||
return requestJson<RouteConfirmResponse>(`/projects/${projectId}/route/confirm`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
@@ -2,27 +2,344 @@
|
||||
* B05_wf2_Route_UI_Page.ts
|
||||
* 로그인 후 05: 2차 워크플로우 (경로 설계)
|
||||
*
|
||||
* ⚠️ 좌측 입력 패널 / 우측 WebCAD 뷰어 본문은 준비 중 — 워크플로우 셸(헤더+
|
||||
* 스텝바 = 3단 레이아웃)만 구성. 실제 본문은 0_old 참고하여 추후 구체화.
|
||||
* 3단 레이아웃 (frontend.md §2):
|
||||
* 상단: 페이지 타이틀 + 진행 단계 스텝바 (createWorkflowShell)
|
||||
* 좌측: 경로 제어점(BP/EP/CP) 좌표 + 기반 지표면 + 설계 제약 폼
|
||||
* 우측: 경로 탐색 결과(연장·경사·비용) 카드
|
||||
*
|
||||
* 제약 준수 (frontend.md §2 3단 레이아웃): createWorkflowShell 재사용.
|
||||
* 이벤트 핸들러 명명 (frontend.md §4): onB05_Route_[기능]_[액션]
|
||||
* 텍스트는 ui_template_locale에 선(先) 등록 후 참조 (frontend.md §3).
|
||||
* ========================================================================== */
|
||||
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { renderPendingWorkflow, workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
createWorkflowShell,
|
||||
hideLoadingOverlay,
|
||||
showLoadingOverlay,
|
||||
showToast,
|
||||
type InputFieldHandle,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import {
|
||||
confirmRoute,
|
||||
solveRoute,
|
||||
type RoutePoint,
|
||||
type RouteSolveResponse,
|
||||
} from "./B05_wf2_Route_Api_Fetch";
|
||||
import "./B05_wf2_Route_UI_Style.css";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 페이지 진입점
|
||||
* -------------------------------------------------------------------------- */
|
||||
/** 임도 등급 (config_system.ROUTE_GRADE_CLASSES) */
|
||||
const GRADE_CLASSES = ["trunk", "branch", "work"] as const;
|
||||
/** 경로 알고리즘 (Schema.algorithm 허용값) */
|
||||
const ALGORITHMS = ["dijkstra", "ridge_valley"] as const;
|
||||
|
||||
/** 라벨 + select 요소 하나 생성. */
|
||||
function buildSelect(
|
||||
label: string,
|
||||
values: readonly string[],
|
||||
): { root: HTMLElement; select: HTMLSelectElement } {
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-field";
|
||||
const labelEl = document.createElement("label");
|
||||
labelEl.className = "ui-field__label";
|
||||
labelEl.textContent = label;
|
||||
const select = document.createElement("select");
|
||||
select.className = "ui-input b05-route__select";
|
||||
for (const value of values) {
|
||||
const option = document.createElement("option");
|
||||
option.value = value;
|
||||
option.textContent = value;
|
||||
select.append(option);
|
||||
}
|
||||
root.append(labelEl, select);
|
||||
return { root, select };
|
||||
}
|
||||
|
||||
/** X/Y 좌표 한 쌍 입력 행 생성. */
|
||||
function buildPointRow(label: string): {
|
||||
root: HTMLElement;
|
||||
x: InputFieldHandle;
|
||||
y: InputFieldHandle;
|
||||
} {
|
||||
const root = document.createElement("div");
|
||||
root.className = "b05-route__point";
|
||||
const caption = document.createElement("span");
|
||||
caption.className = "b05-route__point-label";
|
||||
caption.textContent = label;
|
||||
const x = createInputField({ label: L("B05_Route_Field_X"), type: "number" });
|
||||
const y = createInputField({ label: L("B05_Route_Field_Y"), type: "number" });
|
||||
const row = document.createElement("div");
|
||||
row.className = "b05-route__point-row";
|
||||
row.append(x.root, y.root);
|
||||
root.append(caption, row);
|
||||
return { root, x, y };
|
||||
}
|
||||
|
||||
/** 숫자 입력값을 파싱. 빈 값이면 null. */
|
||||
function parseNumber(value: string): number | null {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return null;
|
||||
const parsed = Number(trimmed);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
export function renderB05Route(root: HTMLElement): void {
|
||||
renderPendingWorkflow(root, {
|
||||
const shell = createWorkflowShell({
|
||||
title: L("B05_Route_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 1,
|
||||
});
|
||||
|
||||
/* ---- 좌측: 경로 제어점 ---- */
|
||||
const pointsGroup = document.createElement("fieldset");
|
||||
pointsGroup.className = "b05-route__group";
|
||||
const pointsLegend = document.createElement("legend");
|
||||
pointsLegend.className = "b05-route__group-legend";
|
||||
pointsLegend.textContent = L("B05_Route_Group_Points");
|
||||
const bpRow = buildPointRow(L("B05_Route_Point_BP"));
|
||||
const epRow = buildPointRow(L("B05_Route_Point_EP"));
|
||||
const cpContainer = document.createElement("div");
|
||||
cpContainer.className = "b05-route__cp-list";
|
||||
const cpRows: { root: HTMLElement; x: InputFieldHandle; y: InputFieldHandle }[] = [];
|
||||
|
||||
function onB05_Route_AddCp_Click(): void {
|
||||
const row = buildPointRow(L("B05_Route_Point_CP"));
|
||||
const removeBtn = createButton({
|
||||
label: L("B05_Route_Btn_RemoveCp"),
|
||||
variant: "ghost",
|
||||
onClick: () => {
|
||||
const idx = cpRows.indexOf(row);
|
||||
if (idx >= 0) cpRows.splice(idx, 1);
|
||||
row.root.remove();
|
||||
},
|
||||
});
|
||||
row.root.append(removeBtn);
|
||||
cpRows.push(row);
|
||||
cpContainer.append(row.root);
|
||||
}
|
||||
|
||||
const addCpButton = createButton({
|
||||
label: L("B05_Route_Btn_AddCp"),
|
||||
variant: "ghost",
|
||||
onClick: () => onB05_Route_AddCp_Click(),
|
||||
});
|
||||
pointsGroup.append(pointsLegend, bpRow.root, epRow.root, cpContainer, addCpButton);
|
||||
|
||||
/* ---- 좌측: 기반 지표면 ---- */
|
||||
const surfaceGroup = document.createElement("fieldset");
|
||||
surfaceGroup.className = "b05-route__group";
|
||||
const surfaceLegend = document.createElement("legend");
|
||||
surfaceLegend.className = "b05-route__group-legend";
|
||||
surfaceLegend.textContent = L("B05_Route_Group_Surface");
|
||||
const filterField = createInputField({ label: L("B05_Route_Field_Filter"), type: "text" });
|
||||
const methodField = createInputField({
|
||||
label: L("B05_Route_Field_Method"),
|
||||
type: "text",
|
||||
value: "dtm",
|
||||
});
|
||||
const surfaceIdField = createInputField({
|
||||
label: L("B05_Route_Field_SurfaceId"),
|
||||
type: "number",
|
||||
min: 1,
|
||||
});
|
||||
surfaceGroup.append(surfaceLegend, filterField.root, methodField.root, surfaceIdField.root);
|
||||
|
||||
/* ---- 좌측: 설계 제약 ---- */
|
||||
const constraintGroup = document.createElement("fieldset");
|
||||
constraintGroup.className = "b05-route__group";
|
||||
const constraintLegend = document.createElement("legend");
|
||||
constraintLegend.className = "b05-route__group-legend";
|
||||
constraintLegend.textContent = L("B05_Route_Group_Constraints");
|
||||
const gradeSelect = buildSelect(L("B05_Route_Field_GradeClass"), GRADE_CLASSES);
|
||||
const algorithmSelect = buildSelect(L("B05_Route_Field_Algorithm"), ALGORITHMS);
|
||||
const maxUphillField = createInputField({
|
||||
label: L("B05_Route_Field_MaxUphill"),
|
||||
type: "number",
|
||||
});
|
||||
const maxDownhillField = createInputField({
|
||||
label: L("B05_Route_Field_MaxDownhill"),
|
||||
type: "number",
|
||||
});
|
||||
const minRadiusField = createInputField({
|
||||
label: L("B05_Route_Field_MinRadius"),
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const smoothLabel = document.createElement("label");
|
||||
smoothLabel.className = "b05-route__check";
|
||||
const smoothBox = document.createElement("input");
|
||||
smoothBox.type = "checkbox";
|
||||
const smoothText = document.createElement("span");
|
||||
smoothText.textContent = L("B05_Route_Field_Smooth");
|
||||
smoothLabel.append(smoothBox, smoothText);
|
||||
|
||||
constraintGroup.append(
|
||||
constraintLegend,
|
||||
gradeSelect.root,
|
||||
algorithmSelect.root,
|
||||
maxUphillField.root,
|
||||
maxDownhillField.root,
|
||||
minRadiusField.root,
|
||||
smoothLabel,
|
||||
);
|
||||
|
||||
const solveButton = createButton({
|
||||
label: L("B05_Route_Btn_Solve"),
|
||||
variant: "filled",
|
||||
onClick: () => void onB05_Route_Solve_Click(),
|
||||
});
|
||||
const confirmButton = createButton({
|
||||
label: L("B05_Route_Btn_Confirm"),
|
||||
variant: "ghost",
|
||||
onClick: () => void onB05_Route_Confirm_Click(),
|
||||
});
|
||||
const actionRow = document.createElement("div");
|
||||
actionRow.className = "b05-route__actions";
|
||||
actionRow.append(solveButton, confirmButton);
|
||||
|
||||
const leftForm = document.createElement("div");
|
||||
leftForm.className = "b05-route__form";
|
||||
leftForm.append(pointsGroup, surfaceGroup, constraintGroup, actionRow);
|
||||
shell.leftPanel.append(leftForm);
|
||||
|
||||
/* ---- 우측: 결과 ---- */
|
||||
const resultTitle = document.createElement("h3");
|
||||
resultTitle.className = "b05-route__result-title";
|
||||
resultTitle.textContent = L("B05_Route_Result_Title");
|
||||
const resultBody = document.createElement("div");
|
||||
resultBody.className = "b05-route__result-body";
|
||||
|
||||
function renderEmptyResult(): void {
|
||||
resultBody.replaceChildren();
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b05-route__empty";
|
||||
empty.textContent = L("B05_Route_Result_Empty");
|
||||
resultBody.append(empty);
|
||||
}
|
||||
|
||||
function metricRow(label: string, value: string): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "b05-route__metric";
|
||||
const key = document.createElement("span");
|
||||
key.className = "b05-route__metric-key";
|
||||
key.textContent = label;
|
||||
const val = document.createElement("span");
|
||||
val.className = "b05-route__metric-val";
|
||||
val.textContent = value;
|
||||
row.append(key, val);
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderResult(result: RouteSolveResponse): void {
|
||||
resultBody.replaceChildren();
|
||||
const metrics = result.metrics as Record<string, number | undefined>;
|
||||
const fmt = (v: number | undefined): string => (v === undefined ? "-" : v.toFixed(3));
|
||||
resultBody.append(
|
||||
metricRow(L("B05_Route_Result_Length"), result.total_length_m.toFixed(2)),
|
||||
metricRow(L("B05_Route_Result_MinSlope"), fmt(metrics.min_slope)),
|
||||
metricRow(L("B05_Route_Result_MaxSlope"), fmt(metrics.max_slope)),
|
||||
metricRow(L("B05_Route_Result_MeanSlope"), fmt(metrics.mean_slope)),
|
||||
metricRow(L("B05_Route_Result_Cost"), fmt(metrics.cost_score)),
|
||||
metricRow(L("B05_Route_Result_Path"), result.route_data_path),
|
||||
);
|
||||
}
|
||||
|
||||
const resultCard = document.createElement("div");
|
||||
resultCard.className = "b05-route__result";
|
||||
resultCard.append(resultTitle, resultBody);
|
||||
shell.rightArea.append(resultCard);
|
||||
|
||||
/* ---- 이벤트 핸들러 ---- */
|
||||
function getProjectId(): string | null {
|
||||
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
||||
if (!projectId) showToast(L("B05_Route_Error_Project"), "error");
|
||||
return projectId;
|
||||
}
|
||||
|
||||
/** 필수 좌표(BP/EP)와 필터 키를 검증하고, 유효하면 요청 페이로드를 반환. */
|
||||
function collectRequest(): {
|
||||
filter_key: string;
|
||||
bp: RoutePoint;
|
||||
ep: RoutePoint;
|
||||
cp: RoutePoint[];
|
||||
} | null {
|
||||
const bpX = parseNumber(bpRow.x.input.value);
|
||||
const bpY = parseNumber(bpRow.y.input.value);
|
||||
const epX = parseNumber(epRow.x.input.value);
|
||||
const epY = parseNumber(epRow.y.input.value);
|
||||
if (bpX === null || bpY === null || epX === null || epY === null) {
|
||||
showToast(L("B05_Route_Error_Points"), "error");
|
||||
return null;
|
||||
}
|
||||
const filterKey = filterField.input.value.trim();
|
||||
if (!filterKey) {
|
||||
filterField.setError(L("B05_Route_Error_Filter"));
|
||||
return null;
|
||||
}
|
||||
filterField.setError();
|
||||
|
||||
const cp: RoutePoint[] = [];
|
||||
for (const row of cpRows) {
|
||||
const x = parseNumber(row.x.input.value);
|
||||
const y = parseNumber(row.y.input.value);
|
||||
if (x !== null && y !== null) cp.push({ x, y });
|
||||
}
|
||||
return { filter_key: filterKey, bp: { x: bpX, y: bpY }, ep: { x: epX, y: epY }, cp };
|
||||
}
|
||||
|
||||
async function onB05_Route_Solve_Click(): Promise<void> {
|
||||
const projectId = getProjectId();
|
||||
if (!projectId) return;
|
||||
const base = collectRequest();
|
||||
if (!base) return;
|
||||
|
||||
const surfaceId = parseNumber(surfaceIdField.input.value);
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const result = await solveRoute(projectId, {
|
||||
...base,
|
||||
method: methodField.input.value.trim() || "dtm",
|
||||
smooth: smoothBox.checked,
|
||||
surface_model_id: surfaceId,
|
||||
algorithm: algorithmSelect.select.value,
|
||||
grade_class: gradeSelect.select.value,
|
||||
max_uphill_grade: parseNumber(maxUphillField.input.value),
|
||||
max_downhill_grade: parseNumber(maxDownhillField.input.value),
|
||||
min_curve_radius_m: parseNumber(minRadiusField.input.value),
|
||||
});
|
||||
renderResult(result);
|
||||
showToast(L("B05_Route_Solve_Success"), "success");
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? error.message : L("B05_Route_Solve_Failed");
|
||||
showToast(`${L("B05_Route_Solve_Failed")} ${detail}`, "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
async function onB05_Route_Confirm_Click(): Promise<void> {
|
||||
const projectId = getProjectId();
|
||||
if (!projectId) return;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
await confirmRoute(projectId);
|
||||
showToast(L("B05_Route_Confirm_Success"), "success");
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? error.message : L("B05_Route_Confirm_Failed");
|
||||
showToast(`${L("B05_Route_Confirm_Failed")} ${detail}`, "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
renderEmptyResult();
|
||||
root.replaceChildren(shell.root);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/* =============================================================================
|
||||
* B05_wf2_Route_UI_Style.css
|
||||
* 2차 워크플로우(경로 설계) 페이지 전용 스타일.
|
||||
*
|
||||
* 원칙(frontend.md §1): 하드코딩 색상 금지. theme.css 변수(var(--...))만 참조.
|
||||
* 공통 컴포넌트 스타일은 ui_template_elements.ts가 주입하므로 여기서는
|
||||
* B05 고유 레이아웃(제어점 행/제약 그룹/결과 메트릭)만 정의한다.
|
||||
* ========================================================================== */
|
||||
|
||||
/* --- 좌측 입력 폼 --- */
|
||||
.b05-route__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b05-route__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
margin: 0;
|
||||
padding: var(--spacing-16);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.b05-route__group-legend {
|
||||
padding: 0 var(--spacing-8);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* --- 제어점 (X/Y 쌍) --- */
|
||||
.b05-route__point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b05-route__point-label {
|
||||
font-size: var(--text-body-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
.b05-route__point-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b05-route__cp-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
/* --- select (지표면/제약) --- */
|
||||
.b05-route__select {
|
||||
appearance: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* --- 체크박스 --- */
|
||||
.b05-route__check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.b05-route__check input {
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* --- 액션 버튼 행 --- */
|
||||
.b05-route__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* --- 우측 결과 --- */
|
||||
.b05-route__result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-24);
|
||||
}
|
||||
|
||||
.b05-route__result-title {
|
||||
font-size: var(--text-subheading);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.b05-route__empty {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.b05-route__result-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.b05-route__metric {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-8) var(--spacing-16);
|
||||
border-radius: var(--radius-inputs);
|
||||
background-color: var(--color-surface);
|
||||
}
|
||||
|
||||
.b05-route__metric-key {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.b05-route__metric-val {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* =============================================================================
|
||||
* B06_wf3_ProfileCross_Api_Fetch.ts
|
||||
* 3차 워크플로우(종·횡단 생성) API 클라이언트
|
||||
*
|
||||
* 백엔드 계약 (B06_wf3_ProfileCross_Router.py):
|
||||
* POST /api/projects/{project_id}/sections/generate → 종횡단 생성 + DB 기록
|
||||
* GET /api/projects/{project_id}/sections/{route_id} → 종단 요약 조회
|
||||
* POST /api/projects/{project_id}/sections/{route_id}/confirm → 종횡단 확정
|
||||
*
|
||||
* 규칙:
|
||||
* - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지).
|
||||
* - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환.
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL, API_TIMEOUT_MS, AUTH_TOKEN_KEY } from "@config/config_frontend";
|
||||
|
||||
/** 종횡단 생성 실행 요청 (SectionGenerateRequest) */
|
||||
export interface SectionGenerateRequest {
|
||||
route_id: number;
|
||||
filter_key: string;
|
||||
method?: string;
|
||||
smooth?: boolean;
|
||||
crs?: string | null;
|
||||
station_interval_m?: number | null;
|
||||
cross_half_width_m?: number | null;
|
||||
cross_sample_interval_m?: number | null;
|
||||
long_sample_interval_m?: number | null;
|
||||
}
|
||||
|
||||
/** 종횡단 생성 결과 (SectionGenerateResponse) */
|
||||
export interface SectionGenerateResponse {
|
||||
status: string;
|
||||
project_id: string;
|
||||
route_id: number;
|
||||
longitudinal_id: number;
|
||||
cross_section_count: number;
|
||||
length_m: number;
|
||||
longitudinal_file_path: string;
|
||||
}
|
||||
|
||||
/** 종단 요약 조회 결과 (SectionSummaryResponse) */
|
||||
export interface SectionSummaryResponse {
|
||||
status: string;
|
||||
project_id: string;
|
||||
route_id: number;
|
||||
longitudinal: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
/** 종횡단 확정 결과 (SectionConfirmResponse) */
|
||||
export interface SectionConfirmResponse {
|
||||
status: string;
|
||||
project_id: string;
|
||||
route_id: number;
|
||||
confirmed: boolean;
|
||||
}
|
||||
|
||||
/** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환. */
|
||||
async function requestJson<T>(path: string, init: RequestInit): Promise<T> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
||||
const token = localStorage.getItem(AUTH_TOKEN_KEY);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(init.headers ?? {}),
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
const payload = (await response.json()) as T & { message?: string };
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.message ?? `HTTP ${response.status}`);
|
||||
}
|
||||
return payload;
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
/** 확정 경로에서 종·횡단을 생성한다 (측점 배열 → 종단 프로필 → 횡단 샘플). */
|
||||
export async function generateSections(
|
||||
projectId: string,
|
||||
request: SectionGenerateRequest,
|
||||
): Promise<SectionGenerateResponse> {
|
||||
return requestJson<SectionGenerateResponse>(`/projects/${projectId}/sections/generate`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
/** 경로의 종단면 요약을 조회한다. */
|
||||
export async function getSections(
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): Promise<SectionSummaryResponse> {
|
||||
return requestJson<SectionSummaryResponse>(`/projects/${projectId}/sections/${routeId}`, {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
||||
/** 경로의 종·횡단면을 확정한다. */
|
||||
export async function confirmSections(
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): Promise<SectionConfirmResponse> {
|
||||
return requestJson<SectionConfirmResponse>(`/projects/${projectId}/sections/${routeId}/confirm`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
@@ -2,27 +2,251 @@
|
||||
* B06_wf3_ProfileCross_UI_Page.ts
|
||||
* 로그인 후 06: 3차 워크플로우 (종·횡단 생성)
|
||||
*
|
||||
* ⚠️ 좌측 입력 패널 / 우측 WebCAD 뷰어 본문은 준비 중 — 워크플로우 셸(헤더+
|
||||
* 스텝바 = 3단 레이아웃)만 구성. 실제 본문은 0_old 참고하여 추후 구체화.
|
||||
* 3단 레이아웃 (frontend.md §2):
|
||||
* 상단: 페이지 타이틀 + 진행 단계 스텝바 (createWorkflowShell)
|
||||
* 좌측: 대상 경로 ID + 지표면 참조 + 측점/횡단 옵션 폼
|
||||
* 우측: 종·횡단 생성 결과(연장·횡단 개수·파일) 카드
|
||||
*
|
||||
* 제약 준수 (frontend.md §2 3단 레이아웃): createWorkflowShell 재사용.
|
||||
* 이벤트 핸들러 명명 (frontend.md §4): onB06_Profile_[기능]_[액션]
|
||||
* 텍스트는 ui_template_locale에 선(先) 등록 후 참조 (frontend.md §3).
|
||||
* ========================================================================== */
|
||||
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { renderPendingWorkflow, workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
|
||||
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
createWorkflowShell,
|
||||
hideLoadingOverlay,
|
||||
showLoadingOverlay,
|
||||
showToast,
|
||||
} from "@ui/ui_template_elements";
|
||||
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import {
|
||||
confirmSections,
|
||||
generateSections,
|
||||
type SectionGenerateResponse,
|
||||
} from "./B06_wf3_ProfileCross_Api_Fetch";
|
||||
import "./B06_wf3_ProfileCross_UI_Style.css";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 페이지 진입점
|
||||
* -------------------------------------------------------------------------- */
|
||||
/** 라벨 + fieldset 그룹 컨테이너 생성. */
|
||||
function buildGroup(legend: string): HTMLElement {
|
||||
const group = document.createElement("fieldset");
|
||||
group.className = "b06-profile__group";
|
||||
const legendEl = document.createElement("legend");
|
||||
legendEl.className = "b06-profile__group-legend";
|
||||
legendEl.textContent = legend;
|
||||
group.append(legendEl);
|
||||
return group;
|
||||
}
|
||||
|
||||
/** 숫자 입력값을 파싱. 빈 값이면 null. */
|
||||
function parseNumber(value: string): number | null {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return null;
|
||||
const parsed = Number(trimmed);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
export function renderB06ProfileCross(root: HTMLElement): void {
|
||||
renderPendingWorkflow(root, {
|
||||
const shell = createWorkflowShell({
|
||||
title: L("B06_Profile_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 2,
|
||||
});
|
||||
|
||||
let currentRouteId: number | null = null;
|
||||
|
||||
/* ---- 좌측: 대상 경로 ---- */
|
||||
const routeGroup = buildGroup(L("B06_Profile_Group_Route"));
|
||||
const routeIdField = createInputField({
|
||||
label: L("B06_Profile_Field_RouteId"),
|
||||
type: "number",
|
||||
min: 1,
|
||||
});
|
||||
const filterField = createInputField({ label: L("B06_Profile_Field_Filter"), type: "text" });
|
||||
const methodField = createInputField({
|
||||
label: L("B06_Profile_Field_Method"),
|
||||
type: "text",
|
||||
value: "dtm",
|
||||
});
|
||||
const crsField = createInputField({
|
||||
label: L("B06_Profile_Field_Crs"),
|
||||
type: "text",
|
||||
placeholder: "EPSG:5178",
|
||||
});
|
||||
routeGroup.append(routeIdField.root, filterField.root, methodField.root, crsField.root);
|
||||
|
||||
/* ---- 좌측: 측점/횡단 옵션 ---- */
|
||||
const optionGroup = buildGroup(L("B06_Profile_Group_Options"));
|
||||
const stationField = createInputField({
|
||||
label: L("B06_Profile_Field_StationInterval"),
|
||||
type: "number",
|
||||
});
|
||||
const halfWidthField = createInputField({
|
||||
label: L("B06_Profile_Field_CrossHalfWidth"),
|
||||
type: "number",
|
||||
});
|
||||
const crossSampleField = createInputField({
|
||||
label: L("B06_Profile_Field_CrossSample"),
|
||||
type: "number",
|
||||
});
|
||||
const longSampleField = createInputField({
|
||||
label: L("B06_Profile_Field_LongSample"),
|
||||
type: "number",
|
||||
});
|
||||
|
||||
const smoothLabel = document.createElement("label");
|
||||
smoothLabel.className = "b06-profile__check";
|
||||
const smoothBox = document.createElement("input");
|
||||
smoothBox.type = "checkbox";
|
||||
const smoothText = document.createElement("span");
|
||||
smoothText.textContent = L("B06_Profile_Field_Smooth");
|
||||
smoothLabel.append(smoothBox, smoothText);
|
||||
|
||||
optionGroup.append(
|
||||
stationField.root,
|
||||
halfWidthField.root,
|
||||
crossSampleField.root,
|
||||
longSampleField.root,
|
||||
smoothLabel,
|
||||
);
|
||||
|
||||
const generateButton = createButton({
|
||||
label: L("B06_Profile_Btn_Generate"),
|
||||
variant: "filled",
|
||||
onClick: () => void onB06_Profile_Generate_Click(),
|
||||
});
|
||||
const confirmButton = createButton({
|
||||
label: L("B06_Profile_Btn_Confirm"),
|
||||
variant: "ghost",
|
||||
onClick: () => void onB06_Profile_Confirm_Click(),
|
||||
});
|
||||
const actionRow = document.createElement("div");
|
||||
actionRow.className = "b06-profile__actions";
|
||||
actionRow.append(generateButton, confirmButton);
|
||||
|
||||
const leftForm = document.createElement("div");
|
||||
leftForm.className = "b06-profile__form";
|
||||
leftForm.append(routeGroup, optionGroup, actionRow);
|
||||
shell.leftPanel.append(leftForm);
|
||||
|
||||
/* ---- 우측: 결과 ---- */
|
||||
const resultTitle = document.createElement("h3");
|
||||
resultTitle.className = "b06-profile__result-title";
|
||||
resultTitle.textContent = L("B06_Profile_Result_Title");
|
||||
const resultBody = document.createElement("div");
|
||||
resultBody.className = "b06-profile__result-body";
|
||||
|
||||
function renderEmptyResult(): void {
|
||||
resultBody.replaceChildren();
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b06-profile__empty";
|
||||
empty.textContent = L("B06_Profile_Result_Empty");
|
||||
resultBody.append(empty);
|
||||
}
|
||||
|
||||
function metricRow(label: string, value: string): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "b06-profile__metric";
|
||||
const key = document.createElement("span");
|
||||
key.className = "b06-profile__metric-key";
|
||||
key.textContent = label;
|
||||
const val = document.createElement("span");
|
||||
val.className = "b06-profile__metric-val";
|
||||
val.textContent = value;
|
||||
row.append(key, val);
|
||||
return row;
|
||||
}
|
||||
|
||||
function renderResult(result: SectionGenerateResponse): void {
|
||||
resultBody.replaceChildren();
|
||||
resultBody.append(
|
||||
metricRow(L("B06_Profile_Result_Length"), result.length_m.toFixed(2)),
|
||||
metricRow(L("B06_Profile_Result_CrossCount"), String(result.cross_section_count)),
|
||||
metricRow(L("B06_Profile_Result_Path"), result.longitudinal_file_path),
|
||||
);
|
||||
}
|
||||
|
||||
const resultCard = document.createElement("div");
|
||||
resultCard.className = "b06-profile__result";
|
||||
resultCard.append(resultTitle, resultBody);
|
||||
shell.rightArea.append(resultCard);
|
||||
|
||||
/* ---- 이벤트 핸들러 ---- */
|
||||
function getProjectId(): string | null {
|
||||
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
||||
if (!projectId) showToast(L("B06_Profile_Error_Project"), "error");
|
||||
return projectId;
|
||||
}
|
||||
|
||||
async function onB06_Profile_Generate_Click(): Promise<void> {
|
||||
const projectId = getProjectId();
|
||||
if (!projectId) return;
|
||||
|
||||
const routeId = parseNumber(routeIdField.input.value);
|
||||
if (routeId === null || !Number.isInteger(routeId) || routeId <= 0) {
|
||||
routeIdField.setError(L("B06_Profile_Error_RouteId"));
|
||||
return;
|
||||
}
|
||||
routeIdField.setError();
|
||||
|
||||
const filterKey = filterField.input.value.trim();
|
||||
if (!filterKey) {
|
||||
filterField.setError(L("B06_Profile_Error_Filter"));
|
||||
return;
|
||||
}
|
||||
filterField.setError();
|
||||
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const result = await generateSections(projectId, {
|
||||
route_id: routeId,
|
||||
filter_key: filterKey,
|
||||
method: methodField.input.value.trim() || "dtm",
|
||||
smooth: smoothBox.checked,
|
||||
crs: crsField.input.value.trim() || null,
|
||||
station_interval_m: parseNumber(stationField.input.value),
|
||||
cross_half_width_m: parseNumber(halfWidthField.input.value),
|
||||
cross_sample_interval_m: parseNumber(crossSampleField.input.value),
|
||||
long_sample_interval_m: parseNumber(longSampleField.input.value),
|
||||
});
|
||||
currentRouteId = result.route_id;
|
||||
renderResult(result);
|
||||
showToast(L("B06_Profile_Generate_Success"), "success");
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? error.message : L("B06_Profile_Generate_Failed");
|
||||
showToast(`${L("B06_Profile_Generate_Failed")} ${detail}`, "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
async function onB06_Profile_Confirm_Click(): Promise<void> {
|
||||
const projectId = getProjectId();
|
||||
if (!projectId) return;
|
||||
const routeId = currentRouteId ?? parseNumber(routeIdField.input.value);
|
||||
if (routeId === null || !Number.isInteger(routeId) || routeId <= 0) {
|
||||
routeIdField.setError(L("B06_Profile_Error_RouteId"));
|
||||
return;
|
||||
}
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
await confirmSections(projectId, routeId);
|
||||
showToast(L("B06_Profile_Confirm_Success"), "success");
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? error.message : L("B06_Profile_Confirm_Failed");
|
||||
showToast(`${L("B06_Profile_Confirm_Failed")} ${detail}`, "error");
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
renderEmptyResult();
|
||||
root.replaceChildren(shell.root);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* =============================================================================
|
||||
* B06_wf3_ProfileCross_UI_Style.css
|
||||
* 3차 워크플로우(종·횡단 생성) 페이지 전용 스타일.
|
||||
*
|
||||
* 원칙(frontend.md §1): 하드코딩 색상 금지. theme.css 변수(var(--...))만 참조.
|
||||
* 공통 컴포넌트 스타일은 ui_template_elements.ts가 주입하므로 여기서는
|
||||
* B06 고유 레이아웃(옵션 그룹/결과 메트릭)만 정의한다.
|
||||
* ========================================================================== */
|
||||
|
||||
/* --- 좌측 입력 폼 --- */
|
||||
.b06-profile__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
}
|
||||
|
||||
.b06-profile__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
margin: 0;
|
||||
padding: var(--spacing-16);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background-color: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.b06-profile__group-legend {
|
||||
padding: 0 var(--spacing-8);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* --- 체크박스 --- */
|
||||
.b06-profile__check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.b06-profile__check input {
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* --- 액션 버튼 행 --- */
|
||||
.b06-profile__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* --- 우측 결과 --- */
|
||||
.b06-profile__result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-24);
|
||||
}
|
||||
|
||||
.b06-profile__result-title {
|
||||
font-size: var(--text-subheading);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.b06-profile__empty {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.b06-profile__result-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.b06-profile__metric {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-16);
|
||||
padding: var(--spacing-8) var(--spacing-16);
|
||||
border-radius: var(--radius-inputs);
|
||||
background-color: var(--color-surface);
|
||||
}
|
||||
|
||||
.b06-profile__metric-key {
|
||||
font-size: var(--text-caption);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.b06-profile__metric-val {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-body-sm);
|
||||
color: var(--color-text-body);
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1 +1,148 @@
|
||||
.agent/agent.md
|
||||
# 프로젝트 행동지침 및 기술 스택 명세 (agent.md)
|
||||
|
||||
## 프로젝트 & DB 정보
|
||||
|
||||
### 📌 프로그램명
|
||||
**Aislo (아이슬로)**
|
||||
- **의미**: AI (인공지능) + Slotti (핀란드어: 임도/산림 도로)
|
||||
- **콘셉트**: 산림의 미래를 열어가는 인공지능 경로 설계 솔루션
|
||||
|
||||
---
|
||||
|
||||
## 1. 기술 환경 및 기본 스택 (Technical Stack Baseline)
|
||||
프로젝트의 코드를 작성, 테스트 또는 검증할 환경 명확화 및 호환 기준 제시.
|
||||
|
||||
### A. 백엔드 프레임워크 (Python)
|
||||
* **Runtime:** Python v3.12 or v3.13.7
|
||||
* **Framework:** FastAPI / Pydantic
|
||||
* **Geometry/GIS Engine:** Trimesh, Whitebox, Geopandas, Shapely, Rasterio, Laspy
|
||||
|
||||
### B. 데이터베이스 (MariaDB)
|
||||
* **DBMS:** MariaDB v10.6+
|
||||
* **Character Set:** utf8mb4 (한글 완벽 지원)
|
||||
* **Collation:** utf8mb4_unicode_ci
|
||||
* **DB Driver (비동기):** `aiomysql` (순수 Python, Windows 호환. asyncmy는 Cython 빌드 필요로 미채택)
|
||||
* **쿼리 방식:** Raw SQL (ORM 사용 금지)
|
||||
* **공간 데이터:** JSON 기반 저장 (MariaDB는 PostGIS 미지원)
|
||||
|
||||
### C. 프론트엔드 & WebCAD (TypeScript & WebGL)
|
||||
* **Language/Runtime:** TypeScript / Node.js
|
||||
* **Rendering:** HTML5 Canvas 및 WebGL 기반 WebCAD 시스템 구현
|
||||
|
||||
### D. 인코딩 및 다국어 표준
|
||||
* **인코딩:** 텍스트 파일은 UTF-8 표준으로 통일
|
||||
|
||||
---
|
||||
|
||||
## 2. 문맥 라우팅 및 문서 구조 (Context Routing)
|
||||
코드 작성 및 기술 검증 시 문서 우선순위에 따른 계층적 레퍼런스 필수 읽기.
|
||||
|
||||
* **1단계 (필수):** `.agent/structure.md` 읽기 후 신규 폴더 생성 기준 분석.
|
||||
* **2단계 (선택 읽기):** 프로젝트 구현에 필요한 세부 기술 명세 읽기.
|
||||
* **그룹 A (UI, 스타일, 컴포넌트, WebCAD):** `.agent/frontend.md` 필수 추가 읽기.
|
||||
* **그룹 B (알고리즘, 저장, DB, MariaDB 값 제어):** `.agent/backend.md` 필수 추가 읽기.
|
||||
* **그룹 C (DB 구조 변경):** DB 구조 변경 시 `.agent/db_schema.md` 선택적 읽기.
|
||||
|
||||
---
|
||||
|
||||
## 3. 코드 작성 기본 제약 (Constraints)
|
||||
* **구조 준수:** `.agent/structure.md` 트리 구조 엄격 준수.
|
||||
* **700줄 제한:** 단일 파일 코드 작성/수정 시 700줄 이상이 될 경우 사전 공지. 기능별 파일 분할 후 `structure.md` 갱신 필수.
|
||||
* **안전 보관:** 백엔드 작업 시 보안 저장소 경로 명시 및 임의 삭제 방지 필수.
|
||||
* **경로 활용:** 단계별 페이지 기반 폴더 구조(B03~B09)와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
|
||||
* **일관성 검증:** 설계 단계에서부터 DB 설계, 파일 경로, API 라우팅이 모두 동일한 워크플로우 기준으로 통일되어야 한다. (불일치 발생 시 설계 단계에서 재논의 필수)
|
||||
* **코드 포맷팅 (자동화 도구):** 프로젝트의 코드 작성이 완료되는 시, 반드시 프로젝트 루트에서 각 언어별 포맷터를 재실행하여 스타일을 통일해야 한다.
|
||||
* *Python 포맷팅 명령어:* `ruff format [파일명]` 및 `ruff check --fix [파일명]` 실행
|
||||
* *TypeScript/CSS 포맷팅 명령어:* `npx prettier --write [파일명]` 실행
|
||||
* **외부 라이브러리 활용:** 외부 라이브러리 활용 시 프로젝트에 맞춰 호환 라이브러리 선정하고, 불필요한 함수는 작성하지 않도록 최소화.
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터베이스 상세 명세 (Database Specification)
|
||||
|
||||
### 4.1 스키마 기본 정보
|
||||
- **DB 명:** `aislo_db`
|
||||
- **DBMS:** MariaDB v10.6+
|
||||
- **인코딩:** utf8mb4_unicode_ci
|
||||
- **테이블 수:** 18개
|
||||
- **드라이버:** aiomysql (비동기)
|
||||
|
||||
### 4.2 파일 경로 추적 (Path Tracking)
|
||||
**모든 중간 산출물의 파일 경로를 DB에 기록:**
|
||||
- `input_files.raw_file_path` — 원본 입력 파일
|
||||
- `processed_point_cloud.converted_file_path` — 변환된 포인트클라우드
|
||||
- `surface_models.model_file_path` — 지표면 모델
|
||||
- `routes.route_data_path` — 경로 데이터
|
||||
- `longitudinal_sections.longitudinal_file_path` — 종단면
|
||||
- `cross_sections.cross_section_file_path` — 횡단면
|
||||
- `structures.structure_data_path` — 구조물 배치
|
||||
- `quantity_items.quantity_data_path` — 수량 항목
|
||||
- `outputs.outputs_directory_path` — 산출물 폴더
|
||||
- `output_files.output_file_path` — 개별 산출 파일
|
||||
|
||||
### 4.3 저장소 구조 (Workflow-based Folder Structure)
|
||||
```
|
||||
storage/{company_slug}/{user_slug}/{project_id}/
|
||||
├── B03_FileInput/input/ (WF0: 원본 입력)
|
||||
├── B04_wf1_Surface/processed/ (WF1: 변환된 포인트클라우드 & 모델)
|
||||
├── B05_wf2_Route/route/ (WF2: 경로 설계)
|
||||
├── B06_wf3_ProfileCross/ (WF3: 종단면 & 횡단면)
|
||||
├── B07_wf4_DesignDetail/structures/ (WF4: 구조물 배치)
|
||||
├── B08_wf5_Quantity/quantities/ (WF5: 수량 산출)
|
||||
└── B09_wf6_Estimation/v1,v2,.../ (WF6: 최종 산출물)
|
||||
```
|
||||
|
||||
### 4.4 공간 데이터 처리 (MariaDB 특성)
|
||||
- **지하형 기하 데이터:** GEOMETRY 타입 미지원 → JSON으로 저장
|
||||
- **좌표 저장 예:** `{"type": "Point", "coordinates": [127.5, 37.5]}`
|
||||
- **경로 저장 예:** `{"type": "LineString", "coordinates": [[127.5, 37.5], [127.6, 37.6]]}`
|
||||
- **애플리케이션 처리:** Python의 Shapely, Geopandas에서 JSON 파싱 후 기하 연산
|
||||
|
||||
---
|
||||
|
||||
## 5. 워크플로우 6단계 (6-Stage Workflow)
|
||||
|
||||
```
|
||||
WF0: B03_FileInput (파일 입력)
|
||||
↓ (LAS, TIF, TFW, PRJ, DXF 업로드)
|
||||
WF1: B04_wf1_Surface (지표면 분석)
|
||||
↓ (DEM, TIN, 포인트클라우드 변환)
|
||||
WF2: B05_wf2_Route (경로 설계)
|
||||
↓ (최적 경로 계산)
|
||||
WF3: B06_wf3_ProfileCross (종횡단 생성)
|
||||
↓ (종단면, 횡단면 생성)
|
||||
WF4: B07_wf4_DesignDetail (상세 설계)
|
||||
↓ (구조물 배치)
|
||||
WF5: B08_wf5_Quantity (수량 산출)
|
||||
↓ (수량 항목 계산)
|
||||
WF6: B09_wf6_Estimation (견적·문서)
|
||||
↓ (Excel, PDF, DXF 생성)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 주요 문서 참고 순서
|
||||
|
||||
1. **구조 설계:** `.agent/structure.md` 읽기
|
||||
2. **프론트 구현:** `.agent/frontend.md` 읽기
|
||||
3. **백엔드 구현:** `.agent/backend.md` 읽기
|
||||
4. **마이그레이션:** `.agent/migration_plan.md` 읽기
|
||||
5. **DB 스키마:** `.agent/db_schema_simple.md` 읽기
|
||||
6. **프로젝트 정보:** `.agent/project_info.md` 읽기
|
||||
|
||||
---
|
||||
|
||||
## 7. 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| **프로그램명** | Aislo (아이슬로) |
|
||||
| **DB 명** | aislo_db |
|
||||
| **DBMS** | MariaDB v10.6+ |
|
||||
| **인코딩** | utf8mb4_unicode_ci |
|
||||
| **Backend** | Python 3.12+ / FastAPI |
|
||||
| **Frontend** | TypeScript / Node.js |
|
||||
| **드라이버** | aiomysql |
|
||||
| **워크플로우** | 6단계 (WF0~WF6) |
|
||||
| **테이블 수** | 18개 |
|
||||
| **폴더 구조** | 워크플로우 기반 (B03~B09) |
|
||||
|
||||
@@ -13,7 +13,7 @@ const __dirname = resolve(fileURLToPath(import.meta.url), "..");
|
||||
*/
|
||||
export default defineConfig({
|
||||
root: "../A00_Common",
|
||||
publicDir: "../resources",
|
||||
publicDir: "..",
|
||||
resolve: {
|
||||
alias: {
|
||||
"@ui": resolve(__dirname, "../ui_template"),
|
||||
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*|*MINGW*|*MSYS*)
|
||||
if command -v cygpath > /dev/null 2>&1; then
|
||||
basedir=`cygpath -w "$basedir"`
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../prettier/bin/prettier.cjs" "$@"
|
||||
else
|
||||
exec node "$basedir/../prettier/bin/prettier.cjs" "$@"
|
||||
fi
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
@ECHO off
|
||||
GOTO start
|
||||
:find_dp0
|
||||
SET dp0=%~dp0
|
||||
EXIT /b
|
||||
:start
|
||||
SETLOCAL
|
||||
CALL :find_dp0
|
||||
|
||||
IF EXIST "%dp0%\node.exe" (
|
||||
SET "_prog=%dp0%\node.exe"
|
||||
) ELSE (
|
||||
SET "_prog=node"
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
||||
)
|
||||
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\prettier\bin\prettier.cjs" %*
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
||||
|
||||
$exe=""
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
||||
# Fix case when both the Windows and Linux builds of Node
|
||||
# are installed in the same directory
|
||||
$exe=".exe"
|
||||
}
|
||||
$ret=0
|
||||
if (Test-Path "$basedir/node$exe") {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "$basedir/node$exe" "$basedir/../prettier/bin/prettier.cjs" $args
|
||||
} else {
|
||||
& "$basedir/node$exe" "$basedir/../prettier/bin/prettier.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
} else {
|
||||
# Support pipeline input
|
||||
if ($MyInvocation.ExpectingInput) {
|
||||
$input | & "node$exe" "$basedir/../prettier/bin/prettier.cjs" $args
|
||||
} else {
|
||||
& "node$exe" "$basedir/../prettier/bin/prettier.cjs" $args
|
||||
}
|
||||
$ret=$LASTEXITCODE
|
||||
}
|
||||
exit $ret
|
||||
-584
@@ -29,43 +29,6 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
|
||||
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/jsonlint-lines-primitives": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.3.tgz",
|
||||
@@ -179,26 +142,6 @@
|
||||
"pbf": "bin/pbf"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
||||
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.137.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz",
|
||||
@@ -215,260 +158,6 @@
|
||||
"integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz",
|
||||
"integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz",
|
||||
"integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz",
|
||||
"integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz",
|
||||
"integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz",
|
||||
"integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz",
|
||||
"integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz",
|
||||
"integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz",
|
||||
"integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@napi-rs/wasm-runtime": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz",
|
||||
"integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz",
|
||||
@@ -500,18 +189,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
|
||||
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
@@ -645,22 +322,6 @@
|
||||
"resolved": "frontend",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/geotiff": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-3.0.5.tgz",
|
||||
@@ -734,226 +395,6 @@
|
||||
"lightningcss-win32-x64-msvc": "1.32.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
||||
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
||||
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
||||
@@ -1148,22 +589,6 @@
|
||||
"integrity": "sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.9.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz",
|
||||
"integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/protocol-buffers-schema": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz",
|
||||
@@ -1297,15 +722,6 @@
|
||||
"integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"ideallyInert": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
Copyright © James Long and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
[](https://prettier.io)
|
||||
|
||||
<h2 align="center">Opinionated Code Formatter</h2>
|
||||
|
||||
<p align="center">
|
||||
<em>
|
||||
JavaScript
|
||||
· TypeScript
|
||||
· Flow
|
||||
· JSX
|
||||
· JSON
|
||||
</em>
|
||||
<br />
|
||||
<em>
|
||||
CSS
|
||||
· SCSS
|
||||
· Less
|
||||
</em>
|
||||
<br />
|
||||
<em>
|
||||
HTML
|
||||
· Vue
|
||||
· Angular
|
||||
</em>
|
||||
<br />
|
||||
<em>
|
||||
GraphQL
|
||||
· Markdown
|
||||
· YAML
|
||||
</em>
|
||||
<br />
|
||||
<em>
|
||||
<a href="https://prettier.io/docs/plugins">
|
||||
Your favorite language?
|
||||
</a>
|
||||
</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/prettier/prettier/actions?query=branch%3Amain">
|
||||
<img alt="CI Status" src="https://img.shields.io/github/check-runs/prettier/prettier/main?style=flat-square&label=CI"></a>
|
||||
<a href="https://codecov.io/gh/prettier/prettier">
|
||||
<img alt="Coverage Status" src="https://img.shields.io/codecov/c/github/prettier/prettier.svg?style=flat-square"></a>
|
||||
<a href="https://x.com/acdlite/status/974390255393505280">
|
||||
<img alt="Blazing Fast" src="https://img.shields.io/badge/speed-blazing%20%F0%9F%94%A5-brightgreen.svg?style=flat-square"></a>
|
||||
<br/>
|
||||
<a href="https://www.npmjs.com/package/prettier">
|
||||
<img alt="npm version" src="https://img.shields.io/npm/v/prettier.svg?style=flat-square"></a>
|
||||
<a href="https://www.npmjs.com/package/prettier">
|
||||
<img alt="weekly downloads from npm" src="https://img.shields.io/npm/dw/prettier.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/prettier/prettier#badge">
|
||||
<img alt="code style: prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"></a>
|
||||
<a href="https://x.com/intent/follow?screen_name=PrettierCode">
|
||||
<img alt="Follow Prettier on X" src="https://img.shields.io/badge/%40PrettierCode-9f9f9f?style=flat-square&logo=x&labelColor=555"></a>
|
||||
</p>
|
||||
|
||||
## Intro
|
||||
|
||||
Prettier is an opinionated code formatter. It enforces a consistent style by parsing your code and re-printing it with its own rules that take the maximum line length into account, wrapping code when necessary.
|
||||
|
||||
### Input
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne());
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
```js
|
||||
foo(
|
||||
reallyLongArg(),
|
||||
omgSoManyParameters(),
|
||||
IShouldRefactorThis(),
|
||||
isThereSeriouslyAnotherOne(),
|
||||
);
|
||||
```
|
||||
|
||||
Prettier can be run [in your editor](https://prettier.io/docs/editors) on-save, in a [pre-commit hook](https://prettier.io/docs/precommit), or in [CI environments](https://prettier.io/docs/cli#list-different) to ensure your codebase has a consistent style without devs ever having to post a nit-picky comment on a code review ever again!
|
||||
|
||||
---
|
||||
|
||||
**[Documentation](https://prettier.io/docs/)**
|
||||
|
||||
[Install](https://prettier.io/docs/install) ·
|
||||
[Options](https://prettier.io/docs/options) ·
|
||||
[CLI](https://prettier.io/docs/cli) ·
|
||||
[API](https://prettier.io/docs/api)
|
||||
|
||||
**[Playground](https://prettier.io/playground/)**
|
||||
|
||||
---
|
||||
|
||||
## Badge
|
||||
|
||||
Show the world you're using _Prettier_ → [](https://github.com/prettier/prettier)
|
||||
|
||||
```md
|
||||
[](https://github.com/prettier/prettier)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
-8013
File diff suppressed because it is too large
Load Diff
-84
@@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __commonJS = function(cb, mod) {
|
||||
return function __require() {
|
||||
try {
|
||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||
} catch (e) {
|
||||
throw mod = 0, e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// node_modules/semver-compare/index.js
|
||||
var require_semver_compare = __commonJS({
|
||||
"node_modules/semver-compare/index.js": function(exports2, module2) {
|
||||
module2.exports = function cmp(a, b) {
|
||||
var pa = a.split(".");
|
||||
var pb = b.split(".");
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var na = Number(pa[i]);
|
||||
var nb = Number(pb[i]);
|
||||
if (na > nb) return 1;
|
||||
if (nb > na) return -1;
|
||||
if (!isNaN(na) && isNaN(nb)) return 1;
|
||||
if (isNaN(na) && !isNaN(nb)) return -1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// node_modules/please-upgrade-node/index.js
|
||||
var require_please_upgrade_node = __commonJS({
|
||||
"node_modules/please-upgrade-node/index.js": function(exports2, module2) {
|
||||
var semverCompare = require_semver_compare();
|
||||
module2.exports = function pleaseUpgradeNode2(pkg, opts) {
|
||||
var opts = opts || {};
|
||||
var requiredVersion = pkg.engines.node.replace(">=", "");
|
||||
var currentVersion = process.version.replace("v", "");
|
||||
if (semverCompare(currentVersion, requiredVersion) === -1) {
|
||||
if (opts.message) {
|
||||
console.error(opts.message(requiredVersion));
|
||||
} else {
|
||||
console.error(
|
||||
pkg.name + " requires at least version " + requiredVersion + " of Node, please upgrade"
|
||||
);
|
||||
}
|
||||
if (opts.hasOwnProperty("exitCode")) {
|
||||
process.exit(opts.exitCode);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// bin/prettier.cjs
|
||||
var nodeModule = require("module");
|
||||
if (typeof nodeModule.enableCompileCache === "function") {
|
||||
nodeModule.enableCompileCache();
|
||||
}
|
||||
var pleaseUpgradeNode = require_please_upgrade_node();
|
||||
var packageJson = require("../package.json");
|
||||
pleaseUpgradeNode(packageJson);
|
||||
var dynamicImport = new Function("module", "return import(module)");
|
||||
var promise;
|
||||
var index = process.argv.indexOf("--experimental-cli");
|
||||
if (process.env.PRETTIER_EXPERIMENTAL_CLI || index !== -1) {
|
||||
if (index !== -1) {
|
||||
process.argv.splice(index, 1);
|
||||
}
|
||||
promise = dynamicImport("../internal/experimental-cli.mjs").then(
|
||||
function(cli) {
|
||||
return cli.__promise;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
promise = dynamicImport("../internal/legacy-cli.mjs").then(function runCli(cli) {
|
||||
return cli.run();
|
||||
});
|
||||
}
|
||||
module.exports.__promise = promise;
|
||||
-254
@@ -1,254 +0,0 @@
|
||||
// https://github.com/prettier/prettier/blob/next/src/document/public.js
|
||||
export namespace builders {
|
||||
type DocCommand =
|
||||
| Align
|
||||
| BreakParent
|
||||
| Cursor
|
||||
| Fill
|
||||
| Group
|
||||
| IfBreak
|
||||
| Indent
|
||||
| IndentIfBreak
|
||||
| Label
|
||||
| Line
|
||||
| LineSuffix
|
||||
| LineSuffixBoundary
|
||||
| Trim;
|
||||
type Doc = string | Doc[] | DocCommand;
|
||||
|
||||
interface Align {
|
||||
type: "align";
|
||||
contents: Doc;
|
||||
n: number | string | { type: "root" };
|
||||
}
|
||||
|
||||
interface BreakParent {
|
||||
type: "break-parent";
|
||||
}
|
||||
|
||||
interface Cursor {
|
||||
type: "cursor";
|
||||
placeholder: symbol;
|
||||
}
|
||||
|
||||
interface Fill {
|
||||
type: "fill";
|
||||
parts: Doc[];
|
||||
}
|
||||
|
||||
interface Group {
|
||||
type: "group";
|
||||
id?: symbol;
|
||||
contents: Doc;
|
||||
break: boolean;
|
||||
expandedStates: Doc[];
|
||||
}
|
||||
|
||||
interface HardlineWithoutBreakParent extends Line {
|
||||
hard: true;
|
||||
}
|
||||
|
||||
interface IfBreak {
|
||||
type: "if-break";
|
||||
breakContents: Doc;
|
||||
flatContents: Doc;
|
||||
}
|
||||
|
||||
interface Indent {
|
||||
type: "indent";
|
||||
contents: Doc;
|
||||
}
|
||||
|
||||
interface IndentIfBreak {
|
||||
type: "indent-if-break";
|
||||
}
|
||||
|
||||
interface Label {
|
||||
type: "label";
|
||||
label: any;
|
||||
contents: Doc;
|
||||
}
|
||||
|
||||
interface Line {
|
||||
type: "line";
|
||||
soft?: boolean | undefined;
|
||||
hard?: boolean | undefined;
|
||||
literal?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface LineSuffix {
|
||||
type: "line-suffix";
|
||||
contents: Doc;
|
||||
}
|
||||
|
||||
interface LineSuffixBoundary {
|
||||
type: "line-suffix-boundary";
|
||||
}
|
||||
|
||||
interface LiterallineWithoutBreakParent extends Line {
|
||||
hard: true;
|
||||
literal: true;
|
||||
}
|
||||
|
||||
type LiteralLine = [LiterallineWithoutBreakParent, BreakParent];
|
||||
|
||||
interface Softline extends Line {
|
||||
soft: true;
|
||||
}
|
||||
|
||||
type Hardline = [HardlineWithoutBreakParent, BreakParent];
|
||||
|
||||
interface Trim {
|
||||
type: "trim";
|
||||
}
|
||||
|
||||
interface GroupOptions {
|
||||
shouldBreak?: boolean | undefined;
|
||||
id?: symbol | undefined;
|
||||
}
|
||||
|
||||
function addAlignmentToDoc(doc: Doc, size: number, tabWidth: number): Doc;
|
||||
|
||||
/** @see [align](https://github.com/prettier/prettier/blob/main/commands.md#align) */
|
||||
function align(widthOrString: Align["n"], doc: Doc): Align;
|
||||
|
||||
/** @see [breakParent](https://github.com/prettier/prettier/blob/main/commands.md#breakparent) */
|
||||
const breakParent: BreakParent;
|
||||
|
||||
/** @see [conditionalGroup](https://github.com/prettier/prettier/blob/main/commands.md#conditionalgroup) */
|
||||
function conditionalGroup(alternatives: Doc[], options?: GroupOptions): Group;
|
||||
|
||||
/** @see [dedent](https://github.com/prettier/prettier/blob/main/commands.md#dedent) */
|
||||
function dedent(doc: Doc): Align;
|
||||
|
||||
/** @see [dedentToRoot](https://github.com/prettier/prettier/blob/main/commands.md#dedenttoroot) */
|
||||
function dedentToRoot(doc: Doc): Align;
|
||||
|
||||
/** @see [fill](https://github.com/prettier/prettier/blob/main/commands.md#fill) */
|
||||
function fill(docs: Doc[]): Fill;
|
||||
|
||||
/** @see [group](https://github.com/prettier/prettier/blob/main/commands.md#group) */
|
||||
function group(doc: Doc, opts?: GroupOptions): Group;
|
||||
|
||||
/** @see [hardline](https://github.com/prettier/prettier/blob/main/commands.md#hardline) */
|
||||
const hardline: Hardline;
|
||||
|
||||
/** @see [hardlineWithoutBreakParent](https://github.com/prettier/prettier/blob/main/commands.md#hardlinewithoutbreakparent-and-literallinewithoutbreakparent) */
|
||||
const hardlineWithoutBreakParent: HardlineWithoutBreakParent;
|
||||
|
||||
/** @see [ifBreak](https://github.com/prettier/prettier/blob/main/commands.md#ifbreak) */
|
||||
function ifBreak(
|
||||
ifBreak: Doc,
|
||||
noBreak?: Doc,
|
||||
options?: { groupId?: symbol | undefined },
|
||||
): IfBreak;
|
||||
|
||||
/** @see [indent](https://github.com/prettier/prettier/blob/main/commands.md#indent) */
|
||||
function indent(doc: Doc): Indent;
|
||||
|
||||
/** @see [indentIfBreak](https://github.com/prettier/prettier/blob/main/commands.md#indentifbreak) */
|
||||
function indentIfBreak(
|
||||
doc: Doc,
|
||||
opts: { groupId: symbol; negate?: boolean | undefined },
|
||||
): IndentIfBreak;
|
||||
|
||||
/** @see [join](https://github.com/prettier/prettier/blob/main/commands.md#join) */
|
||||
function join(sep: Doc, docs: Doc[]): Doc[];
|
||||
|
||||
/** @see [label](https://github.com/prettier/prettier/blob/main/commands.md#label) */
|
||||
function label(label: any | undefined, contents: Doc): Doc;
|
||||
|
||||
/** @see [line](https://github.com/prettier/prettier/blob/main/commands.md#line) */
|
||||
const line: Line;
|
||||
|
||||
/** @see [lineSuffix](https://github.com/prettier/prettier/blob/main/commands.md#linesuffix) */
|
||||
function lineSuffix(suffix: Doc): LineSuffix;
|
||||
|
||||
/** @see [lineSuffixBoundary](https://github.com/prettier/prettier/blob/main/commands.md#linesuffixboundary) */
|
||||
const lineSuffixBoundary: LineSuffixBoundary;
|
||||
|
||||
/** @see [literalline](https://github.com/prettier/prettier/blob/main/commands.md#literalline) */
|
||||
const literalline: LiteralLine;
|
||||
|
||||
/** @see [literallineWithoutBreakParent](https://github.com/prettier/prettier/blob/main/commands.md#hardlinewithoutbreakparent-and-literallinewithoutbreakparent) */
|
||||
const literallineWithoutBreakParent: LiterallineWithoutBreakParent;
|
||||
|
||||
/** @see [markAsRoot](https://github.com/prettier/prettier/blob/main/commands.md#markasroot) */
|
||||
function markAsRoot(doc: Doc): Align;
|
||||
|
||||
/** @see [softline](https://github.com/prettier/prettier/blob/main/commands.md#softline) */
|
||||
const softline: Softline;
|
||||
|
||||
/** @see [trim](https://github.com/prettier/prettier/blob/main/commands.md#trim) */
|
||||
const trim: Trim;
|
||||
|
||||
/** @see [cursor](https://github.com/prettier/prettier/blob/main/commands.md#cursor) */
|
||||
const cursor: Cursor;
|
||||
}
|
||||
|
||||
export namespace printer {
|
||||
function printDocToString(
|
||||
doc: builders.Doc,
|
||||
options: Options,
|
||||
): {
|
||||
formatted: string;
|
||||
/**
|
||||
* This property is a misnomer, and has been since the changes in
|
||||
* https://github.com/prettier/prettier/pull/15709.
|
||||
* The region of the document indicated by `cursorNodeStart` and `cursorNodeText` will
|
||||
* sometimes actually be what lies BETWEEN a pair of leaf nodes in the AST, rather than a node.
|
||||
*/
|
||||
cursorNodeStart?: number | undefined;
|
||||
|
||||
/**
|
||||
* Note that, like cursorNodeStart, this is a misnomer and may actually be the text between two
|
||||
* leaf nodes in the AST instead of the text of a node.
|
||||
*/
|
||||
cursorNodeText?: string | undefined;
|
||||
};
|
||||
interface Options {
|
||||
/**
|
||||
* Specify the line length that the printer will wrap on.
|
||||
* @default 80
|
||||
*/
|
||||
printWidth: number;
|
||||
/**
|
||||
* Specify the number of spaces per indentation-level.
|
||||
* @default 2
|
||||
*/
|
||||
tabWidth: number;
|
||||
/**
|
||||
* Indent lines with tabs instead of spaces
|
||||
* @default false
|
||||
*/
|
||||
useTabs?: boolean;
|
||||
parentParser?: string | undefined;
|
||||
__embeddedInHtml?: boolean | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace utils {
|
||||
function willBreak(doc: builders.Doc): boolean;
|
||||
function traverseDoc(
|
||||
doc: builders.Doc,
|
||||
onEnter?: (doc: builders.Doc) => void | boolean,
|
||||
onExit?: (doc: builders.Doc) => void,
|
||||
shouldTraverseConditionalGroups?: boolean,
|
||||
): void;
|
||||
function findInDoc<T = builders.Doc>(
|
||||
doc: builders.Doc,
|
||||
callback: (doc: builders.Doc) => T,
|
||||
defaultValue: T,
|
||||
): T;
|
||||
function mapDoc<T = builders.Doc>(
|
||||
doc: builders.Doc,
|
||||
callback: (doc: builders.Doc) => T,
|
||||
): T;
|
||||
function removeLines(doc: builders.Doc): builders.Doc;
|
||||
function stripTrailingHardline(doc: builders.Doc): builders.Doc;
|
||||
function replaceEndOfLine(
|
||||
doc: builders.Doc,
|
||||
replacement?: builders.Doc,
|
||||
): builders.Doc;
|
||||
function canBreak(doc: builders.Doc): boolean;
|
||||
}
|
||||
-1575
File diff suppressed because one or more lines are too long
-1544
File diff suppressed because one or more lines are too long
-790
File diff suppressed because one or more lines are too long
-959
@@ -1,959 +0,0 @@
|
||||
// Copied from `@types/prettier`
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/5bb07fc4b087cb7ee91084afa6fe750551a7bbb1/types/prettier/index.d.ts
|
||||
|
||||
// Minimum TypeScript Version: 4.2
|
||||
|
||||
// Add `export {}` here to shut off automatic exporting from index.d.ts. There
|
||||
// are quite a few utility types here that don't need to be shipped with the
|
||||
// exported module.
|
||||
export {};
|
||||
|
||||
import { builders, printer, utils } from "./doc.js";
|
||||
|
||||
export namespace doc {
|
||||
export { builders, printer, utils };
|
||||
}
|
||||
|
||||
// This utility is here to handle the case where you have an explicit union
|
||||
// between string literals and the generic string type. It would normally
|
||||
// resolve out to just the string type, but this generic LiteralUnion maintains
|
||||
// the intellisense of the original union.
|
||||
//
|
||||
// It comes from this issue: microsoft/TypeScript#29729:
|
||||
// https://github.com/microsoft/TypeScript/issues/29729#issuecomment-700527227
|
||||
export type LiteralUnion<T extends U, U = string> =
|
||||
T | (Pick<U, never> & { _?: never | undefined });
|
||||
|
||||
export type AST = any;
|
||||
export type Doc = doc.builders.Doc;
|
||||
|
||||
// The type of elements that make up the given array T.
|
||||
type ArrayElement<T> = T extends Array<infer E> ? E : never;
|
||||
|
||||
// A union of the properties of the given object that are arrays.
|
||||
type ArrayProperties<T> = {
|
||||
[K in keyof T]: NonNullable<T[K]> extends readonly any[] ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
// A union of the properties of the given array T that can be used to index it.
|
||||
// If the array is a tuple, then that's going to be the explicit indices of the
|
||||
// array, otherwise it's going to just be number.
|
||||
type IndexProperties<T extends { length: number }> =
|
||||
IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
|
||||
|
||||
// Effectively performing T[P], except that it's telling TypeScript that it's
|
||||
// safe to do this for tuples, arrays, or objects.
|
||||
type IndexValue<T, P> = T extends any[]
|
||||
? P extends number
|
||||
? T[P]
|
||||
: never
|
||||
: P extends keyof T
|
||||
? T[P]
|
||||
: never;
|
||||
|
||||
// Determines if an object T is an array like string[] (in which case this
|
||||
// evaluates to false) or a tuple like [string] (in which case this evaluates to
|
||||
// true).
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type IsTuple<T> = T extends []
|
||||
? true
|
||||
: T extends [infer First, ...infer Remain]
|
||||
? IsTuple<Remain>
|
||||
: false;
|
||||
|
||||
type CallProperties<T> = T extends any[] ? IndexProperties<T> : keyof T;
|
||||
type IterProperties<T> = T extends any[]
|
||||
? IndexProperties<T>
|
||||
: ArrayProperties<T>;
|
||||
|
||||
type CallCallback<T, U> = (path: AstPath<T>, index: number, value: any) => U;
|
||||
type EachCallback<T> = (
|
||||
path: AstPath<ArrayElement<T>>,
|
||||
index: number,
|
||||
value: any,
|
||||
) => void;
|
||||
type MapCallback<T, U> = (
|
||||
path: AstPath<ArrayElement<T>>,
|
||||
index: number,
|
||||
value: any,
|
||||
) => U;
|
||||
|
||||
// https://github.com/prettier/prettier/blob/next/src/common/ast-path.js
|
||||
export class AstPath<T = any> {
|
||||
constructor(value: T);
|
||||
|
||||
get key(): string | null;
|
||||
get index(): number | null;
|
||||
get node(): T;
|
||||
get parent(): T | null;
|
||||
get grandparent(): T | null;
|
||||
get isInArray(): boolean;
|
||||
get siblings(): T[] | null;
|
||||
get next(): T | null;
|
||||
get previous(): T | null;
|
||||
get isFirst(): boolean;
|
||||
get isLast(): boolean;
|
||||
get isRoot(): boolean;
|
||||
get root(): T;
|
||||
get ancestors(): T[];
|
||||
|
||||
stack: T[];
|
||||
|
||||
callParent<U>(callback: (path: this) => U, count?: number): U;
|
||||
|
||||
/**
|
||||
* @deprecated Please use `AstPath#key` or `AstPath#index`
|
||||
*/
|
||||
getName(): PropertyKey | null;
|
||||
|
||||
/**
|
||||
* @deprecated Please use `AstPath#node` or `AstPath#siblings`
|
||||
*/
|
||||
getValue(): T;
|
||||
|
||||
getNode(count?: number): T | null;
|
||||
|
||||
getParentNode(count?: number): T | null;
|
||||
|
||||
match(
|
||||
...predicates: Array<
|
||||
(node: any, name: string | null, number: number | null) => boolean
|
||||
>
|
||||
): boolean;
|
||||
|
||||
// For each of the tree walk functions (call, each, and map) this provides 5
|
||||
// strict type signatures, along with a fallback at the end if you end up
|
||||
// calling more than 5 properties deep. This helps a lot with typing because
|
||||
// for the majority of cases you're calling fewer than 5 properties, so the
|
||||
// tree walk functions have a clearer understanding of what you're doing.
|
||||
//
|
||||
// Note that resolving these types is somewhat complicated, and it wasn't
|
||||
// even supported until TypeScript 4.2 (before it would just say that the
|
||||
// type instantiation was excessively deep and possibly infinite).
|
||||
|
||||
call<U>(callback: CallCallback<T, U>): U;
|
||||
call<U, P1 extends CallProperties<T>>(
|
||||
callback: CallCallback<IndexValue<T, P1>, U>,
|
||||
prop1: P1,
|
||||
): U;
|
||||
call<U, P1 extends keyof T, P2 extends CallProperties<T[P1]>>(
|
||||
callback: CallCallback<IndexValue<IndexValue<T, P1>, P2>, U>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
): U;
|
||||
call<
|
||||
U,
|
||||
P1 extends keyof T,
|
||||
P2 extends CallProperties<T[P1]>,
|
||||
P3 extends CallProperties<IndexValue<T[P1], P2>>,
|
||||
>(
|
||||
callback: CallCallback<
|
||||
IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>,
|
||||
U
|
||||
>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
): U;
|
||||
call<
|
||||
U,
|
||||
P1 extends keyof T,
|
||||
P2 extends CallProperties<T[P1]>,
|
||||
P3 extends CallProperties<IndexValue<T[P1], P2>>,
|
||||
P4 extends CallProperties<IndexValue<IndexValue<T[P1], P2>, P3>>,
|
||||
>(
|
||||
callback: CallCallback<
|
||||
IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>,
|
||||
U
|
||||
>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
prop4: P4,
|
||||
): U;
|
||||
call<U, P extends PropertyKey>(
|
||||
callback: CallCallback<any, U>,
|
||||
prop1: P,
|
||||
prop2: P,
|
||||
prop3: P,
|
||||
prop4: P,
|
||||
...props: P[]
|
||||
): U;
|
||||
|
||||
each(callback: EachCallback<T>): void;
|
||||
each<P1 extends IterProperties<T>>(
|
||||
callback: EachCallback<IndexValue<T, P1>>,
|
||||
prop1: P1,
|
||||
): void;
|
||||
each<P1 extends keyof T, P2 extends IterProperties<T[P1]>>(
|
||||
callback: EachCallback<IndexValue<IndexValue<T, P1>, P2>>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
): void;
|
||||
each<
|
||||
P1 extends keyof T,
|
||||
P2 extends IterProperties<T[P1]>,
|
||||
P3 extends IterProperties<IndexValue<T[P1], P2>>,
|
||||
>(
|
||||
callback: EachCallback<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
): void;
|
||||
each<
|
||||
P1 extends keyof T,
|
||||
P2 extends IterProperties<T[P1]>,
|
||||
P3 extends IterProperties<IndexValue<T[P1], P2>>,
|
||||
P4 extends IterProperties<IndexValue<IndexValue<T[P1], P2>, P3>>,
|
||||
>(
|
||||
callback: EachCallback<
|
||||
IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>
|
||||
>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
prop4: P4,
|
||||
): void;
|
||||
each(
|
||||
callback: EachCallback<any[]>,
|
||||
prop1: PropertyKey,
|
||||
prop2: PropertyKey,
|
||||
prop3: PropertyKey,
|
||||
prop4: PropertyKey,
|
||||
...props: PropertyKey[]
|
||||
): void;
|
||||
|
||||
map<U>(callback: MapCallback<T, U>): U[];
|
||||
map<U, P1 extends IterProperties<T>>(
|
||||
callback: MapCallback<IndexValue<T, P1>, U>,
|
||||
prop1: P1,
|
||||
): U[];
|
||||
map<U, P1 extends keyof T, P2 extends IterProperties<T[P1]>>(
|
||||
callback: MapCallback<IndexValue<IndexValue<T, P1>, P2>, U>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
): U[];
|
||||
map<
|
||||
U,
|
||||
P1 extends keyof T,
|
||||
P2 extends IterProperties<T[P1]>,
|
||||
P3 extends IterProperties<IndexValue<T[P1], P2>>,
|
||||
>(
|
||||
callback: MapCallback<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, U>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
): U[];
|
||||
map<
|
||||
U,
|
||||
P1 extends keyof T,
|
||||
P2 extends IterProperties<T[P1]>,
|
||||
P3 extends IterProperties<IndexValue<T[P1], P2>>,
|
||||
P4 extends IterProperties<IndexValue<IndexValue<T[P1], P2>, P3>>,
|
||||
>(
|
||||
callback: MapCallback<
|
||||
IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>,
|
||||
U
|
||||
>,
|
||||
prop1: P1,
|
||||
prop2: P2,
|
||||
prop3: P3,
|
||||
prop4: P4,
|
||||
): U[];
|
||||
map<U>(
|
||||
callback: MapCallback<any[], U>,
|
||||
prop1: PropertyKey,
|
||||
prop2: PropertyKey,
|
||||
prop3: PropertyKey,
|
||||
prop4: PropertyKey,
|
||||
...props: PropertyKey[]
|
||||
): U[];
|
||||
}
|
||||
|
||||
/** @deprecated `FastPath` was renamed to `AstPath` */
|
||||
export type FastPath<T = any> = AstPath<T>;
|
||||
|
||||
export type BuiltInParser = (text: string, options?: any) => AST;
|
||||
export type BuiltInParserName =
|
||||
| "acorn"
|
||||
| "angular"
|
||||
| "babel-flow"
|
||||
| "babel-ts"
|
||||
| "babel"
|
||||
| "css"
|
||||
| "espree"
|
||||
| "flow"
|
||||
| "glimmer"
|
||||
| "graphql"
|
||||
| "html"
|
||||
| "json-stringify"
|
||||
| "json"
|
||||
| "json5"
|
||||
| "jsonc"
|
||||
| "less"
|
||||
| "lwc"
|
||||
| "markdown"
|
||||
| "mdx"
|
||||
| "meriyah"
|
||||
| "mjml"
|
||||
| "scss"
|
||||
| "typescript"
|
||||
| "vue"
|
||||
| "yaml";
|
||||
export type BuiltInParsers = Record<BuiltInParserName, BuiltInParser>;
|
||||
|
||||
/**
|
||||
* For use in `.prettierrc.js`, `.prettierrc.ts`, `.prettierrc.cjs`, `.prettierrc.cts`, `prettierrc.mjs`, `prettierrc.mts`, `prettier.config.js`, `prettier.config.ts`, `prettier.config.cjs`, `prettier.config.cts`, `prettier.config.mjs`, `prettier.config.mts`
|
||||
*/
|
||||
export interface Config extends Options {
|
||||
overrides?: Array<{
|
||||
files: string | string[];
|
||||
excludeFiles?: string | string[];
|
||||
options?: Options;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface Options extends Partial<RequiredOptions> {}
|
||||
|
||||
export interface RequiredOptions extends doc.printer.Options {
|
||||
/**
|
||||
* Print semicolons at the ends of statements.
|
||||
* @default true
|
||||
*/
|
||||
semi: boolean;
|
||||
/**
|
||||
* Use single quotes instead of double quotes.
|
||||
* @default false
|
||||
*/
|
||||
singleQuote: boolean;
|
||||
/**
|
||||
* Use single quotes in JSX.
|
||||
* @default false
|
||||
*/
|
||||
jsxSingleQuote: boolean;
|
||||
/**
|
||||
* Print trailing commas wherever possible.
|
||||
* @default "all"
|
||||
*/
|
||||
trailingComma: "none" | "es5" | "all";
|
||||
/**
|
||||
* Print spaces between brackets in object literals.
|
||||
* @default true
|
||||
*/
|
||||
bracketSpacing: boolean;
|
||||
/**
|
||||
* How to wrap object literals.
|
||||
* @default "preserve"
|
||||
*/
|
||||
objectWrap: "preserve" | "collapse";
|
||||
/**
|
||||
* Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being
|
||||
* alone on the next line (does not apply to self closing elements).
|
||||
* @default false
|
||||
*/
|
||||
bracketSameLine: boolean;
|
||||
/**
|
||||
* Format only a segment of a file.
|
||||
* @default 0
|
||||
*/
|
||||
rangeStart: number;
|
||||
/**
|
||||
* Format only a segment of a file.
|
||||
* @default Number.POSITIVE_INFINITY
|
||||
*/
|
||||
rangeEnd: number;
|
||||
/**
|
||||
* Specify which parser to use.
|
||||
*/
|
||||
parser: LiteralUnion<BuiltInParserName>;
|
||||
/**
|
||||
* Specify the input filepath. This will be used to do parser inference.
|
||||
*/
|
||||
filepath: string;
|
||||
/**
|
||||
* Prettier can restrict itself to only format files that contain a special comment, called a pragma, at the top of the file.
|
||||
* This is very useful when gradually transitioning large, unformatted codebases to prettier.
|
||||
* @default false
|
||||
*/
|
||||
requirePragma: boolean;
|
||||
/**
|
||||
* Prettier can insert a special @format marker at the top of files specifying that
|
||||
* the file has been formatted with prettier. This works well when used in tandem with
|
||||
* the --require-pragma option. If there is already a docblock at the top of
|
||||
* the file then this option will add a newline to it with the @format marker.
|
||||
* @default false
|
||||
*/
|
||||
insertPragma: boolean;
|
||||
/**
|
||||
* Prettier can allow individual files to opt out of formatting if they contain a special comment, called a pragma, at the top of the file.
|
||||
* @default false
|
||||
*/
|
||||
checkIgnorePragma: boolean;
|
||||
/**
|
||||
* By default, Prettier will wrap markdown text as-is since some services use a linebreak-sensitive renderer.
|
||||
* In some cases you may want to rely on editor/viewer soft wrapping instead, so this option allows you to opt out.
|
||||
* @default "preserve"
|
||||
*/
|
||||
proseWrap: "always" | "never" | "preserve";
|
||||
/**
|
||||
* Include parentheses around a sole arrow function parameter.
|
||||
* @default "always"
|
||||
*/
|
||||
arrowParens: "avoid" | "always";
|
||||
/**
|
||||
* Provide ability to support new languages to prettier.
|
||||
*/
|
||||
plugins: Array<string | URL | Plugin>;
|
||||
/**
|
||||
* How to handle whitespaces in HTML.
|
||||
* @default "css"
|
||||
*/
|
||||
htmlWhitespaceSensitivity: "css" | "strict" | "ignore";
|
||||
/**
|
||||
* Which end of line characters to apply.
|
||||
* @default "lf"
|
||||
*/
|
||||
endOfLine: "auto" | "lf" | "crlf" | "cr";
|
||||
/**
|
||||
* Change when properties in objects are quoted.
|
||||
* @default "as-needed"
|
||||
*/
|
||||
quoteProps: "as-needed" | "consistent" | "preserve";
|
||||
/**
|
||||
* Whether or not to indent the code inside <script> and <style> tags in Vue files.
|
||||
* @default false
|
||||
*/
|
||||
vueIndentScriptAndStyle: boolean;
|
||||
/**
|
||||
* Control whether Prettier formats quoted code embedded in the file.
|
||||
* @default "auto"
|
||||
*/
|
||||
embeddedLanguageFormatting: "auto" | "off";
|
||||
/**
|
||||
* Enforce single attribute per line in HTML, Vue and JSX.
|
||||
* @default false
|
||||
*/
|
||||
singleAttributePerLine: boolean;
|
||||
/**
|
||||
* Where to print operators when binary expressions wrap lines.
|
||||
* @default "end"
|
||||
*/
|
||||
experimentalOperatorPosition: "start" | "end";
|
||||
/**
|
||||
* Use curious ternaries, with the question mark after the condition, instead
|
||||
* of on the same line as the consequent.
|
||||
* @default false
|
||||
*/
|
||||
experimentalTernaries: boolean;
|
||||
/**
|
||||
* Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line.
|
||||
* @default false
|
||||
* @deprecated use bracketSameLine instead
|
||||
*/
|
||||
jsxBracketSameLine?: boolean;
|
||||
/**
|
||||
* Arbitrary additional values on an options object are always allowed.
|
||||
*/
|
||||
[_: string]: unknown;
|
||||
}
|
||||
|
||||
export interface ParserOptions<T = any> extends RequiredOptions {
|
||||
locStart: (node: T) => number;
|
||||
locEnd: (node: T) => number;
|
||||
originalText: string;
|
||||
}
|
||||
|
||||
export interface Plugin<T = any> {
|
||||
languages?: SupportLanguage[] | undefined;
|
||||
parsers?: { [parserName: string]: Parser<T> } | undefined;
|
||||
printers?: { [astFormat: string]: Printer<T> } | undefined;
|
||||
options?: SupportOptions | undefined;
|
||||
defaultOptions?: Partial<RequiredOptions> | undefined;
|
||||
}
|
||||
|
||||
export interface Parser<T = any> {
|
||||
parse: (text: string, options: ParserOptions<T>) => T | Promise<T>;
|
||||
astFormat: string;
|
||||
hasPragma?: ((text: string) => boolean) | undefined;
|
||||
hasIgnorePragma?: ((text: string) => boolean) | undefined;
|
||||
locStart: (node: T) => number;
|
||||
locEnd: (node: T) => number;
|
||||
preprocess?:
|
||||
| ((text: string, options: ParserOptions<T>) => string | Promise<string>)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export interface Printer<T = any> {
|
||||
print(
|
||||
path: AstPath<T>,
|
||||
options: ParserOptions<T>,
|
||||
print: (
|
||||
selector?: string | number | Array<string | number> | AstPath<T>,
|
||||
args?: unknown,
|
||||
) => Doc,
|
||||
args?: unknown,
|
||||
): Doc;
|
||||
printPrettierIgnored?(
|
||||
path: AstPath<T>,
|
||||
options: ParserOptions<T>,
|
||||
print: (
|
||||
selector?: string | number | Array<string | number> | AstPath<T>,
|
||||
args?: unknown,
|
||||
) => Doc,
|
||||
args?: unknown,
|
||||
): Doc;
|
||||
embed?:
|
||||
| ((
|
||||
path: AstPath,
|
||||
options: Options,
|
||||
) =>
|
||||
| ((
|
||||
textToDoc: (text: string, options: Options) => Promise<Doc>,
|
||||
print: (
|
||||
selector?: string | number | Array<string | number> | AstPath,
|
||||
args?: unknown,
|
||||
) => Doc,
|
||||
path: AstPath,
|
||||
options: Options,
|
||||
) => Promise<Doc | undefined> | Doc | undefined)
|
||||
| Doc
|
||||
| null)
|
||||
| undefined;
|
||||
preprocess?:
|
||||
((ast: T, options: ParserOptions<T>) => T | Promise<T>) | undefined;
|
||||
insertPragma?: (text: string) => string;
|
||||
/**
|
||||
* @returns `null` if you want to remove this node
|
||||
* @returns `void` if you want to use modified `cloned`
|
||||
* @returns anything if you want to replace the node with it
|
||||
*/
|
||||
massageAstNode?:
|
||||
((original: any, cloned: any, parent: any) => any) | undefined;
|
||||
hasPrettierIgnore?: ((path: AstPath<T>) => boolean) | undefined;
|
||||
canAttachComment?: ((node: T, ancestors: T[]) => boolean) | undefined;
|
||||
isBlockComment?: ((node: T) => boolean) | undefined;
|
||||
willPrintOwnComments?: ((path: AstPath<T>) => boolean) | undefined;
|
||||
printComment?:
|
||||
((commentPath: AstPath<T>, options: ParserOptions<T>) => Doc) | undefined;
|
||||
/**
|
||||
* By default, Prettier searches all object properties (except for a few predefined ones) of each node recursively.
|
||||
* This function can be provided to override that behavior.
|
||||
* @param node The node whose children should be returned.
|
||||
* @param options Current options.
|
||||
* @returns `[]` if the node has no children or `undefined` to fall back on the default behavior.
|
||||
*/
|
||||
getCommentChildNodes?:
|
||||
((node: T, options: ParserOptions<T>) => T[] | undefined) | undefined;
|
||||
handleComments?:
|
||||
| {
|
||||
ownLine?:
|
||||
| ((
|
||||
commentNode: any,
|
||||
text: string,
|
||||
options: ParserOptions<T>,
|
||||
ast: T,
|
||||
isLastComment: boolean,
|
||||
) => boolean)
|
||||
| undefined;
|
||||
endOfLine?:
|
||||
| ((
|
||||
commentNode: any,
|
||||
text: string,
|
||||
options: ParserOptions<T>,
|
||||
ast: T,
|
||||
isLastComment: boolean,
|
||||
) => boolean)
|
||||
| undefined;
|
||||
remaining?:
|
||||
| ((
|
||||
commentNode: any,
|
||||
text: string,
|
||||
options: ParserOptions<T>,
|
||||
ast: T,
|
||||
isLastComment: boolean,
|
||||
) => boolean)
|
||||
| undefined;
|
||||
}
|
||||
| undefined;
|
||||
getVisitorKeys?:
|
||||
((node: T, nonTraversableKeys: Set<string>) => string[]) | undefined;
|
||||
}
|
||||
|
||||
export interface CursorOptions extends Options {
|
||||
/**
|
||||
* Specify where the cursor is.
|
||||
*/
|
||||
cursorOffset: number;
|
||||
}
|
||||
|
||||
export interface CursorResult {
|
||||
formatted: string;
|
||||
cursorOffset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* `format` is used to format text using Prettier. [Options](https://prettier.io/docs/options) may be provided to override the defaults.
|
||||
*/
|
||||
export function format(source: string, options?: Options): Promise<string>;
|
||||
|
||||
/**
|
||||
* `check` checks to see if the file has been formatted with Prettier given those options and returns a `Boolean`.
|
||||
* This is similar to the `--list-different` parameter in the CLI and is useful for running Prettier in CI scenarios.
|
||||
*/
|
||||
export function check(source: string, options?: Options): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* `formatWithCursor` both formats the code, and translates a cursor position from unformatted code to formatted code.
|
||||
* This is useful for editor integrations, to prevent the cursor from moving when code is formatted.
|
||||
*
|
||||
* The `cursorOffset` option should be provided, to specify where the cursor is.
|
||||
*/
|
||||
export function formatWithCursor(
|
||||
source: string,
|
||||
options: CursorOptions,
|
||||
): Promise<CursorResult>;
|
||||
|
||||
export interface ResolveConfigOptions {
|
||||
/**
|
||||
* If set to `false`, all caching will be bypassed.
|
||||
*/
|
||||
useCache?: boolean | undefined;
|
||||
/**
|
||||
* Pass directly the path of the config file if you don't wish to search for it.
|
||||
*/
|
||||
config?: string | URL | undefined;
|
||||
/**
|
||||
* If set to `true` and an `.editorconfig` file is in your project,
|
||||
* Prettier will parse it and convert its properties to the corresponding prettier configuration.
|
||||
* This configuration will be overridden by `.prettierrc`, etc. Currently,
|
||||
* the following EditorConfig properties are supported:
|
||||
* - indent_style
|
||||
* - indent_size/tab_width
|
||||
* - max_line_length
|
||||
*/
|
||||
editorconfig?: boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* `resolveConfig` can be used to resolve configuration for a given source file,
|
||||
* passing its path or url as the first argument. The config search will start at
|
||||
* the directory of the file location and continue to search up the directory.
|
||||
*
|
||||
* A promise is returned which will resolve to:
|
||||
*
|
||||
* - An options object, providing a [config file](https://prettier.io/docs/configuration) was found.
|
||||
* - `null`, if no file was found.
|
||||
*
|
||||
* The promise will be rejected if there was an error parsing the configuration file.
|
||||
*/
|
||||
export function resolveConfig(
|
||||
fileUrlOrPath: string | URL,
|
||||
options?: ResolveConfigOptions,
|
||||
): Promise<Options | null>;
|
||||
|
||||
/**
|
||||
* `resolveConfigFile` can be used to find the path of the Prettier configuration file,
|
||||
* that will be used when resolving the config (i.e. when calling `resolveConfig`).
|
||||
*
|
||||
* A promise is returned which will resolve to:
|
||||
*
|
||||
* - The path of the configuration file.
|
||||
* - `null`, if no file was found.
|
||||
*
|
||||
* The promise will be rejected if there was an error parsing the configuration file.
|
||||
*/
|
||||
export function resolveConfigFile(
|
||||
fileUrlOrPath?: string | URL,
|
||||
): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* As you repeatedly call `resolveConfig`, the file system structure will be cached for performance. This function will clear the cache.
|
||||
* Generally this is only needed for editor integrations that know that the file system has changed since the last format took place.
|
||||
*/
|
||||
export function clearConfigCache(): Promise<void>;
|
||||
|
||||
export interface SupportLanguage {
|
||||
name: string;
|
||||
parsers: BuiltInParserName[] | string[];
|
||||
group?: string | undefined;
|
||||
tmScope?: string | undefined;
|
||||
aceMode?: string | undefined;
|
||||
codemirrorMode?: string | undefined;
|
||||
codemirrorMimeType?: string | undefined;
|
||||
aliases?: string[] | undefined;
|
||||
extensions?: string[] | undefined;
|
||||
filenames?: string[] | undefined;
|
||||
linguistLanguageId?: number | undefined;
|
||||
vscodeLanguageIds?: string[] | undefined;
|
||||
interpreters?: string[] | undefined;
|
||||
isSupported?: ((options: { filepath: string }) => boolean) | undefined;
|
||||
}
|
||||
|
||||
export interface SupportOptionRange {
|
||||
start: number;
|
||||
end: number;
|
||||
step: number;
|
||||
}
|
||||
|
||||
export type SupportOptionType =
|
||||
"int" | "string" | "boolean" | "choice" | "path";
|
||||
|
||||
export type CoreCategoryType =
|
||||
"Config" | "Editor" | "Format" | "Other" | "Output" | "Global" | "Special";
|
||||
|
||||
export interface BaseSupportOption<Type extends SupportOptionType> {
|
||||
readonly name?: string | undefined;
|
||||
/**
|
||||
* Usually you can use {@link CoreCategoryType}
|
||||
*/
|
||||
category: string;
|
||||
/**
|
||||
* The type of the option.
|
||||
*
|
||||
* When passing a type other than the ones listed below, the option is
|
||||
* treated as taking any string as argument, and `--option <${type}>` will
|
||||
* be displayed in --help.
|
||||
*/
|
||||
type: Type;
|
||||
/**
|
||||
* Indicate that the option is deprecated.
|
||||
*
|
||||
* Use a string to add an extra message to --help for the option,
|
||||
* for example to suggest a replacement option.
|
||||
*/
|
||||
deprecated?: true | string | undefined;
|
||||
/**
|
||||
* Description to be displayed in --help. If omitted, the option won't be
|
||||
* shown at all in --help.
|
||||
*/
|
||||
description?: string | undefined;
|
||||
}
|
||||
|
||||
export interface IntSupportOption extends BaseSupportOption<"int"> {
|
||||
default?: number | undefined;
|
||||
array?: false | undefined;
|
||||
range?: SupportOptionRange | undefined;
|
||||
}
|
||||
|
||||
export interface IntArraySupportOption extends BaseSupportOption<"int"> {
|
||||
default?: Array<{ value: number[] }> | undefined;
|
||||
array: true;
|
||||
}
|
||||
|
||||
export interface StringSupportOption extends BaseSupportOption<"string"> {
|
||||
default?: string | undefined;
|
||||
array?: false | undefined;
|
||||
}
|
||||
|
||||
export interface StringArraySupportOption extends BaseSupportOption<"string"> {
|
||||
default?: Array<{ value: string[] }> | undefined;
|
||||
array: true;
|
||||
}
|
||||
|
||||
export interface BooleanSupportOption extends BaseSupportOption<"boolean"> {
|
||||
default?: boolean | undefined;
|
||||
array?: false | undefined;
|
||||
description: string;
|
||||
oppositeDescription?: string | undefined;
|
||||
}
|
||||
|
||||
export interface BooleanArraySupportOption extends BaseSupportOption<"boolean"> {
|
||||
default?: Array<{ value: boolean[] }> | undefined;
|
||||
array: true;
|
||||
}
|
||||
|
||||
export interface ChoiceSupportOption<
|
||||
Value = any,
|
||||
> extends BaseSupportOption<"choice"> {
|
||||
default?: Value | Array<{ value: Value }> | undefined;
|
||||
description: string;
|
||||
choices: Array<{
|
||||
value: Value;
|
||||
description: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PathSupportOption extends BaseSupportOption<"path"> {
|
||||
default?: string | undefined;
|
||||
array?: false | undefined;
|
||||
}
|
||||
|
||||
export interface PathArraySupportOption extends BaseSupportOption<"path"> {
|
||||
default?: Array<{ value: string[] }> | undefined;
|
||||
array: true;
|
||||
}
|
||||
|
||||
export type SupportOption =
|
||||
| IntSupportOption
|
||||
| IntArraySupportOption
|
||||
| StringSupportOption
|
||||
| StringArraySupportOption
|
||||
| BooleanSupportOption
|
||||
| BooleanArraySupportOption
|
||||
| ChoiceSupportOption
|
||||
| PathSupportOption
|
||||
| PathArraySupportOption;
|
||||
|
||||
export interface SupportOptions extends Record<string, SupportOption> {}
|
||||
|
||||
export interface SupportInfo {
|
||||
languages: SupportLanguage[];
|
||||
options: SupportOption[];
|
||||
}
|
||||
|
||||
export interface FileInfoOptions {
|
||||
ignorePath?: string | URL | (string | URL)[] | undefined;
|
||||
withNodeModules?: boolean | undefined;
|
||||
plugins?: Array<string | URL | Plugin> | undefined;
|
||||
resolveConfig?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface FileInfoResult {
|
||||
ignored: boolean;
|
||||
inferredParser: string | null;
|
||||
}
|
||||
|
||||
export function getFileInfo(
|
||||
file: string | URL,
|
||||
options?: FileInfoOptions,
|
||||
): Promise<FileInfoResult>;
|
||||
|
||||
export interface SupportInfoOptions {
|
||||
plugins?: Array<string | URL | Plugin> | undefined;
|
||||
showDeprecated?: boolean | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object representing the parsers, languages and file types Prettier supports for the current version.
|
||||
*/
|
||||
export function getSupportInfo(
|
||||
options?: SupportInfoOptions,
|
||||
): Promise<SupportInfo>;
|
||||
|
||||
/**
|
||||
* `version` field in `package.json`
|
||||
*/
|
||||
export const version: string;
|
||||
|
||||
// https://github.com/prettier/prettier/blob/main/src/utilities/public.js
|
||||
export namespace util {
|
||||
interface SkipOptions {
|
||||
backwards?: boolean | undefined;
|
||||
}
|
||||
|
||||
type Quote = "'" | '"';
|
||||
|
||||
function getMaxContinuousCount(text: string, searchString: string): number;
|
||||
|
||||
function getStringWidth(text: string): number;
|
||||
|
||||
function getAlignmentSize(
|
||||
text: string,
|
||||
tabWidth: number,
|
||||
startIndex?: number | undefined,
|
||||
): number;
|
||||
|
||||
function getIndentSize(value: string, tabWidth: number): number;
|
||||
|
||||
function skipNewline(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions | undefined,
|
||||
): number | false;
|
||||
|
||||
function skipInlineComment(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
): number | false;
|
||||
|
||||
function skipTrailingComment(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
): number | false;
|
||||
|
||||
function skipTrailingComment(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
): number | false;
|
||||
|
||||
function hasNewline(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
options?: SkipOptions | undefined,
|
||||
): boolean;
|
||||
|
||||
function hasNewlineInRange(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
): boolean;
|
||||
|
||||
function hasSpaces(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
options?: SkipOptions | undefined,
|
||||
): boolean;
|
||||
|
||||
function getNextNonSpaceNonCommentCharacterIndex(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
): number | false;
|
||||
|
||||
function getNextNonSpaceNonCommentCharacter(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
): string;
|
||||
|
||||
function isNextLineEmpty(text: string, startIndex: number): boolean;
|
||||
|
||||
function isPreviousLineEmpty(text: string, startIndex: number): boolean;
|
||||
|
||||
function makeString(
|
||||
rawText: string,
|
||||
enclosingQuote: Quote,
|
||||
unescapeUnnecessaryEscapes?: boolean | undefined,
|
||||
): string;
|
||||
|
||||
function skip(
|
||||
characters: string | RegExp,
|
||||
): (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions,
|
||||
) => number | false;
|
||||
|
||||
const skipWhitespace: (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions,
|
||||
) => number | false;
|
||||
|
||||
const skipSpaces: (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions,
|
||||
) => number | false;
|
||||
|
||||
const skipToLineEnd: (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions,
|
||||
) => number | false;
|
||||
|
||||
const skipEverythingButNewLine: (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions,
|
||||
) => number | false;
|
||||
|
||||
function addLeadingComment(node: any, comment: any): void;
|
||||
|
||||
function addDanglingComment(node: any, comment: any, marker: any): void;
|
||||
|
||||
function addTrailingComment(node: any, comment: any): void;
|
||||
|
||||
function getPreferredQuote(
|
||||
text: string,
|
||||
preferredQuoteOrPreferSingleQuote: Quote | boolean,
|
||||
): Quote;
|
||||
}
|
||||
-19073
File diff suppressed because one or more lines are too long
-2756
File diff suppressed because it is too large
Load Diff
-12572
File diff suppressed because one or more lines are too long
-7059
File diff suppressed because it is too large
Load Diff
-207
@@ -1,207 +0,0 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "3.9.4",
|
||||
"description": "Prettier is an opinionated code formatter",
|
||||
"bin": "./bin/prettier.cjs",
|
||||
"repository": "prettier/prettier",
|
||||
"funding": "https://github.com/prettier/prettier?sponsor=1",
|
||||
"homepage": "https://prettier.io",
|
||||
"author": "James Long",
|
||||
"license": "MIT",
|
||||
"main": "./index.cjs",
|
||||
"browser": "./standalone.js",
|
||||
"unpkg": "./standalone.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"require": "./index.cjs",
|
||||
"browser": {
|
||||
"import": "./standalone.mjs",
|
||||
"default": "./standalone.js"
|
||||
},
|
||||
"default": "./index.mjs"
|
||||
},
|
||||
"./*": "./*",
|
||||
"./standalone": {
|
||||
"types": "./standalone.d.ts",
|
||||
"require": "./standalone.js",
|
||||
"default": "./standalone.mjs"
|
||||
},
|
||||
"./doc": {
|
||||
"types": "./doc.d.ts",
|
||||
"require": "./doc.js",
|
||||
"default": "./doc.mjs"
|
||||
},
|
||||
"./plugins/estree": {
|
||||
"types": "./plugins/estree.d.ts",
|
||||
"require": "./plugins/estree.js",
|
||||
"default": "./plugins/estree.mjs"
|
||||
},
|
||||
"./plugins/babel": {
|
||||
"types": "./plugins/babel.d.ts",
|
||||
"require": "./plugins/babel.js",
|
||||
"default": "./plugins/babel.mjs"
|
||||
},
|
||||
"./plugins/flow": {
|
||||
"types": "./plugins/flow.d.ts",
|
||||
"require": "./plugins/flow.js",
|
||||
"default": "./plugins/flow.mjs"
|
||||
},
|
||||
"./plugins/typescript": {
|
||||
"types": "./plugins/typescript.d.ts",
|
||||
"require": "./plugins/typescript.js",
|
||||
"default": "./plugins/typescript.mjs"
|
||||
},
|
||||
"./plugins/acorn": {
|
||||
"types": "./plugins/acorn.d.ts",
|
||||
"require": "./plugins/acorn.js",
|
||||
"default": "./plugins/acorn.mjs"
|
||||
},
|
||||
"./plugins/meriyah": {
|
||||
"types": "./plugins/meriyah.d.ts",
|
||||
"require": "./plugins/meriyah.js",
|
||||
"default": "./plugins/meriyah.mjs"
|
||||
},
|
||||
"./plugins/angular": {
|
||||
"types": "./plugins/angular.d.ts",
|
||||
"require": "./plugins/angular.js",
|
||||
"default": "./plugins/angular.mjs"
|
||||
},
|
||||
"./plugins/postcss": {
|
||||
"types": "./plugins/postcss.d.ts",
|
||||
"require": "./plugins/postcss.js",
|
||||
"default": "./plugins/postcss.mjs"
|
||||
},
|
||||
"./plugins/graphql": {
|
||||
"types": "./plugins/graphql.d.ts",
|
||||
"require": "./plugins/graphql.js",
|
||||
"default": "./plugins/graphql.mjs"
|
||||
},
|
||||
"./plugins/markdown": {
|
||||
"types": "./plugins/markdown.d.ts",
|
||||
"require": "./plugins/markdown.js",
|
||||
"default": "./plugins/markdown.mjs"
|
||||
},
|
||||
"./plugins/glimmer": {
|
||||
"types": "./plugins/glimmer.d.ts",
|
||||
"require": "./plugins/glimmer.js",
|
||||
"default": "./plugins/glimmer.mjs"
|
||||
},
|
||||
"./plugins/html": {
|
||||
"types": "./plugins/html.d.ts",
|
||||
"require": "./plugins/html.js",
|
||||
"default": "./plugins/html.mjs"
|
||||
},
|
||||
"./plugins/yaml": {
|
||||
"types": "./plugins/yaml.d.ts",
|
||||
"require": "./plugins/yaml.js",
|
||||
"default": "./plugins/yaml.mjs"
|
||||
},
|
||||
"./esm/standalone.mjs": "./standalone.mjs",
|
||||
"./parser-babel": "./plugins/babel.js",
|
||||
"./parser-babel.js": "./plugins/babel.js",
|
||||
"./esm/parser-babel.mjs": "./plugins/babel.mjs",
|
||||
"./parser-flow": "./plugins/flow.js",
|
||||
"./parser-flow.js": "./plugins/flow.js",
|
||||
"./esm/parser-flow.mjs": "./plugins/flow.mjs",
|
||||
"./parser-typescript": "./plugins/typescript.js",
|
||||
"./parser-typescript.js": "./plugins/typescript.js",
|
||||
"./esm/parser-typescript.mjs": "./plugins/typescript.mjs",
|
||||
"./parser-espree": "./plugins/acorn.js",
|
||||
"./parser-espree.js": "./plugins/acorn.js",
|
||||
"./esm/parser-espree.mjs": "./plugins/acorn.mjs",
|
||||
"./parser-meriyah": "./plugins/meriyah.js",
|
||||
"./parser-meriyah.js": "./plugins/meriyah.js",
|
||||
"./esm/parser-meriyah.mjs": "./plugins/meriyah.mjs",
|
||||
"./parser-angular": "./plugins/angular.js",
|
||||
"./parser-angular.js": "./plugins/angular.js",
|
||||
"./esm/parser-angular.mjs": "./plugins/angular.mjs",
|
||||
"./parser-postcss": "./plugins/postcss.js",
|
||||
"./parser-postcss.js": "./plugins/postcss.js",
|
||||
"./esm/parser-postcss.mjs": "./plugins/postcss.mjs",
|
||||
"./parser-graphql": "./plugins/graphql.js",
|
||||
"./parser-graphql.js": "./plugins/graphql.js",
|
||||
"./esm/parser-graphql.mjs": "./plugins/graphql.mjs",
|
||||
"./parser-markdown": "./plugins/markdown.js",
|
||||
"./parser-markdown.js": "./plugins/markdown.js",
|
||||
"./esm/parser-markdown.mjs": "./plugins/markdown.mjs",
|
||||
"./parser-glimmer": "./plugins/glimmer.js",
|
||||
"./parser-glimmer.js": "./plugins/glimmer.js",
|
||||
"./esm/parser-glimmer.mjs": "./plugins/glimmer.mjs",
|
||||
"./parser-html": "./plugins/html.js",
|
||||
"./parser-html.js": "./plugins/html.js",
|
||||
"./esm/parser-html.mjs": "./plugins/html.mjs",
|
||||
"./parser-yaml": "./plugins/yaml.js",
|
||||
"./parser-yaml.js": "./plugins/yaml.js",
|
||||
"./esm/parser-yaml.mjs": "./plugins/yaml.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"THIRD-PARTY-NOTICES.md",
|
||||
"bin/prettier.cjs",
|
||||
"doc.d.ts",
|
||||
"doc.js",
|
||||
"doc.mjs",
|
||||
"index.cjs",
|
||||
"index.d.ts",
|
||||
"index.d.ts",
|
||||
"index.mjs",
|
||||
"internal/experimental-cli-worker.mjs",
|
||||
"internal/experimental-cli.mjs",
|
||||
"internal/legacy-cli.mjs",
|
||||
"package.json",
|
||||
"plugins/acorn.d.ts",
|
||||
"plugins/acorn.js",
|
||||
"plugins/acorn.mjs",
|
||||
"plugins/angular.d.ts",
|
||||
"plugins/angular.js",
|
||||
"plugins/angular.mjs",
|
||||
"plugins/babel.d.ts",
|
||||
"plugins/babel.js",
|
||||
"plugins/babel.mjs",
|
||||
"plugins/estree.d.ts",
|
||||
"plugins/estree.js",
|
||||
"plugins/estree.mjs",
|
||||
"plugins/flow.d.ts",
|
||||
"plugins/flow.js",
|
||||
"plugins/flow.mjs",
|
||||
"plugins/glimmer.d.ts",
|
||||
"plugins/glimmer.js",
|
||||
"plugins/glimmer.mjs",
|
||||
"plugins/graphql.d.ts",
|
||||
"plugins/graphql.js",
|
||||
"plugins/graphql.mjs",
|
||||
"plugins/html.d.ts",
|
||||
"plugins/html.js",
|
||||
"plugins/html.mjs",
|
||||
"plugins/markdown.d.ts",
|
||||
"plugins/markdown.js",
|
||||
"plugins/markdown.mjs",
|
||||
"plugins/meriyah.d.ts",
|
||||
"plugins/meriyah.js",
|
||||
"plugins/meriyah.mjs",
|
||||
"plugins/postcss.d.ts",
|
||||
"plugins/postcss.js",
|
||||
"plugins/postcss.mjs",
|
||||
"plugins/typescript.d.ts",
|
||||
"plugins/typescript.js",
|
||||
"plugins/typescript.mjs",
|
||||
"plugins/yaml.d.ts",
|
||||
"plugins/yaml.js",
|
||||
"plugins/yaml.mjs",
|
||||
"standalone.d.ts",
|
||||
"standalone.js",
|
||||
"standalone.mjs"
|
||||
],
|
||||
"preferUnplugged": true,
|
||||
"sideEffects": false,
|
||||
"type": "commonjs",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
acorn: Parser;
|
||||
espree: Parser;
|
||||
};
|
||||
-17
File diff suppressed because one or more lines are too long
-17
File diff suppressed because one or more lines are too long
-8
@@ -1,8 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
__ng_action: Parser;
|
||||
__ng_binding: Parser;
|
||||
__ng_directive: Parser;
|
||||
__ng_interpolation: Parser;
|
||||
};
|
||||
-5
File diff suppressed because one or more lines are too long
-5
File diff suppressed because one or more lines are too long
-18
@@ -1,18 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
__babel_estree: Parser;
|
||||
__js_expression: Parser;
|
||||
__ts_expression: Parser;
|
||||
__vue_event_binding: Parser;
|
||||
__vue_expression: Parser;
|
||||
__vue_ts_event_binding: Parser;
|
||||
__vue_ts_expression: Parser;
|
||||
babel: Parser;
|
||||
"babel-flow": Parser;
|
||||
"babel-ts": Parser;
|
||||
json: Parser;
|
||||
"json-stringify": Parser;
|
||||
json5: Parser;
|
||||
jsonc: Parser;
|
||||
};
|
||||
-16
File diff suppressed because one or more lines are too long
-16
File diff suppressed because one or more lines are too long
-6
@@ -1,6 +0,0 @@
|
||||
import { Printer } from "../index.js";
|
||||
|
||||
export declare const printers: {
|
||||
estree: Printer;
|
||||
"estree-json": Printer;
|
||||
};
|
||||
-44
File diff suppressed because one or more lines are too long
-44
File diff suppressed because one or more lines are too long
-5
@@ -1,5 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
flow: Parser;
|
||||
};
|
||||
-7
File diff suppressed because one or more lines are too long
-7
File diff suppressed because one or more lines are too long
-9
@@ -1,9 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
glimmer: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
glimmer: Printer;
|
||||
};
|
||||
-37
File diff suppressed because one or more lines are too long
-37
File diff suppressed because one or more lines are too long
-9
@@ -1,9 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
graphql: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
graphql: Printer;
|
||||
};
|
||||
-17
File diff suppressed because one or more lines are too long
-17
File diff suppressed because one or more lines are too long
-13
@@ -1,13 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
angular: Parser;
|
||||
html: Parser;
|
||||
lwc: Parser;
|
||||
mjml: Parser;
|
||||
vue: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
html: Printer;
|
||||
};
|
||||
-25
File diff suppressed because one or more lines are too long
-25
File diff suppressed because one or more lines are too long
-11
@@ -1,11 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
markdown: Parser;
|
||||
mdx: Parser;
|
||||
remark: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
mdast: Printer;
|
||||
};
|
||||
-70
File diff suppressed because one or more lines are too long
-70
File diff suppressed because one or more lines are too long
-5
@@ -1,5 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
meriyah: Parser;
|
||||
};
|
||||
-6
File diff suppressed because one or more lines are too long
-6
File diff suppressed because one or more lines are too long
-11
@@ -1,11 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
css: Parser;
|
||||
less: Parser;
|
||||
scss: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
postcss: Printer;
|
||||
};
|
||||
-62
File diff suppressed because one or more lines are too long
-62
File diff suppressed because one or more lines are too long
-5
@@ -1,5 +0,0 @@
|
||||
import { Parser } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
typescript: Parser;
|
||||
};
|
||||
-22
File diff suppressed because one or more lines are too long
-22
File diff suppressed because one or more lines are too long
-9
@@ -1,9 +0,0 @@
|
||||
import { Parser, Printer } from "../index.js";
|
||||
|
||||
export declare const parsers: {
|
||||
yaml: Parser;
|
||||
};
|
||||
|
||||
export declare const printers: {
|
||||
yaml: Printer;
|
||||
};
|
||||
-141
File diff suppressed because one or more lines are too long
-154
File diff suppressed because one or more lines are too long
-33
@@ -1,33 +0,0 @@
|
||||
import { CursorOptions, CursorResult, Options, SupportInfo } from "./index.js";
|
||||
|
||||
/**
|
||||
* formatWithCursor both formats the code, and translates a cursor position from unformatted code to formatted code.
|
||||
* This is useful for editor integrations, to prevent the cursor from moving when code is formatted
|
||||
*
|
||||
* The cursorOffset option should be provided, to specify where the cursor is.
|
||||
*
|
||||
* ```js
|
||||
* await prettier.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" });
|
||||
* ```
|
||||
* `-> { formatted: "1;\n", cursorOffset: 1 }`
|
||||
*/
|
||||
export function formatWithCursor(
|
||||
source: string,
|
||||
options: CursorOptions,
|
||||
): Promise<CursorResult>;
|
||||
|
||||
/**
|
||||
* `format` is used to format text using Prettier. [Options](https://prettier.io/docs/options) may be provided to override the defaults.
|
||||
*/
|
||||
export function format(source: string, options?: Options): Promise<string>;
|
||||
|
||||
/**
|
||||
* `check` checks to see if the file has been formatted with Prettier given those options and returns a `Boolean`.
|
||||
* This is similar to the `--list-different` parameter in the CLI and is useful for running Prettier in CI scenarios.
|
||||
*/
|
||||
export function check(source: string, options?: Options): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Returns an object representing the parsers, languages and file types Prettier supports for the current version.
|
||||
*/
|
||||
export function getSupportInfo(): Promise<SupportInfo>;
|
||||
-29
File diff suppressed because one or more lines are too long
-29
File diff suppressed because one or more lines are too long
+47
-11
@@ -1,15 +1,51 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
pydantic==2.5.0
|
||||
affine==2.4.0
|
||||
aiomysql==0.2.0
|
||||
python-multipart==0.0.6
|
||||
python-dotenv==1.0.0
|
||||
|
||||
trimesh==3.23.0
|
||||
annotated-types==0.7.0
|
||||
anyio==3.7.1
|
||||
attrs==26.1.0
|
||||
certifi==2026.6.17
|
||||
click==8.4.2
|
||||
click-plugins==1.1.1.2
|
||||
cligj==0.7.2
|
||||
colorama==0.4.6
|
||||
et_xmlfile==2.0.0
|
||||
fastapi==0.104.1
|
||||
fiona==1.10.1
|
||||
geopandas==0.14.0
|
||||
shapely==2.0.1
|
||||
rasterio==1.3.8
|
||||
h11==0.16.0
|
||||
idna==3.18
|
||||
ImageIO==2.37.3
|
||||
Jinja2==3.1.2
|
||||
laspy==2.4.1
|
||||
whitebox==2.3.0
|
||||
jinja2==3.1.2
|
||||
lazy-loader==0.5
|
||||
MarkupSafe==3.0.3
|
||||
networkx==3.6.1
|
||||
numpy==1.26.4
|
||||
openpyxl==3.1.2
|
||||
packaging==26.2
|
||||
pandas==3.0.3
|
||||
pillow==12.3.0
|
||||
pydantic==2.5.0
|
||||
pydantic_core==2.14.1
|
||||
PyMySQL==1.2.0
|
||||
pyparsing==3.3.2
|
||||
pyproj==3.7.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.0.0
|
||||
python-multipart==0.0.6
|
||||
rasterio==1.3.9
|
||||
scikit-image==0.26.0
|
||||
scipy==1.13.1
|
||||
setuptools==83.0.0
|
||||
shapely==2.0.6
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
snuggs==1.4.7
|
||||
starlette==0.27.0
|
||||
tifffile==2024.8.28
|
||||
trimesh==3.23.0
|
||||
typing_extensions==4.16.0
|
||||
tzdata==2026.2
|
||||
uvicorn==0.24.0
|
||||
wheel==0.47.0
|
||||
whitebox==2.3.0
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
@@ -1,110 +0,0 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import laspy
|
||||
import numpy as np
|
||||
import rasterio
|
||||
from pyproj import CRS
|
||||
from rasterio.transform import from_origin
|
||||
|
||||
from B03_FileInput.B03_FileInput_Engine_Analyze import (
|
||||
analyze_input_metadata,
|
||||
analyze_las_metadata,
|
||||
analyze_prj_metadata,
|
||||
analyze_tfw_metadata,
|
||||
analyze_tif_metadata,
|
||||
)
|
||||
|
||||
|
||||
class AnalyzeLasMetadataTest(unittest.TestCase):
|
||||
def test_analyze_las_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
source = Path(temporary_directory) / "sample.las"
|
||||
las = laspy.LasData(laspy.LasHeader(point_format=3, version="1.2"))
|
||||
las.x = np.array([100.0, 102.0])
|
||||
las.y = np.array([200.0, 204.0])
|
||||
las.z = np.array([10.0, 14.0])
|
||||
las.classification = np.array([2, 5], dtype=np.uint8)
|
||||
las.write(source)
|
||||
|
||||
metadata = analyze_las_metadata(source)
|
||||
|
||||
self.assertEqual(metadata["file"], "sample.las")
|
||||
self.assertEqual(metadata["point_count"], 2)
|
||||
self.assertEqual(metadata["bounds"]["x"], [100.0, 102.0])
|
||||
self.assertEqual(metadata["bounds"]["y"], [200.0, 204.0])
|
||||
self.assertEqual(metadata["bounds"]["z"], [10.0, 14.0])
|
||||
self.assertEqual(metadata["classification_summary"], {"2": 1, "5": 1})
|
||||
|
||||
def test_analyze_prj_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
source = Path(temporary_directory) / "result.prj"
|
||||
source.write_text(CRS.from_epsg(5179).to_wkt(), encoding="utf-8")
|
||||
|
||||
metadata = analyze_prj_metadata(source)
|
||||
|
||||
self.assertEqual(metadata["file"], "result.prj")
|
||||
self.assertEqual(metadata["epsg"], 5179)
|
||||
self.assertTrue(metadata["is_valid"])
|
||||
|
||||
source.write_text("invalid wkt", encoding="utf-8")
|
||||
invalid_metadata = analyze_prj_metadata(source)
|
||||
self.assertFalse(invalid_metadata["is_valid"])
|
||||
self.assertIn("error", invalid_metadata)
|
||||
|
||||
def test_analyze_tfw_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
source = Path(temporary_directory) / "result.tfw"
|
||||
source.write_text("0.5\n0\n0\n-0.5\n100\n200\n", encoding="utf-8")
|
||||
|
||||
metadata = analyze_tfw_metadata(source)
|
||||
|
||||
self.assertTrue(metadata["is_valid"])
|
||||
self.assertEqual(metadata["pixel_size_x"], 0.5)
|
||||
self.assertEqual(metadata["pixel_size_y"], -0.5)
|
||||
self.assertEqual(metadata["origin_x"], 100.0)
|
||||
self.assertEqual(metadata["origin_y"], 200.0)
|
||||
|
||||
source.write_text("nan\n0\n0\n-0.5\n100\n200\n", encoding="utf-8")
|
||||
with self.assertRaises(ValueError):
|
||||
analyze_tfw_metadata(source)
|
||||
|
||||
def test_analyze_tif_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
source = Path(temporary_directory) / "result.tif"
|
||||
with rasterio.open(
|
||||
source,
|
||||
mode="w",
|
||||
driver="GTiff",
|
||||
width=3,
|
||||
height=2,
|
||||
count=1,
|
||||
dtype="float32",
|
||||
crs="EPSG:5179",
|
||||
transform=from_origin(100, 200, 2, 2),
|
||||
nodata=-9999,
|
||||
) as dataset:
|
||||
dataset.write(np.ones((1, 2, 3), dtype=np.float32))
|
||||
|
||||
metadata = analyze_tif_metadata(source)
|
||||
|
||||
self.assertEqual(metadata["file"], "result.tif")
|
||||
self.assertEqual(metadata["width"], 3)
|
||||
self.assertEqual(metadata["height"], 2)
|
||||
self.assertEqual(metadata["count"], 1)
|
||||
self.assertEqual(metadata["epsg"], 5179)
|
||||
self.assertEqual(metadata["resolution"], [2.0, 2.0])
|
||||
self.assertEqual(metadata["likely_type"], "dem")
|
||||
|
||||
def test_analyze_input_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
source = Path(temporary_directory) / "drawing.dxf"
|
||||
source.write_bytes(b"0\nSECTION\n")
|
||||
|
||||
metadata = analyze_input_metadata(source)
|
||||
|
||||
self.assertEqual(
|
||||
metadata,
|
||||
{"file": "drawing.dxf", "extension": "dxf", "size_bytes": 10},
|
||||
)
|
||||
@@ -1,58 +0,0 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from B03_FileInput.B03_FileInput_Engine import (
|
||||
resolve_upload_destination,
|
||||
save_upload_stream,
|
||||
)
|
||||
from B03_FileInput.B03_FileInput_Schema import FileUploadDescriptor
|
||||
|
||||
|
||||
class ResolveUploadDestinationTest(unittest.TestCase):
|
||||
def test_resolve_upload_destination(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
descriptor = FileUploadDescriptor(
|
||||
original_filename="cloud_merged.LAS",
|
||||
size_bytes=1024,
|
||||
)
|
||||
|
||||
destination = resolve_upload_destination(temporary_directory, descriptor)
|
||||
|
||||
self.assertEqual(
|
||||
destination,
|
||||
Path(temporary_directory).resolve()
|
||||
/ "B03_FileInput"
|
||||
/ "input"
|
||||
/ "las"
|
||||
/ "cloud_merged.LAS",
|
||||
)
|
||||
self.assertTrue(destination.parent.is_dir())
|
||||
|
||||
|
||||
class SaveUploadStreamTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_save_upload_stream(self) -> None:
|
||||
class FakeUpload:
|
||||
def __init__(self, chunks: list[bytes]) -> None:
|
||||
self.chunks = iter(chunks)
|
||||
|
||||
async def read(self, _size: int) -> bytes:
|
||||
return next(self.chunks, b"")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
destination = Path(temporary_directory) / "cloud.las"
|
||||
upload = FakeUpload([b"abc", b"def"])
|
||||
|
||||
written_bytes = await save_upload_stream(upload, destination) # type: ignore[arg-type]
|
||||
|
||||
self.assertEqual(written_bytes, 6)
|
||||
self.assertEqual(destination.read_bytes(), b"abcdef")
|
||||
self.assertEqual(list(destination.parent.glob("*.upload")), [])
|
||||
|
||||
with patch("B03_FileInput.B03_FileInput_Engine.UPLOAD_MAX_MB", 0):
|
||||
with self.assertRaises(ValueError):
|
||||
await save_upload_stream( # type: ignore[arg-type]
|
||||
FakeUpload([b"too-large"]),
|
||||
destination,
|
||||
)
|
||||
@@ -1,100 +0,0 @@
|
||||
import json
|
||||
import unittest
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from B03_FileInput.B03_FileInput_Repository import (
|
||||
create_input_file,
|
||||
get_project_storage_relative_path,
|
||||
)
|
||||
|
||||
|
||||
class FakeCursor:
|
||||
"""asyncmy 커서 흉내 (async context manager + execute/fetchone/lastrowid)."""
|
||||
|
||||
def __init__(self, *, lastrowid: int = 0, row: tuple[Any, ...] | None = None) -> None:
|
||||
self.query = ""
|
||||
self.arguments: tuple[Any, ...] = ()
|
||||
self.lastrowid = lastrowid
|
||||
self._row = row
|
||||
|
||||
async def __aenter__(self) -> "FakeCursor":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_exc: Any) -> None:
|
||||
return None
|
||||
|
||||
async def execute(self, query: str, arguments: tuple[Any, ...] | None = None) -> None:
|
||||
self.query = query
|
||||
self.arguments = arguments or ()
|
||||
|
||||
async def fetchone(self) -> tuple[Any, ...] | None:
|
||||
return self._row
|
||||
|
||||
|
||||
class FakeConnection:
|
||||
"""asyncmy 커넥션 흉내 (cursor() 팩토리)."""
|
||||
|
||||
def __init__(self, cursor: FakeCursor) -> None:
|
||||
self._cursor = cursor
|
||||
|
||||
def cursor(self) -> FakeCursor:
|
||||
return self._cursor
|
||||
|
||||
|
||||
class CreateInputFileTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_create_input_file(self) -> None:
|
||||
cursor = FakeCursor(lastrowid=17)
|
||||
connection = FakeConnection(cursor)
|
||||
project_id = UUID("550e8400-e29b-41d4-a716-446655440000")
|
||||
metadata = {"point_count": 2, "crs": "EPSG:5179"}
|
||||
|
||||
input_file_id = await create_input_file( # type: ignore[arg-type]
|
||||
connection,
|
||||
project_id=project_id,
|
||||
file_type="las",
|
||||
original_filename="cloud.las",
|
||||
relative_path="B03_FileInput/input/las/cloud.las",
|
||||
file_size_bytes=1024 * 1024,
|
||||
upload_by=3,
|
||||
crs_epsg=5179,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
self.assertEqual(input_file_id, 17)
|
||||
self.assertIn("INSERT INTO input_files", cursor.query)
|
||||
self.assertEqual(cursor.arguments[0], str(project_id))
|
||||
self.assertEqual(cursor.arguments[3], "B03_FileInput/input/las/cloud.las")
|
||||
self.assertEqual(cursor.arguments[4], 1.0)
|
||||
self.assertEqual(json.loads(cursor.arguments[7]), metadata)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
await create_input_file( # type: ignore[arg-type]
|
||||
connection,
|
||||
project_id=project_id,
|
||||
file_type="las",
|
||||
original_filename="cloud.las",
|
||||
relative_path="../outside/cloud.las",
|
||||
file_size_bytes=1,
|
||||
upload_by=None,
|
||||
crs_epsg=None,
|
||||
metadata={},
|
||||
)
|
||||
|
||||
async def test_get_project_storage_relative_path(self) -> None:
|
||||
project_id = UUID("550e8400-e29b-41d4-a716-446655440000")
|
||||
|
||||
connection = FakeConnection(FakeCursor(row=("storage/company/user/project-id",)))
|
||||
relative_path = await get_project_storage_relative_path( # type: ignore[arg-type]
|
||||
connection,
|
||||
project_id,
|
||||
)
|
||||
self.assertEqual(relative_path, "storage/company/user/project-id")
|
||||
|
||||
for row in (None, ("../outside",), ("/absolute/path",)):
|
||||
with self.subTest(row=row):
|
||||
with self.assertRaises((LookupError, ValueError)):
|
||||
await get_project_storage_relative_path( # type: ignore[arg-type]
|
||||
FakeConnection(FakeCursor(row=row)),
|
||||
project_id,
|
||||
)
|
||||
@@ -1,122 +0,0 @@
|
||||
import io
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
from uuid import UUID
|
||||
|
||||
import laspy
|
||||
import numpy as np
|
||||
|
||||
from B03_FileInput.B03_FileInput_Router import upload_project_files
|
||||
from B03_FileInput.B03_FileInput_Schema import FileUploadResponse
|
||||
|
||||
|
||||
class UploadProjectFilesTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_upload_project_files(self) -> None:
|
||||
class AsyncContext:
|
||||
def __init__(self, value: Any = None) -> None:
|
||||
self.value = value
|
||||
|
||||
async def __aenter__(self) -> Any:
|
||||
return self.value
|
||||
|
||||
async def __aexit__(self, *_args: Any) -> None:
|
||||
return None
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self, connection: "FakeConnection") -> None:
|
||||
self.connection = connection
|
||||
self.lastrowid = 0
|
||||
|
||||
async def __aenter__(self) -> "FakeCursor":
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *_args: Any) -> None:
|
||||
return None
|
||||
|
||||
async def execute(self, query: str, _arguments: Any = None) -> None:
|
||||
if query.strip().upper().startswith("SELECT"):
|
||||
self._row: tuple[Any, ...] | None = ("storage/company/user/project-id",)
|
||||
else:
|
||||
self.lastrowid = self.connection.next_id
|
||||
self.connection.next_id += 1
|
||||
self._row = None
|
||||
|
||||
async def fetchone(self) -> tuple[Any, ...] | None:
|
||||
return getattr(self, "_row", None)
|
||||
|
||||
class FakeConnection:
|
||||
def __init__(self) -> None:
|
||||
self.next_id = 1
|
||||
self.committed = False
|
||||
self.rolled_back = False
|
||||
|
||||
def cursor(self) -> FakeCursor:
|
||||
return FakeCursor(self)
|
||||
|
||||
async def begin(self) -> None:
|
||||
return None
|
||||
|
||||
async def commit(self) -> None:
|
||||
self.committed = True
|
||||
|
||||
async def rollback(self) -> None:
|
||||
self.rolled_back = True
|
||||
|
||||
class FakePool:
|
||||
def __init__(self, connection: FakeConnection) -> None:
|
||||
self.connection = connection
|
||||
|
||||
def acquire(self) -> AsyncContext:
|
||||
return AsyncContext(self.connection)
|
||||
|
||||
class FakeUpload:
|
||||
def __init__(self, filename: str, content: bytes) -> None:
|
||||
self.filename = filename
|
||||
self.size = len(content)
|
||||
self.file = io.BytesIO(content)
|
||||
self.closed = False
|
||||
|
||||
async def read(self, size: int) -> bytes:
|
||||
return self.file.read(size)
|
||||
|
||||
async def close(self) -> None:
|
||||
self.file.close()
|
||||
self.closed = True
|
||||
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
project_root = Path(temporary_directory) / "project"
|
||||
project_root.mkdir()
|
||||
source = Path(temporary_directory) / "source.las"
|
||||
las = laspy.LasData(laspy.LasHeader(point_format=3, version="1.2"))
|
||||
las.x = np.array([1.0])
|
||||
las.y = np.array([2.0])
|
||||
las.z = np.array([3.0])
|
||||
las.write(source)
|
||||
upload = FakeUpload("cloud.las", source.read_bytes())
|
||||
pool = FakePool(FakeConnection())
|
||||
|
||||
with (
|
||||
patch("B03_FileInput.B03_FileInput_Router.get_db_pool", return_value=pool),
|
||||
patch(
|
||||
"B03_FileInput.B03_FileInput_Router.resolve_stored_project_path",
|
||||
return_value=str(project_root),
|
||||
),
|
||||
):
|
||||
response = await upload_project_files( # type: ignore[arg-type]
|
||||
UUID("550e8400-e29b-41d4-a716-446655440000"),
|
||||
[upload],
|
||||
)
|
||||
|
||||
self.assertIsInstance(response, FileUploadResponse)
|
||||
assert isinstance(response, FileUploadResponse)
|
||||
self.assertEqual(len(response.files), 1)
|
||||
self.assertEqual(response.files[0].input_file_id, 1)
|
||||
self.assertTrue(
|
||||
(project_root / "B03_FileInput" / "input" / "las" / "cloud.las").is_file()
|
||||
)
|
||||
self.assertTrue((project_root / "B03_FileInput" / "metadata.json").is_file())
|
||||
self.assertTrue((project_root / "workflow.json").is_file())
|
||||
self.assertTrue(upload.closed)
|
||||
@@ -1,30 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from B03_FileInput.B03_FileInput_Schema import FileUploadDescriptor
|
||||
from config.config_system import UPLOAD_MAX_MB
|
||||
|
||||
|
||||
class FileUploadDescriptorTest(unittest.TestCase):
|
||||
def test_file_upload_descriptor(self) -> None:
|
||||
descriptor = FileUploadDescriptor(
|
||||
original_filename="cloud_merged.LAS",
|
||||
size_bytes=1024,
|
||||
)
|
||||
self.assertEqual(descriptor.original_filename, "cloud_merged.LAS")
|
||||
|
||||
invalid_values = (
|
||||
{"original_filename": "../cloud.las", "size_bytes": 1024},
|
||||
{"original_filename": "nested/cloud.las", "size_bytes": 1024},
|
||||
{"original_filename": "cloud.exe", "size_bytes": 1024},
|
||||
{
|
||||
"original_filename": "cloud.las",
|
||||
"size_bytes": UPLOAD_MAX_MB * 1024 * 1024 + 1,
|
||||
},
|
||||
{"original_filename": "cloud.las", "size_bytes": 0},
|
||||
)
|
||||
for invalid_value in invalid_values:
|
||||
with self.subTest(invalid_value=invalid_value):
|
||||
with self.assertRaises(ValidationError):
|
||||
FileUploadDescriptor(**invalid_value)
|
||||
@@ -1,64 +0,0 @@
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import laspy
|
||||
import numpy as np
|
||||
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Engine import run_surface_analysis
|
||||
from B04_wf1_Surface.B04_wf1_Surface_Engine_Ground import (
|
||||
build_ground_masks,
|
||||
summarize_masks,
|
||||
)
|
||||
|
||||
|
||||
class GroundMasksTest(unittest.TestCase):
|
||||
def test_build_and_summarize(self) -> None:
|
||||
coords = np.linspace(0.0, 20.0, 21)
|
||||
gx, gy = np.meshgrid(coords, coords)
|
||||
z = 5.0 + 0.03 * gx
|
||||
xyz = np.column_stack([gx.ravel(), gy.ravel(), z.ravel()]).astype(np.float64)
|
||||
bounds = np.array([[0.0, 20.0], [0.0, 20.0], [float(z.min()), float(z.max())]])
|
||||
data = {"xyz": xyz, "bounds": bounds}
|
||||
|
||||
masks = build_ground_masks(data, ["grid_min_z"])
|
||||
self.assertIn("grid_min_z", masks)
|
||||
self.assertEqual(masks["grid_min_z"].dtype, bool)
|
||||
|
||||
summary = summarize_masks(data, masks)
|
||||
self.assertEqual(summary["grid_min_z"]["total_point_count"], len(xyz))
|
||||
self.assertGreater(summary["grid_min_z"]["ground_ratio"], 0.9)
|
||||
|
||||
def test_unknown_filter(self) -> None:
|
||||
data = {"xyz": np.zeros((1, 3)), "bounds": np.zeros((3, 2))}
|
||||
with self.assertRaises(ValueError):
|
||||
build_ground_masks(data, ["unknown"])
|
||||
|
||||
|
||||
class RunSurfaceAnalysisTest(unittest.TestCase):
|
||||
def test_full_pipeline(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory) / "proj"
|
||||
root.mkdir()
|
||||
coords = np.linspace(0.0, 30.0, 31)
|
||||
gx, gy = np.meshgrid(coords, coords)
|
||||
z = 5.0 + 0.04 * gx + 0.02 * gy
|
||||
src = Path(directory) / "cloud.las"
|
||||
las = laspy.LasData(laspy.LasHeader(point_format=3, version="1.2"))
|
||||
las.x, las.y, las.z = gx.ravel(), gy.ravel(), z.ravel()
|
||||
las.write(src)
|
||||
|
||||
result = run_surface_analysis(
|
||||
root, src, source_filters=["grid_min_z"], methods=["dtm", "tin"]
|
||||
)
|
||||
|
||||
self.assertEqual(result["processed"]["point_count"], 961)
|
||||
self.assertEqual(result["manifest"]["status"], "completed")
|
||||
self.assertGreater(result["ground_summary"]["grid_min_z"]["ground_ratio"], 0.9)
|
||||
model_types = {m["model_type"] for m in result["models"]}
|
||||
self.assertEqual(model_types, {"dtm", "tin"})
|
||||
for model in result["models"]:
|
||||
self.assertTrue(model["model_file_path"].startswith("B04_wf1_Surface/"))
|
||||
self.assertTrue(model["layers"])
|
||||
# structured.npz가 processed 폴더에 생성됨
|
||||
self.assertTrue((root / "B04_wf1_Surface" / "processed" / "structured.npz").is_file())
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user