From 3f4d0c582865d72dfc74c4a81b015558f1b42688 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sun, 5 Apr 2026 09:12:56 +0900 Subject: [PATCH] =?UTF-8?q?Phase=207~8=20=EA=B5=AC=ED=98=84:=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20+=20=EB=B9=8C=EB=93=9C=20+=20CI/CD=20+=20S?= =?UTF-8?q?oundEffect=20+=20AutoLaunch=20+=20UI=20=EB=A6=AC=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vitest 41개 단위 테스트 (HistoryService, DictionaryService, CustomInstructionService, VoiceModeService, D3ROError) - electron-builder.yml (NSIS, asarUnpack, extraResources) - .gitlab-ci.yml (lint, typecheck, test, build, release) - SoundEffectService: WAV 프리로드 + PowerShell 재생 + VoiceMode 연동 - AutoLaunchService: app.setLoginItemSettings + ConfigService 동기화 - TextInsertService: 간이 삽입 검증 (EditMonitor 경량) - 번들링 인프라: SoX 다운로드 스크립트, PyInstaller 빌드, 경로 해상도 유틸 - AudioCaptureService/LocalSTTService: 번들 경로 자동 감지 - 08-design-system.md 기반 MUI 테마 (다크+라이트+auto 테마 시스템) - 전체 UI 컴포넌트 리디자인: AppLayout, Dashboard, StatusBar, History, Dictionary, Commands, Settings - 효과음 WAV 생성: recording-start, recording-stop, error - EPIPE 에러 핸들링 추가 --- .gitignore | 6 + .gitlab-ci.yml | 99 + CLAUDE.md | 44 +- electron-builder.yml | 62 + package-lock.json | 3346 ++++++++++++++++- package.json | 16 +- resources/sounds/error.wav | Bin 0 -> 16044 bytes resources/sounds/recording-start.wav | Bin 0 -> 4844 bytes resources/sounds/recording-stop.wav | Bin 0 -> 4844 bytes scripts/build-sidecar.py | 120 + scripts/download-sox.ps1 | 111 + scripts/generate-sounds.js | 147 + src/main/bootstrap.ts | 22 +- src/main/index.ts | 9 + src/main/ipc/config-handlers.ts | 13 + src/main/ipc/system-handlers.ts | 19 +- src/main/services/AudioCaptureService.ts | 15 +- src/main/services/AutoLaunchService.ts | 67 + src/main/services/LocalSTTService.ts | 19 +- src/main/services/SoundEffectService.ts | 126 + src/main/services/TextInsertService.ts | 9 + src/main/utils/paths.ts | 98 + src/main/windows/WindowManager.ts | 3 + src/renderer/App.tsx | 23 +- src/renderer/components/AppLayout.tsx | 195 +- src/renderer/components/SettingsModal.tsx | 7 +- src/renderer/components/StatusBar.tsx | 79 +- src/renderer/pages/CommandsPage.tsx | 162 +- src/renderer/pages/DashboardPage.tsx | 286 +- src/renderer/pages/DictionaryPage.tsx | 125 +- src/renderer/pages/HistoryPage.tsx | 176 +- src/renderer/theme.ts | 320 +- tests/helpers/createTestDb.ts | 87 + .../services/CustomInstructionService.test.ts | 190 + tests/main/services/DictionaryService.test.ts | 116 + tests/main/services/HistoryService.test.ts | 134 + tests/main/services/VoiceModeService.test.ts | 204 + tests/setup.ts | 61 + tests/shared/errors.test.ts | 75 + vitest.config.ts | 23 + 40 files changed, 6034 insertions(+), 580 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 electron-builder.yml create mode 100644 resources/sounds/error.wav create mode 100644 resources/sounds/recording-start.wav create mode 100644 resources/sounds/recording-stop.wav create mode 100644 scripts/build-sidecar.py create mode 100644 scripts/download-sox.ps1 create mode 100644 scripts/generate-sounds.js create mode 100644 src/main/services/AutoLaunchService.ts create mode 100644 src/main/services/SoundEffectService.ts create mode 100644 src/main/utils/paths.ts create mode 100644 tests/helpers/createTestDb.ts create mode 100644 tests/main/services/CustomInstructionService.test.ts create mode 100644 tests/main/services/DictionaryService.test.ts create mode 100644 tests/main/services/HistoryService.test.ts create mode 100644 tests/main/services/VoiceModeService.test.ts create mode 100644 tests/setup.ts create mode 100644 tests/shared/errors.test.ts create mode 100644 vitest.config.ts diff --git a/.gitignore b/.gitignore index 9acb248..d7a8e7e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,12 @@ release/ # Build *.tsbuildinfo +build/ +sidecar-dist/ + +# Bundled binaries (download via scripts) +resources/sox/*.exe +resources/sox/*.dll # IDE .idea/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..5f9c00d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,99 @@ +stages: + - check + - test + - build + - release + +variables: + NODE_VERSION: "20" + npm_config_cache: "$CI_PROJECT_DIR/.npm" + +# Node.js 캐시 +.node-cache: &node-cache + cache: + key: + files: + - package-lock.json + paths: + - .npm/ + - node_modules/ + +# ── Check Stage ────────────────────────────────────────── + +lint: + stage: check + image: node:${NODE_VERSION} + <<: *node-cache + before_script: + - npm ci --ignore-scripts + script: + - npm run lint + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +typecheck: + stage: check + image: node:${NODE_VERSION} + <<: *node-cache + before_script: + - npm ci --ignore-scripts + script: + - npm run typecheck + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +# ── Test Stage ─────────────────────────────────────────── + +unit-test: + stage: test + image: node:${NODE_VERSION} + <<: *node-cache + before_script: + - npm ci --ignore-scripts + script: + - npm run test:unit + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +# ── Build Stage ────────────────────────────────────────── + +build: + stage: build + image: node:${NODE_VERSION} + <<: *node-cache + before_script: + - npm ci + script: + - npm run build + artifacts: + paths: + - out/ + expire_in: 1 day + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +# ── Release Stage (태그 푸시 시만 실행) ────────────────── + +package-windows: + stage: release + tags: + - windows # Windows 러너 필요 (native 모듈 빌드) + before_script: + - npm ci + script: + - npm run build + - npm run dist + artifacts: + paths: + - release/ + expire_in: 30 days + rules: + - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+/ + +# Docker 러너에서 Windows 빌드가 불가한 경우 아래 대안 사용: +# Wine + electron-builder --linux 크로스빌드 또는 +# Windows self-hosted runner 등록 diff --git a/CLAUDE.md b/CLAUDE.md index fd34784..a7062eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,10 +77,46 @@ npm run typecheck # tsc --noEmit 6. **녹음 UI**: 9개 웨이브 바, cos 분포 가중치, 100ms 애니메이션 ## 현재 상태 -Phase: 6 완료 (Phase 1~6 + 3.5 전체 완료) -마지막 완료: Phase 6 — 커스텀 명령어 + i18n (ko/en) -다음 작업: Phase 7 — 테스트 + 빌드 + 배포 (electron-builder, CI/CD) -차단 이슈: SoX 미설치, @nut-tree-fork/nut-js 포크 사용 +Phase: 8 완료 (Phase 1~8 + 3.5 전체 완료) +마지막 완료: Phase 8 — UI 전면 리디자인 (08-design-system.md SSOT 적용) +다음 작업: 전사 테스트 (npm run dev) → 온보딩 위저드 +차단 이슈: @nut-tree-fork/nut-js 포크 사용 + +### Phase 8 구현 내용 +- 08-design-system.md SSOT 기반 MUI 테마 전면 교체 (다크+라이트+auto 테마 시스템) +- theme.ts: d3roPalette(SSOT), createD3ROTheme('dark'|'light'), getTheme(mode, prefersDark) +- App.tsx: auto 모드 → 시스템 설정 따름 (나중에 커스텀 테마 확장 가능) +- AppLayout: 앰버 LED, 라벨 스타일, 아이콘 색상, 네비게이션 앰버 선택 +- DashboardPage: hero 수치(28px mono), 카드 그리드, LED 상태 패널, 태그 시스템 +- StatusBar: LED 인디케이터 + 모노 폰트 + 핫키 힌트 +- HistoryPage: 카드 레이아웃, 앰버/퍼플 태그, 모노 메타데이터 +- DictionaryPage: 카드 레이아웃, 카테고리 태그, 사용 횟수 모노 +- CommandsPage: 카드 레이아웃, BUILT-IN/CUSTOM 태그 +- SettingsModal: 디자인 시스템 border/close 스타일 + +### Phase 7.5 구현 내용 +- SoundEffectService: WAV 프리로드, fire-and-forget, PowerShell SoundPlayer로 재생 +- AutoLaunchService: app.setLoginItemSettings, ConfigService 연동, syncWithConfig +- TextInsertService: 간이 삽입 검증 (EditMonitor 경량), 클립보드 확인 +- VoiceModeService 효과음 연동: session-started(start), completed(stop), cancelled(cancel), error(error) +- IPC: system:playSound/setSoundEnabled/isSoundEnabled, config:setAutoLaunch/setCloseToTray +- Bootstrap: sound-effects, auto-launch 초기화 단계 추가 +- 효과음 WAV 파일: scripts/generate-sounds.js로 생성 (recording-start/stop, error) + +### Phase 7 구현 내용 +- vitest 테스트 환경: vitest.config.ts, tests/setup.ts (electron/electron-log 모킹) +- 테스트 헬퍼: tests/helpers/createTestDb.ts (in-memory SQLite + drizzle) +- 단위 테스트 41개: HistoryService, DictionaryService, CustomInstructionService, VoiceModeService, D3ROError +- electron-builder: electron-builder.yml (NSIS, asarUnpack, extraResources) +- GitLab CI/CD: .gitlab-ci.yml (lint, typecheck, test, build, release) +- 빌드 스크립트: pack, dist, test:unit 추가 +- 참고: better-sqlite3는 Electron용 빌드라 vitest에서 직접 사용 불가 → DB 서비스는 모킹 테스트 +- 번들링 인프라: SoX 다운로드 스크립트, PyInstaller 빌드 스크립트, 경로 해상도 유틸 +- src/main/utils/paths.ts: dev vs production 경로 자동 감지 (SoX, sidecar, sounds) +- AudioCaptureService: 번들 SoX 경로 사용 (getSoxPath) +- LocalSTTService: 번들 sidecar 경로 사용 (getSidecarCommand) +- scripts/download-sox.ps1: SoX Windows 바이너리 다운로드 → resources/sox/ +- scripts/build-sidecar.py: PyInstaller → sidecar-dist/sidecar/sidecar.exe ### Phase 6 구현 내용 - CustomInstructionService: electron-store 기반, 프리셋 5개 (번역/요약/전문리라이트/코드설명/자유프롬프트) diff --git a/electron-builder.yml b/electron-builder.yml new file mode 100644 index 0000000..9bbff6c --- /dev/null +++ b/electron-builder.yml @@ -0,0 +1,62 @@ +appId: com.d3ro.voice +productName: D3RO Voice +copyright: Copyright © 2026 D3RO + +directories: + buildResources: resources + output: release/${version} + +files: + - out/**/* + - "!out/**/*.map" + +# native 모듈은 asar 외부에 배치 +asarUnpack: + - "node_modules/better-sqlite3/**" + - "node_modules/uiohook-napi/**" + - "node_modules/@nut-tree-fork/**" + +win: + target: + - target: nsis + arch: + - x64 + icon: resources/icons/icon.ico + +nsis: + oneClick: false + perMachine: false + allowToChangeInstallationDirectory: true + createDesktopShortcut: true + createStartMenuShortcut: true + shortcutName: D3RO Voice + deleteAppDataOnUninstall: false + +# 번들 리소스 (앱 외부, resources/ 디렉토리에 배치) +extraResources: + # SoX Windows 바이너리 (~5MB) + - from: resources/sox/ + to: sox/ + filter: + - "**/*" + + # Python sidecar (PyInstaller 빌드 결과, ~200MB) + # 빌드 전: python scripts/build-sidecar.py + - from: sidecar-dist/sidecar/ + to: sidecar/ + filter: + - "**/*" + + # 효과음 파일 + - from: resources/sounds/ + to: sounds/ + filter: + - "*.wav" + + # 앱 아이콘 + - from: resources/icons/ + to: icons/ + filter: + - "**/*" + +npmRebuild: true diff --git a/package-lock.json b/package-lock.json index 330175f..3fb8efd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@typescript-eslint/parser": "^8.0.0", "@vitejs/plugin-react": "^4.3.0", "electron": "^33.3.0", + "electron-builder": "^26.8.1", "electron-vite": "^2.3.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -370,6 +371,58 @@ "node": ">=6.9.0" } }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@develar/schema-utils/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@develar/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@develar/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@electron-toolkit/preload": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@electron-toolkit/preload/-/preload-3.0.2.tgz", @@ -398,6 +451,109 @@ "electron": ">=13.0.0" } }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/fuses/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/fuses/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@electron/get": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", @@ -428,6 +584,330 @@ "semver": "bin/semver.js" } }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", + "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "detect-libc": "^2.0.1", + "got": "^11.7.0", + "graceful-fs": "^4.2.11", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^11.2.0", + "ora": "^5.1.0", + "read-binary-file-arch": "^1.0.6", + "semver": "^7.3.5", + "tar": "^7.5.6", + "yargs": "^17.0.1" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/rebuild/node_modules/node-abi": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", + "integrity": "sha512-Qfp5XZL1cJDOabOT8H5gnqMTmM4NjvYzHp4I/Kt/Sl76OVkOBBHRFlPspGV0hYvMoqQsypFjT/Yp7Km0beXW9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/windows-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1162,6 +1642,122 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jimp/bmp": { "version": "0.22.12", "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", @@ -1627,6 +2223,84 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.3.9", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.9.tgz", @@ -1898,6 +2572,43 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/@nut-tree-fork/default-clipboard-provider": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/@nut-tree-fork/default-clipboard-provider/-/default-clipboard-provider-4.2.6.tgz", @@ -2052,6 +2763,17 @@ "node-abort-controller": "3.1.1" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2514,6 +3236,16 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2521,6 +3253,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -2536,6 +3278,13 @@ "@types/node": "*" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", @@ -2551,6 +3300,18 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -2594,6 +3355,14 @@ "@types/node": "*" } }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -2978,6 +3747,33 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3013,6 +3809,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -3078,6 +3884,192 @@ "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", "license": "MIT" }, + "node_modules/app-builder-bin": { + "version": "5.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", + "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", + "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.3", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.8.1", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.0.3", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.8.1", + "electron-builder-squirrel-windows": "26.8.1" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/app-builder-lib/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -3105,6 +4097,17 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -3115,6 +4118,51 @@ "node": ">=12" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/atomically": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", @@ -3334,6 +4382,90 @@ "node": ">=0.4.0" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", + "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "5.0.0-alpha.12", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", + "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -3344,6 +4476,92 @@ "node": ">=8" } }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3371,6 +4589,20 @@ "node": ">=8" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3460,6 +4692,73 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clipboardy": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", @@ -3474,6 +4773,31 @@ "node": ">=8" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -3515,6 +4839,39 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3563,6 +4920,14 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3579,6 +4944,26 @@ "node": ">=10" } }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3685,6 +5070,19 @@ "dev": true, "license": "MIT" }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -3730,6 +5128,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3746,6 +5154,156 @@ "license": "MIT", "optional": true }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", + "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dmg-license/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/dmg-license/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3789,6 +5347,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/drizzle-orm": { "version": "0.45.2", "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz", @@ -3914,6 +5501,44 @@ } } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron": { "version": "33.4.11", "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", @@ -3932,6 +5557,83 @@ "node": ">= 12.20.55" } }, + "node_modules/electron-builder": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", + "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.8.1", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", + "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.8.1", + "builder-util": "26.8.1", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/electron-log": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz", @@ -3941,6 +5643,74 @@ "node": ">= 14" } }, + "node_modules/electron-publish": { + "version": "26.8.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", + "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "26.8.1", + "builder-util-runtime": "9.5.1", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/electron-store": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-10.1.0.tgz", @@ -3994,6 +5764,44 @@ } } }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/electron/node_modules/@types/node": { "version": "20.19.39", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", @@ -4003,6 +5811,23 @@ "undici-types": "~6.21.0" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -4021,6 +5846,13 @@ "node": ">=6" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -4034,8 +5866,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" } @@ -4044,8 +5876,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" } @@ -4057,6 +5889,35 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -4503,6 +6364,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -4523,6 +6391,17 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4632,6 +6511,46 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -4697,6 +6616,53 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -4717,6 +6683,19 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4758,6 +6737,55 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -4933,8 +6961,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" }, @@ -5003,6 +7031,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5030,12 +7087,59 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -5049,6 +7153,59 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-corefoundation/node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5144,6 +7301,16 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5190,6 +7357,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-function": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", @@ -5209,6 +7386,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -5228,6 +7415,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -5240,6 +7440,19 @@ "node": ">=8" } }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5256,6 +7469,40 @@ "whatwg-fetch": "^3.4.1" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jimp": { "version": "0.22.10", "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.10.tgz", @@ -5268,6 +7515,16 @@ "regenerator-runtime": "^0.13.3" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jpeg-js": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", @@ -5374,6 +7631,13 @@ "json-buffer": "3.0.1" } }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5426,6 +7690,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5433,6 +7704,23 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5481,6 +7769,29 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -5494,6 +7805,16 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -5506,6 +7827,39 @@ "node": ">=4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -5561,6 +7915,173 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -5604,6 +8125,16 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -5635,6 +8166,16 @@ "license": "MIT", "optional": true }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5655,6 +8196,31 @@ } } }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -5666,6 +8232,32 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-record-lpcm16": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/node-record-lpcm16/-/node-record-lpcm16-1.0.1.tgz", @@ -5697,6 +8289,22 @@ "dev": true, "license": "MIT" }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -5764,6 +8372,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5782,6 +8406,30 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -5832,6 +8480,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -5932,6 +8600,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5958,6 +8650,21 @@ "node": ">= 14.16" } }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/peek-readable": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", @@ -6030,6 +8737,31 @@ "node": ">=4.0.0" } }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/pngjs": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", @@ -6087,6 +8819,36 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -6140,6 +8902,16 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -6158,6 +8930,20 @@ "node": ">=0.4.0" } }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6175,6 +8961,18 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -6305,6 +9103,19 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, "node_modules/readable-stream": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", @@ -6367,6 +9178,16 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6376,6 +9197,24 @@ "node": ">=0.10.0" } }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -6423,6 +9262,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -6558,6 +9421,23 @@ ], "license": "MIT" }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, "node_modules/sax": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", @@ -6702,6 +9582,76 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -6721,6 +9671,27 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -6728,6 +9699,19 @@ "license": "BSD-3-Clause", "optional": true }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6735,6 +9719,16 @@ "dev": true, "license": "MIT" }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", @@ -6751,6 +9745,37 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6764,6 +9789,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -6861,6 +9900,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -6903,6 +9959,105 @@ "node": ">= 6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6916,6 +10071,26 @@ "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", "license": "MIT" }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -6983,6 +10158,26 @@ "node": ">=14.0.0" } }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, "node_modules/token-types": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", @@ -7006,6 +10201,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -7101,6 +10306,32 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -7151,6 +10382,13 @@ "punycode": "^2.1.0" } }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, "node_modules/utif2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", @@ -7166,6 +10404,22 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -7315,6 +10569,16 @@ } } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -7386,6 +10650,43 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7441,6 +10742,16 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -7457,6 +10768,35 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 1ad844f..5fa0764 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,20 @@ "typecheck": "tsc --noEmit", "lint": "eslint . --ext .ts,.tsx", "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", - "test": "vitest run", - "test:watch": "vitest" + "test": "vitest run --config vitest.config.ts", + "test:unit": "vitest run --config vitest.config.ts", + "test:watch": "vitest --config vitest.config.ts", + "pack": "electron-vite build && electron-builder --dir", + "dist": "electron-vite build && electron-builder", + "build:sidecar": "python scripts/build-sidecar.py", + "setup:sox": "powershell -ExecutionPolicy Bypass -File scripts/download-sox.ps1" }, - "author": "", + "author": "D3RO", "license": "MIT", + "build": { + "extends": null, + "configFile": "electron-builder.yml" + }, "devDependencies": { "@electron-toolkit/tsconfig": "^1.0.1", "@types/better-sqlite3": "^7.6.13", @@ -25,6 +34,7 @@ "@typescript-eslint/parser": "^8.0.0", "@vitejs/plugin-react": "^4.3.0", "electron": "^33.3.0", + "electron-builder": "^26.8.1", "electron-vite": "^2.3.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/resources/sounds/error.wav b/resources/sounds/error.wav new file mode 100644 index 0000000000000000000000000000000000000000..a4c96e97028934e6416bf9a4a054699fafa53b22 GIT binary patch literal 16044 zcmeI3=~Gi#8pac{uq1>KWCsa5Az>$htQUG3rxTd{+JyGR_+lxpe{ehCUgmKfcN2fv^D6?ytJ7;6@d;5)R1uqq9AcB#J3^c1U+{jo zf>p!EQ<68i?XN@V$!2Hzn+h&E762~8=w zbWO${)MgbNNNZ@16U4KI^+V z7@;*xY|JDrUNKnK&fCzaOKz*4TYV}5E`;VqsAEV}M`B~DgkGPiXK6WK@b*J9&>B<+ z{mk3Rtzu^}Z!pTzkjV)&bX;9z-{$ea)=h<8D342MhrMjW#n_}PnA1*+r?2LBg})5&c_HxiQ2{E(y!aTRKDyw+*{PIcz=9!>r}y9lg`Chw&Ac} zLZdtiy;?Vo2lj2Qi$upI(2&VxX*U>I%qsRy?$5kB=Xz$K{m>UYEl1C)&y>&`QyqyU zsyZew;zDRez*e7C&r5D-)Onj_?TR63abw0X5uv>~_*vh+p5Hq!DxS+fmF9`1!WO4K z82Av(7G4$YkX)A0+L;}K?r&AMHLk<0V}cJI^L|SeCX2ZWfyGwhCW*0rS;5id&ZtC6 zc7iQ=fBN2xStgFt$~Ey?oO{*@g+n&(LCzkQCPSCrlwwLSP##3PgvW*W`ww}S)oc0f!uQkn#z%$=22#{}-t~68Za2$bNcu!4h1IYp>;pH#SA`bQn1n80YbSRK z-qovR1GyuF3H%K6jw7~8 zCP>Ba)+y)Pw#ymZzy1XPGbAr@2zzeaHfhLb(u|*Ub5iwL9}X{gc!uiND65h|z`qQrg+ZoRul4}4VR2zOG=GXWqiJzpNr|-`Ejn&Ly^KzZ~SfKlmly{o@l6{t` zX2{d{$=}hA#cf39gz1;|X| z91%{K>)f+6coas79*CbwDQzTWeiyEn(0}l~T{|#2Hyf?1Suw9y*fHoD_hmx%rf&id zZ;p(77~4&KmXw&bje%wEWK+2}coohXT7(Lr(>x59%vNT)GsaT+NySuFY)gb8)EFT5 zIYmIb*Pv{+vUTi=V6k}i$Yg`oJovotRS%&{p;(Zgk(P?5gbHU(qhLN9DLf{MkZhOD zx9ODp?jRLibAKprjQ(NQ{2jf+SY}2bYB5OMQQ|MY%An@3Ls81OX__FJlGCm&MRZlz=sfqHa5a2V*e7}+G0R@J_jc@gm!d8h7#Y4d{{8gZ z1+V4uRg0w>iFGZ<4|)6h$A!3r1OEX20sI5_2k;NzAHY9A{{i|B(0_pb1N0xD{{ZtV zFuwxxD=@$MZ_lrej$WIR&3Wjz8ml%Q*&m=v+e$pN1nu~sf3~>C-T5c{{ zzqLAV{PRktA$d7}Nutl!C+KPVefrn>v?a~bUzg7sPOO|V)|wuz{&P*UzOb=k{%Gm3 zKD6z&FW3(`{^8I$oPR{X1rz`Z00n>oKmnitPyi?Z6aWeU1^!100R9T_SAf3){1xD@ z0DlGeE5Pppei!h&fZqlDF5q_ozYFpoApZgKA0Yn$@*g1o0rG<&KM3-JAV2t@<_G`3 H`Kx~cXZPh> literal 0 HcmV?d00001 diff --git a/resources/sounds/recording-start.wav b/resources/sounds/recording-start.wav new file mode 100644 index 0000000000000000000000000000000000000000..8357cbfe863a2c1125652a032fe703515bd7d487 GIT binary patch literal 4844 zcmWNTcU+TK`^Lja!k%H1utx|ZkR6ieXdOjSQD5t>zExk>>qe^n6c<)$t8dl1YE^JA ztaV$e_GC{I2s;TQY(f$?fiUvR=lSRS@to(rpZoe=*E!1<%%9&8fI_XBy>i~x-3O!n zP$-m><2i{q7KJF3Gs+LO<-3F5LB~5NR2=GORHl=Mv!_d}D}r8#pgtA%BCA_M(flFY3O?Yx8|FD+l@`V?#6NBAEu=KbwiA?`?eGIC$2;86yI!| zKC~y28htC)9`|+p=C~y>-h`K7i*cSlLD&*|yES^?M$dTzSJzehxvF1PTG3NcrCw9L zw_&sycHQV_Ppo$-^R5j!6|*+QKWja6n!^X>pp5&7C8x|!T^*Mbw%y0yrPO-B)NaUa zm{4cRNWws#6SPB&a9a9F?bYzu08E8eXP3P`{|Wms?rf?bwU^b*Re>j950}gepnOg8 zjr|yM!^_9%$SBA7t_7(}R=t-*3;qX#;U`|cC{j_ZIjk$~d^W(G>cM^!NFa12ts$o~ zQaEv73jB%WWVh0ur0Wx;5kkK-^!5pz+1NJPu)K1SEJkRDPB4@AgU}!guIg%Z?Fj4V z+kVGn2XOG*q^=Aj-GdVjronS0J^K-DPWrrr$#9;p&Bb?ohiRYTTs>ONkggNlf;cGU zJr!M5tf_vXpVj?l=+4Y_kIZ0n%*zyQ7K`;O7lIdH6}Og|mt`lCqqBp~xED+x8^m-K z>Sk#k%7=vOcrI`hl7#1E4VC)FSsjo1?@!9GPXlWR?a7BT=Q1(eUqKc4!rjesrN9(Z zbZ3yShi@TIyH5L+ zM5ZMHav`IHlMk9e1NRWCBWq(ycC;2(=9WAq?ZcfGNY4m?$ zmL+``t;w!8{5D4hC(R>YVU%-&r#jOx4W0< zcWM4DKPsGr82FGU7v(5hYG^I3#%mT{&B6qpGc4l$CJI%4u1RaL86R0+xEA@B;){|$X6l*uxmvIde20a{Y$0}xwC`4WA~=^Q!}zn z$Rt>X%&@X3^pssu3j&v6a1${mY-?`qX60+qX@_oK@#vzriu@W}OKe}*_%`&>03qQc zu_;T<0*DQmk@##0H8S-~OhHhATkxdGeBN-PZf?bFv6zQ}LA+k!Dn)ROxY=xU9seHv zJisR^Foi>jW*2o)vr(2&ntDBf97S?AwY zX7QW4=PnNJjNP9$KtplXf;xxyxXk0^pA#Fx`@GkoL_@O9=|+>3i$MwvYp-RdfK*@c@)RW5+OT%-$m)gG_#COmdn5uhX zjl)?>c=tp~<^DQ+8>c^gdakD`G$G+dMjK-TR}a|WRCWvXA?fSre+B**jaZNOHtJ1P zb7YGI%i%F76?n_1HObAWzV`80EHB@P|FMmVgP%>N|*9=6}} z$&|NcTkHNBjoe#!3T}i?1yuRN>SHZ9^V`W^JhY*zgb(CtW}0KyY9x=9oVg-#XBgg- zGIiW6Z>g`&mA@2x3(rBKFi_E66JQvzh^PPZvW3H>)mbTQ88QT}a4Q+s^fPhc!QW%I zjq`g~>$@uhq^tR!Fqh{pc2e;h-gQQe&|L5VuFo4uTxp7I=j%y&0x z)nH)z-ntkiPxt}OhFb(jq4{qx?K$kPADL|v6ediX@a|fc|4;zE;%>}`^hTr z$<&pr10;VrrtZprD7jcUOYd%sn7HP4DI_)FCOL!kUu4R`;sisVJ{jv7q(`q9Ti@N# zctyn)FXZ{bJAActzUE+ascB^Ltml8jza+iQdd!XlD)5>!MsrHt88zs4(5YoGwmquu zwqlm>Fgyv@3gwE8b>FvtJ!En!@E?pinfe`loYM-7NCI1%S)AAs`o_I~qS&}cUs$Jc9PBuSd3WYfSWu!b{>)|#TnqkQT1!~LII2dcHQNI@(N z=Z%Z@sH~0p?ysy=?7QHK_}k=dtP)4&&gaVLhSZek{r)9RuLqyD71SE#Zv=B;4O}O> zTrt+5?n1^EVa9@xc!W%4T|kDx60VdkPaTa4^G|e&8gy@Ktl1`CCm=yjo<(#<^_6yK z4{m(6TTh64;@_FG*d9O*@;TYG8z~I@sJC)jY&JD#YIaJQd?#q)X(c8TPn!aYNW#x>(hfa7%N~4Ey2S5?h z!#t7E9Xk>D#wB(5RQs3OO8G%SGEC-e5*MnQbT5n+Te0W*h}FcOs2LmqzyLHmD6=x& zD_DZYk7jfR)_+l01@oZ^HjAVxU!ATu)z<0J67EV|N5yk^01d1x3%NM{P7ob^VB|>0 z<~ow%njjStdB?>{^*;SieST9-UXsW+DbHxV95XnHd}6w0U}K*J*qz9O+YG#Fvg`og z8G7({NXs<%R(by|)E57dXj(dxnS+dhc&;lwh!lwr@{XR`+n1$3rlyEdygATA=%*Cd zukQLhrob)^4N3~3e4qV0V1Q-pIhilx3xlBR^Wj77i)+bpGv5P#;q^))G&fuJ^yi}v z_>*JKr~i+63^9UoPBQHaF)yOaD94evykkUrZGDq%{7cWC>@( zIG$SEP^}?oeX1aaMZ%Yg< z;~UmE@*I4Zy*sNY;ZYFIHDhR_VO$d-eaH)g*9BTdb_1`QGLhlAFp@~RLxfz}Az4CIajam5>&5tkR1x{R=aC70 zH@V@1;-J7E*6~WDhU&L%UBiJGVaSh3k<`NuqA192MsjK{-rGwv5z@1`;frFgz!!G$ zR8mF_t^LAC9p-XqZgM%bo-+aT+!u5dNfEKgW5hbA>(9C(`6q{W&hv?KW?gyb4y)RI zYeadXW%wk5$hV?T6z~B|cQ;-MVtH$;Cf-M`96W4#xsWX_3O5b()PHRtGesj7sF;B4M4 ziAG~Glnigd+z$PaoJU*CwL4P5gSI?5GE|MW59JwhGzBL6 zU&yM=)`8{7YQ|YoS46dY$Jjp|(=~Q!H80YUHYpWK?c2VsGl>7CSRJ{ZeIHaKhnR#k z8NSj}F}}Skr>@aKjVHVy*rjY~d~eL3-sxwK*-gI9z6Lsx8%&Usjx)Nv>+Z>L ze1FG{qeFR3`=IaK%msf`TwW$Q`wO^)K!%Y-h?KhjGM3S?v}TL6kjI65;eFMN{s+r_ zr(3v>3EL@q90a`NDrwHd!q89X{GqL_?5btrGN^_h#m!Y5!|q`b=0sR0@gN<8bOWP< z!A0@40e9^MCY&y;Lgi4O#`l$X)}8C#V{7!Dj!Mh0v+^Cu#$&S53h^$UMOH<}&6@qv zd|nRp6fIFlGzScvb9oy4N0Obo-a%?9=Q`y>LLP1gb;aDSyISEQ41|e%54lTyL-&a7 zypJflC*vHu6nsDo3_j_2M5)`rNN!t!Wt%rCsBWf>h4+tl0R>fWjGzackoZ zMWe$Z>jamTLhWVK9F!%nEI~l2<=DY6$3_`SVBv~U5>uzPRJmT@47>O|#fQdSeew2@ zfZq5ylpCBGK;X`z&Q3guD@PejJ=!bEB?2etByd$GYSE@<``>|&5_FUS$M?!P2Pgpv z=)fR5yKhfpf#NkE1K$ZYRfOp`TlP95!SzXpX(DbfD9b*Qxgr+&-k5CdzF4C)pwu{BqM3S?YXN^}Z_9L!CHR)v zL|p~7Mbde^V~`{6uiDhsF}mNwgkP54&$l8_xPFDw#WxiZF~ z!y7?WMNZSffd%Ma!Xin&Of&%CJu@Oz5`G2qd@#59tjbS_g-!gqO13V+GVOdOq=@Ln zc#NC^MXaT1tjHiYzu|o?E7V7Y0q_d{v*MyQ-@L{d6+$Fx>DQ5BaFO**8Y!|LTRTK- z@l!7ndO;CCLpfWw#zZ08eTuW)XqsnuWo&l7JTG>pw=do z2b`O}*|VUoM4G^R1(!({)FgKG+JK)hE{Ed9wSu?VpUJt=3@`rJso`X@B$?7V(IGAyQAsn;rkth- OQ&m%S(}tO6_WuXDz)^t! literal 0 HcmV?d00001 diff --git a/resources/sounds/recording-stop.wav b/resources/sounds/recording-stop.wav new file mode 100644 index 0000000000000000000000000000000000000000..680d8eb594215fce76ca76c49ce028d7395a521f GIT binary patch literal 4844 zcmWkvcUaSB+f4{7Kmvh;J;DkENC=RSou@A9sI!WtPOMtouW!}5aH3QzT2yMStM0Z| zt+Q{f)#sNzAPE7&3L~trM<9e5zI@m7&;7@9pX*%b-1mJ}EXc}g^GBgp&&r;+b=N+! z9}0y+JKqVBv&cuGTu^?f&AIz>q4N(4H4o*6Cb-07hA}MHx0v-V097=7Z=%QEU^9=* zwI$jQPYgPIU3plD8~4lc|Bmoq{37fX7x&4+VQk;-&TSo=E&u7^TSF!ZZe#fCL6s3I zvNehq<{CipET7ynaJ+4!DY52MO>sk4$B)A-moUPaNL}K6dQxgak}Fw3m_c10C~VRi zX4F*G^D=JDTg$f-xp-=H2}8_VE7I~lXIzcqdSA0q%mTeaN`ilgXO;d_pFLo7RfMRL zb)1dhU(rP7MB>W81aw`Gv1X21fn>uFd7h!6^~BgDt~uJ8woxzx@&uUlspwez>#^Xr zUkr2Po8Tj)TLo%7dZ?&*Bu9cEGf(sr_=CHY))JQEfwHn2nc9yMG~6sXTb9*e9!zqV zgyyCYxZA-y5s95mbqR`f!S-S6gqlyq@z76}uTM1dY=^xrM=ne)#NZ=7D)#i>53DnZu5A=$g~eJ!g-h2Dda*{Of>lz=3XvpysoBiT?Zd)2keG<(E> zut55WF23oEb(zPiuze}bocRC^0ywPbe2#HP`AA`(B}a149!B zSP>!>Q1EW29*M&HY_wl*)fnE&Z@>tIuJo)1Yl@^z0nnP%h&Od6#*pkOAm zhfq7BUL_8K`=sOAqQ zTi`mRM7gqRWrum(3$G#1XV{&(%@SS6+(k_bE_Tfy9BC-k7D&kuFAi59t8VK2Zt6R~ z_85IyIlmXY7an8{CFBQIx^(nEGBuaEOMT%w@f-ELn!meprul@Ov6S?2{t)10h5Fnfg|BZRh7x34YsRR;C@|n*c%dX{LzE3pTs@4ZUbQr^CxW zf!C0JWpUN~j&H~3;Yy?CrZRaKfnJ2?3?$zSd*s`12FnEVqpBRSqNq&XxLUi%r}6yU~2oc?yR);oKOMa31q6 zWj!g>nbhVS9S}P~~&w z4+tOXrOn!-4GRYDy1XOlC`*`jCn8yRn>i<8VPF=r>1b-Cu$d#UXN8Y(#4E{1v&+HOR z0=e)==BuQ>sHZ~O zJs#OWTWHmPRa~(vL`nM83DwU!W{>Un(uP@*3NxW_3it`xEE4rWNSvp`_PFhIl}5G8 ziTjs$wR*0xu)TBC1^ZR#BWekY@5Fs1+?-jOR2KG=*M+eV%f0Gc%@PS7HY0bGNX6!s z^~0;(S^{rV7G-qvb>MeV3CD}x9L4lqJ^iBRLH#~0RhAD+p`W}-x3}SceLH882$!N) zrGCww7X1!3@b{(PjISnbbiHCVn0HhhQPv?i$dveNu+_;Oi|j#IdZ>sR$!Zmhf>6$++EVcX(UCW^jwQJFPITh4>VcZWWuaRfv@Y zr*eomK)t(aw6$ae=S~P(Ls6#hRC0lx$=0iD;vK*b#!|rQ}IzZE~BSaJ6 z6JbndY_e~J#iwEt*Q2Zt)pg67VK!VXy;gd>Ce!k#{iA1UNJYZ^3>E(c2m}~j6(b`K zL&|a8IJ~%ZRuxa}5aZz;9TPnCp~d1EJUbw8=+>SE+> z|9`0@XEhZ@Yg^aIJvTc^6BnB1%fo>WAV`qI^rjAndU&s$Aa*aRN9jD}GmsCJk~~d` z@lxBSk!jaYNW{2lP~N2pcpjh(6bLr5 zYLl)<9K`Q(9P5A5)M0QaFN?A80^*|Z)5n{RcGD*WUJpWcC%jLu;mrbn01X3@T>Ymltc6~LTjeQk-mSRg=$a4V|;DtcTx}N+p(&8I~ zS~qyfe6KQBy<5V9o$!e4VOe|4_Z=T>m)&qAVeGurJWhyc1~`P79CYen%tzv4w{y1b z9kn&Tl@-dYkPhcd@an)ydGqRlPRBX?f{3|EFPS6!pTSPBhi}ecQ;FeoaKBAw^y`{( zDlVzqB{2|({4HxNo2l8`k!N$b&L^&o*+PHHek>dZr$k?H)r>FW{|b8M89R>dPBHD% zpHo;7G&CTK6hZoz^*g%`joCcL1MkOC84VoZWQGy#;G9owk4*}sc)YW#Iy35_E<^#H zJ)_7e#d5v5{!G`tF@wiHfh*&xQm=CSojpL*$N7RW9d|b9t>?D!qur&ZsB*G$g)=!e zBTMC{wS~3sI@Z{JbS(<_I(iN5-%NL59qCiD8e){Yul+=)sNP+FK;iDph(aV^ zen)$-c6UeFhywGy|3dQY6b&m^@GJNR$oK}PBdI)c)Q{n^csQ(mQ%!lM%3mtm#0>a9 zxL?ZB+^rNf@9+D0(vCF*9g3Tu`i8wy*Z>$Hn}3>#Pu?3f+ds**(MIY#UjJU7t^7-z z0A=ty=`77pm4VIFzU0X~EG)F8%SmDn<2bAL zY}3Zdg_@UA3namL;`Iu$j#lf_PPf`--s4Q6?ubsBE@6P zxVdL_)AGs{8mZIjB32VzT#t|L=*nrRG^|jwqzDX$OT+@jGwsrv7pT)}ow z4z>%Ib3QV>6Yhp#eE&sn9VxbG>+hByQN5KELk27nKT=S2>Y9kQhlADAQQkd4Ph(qY z5t(SgQeXzJg-x96X=#ash*y5MF<*^d?)qQD>WU`yA5s@*QYsLd+_MaCtZyFgZ66Qt zum+4p-A_86LE~)|O@n4pB)^flHH8qfIPfO6VCwR~=dGXCbZD%J?4{u9n2Nsc_%YklFpOch9ak!Fm?`2#G>*w&b#$qgy)B` z!fBgg7Y8r&(a(G_yxx*+dTM~`2C2X~nVE=A`kVT^A}?^;s&vnGCY zf8_5PnU(03=3uu73V{)n3iG)E8LFgyax=-@OE>*&h}f~KKEiNFt&+}iG9V)Vk=j(d z%dgdiv|ky#F@<=R5Py&Qnd+7P2WO7(S0Di;!gQ`Ay&wr8SCAsTd>#JQk`A=#t-(_x zkezjUuZ8835hYfys~K&H=^q#$a(hp(gyRwnsoj}GK@6A#*F+e8F3U_)$L$D>_FaMb z+OFzpZuY5updD6ZNQz(xtV2G?9%^zbN=%-XRn|~PKXyAQkz7V~Nk7RU2rmE=m=U`3 zh|F~#H#;A)!Fv2JL1>b|E3@nhtF z5*3VN?6m?8m;kp#G`@m$gWeuL5QZn*c0)~+^nYxvu1zZsR72@n=k6)s&yuZ57ahC$ zWAmb(Q==X(hjB-PU&hFj_hjtiUKG9r3ZNBMb1}@*DJ8K5A@}eLFh=|1-r$zwHA(ul zswL6{=Tx-A@1;|!8U3=_doA~R$48f=S9-rDQX{^J+mxiG-$~P?uS#FaFsAq?vSJQ} zg%daWoOLaqI%bpi*K{_ujy3Tbp4VsBQ%v5C-OYE}FkPK}(Zl&;+UYKrM;>PHW4?L* zmx%qOg20@>!=$MI8{xDs;-kmv+-ou3=%WrWwRvJ>Y{b6VF0voAyN?;ijN{gcpsBQJ Nx?{$1ZKi+b{{RIIPig=F literal 0 HcmV?d00001 diff --git a/scripts/build-sidecar.py b/scripts/build-sidecar.py new file mode 100644 index 0000000..9aac52f --- /dev/null +++ b/scripts/build-sidecar.py @@ -0,0 +1,120 @@ +""" +scripts/build-sidecar.py +faster-whisper sidecar를 PyInstaller로 빌드한다. + +사용법: + pip install pyinstaller + python scripts/build-sidecar.py + +출력: + sidecar-dist/sidecar.exe (단일 디렉토리 모드) +""" + +import subprocess +import sys +import shutil +from pathlib import Path + +PROJECT_ROOT = Path(__file__).parent.parent +SIDECAR_DIR = PROJECT_ROOT / "sidecar" +MAIN_PY = SIDECAR_DIR / "main.py" +OUTPUT_DIR = PROJECT_ROOT / "sidecar-dist" + + +def check_prerequisites(): + """필수 도구가 설치되어 있는지 확인한다.""" + # PyInstaller + try: + import PyInstaller # noqa: F401 + except ImportError: + print("PyInstaller가 설치되어 있지 않습니다.") + print("실행: pip install pyinstaller") + sys.exit(1) + + # faster-whisper + try: + import faster_whisper # noqa: F401 + except ImportError: + print("faster-whisper가 설치되어 있지 않습니다.") + print("실행: pip install -r sidecar/requirements.txt") + sys.exit(1) + + if not MAIN_PY.exists(): + print(f"sidecar 소스를 찾을 수 없습니다: {MAIN_PY}") + sys.exit(1) + + +def build(): + """PyInstaller로 sidecar를 빌드한다.""" + print("=" * 60) + print("D3RO-VOICE Sidecar 빌드 시작") + print("=" * 60) + + # 기존 빌드 정리 + if OUTPUT_DIR.exists(): + shutil.rmtree(OUTPUT_DIR) + + # PyInstaller 실행 — onedir 모드 (onefile보다 시작이 빠름) + cmd = [ + sys.executable, "-m", "PyInstaller", + "--name", "sidecar", + "--distpath", str(OUTPUT_DIR), + "--workpath", str(PROJECT_ROOT / "build" / "sidecar-build"), + "--specpath", str(PROJECT_ROOT / "build"), + # onedir 모드 (단일 디렉토리) + "--noconfirm", + "--clean", + # hidden imports (PyInstaller가 자동 감지 못하는 것) + "--hidden-import", "faster_whisper", + "--hidden-import", "ctranslate2", + "--hidden-import", "huggingface_hub", + "--hidden-import", "tokenizers", + "--hidden-import", "uvicorn.logging", + "--hidden-import", "uvicorn.protocols.http", + "--hidden-import", "uvicorn.protocols.http.auto", + "--hidden-import", "uvicorn.protocols.http.h11_impl", + "--hidden-import", "uvicorn.protocols.websockets", + "--hidden-import", "uvicorn.protocols.websockets.auto", + "--hidden-import", "uvicorn.lifespan", + "--hidden-import", "uvicorn.lifespan.on", + "--hidden-import", "uvicorn.lifespan.off", + # 콘솔 없음 (Windows) + "--noconsole", + str(MAIN_PY), + ] + + print(f"\n실행 명령:\n {' '.join(cmd)}\n") + + result = subprocess.run(cmd, cwd=str(PROJECT_ROOT)) + + if result.returncode != 0: + print(f"\nPyInstaller 빌드 실패 (exit code: {result.returncode})") + sys.exit(1) + + # 빌드 결과 확인 + exe_path = OUTPUT_DIR / "sidecar" / "sidecar.exe" + if exe_path.exists(): + size_mb = exe_path.stat().st_size / (1024 * 1024) + print(f"\n빌드 성공!") + print(f" 경로: {exe_path}") + print(f" 크기: {size_mb:.1f} MB") + + # 전체 디렉토리 크기 + total_size = sum(f.stat().st_size for f in (OUTPUT_DIR / "sidecar").rglob("*") if f.is_file()) + total_mb = total_size / (1024 * 1024) + print(f" 전체 디렉토리: {total_mb:.1f} MB") + else: + print(f"\n빌드 출력을 찾을 수 없습니다: {exe_path}") + # onedir 모드에서는 디렉토리 내부에 exe가 있음 + for exe in OUTPUT_DIR.rglob("*.exe"): + print(f" 발견: {exe}") + + print("\n빌드 완료!") + print(f"electron-builder에서 이 경로를 extraResources로 지정하세요:") + print(f" from: sidecar-dist/sidecar/") + print(f" to: sidecar/") + + +if __name__ == "__main__": + check_prerequisites() + build() diff --git a/scripts/download-sox.ps1 b/scripts/download-sox.ps1 new file mode 100644 index 0000000..0976ce1 --- /dev/null +++ b/scripts/download-sox.ps1 @@ -0,0 +1,111 @@ +# scripts/download-sox.ps1 +# SoX Windows 바이너리를 resources/sox/에 다운로드한다. +# 실행: powershell -ExecutionPolicy Bypass -File scripts/download-sox.ps1 + +$SOX_VERSION = "14.4.1" +# SourceForge 직접 다운로드 URL (리다이렉트 따라감) +$SOX_URL = "https://downloads.sourceforge.net/project/sox/sox/$SOX_VERSION/sox-${SOX_VERSION}-win32.zip" +$DEST_DIR = Join-Path $PSScriptRoot "..\resources\sox" +$TEMP_ZIP = Join-Path $env:TEMP "sox-${SOX_VERSION}-win32.zip" +$TEMP_DIR = Join-Path $env:TEMP "sox-extract" + +Write-Host "=== SoX $SOX_VERSION Windows 바이너리 다운로드 ===" -ForegroundColor Cyan + +# 이미 존재하면 스킵 +if (Test-Path (Join-Path $DEST_DIR "sox.exe")) { + Write-Host "SoX가 이미 존재합니다: $DEST_DIR\sox.exe" -ForegroundColor Green + exit 0 +} + +# 기존 임시 파일 정리 +if (Test-Path $TEMP_ZIP) { Remove-Item -Force $TEMP_ZIP } + +# 다운로드 (SourceForge 리다이렉트를 따라감) +Write-Host "다운로드 중: $SOX_URL" +Write-Host "(SourceForge 리다이렉트를 따라가므로 시간이 걸릴 수 있습니다)" +try { + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $webClient = New-Object System.Net.WebClient + $webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)") + $webClient.DownloadFile($SOX_URL, $TEMP_ZIP) +} catch { + Write-Host "자동 다운로드 실패: $_" -ForegroundColor Red + Write-Host "" + Write-Host "=== 수동 설치 방법 ===" -ForegroundColor Yellow + Write-Host "1. 브라우저에서 다운로드:" -ForegroundColor White + Write-Host " https://sourceforge.net/projects/sox/files/sox/14.4.1/sox-14.4.1-win32.zip/download" + Write-Host "" + Write-Host "2. 다운로드한 zip에서 아래 파일들을 복사:" -ForegroundColor White + Write-Host " sox.exe, rec.exe, libmad-0.dll, libmp3lame-0.dll, libsox-3.dll" + Write-Host "" + Write-Host "3. 복사 위치:" -ForegroundColor White + Write-Host " $DEST_DIR" + exit 1 +} + +# zip 파일 검증 +$fileSize = (Get-Item $TEMP_ZIP).Length +if ($fileSize -lt 100000) { + Write-Host "다운로드된 파일이 너무 작습니다 (${fileSize} bytes). HTML 페이지가 다운로드된 것 같습니다." -ForegroundColor Red + Remove-Item -Force $TEMP_ZIP -ErrorAction SilentlyContinue + Write-Host "" + Write-Host "=== 수동 설치 방법 ===" -ForegroundColor Yellow + Write-Host "1. 브라우저에서 다운로드:" -ForegroundColor White + Write-Host " https://sourceforge.net/projects/sox/files/sox/14.4.1/sox-14.4.1-win32.zip/download" + Write-Host "" + Write-Host "2. 다운로드한 zip 압축 해제 후 아래 파일들을 복사:" -ForegroundColor White + Write-Host " sox.exe, rec.exe, libmad-0.dll, libmp3lame-0.dll, libsox-3.dll" + Write-Host "" + Write-Host "3. 복사 위치:" -ForegroundColor White + Write-Host " $DEST_DIR" + exit 1 +} + +Write-Host "다운로드 완료 (${fileSize} bytes)" + +# 압축 해제 +Write-Host "압축 해제 중..." +if (Test-Path $TEMP_DIR) { Remove-Item -Recurse -Force $TEMP_DIR } + +try { + Expand-Archive -Path $TEMP_ZIP -DestinationPath $TEMP_DIR -Force +} catch { + Write-Host "압축 해제 실패: $_" -ForegroundColor Red + Write-Host "수동으로 $TEMP_ZIP 을 압축 해제한 후 sox.exe 등을 $DEST_DIR 에 복사하세요." -ForegroundColor Yellow + exit 1 +} + +# 필요한 파일만 복사 +$SOX_EXTRACTED = Get-ChildItem $TEMP_DIR -Directory | Select-Object -First 1 +if (-not $SOX_EXTRACTED) { + # 디렉토리 없이 바로 파일이 있는 경우 + $SOX_EXTRACTED = Get-Item $TEMP_DIR +} + +$filesToCopy = @("sox.exe", "rec.exe", "libmad-0.dll", "libmp3lame-0.dll", "libsox-3.dll") +New-Item -ItemType Directory -Force -Path $DEST_DIR | Out-Null + +$copied = 0 +foreach ($file in $filesToCopy) { + $src = Get-ChildItem -Path $TEMP_DIR -Recurse -Filter $file | Select-Object -First 1 + if ($src) { + Copy-Item $src.FullName -Destination $DEST_DIR + Write-Host " 복사: $file" -ForegroundColor Green + $copied++ + } else { + Write-Host " 누락: $file (선택적)" -ForegroundColor Yellow + } +} + +# 정리 +Remove-Item -Recurse -Force $TEMP_DIR -ErrorAction SilentlyContinue +Remove-Item -Force $TEMP_ZIP -ErrorAction SilentlyContinue + +if ($copied -ge 1) { + Write-Host "" + Write-Host "SoX 설치 완료: $DEST_DIR ($copied 파일)" -ForegroundColor Green +} else { + Write-Host "" + Write-Host "파일 복사 실패. 수동으로 설치해주세요." -ForegroundColor Red + exit 1 +} diff --git a/scripts/generate-sounds.js b/scripts/generate-sounds.js new file mode 100644 index 0000000..d0c823f --- /dev/null +++ b/scripts/generate-sounds.js @@ -0,0 +1,147 @@ +/** + * D3RO-VOICE 효과음 WAV 파일 생성 스크립트 + * + * 생성 파일: + * resources/sounds/recording-start.wav — 상승 톤 (440→880Hz, 150ms) + * resources/sounds/recording-stop.wav — 하강 톤 (880→440Hz, 150ms) + * resources/sounds/error.wav — 저음 비프 2회 (220Hz, 200ms×2) + * + * WAV 포맷: 16kHz, mono, 16bit PCM + */ + +const fs = require('fs'); +const path = require('path'); + +const SAMPLE_RATE = 16000; +const BIT_DEPTH = 16; +const NUM_CHANNELS = 1; +const BYTES_PER_SAMPLE = BIT_DEPTH / 8; + +/** + * 사인파 샘플 생성 (주파수 선형 스윕 지원) + * @param {number} durationMs - 길이 (ms) + * @param {number} freqStart - 시작 주파수 (Hz) + * @param {number} freqEnd - 끝 주파수 (Hz) + * @param {number} volume - 볼륨 (0.0~1.0) + * @returns {Int16Array} + */ +function generateTone(durationMs, freqStart, freqEnd, volume = 0.6) { + const numSamples = Math.floor((SAMPLE_RATE * durationMs) / 1000); + const samples = new Int16Array(numSamples); + const maxVal = 32767 * volume; + + // 페이드 인/아웃 길이 (클릭 방지) + const fadeSamples = Math.min(Math.floor(numSamples * 0.05), 80); + + let phase = 0; + for (let i = 0; i < numSamples; i++) { + const t = i / numSamples; + const freq = freqStart + (freqEnd - freqStart) * t; + + // 페이드 인/아웃 엔벨로프 + let envelope = 1.0; + if (i < fadeSamples) { + envelope = i / fadeSamples; + } else if (i > numSamples - fadeSamples) { + envelope = (numSamples - i) / fadeSamples; + } + + samples[i] = Math.round(Math.sin(phase) * maxVal * envelope); + phase += (2 * Math.PI * freq) / SAMPLE_RATE; + } + + return samples; +} + +/** + * 무음 생성 + * @param {number} durationMs + * @returns {Int16Array} + */ +function generateSilence(durationMs) { + const numSamples = Math.floor((SAMPLE_RATE * durationMs) / 1000); + return new Int16Array(numSamples); +} + +/** + * 여러 샘플 배열을 이어붙임 + * @param {Int16Array[]} arrays + * @returns {Int16Array} + */ +function concatenate(arrays) { + const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0); + const result = new Int16Array(totalLength); + let offset = 0; + for (const arr of arrays) { + result.set(arr, offset); + offset += arr.length; + } + return result; +} + +/** + * PCM 데이터를 WAV 파일 버퍼로 변환 + * @param {Int16Array} samples + * @returns {Buffer} + */ +function createWavBuffer(samples) { + const dataSize = samples.length * BYTES_PER_SAMPLE; + const headerSize = 44; + const buffer = Buffer.alloc(headerSize + dataSize); + + // RIFF header + buffer.write('RIFF', 0); + buffer.writeUInt32LE(headerSize - 8 + dataSize, 4); + buffer.write('WAVE', 8); + + // fmt chunk + buffer.write('fmt ', 12); + buffer.writeUInt32LE(16, 16); // chunk size + buffer.writeUInt16LE(1, 20); // PCM format + buffer.writeUInt16LE(NUM_CHANNELS, 22); + buffer.writeUInt32LE(SAMPLE_RATE, 24); + buffer.writeUInt32LE(SAMPLE_RATE * NUM_CHANNELS * BYTES_PER_SAMPLE, 28); // byte rate + buffer.writeUInt16LE(NUM_CHANNELS * BYTES_PER_SAMPLE, 32); // block align + buffer.writeUInt16LE(BIT_DEPTH, 34); + + // data chunk + buffer.write('data', 36); + buffer.writeUInt32LE(dataSize, 40); + + // PCM data (Int16 little-endian) + const pcmBuffer = Buffer.from(samples.buffer, samples.byteOffset, samples.byteLength); + pcmBuffer.copy(buffer, headerSize); + + return buffer; +} + +// --- 효과음 생성 --- + +const outputDir = path.resolve(__dirname, '..', 'resources', 'sounds'); +fs.mkdirSync(outputDir, { recursive: true }); + +// 1. recording-start.wav: 상승 톤 440→880Hz, 150ms +const startSamples = generateTone(150, 440, 880, 0.5); +const startWav = createWavBuffer(startSamples); +const startPath = path.join(outputDir, 'recording-start.wav'); +fs.writeFileSync(startPath, startWav); +console.log(`Created: ${startPath} (${startWav.length} bytes)`); + +// 2. recording-stop.wav: 하강 톤 880→440Hz, 150ms +const stopSamples = generateTone(150, 880, 440, 0.5); +const stopWav = createWavBuffer(stopSamples); +const stopPath = path.join(outputDir, 'recording-stop.wav'); +fs.writeFileSync(stopPath, stopWav); +console.log(`Created: ${stopPath} (${stopWav.length} bytes)`); + +// 3. error.wav: 220Hz 비프 200ms × 2회, 중간 100ms 무음 +const beep1 = generateTone(200, 220, 220, 0.5); +const gap = generateSilence(100); +const beep2 = generateTone(200, 220, 220, 0.5); +const errorSamples = concatenate([beep1, gap, beep2]); +const errorWav = createWavBuffer(errorSamples); +const errorPath = path.join(outputDir, 'error.wav'); +fs.writeFileSync(errorPath, errorWav); +console.log(`Created: ${errorPath} (${errorWav.length} bytes)`); + +console.log('\nDone! All 3 sound files generated.'); diff --git a/src/main/bootstrap.ts b/src/main/bootstrap.ts index a1a8c62..4128fde 100644 --- a/src/main/bootstrap.ts +++ b/src/main/bootstrap.ts @@ -9,6 +9,8 @@ import { getLocalLLMService } from './services/LocalLLMService' import { getHistoryService } from './services/HistoryService' import { getTextInsertService } from './services/TextInsertService' import { getCustomInstructionService } from './services/CustomInstructionService' +import { getSoundEffectService } from './services/SoundEffectService' +import { getAutoLaunchService } from './services/AutoLaunchService' import { initDatabase } from './db' import { createMainWindow, @@ -43,6 +45,8 @@ export async function bootstrap(): Promise { { name: 'tray', critical: false, fn: initTray }, { name: 'ipc-handlers', critical: true, fn: initIpcHandlers }, { name: 'custom-instructions', critical: false, fn: initCustomInstructions }, + { name: 'sound-effects', critical: false, fn: initSoundEffects }, + { name: 'auto-launch', critical: false, fn: initAutoLaunch }, { name: 'popup-preload', critical: false, fn: initPopupWindows }, { name: 'hotkey', critical: false, fn: initHotkey }, { name: 'voice-mode', critical: false, fn: initVoiceMode }, @@ -101,6 +105,14 @@ async function initCustomInstructions(): Promise { getCustomInstructionService().initialize() } +async function initSoundEffects(): Promise { + getSoundEffectService().initialize() +} + +async function initAutoLaunch(): Promise { + getAutoLaunchService().syncWithConfig() +} + async function initPopupWindows(): Promise { preloadPopupWindows() setupHistoryPopupIPC() @@ -120,8 +132,11 @@ async function initVoiceMode(): Promise { const voiceMode = getVoiceModeService() voiceMode.connectHotkey() + const soundEffect = getSoundEffectService() + // RecordingTip 연동: 세션 시작/종료 시 팝업 표시/숨김 voiceMode.on('session-started', () => { + soundEffect.play('recording-start') showRecordingTip('recording') }) @@ -136,6 +151,7 @@ async function initVoiceMode(): Promise { }) voiceMode.on('session-completed', ({ session, finalText }) => { + soundEffect.play('recording-stop') hideRecordingTip() if (finalText.length > 0) { showResultPopup(finalText) @@ -157,11 +173,15 @@ async function initVoiceMode(): Promise { } }) - voiceMode.on('session-cancelled', () => { + voiceMode.on('session-cancelled', ({ reason }) => { + if (reason !== 'too-short') { + soundEffect.play('cancel') + } hideRecordingTip() }) voiceMode.on('error', ({ error }) => { + soundEffect.play('error') updateRecordingTipState('error', { errorMessage: error.message }) setTimeout(() => hideRecordingTip(), 3000) }) diff --git a/src/main/index.ts b/src/main/index.ts index f185d39..d635699 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -5,6 +5,15 @@ import { bootstrap } from './bootstrap' import { setupLifecycle } from './lifecycle' import { getMainWindow } from './windows/WindowManager' +// EPIPE 에러 방지: electron-log가 stdout/stderr에 쓸 때 파이프가 끊기면 크래시 방지 +process.stdout?.on?.('error', () => { /* ignore EPIPE */ }) +process.stderr?.on?.('error', () => { /* ignore EPIPE */ }) +process.on('uncaughtException', (err) => { + if (err.message?.includes('EPIPE')) return // EPIPE는 무시 + // 기타 예외는 로그만 + try { require('electron-log').default?.error?.('Uncaught:', err) } catch { /* noop */ } +}) + // 단일 인스턴스 잠금 const gotTheLock = app.requestSingleInstanceLock() diff --git a/src/main/ipc/config-handlers.ts b/src/main/ipc/config-handlers.ts index b58f6ea..6cacbd5 100644 --- a/src/main/ipc/config-handlers.ts +++ b/src/main/ipc/config-handlers.ts @@ -4,12 +4,15 @@ import { ipcMain } from 'electron' import { IPC_CHANNELS } from '@shared/ipc-channels' import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors' import { configGet, configSet, configGetAll, configReset } from '../services/ConfigService' +import { getAutoLaunchService } from '../services/AutoLaunchService' import type { ConfigGetParams, ConfigSetParams, ConfigResetParams, SetThemeParams, SetLanguageParams, + SetAutoLaunchParams, + SetCloseToTrayParams, AppConfig } from '@shared/types' @@ -66,7 +69,17 @@ export function registerConfigHandlers(): void { return ipcSuccess(configGet('autoLaunch')) }) + ipcMain.handle(IPC_CHANNELS.CONFIG.SET_AUTO_LAUNCH, async (_event, params: SetAutoLaunchParams) => { + getAutoLaunchService().setEnabled(params.enabled) + return ipcSuccess(undefined) + }) + ipcMain.handle(IPC_CHANNELS.CONFIG.GET_CLOSE_TO_TRAY, async () => { return ipcSuccess(configGet('closeToTray')) }) + + ipcMain.handle(IPC_CHANNELS.CONFIG.SET_CLOSE_TO_TRAY, async (_event, params: SetCloseToTrayParams) => { + configSet('closeToTray', params.enabled) + return ipcSuccess(undefined) + }) } diff --git a/src/main/ipc/system-handlers.ts b/src/main/ipc/system-handlers.ts index 2e97141..17deced 100644 --- a/src/main/ipc/system-handlers.ts +++ b/src/main/ipc/system-handlers.ts @@ -3,7 +3,8 @@ import { ipcMain, app, systemPreferences } from 'electron' import { IPC_CHANNELS } from '@shared/ipc-channels' import { ipcSuccess } from '@shared/errors' -import type { PermissionStatus } from '@shared/types' +import type { PermissionStatus, PlaySoundParams, SetSoundEnabledParams } from '@shared/types' +import { getSoundEffectService } from '../services/SoundEffectService' export function registerSystemHandlers(): void { ipcMain.handle(IPC_CHANNELS.SYSTEM.GET_PLATFORM, async () => { @@ -25,4 +26,20 @@ export function registerSystemHandlers(): void { } return ipcSuccess(status) }) + + // ── Sound Effect ── + + ipcMain.handle(IPC_CHANNELS.SYSTEM.PLAY_SOUND, async (_event, params: PlaySoundParams) => { + getSoundEffectService().play(params.sound as 'recording-start' | 'recording-stop' | 'error' | 'cancel') + return ipcSuccess(undefined) + }) + + ipcMain.handle(IPC_CHANNELS.SYSTEM.SET_SOUND_ENABLED, async (_event, params: SetSoundEnabledParams) => { + getSoundEffectService().setEnabled(params.enabled) + return ipcSuccess(undefined) + }) + + ipcMain.handle(IPC_CHANNELS.SYSTEM.IS_SOUND_ENABLED, async () => { + return ipcSuccess(getSoundEffectService().isEnabled()) + }) } diff --git a/src/main/services/AudioCaptureService.ts b/src/main/services/AudioCaptureService.ts index fd506fc..bdd7c05 100644 --- a/src/main/services/AudioCaptureService.ts +++ b/src/main/services/AudioCaptureService.ts @@ -3,11 +3,14 @@ // node-record-lpcm16 + SoX로 PCM16 16kHz mono 캡처. import { EventEmitter } from 'events' +import path from 'path' +import { existsSync } from 'fs' import { record } from 'node-record-lpcm16' import type { Recording } from 'node-record-lpcm16' import type { Readable } from 'stream' import { getLogger } from './LoggerService' import { configGet } from './ConfigService' +import { getSoxPath } from '../utils/paths' import type { AudioDevice } from '@shared/types' import { AUDIO_FORMAT, TIMING } from '@shared/constants' import { D3ROError, ErrorCode } from '@shared/errors' @@ -93,7 +96,17 @@ class AudioCaptureService extends EventEmitter { `format: ${AUDIO_FORMAT.SAMPLE_RATE}Hz ${AUDIO_FORMAT.CHANNELS}ch ${AUDIO_FORMAT.BIT_DEPTH}bit)` ) - // node-record-lpcm16 으로 SoX rec 프로세스 spawn + // node-record-lpcm16은 'sox' 명령어를 PATH에서 찾으므로, + // 번들된 SoX 디렉토리를 PATH 앞에 추가한다. + const soxExe = getSoxPath() + const soxDir = path.dirname(soxExe) + if (existsSync(soxExe) && soxExe !== 'sox') { + const sep = process.platform === 'win32' ? ';' : ':' + process.env.PATH = soxDir + sep + (process.env.PATH ?? '') + logger.info(`Bundled SoX added to PATH: ${soxDir}`) + } + logger.info(`Using SoX: ${soxExe}`) + const recordingOptions: Record = { sampleRate: AUDIO_FORMAT.SAMPLE_RATE, channels: AUDIO_FORMAT.CHANNELS, diff --git a/src/main/services/AutoLaunchService.ts b/src/main/services/AutoLaunchService.ts new file mode 100644 index 0000000..aeae9e0 --- /dev/null +++ b/src/main/services/AutoLaunchService.ts @@ -0,0 +1,67 @@ +// src/main/services/AutoLaunchService.ts +// 시스템 시작 시 자동 실행 관리. 설계서 01 IAutoLaunchService 구현. + +import { app } from 'electron' +import { getLogger } from './LoggerService' +import { configGet, configSet } from './ConfigService' + +const logger = getLogger('AutoLaunchService') + +class AutoLaunchService { + /** + * 현재 자동 실행 설정 상태를 조회한다. + */ + isEnabled(): boolean { + // Electron API로 실제 OS 설정 확인 + const settings = app.getLoginItemSettings() + return settings.openAtLogin + } + + /** + * 자동 실행을 활성화/비활성화한다. + */ + setEnabled(enabled: boolean): void { + try { + app.setLoginItemSettings({ + openAtLogin: enabled, + // Windows: 시작 프로그램에 등록 + // 개발 모드에서는 electron.exe 경로가 등록되므로 주의 + args: app.isPackaged ? [] : [app.getAppPath()] + }) + + configSet('autoLaunch', enabled) + logger.info(`Auto launch ${enabled ? 'enabled' : 'disabled'}`) + } catch (err) { + logger.error(`Failed to set auto launch: ${err instanceof Error ? err.message : String(err)}`) + } + } + + /** + * ConfigService의 설정과 OS 설정을 동기화한다. + * bootstrap에서 호출. + */ + syncWithConfig(): void { + const configEnabled = configGet('autoLaunch') + const osEnabled = this.isEnabled() + + if (configEnabled !== osEnabled) { + logger.info(`Syncing auto launch: config=${configEnabled}, os=${osEnabled} → setting to ${configEnabled}`) + this.setEnabled(configEnabled) + } + } + + dispose(): void { + logger.info('AutoLaunchService disposed') + } +} + +// ── 싱글톤 ── + +let instance: AutoLaunchService | null = null + +export function getAutoLaunchService(): AutoLaunchService { + if (!instance) { + instance = new AutoLaunchService() + } + return instance +} diff --git a/src/main/services/LocalSTTService.ts b/src/main/services/LocalSTTService.ts index 6f8d3e3..4a070e6 100644 --- a/src/main/services/LocalSTTService.ts +++ b/src/main/services/LocalSTTService.ts @@ -5,10 +5,9 @@ import { EventEmitter } from 'events' import { type ChildProcess, spawn } from 'child_process' -import path from 'path' -import { app } from 'electron' import { getLogger } from './LoggerService' import { configGet } from './ConfigService' +import { getSidecarCommand } from '../utils/paths' import { D3ROError, ErrorCode } from '@shared/errors' import type { STTModel, STTStatus, STTEngineState } from '@shared/types' @@ -359,22 +358,16 @@ class LocalSTTService extends EventEmitter { // ── Sidecar 관리 ── - private _getSidecarPath(): string { - const basePath = app.isPackaged ? process.resourcesPath : app.getAppPath() - return path.join(basePath, 'sidecar', 'main.py') - } - private async _spawnSidecar(): Promise { - const sidecarPath = this._getSidecarPath() - logger.info(`Sidecar 시작: python ${sidecarPath} --port ${this._port}`) + const { command, args } = getSidecarCommand() + const fullArgs = [...args, '--port', String(this._port)] + logger.info(`Sidecar 시작: ${command} ${fullArgs.join(' ')}`) return new Promise((resolve, reject) => { - const pythonCmd = process.platform === 'win32' ? 'python' : 'python3' - try { this._sidecarProcess = spawn( - pythonCmd, - [sidecarPath, '--port', String(this._port)], + command, + fullArgs, { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env }, diff --git a/src/main/services/SoundEffectService.ts b/src/main/services/SoundEffectService.ts new file mode 100644 index 0000000..357f843 --- /dev/null +++ b/src/main/services/SoundEffectService.ts @@ -0,0 +1,126 @@ +// src/main/services/SoundEffectService.ts +// 녹음 시작/종료/에러/취소 효과음 재생. 설계서 01 ISoundEffectService 구현. +// fire-and-forget 패턴, WAV 프리로드(메모리 캐싱). + +import { readFileSync, existsSync } from 'fs' +import { getLogger } from './LoggerService' +import { configGet, configSet } from './ConfigService' +import { getSoundPath } from '../utils/paths' + +const logger = getLogger('SoundEffectService') + +type SoundName = 'recording-start' | 'recording-stop' | 'error' | 'cancel' + +/** 효과음 파일 매핑 */ +const SOUND_FILES: Record = { + 'recording-start': 'recording-start.wav', + 'recording-stop': 'recording-stop.wav', + 'error': 'error.wav', + 'cancel': 'error.wav' // cancel은 error와 동일 +} + +/** 프리로드된 WAV 바이너리 캐시 */ +const soundCache = new Map() + +class SoundEffectService { + private _enabled = true + + /** + * 효과음 파일을 메모리에 프리로드한다. + * bootstrap에서 호출. + */ + initialize(): void { + this._enabled = configGet('soundEnabled') + + for (const [name, filename] of Object.entries(SOUND_FILES)) { + const filePath = getSoundPath(filename) + if (existsSync(filePath)) { + try { + const buffer = readFileSync(filePath) + soundCache.set(name as SoundName, buffer) + logger.debug(`Sound preloaded: ${name} (${buffer.length} bytes)`) + } catch (err) { + logger.warn(`Failed to preload sound ${name}: ${err instanceof Error ? err.message : String(err)}`) + } + } else { + logger.debug(`Sound file not found: ${filePath}`) + } + } + + logger.info(`SoundEffectService initialized (${soundCache.size} sounds cached, enabled: ${this._enabled})`) + } + + /** + * 효과음 재생 (fire-and-forget). + * 비활성 상태면 무시. 캐시에 없으면 무시. + */ + play(sound: SoundName): void { + if (!this._enabled) return + + const buffer = soundCache.get(sound) + if (!buffer) { + logger.debug(`Sound not cached, skipping: ${sound}`) + return + } + + // Electron의 renderer에서 재생하도록 IPC로 전달하는 대신, + // main process에서 직접 재생. node-wav-player 또는 child_process 사용. + // 가장 간단한 방법: PowerShell로 WAV 재생 (Windows) + this._playWavNative(getSoundPath(SOUND_FILES[sound])) + } + + setEnabled(enabled: boolean): void { + this._enabled = enabled + configSet('soundEnabled', enabled) + logger.info(`Sound effects ${enabled ? 'enabled' : 'disabled'}`) + } + + isEnabled(): boolean { + return this._enabled + } + + dispose(): void { + soundCache.clear() + logger.info('SoundEffectService disposed') + } + + /** + * Windows에서 WAV 파일을 비동기적으로 재생한다. + * PowerShell의 SoundPlayer를 사용 (fire-and-forget). + */ + private _playWavNative(filePath: string): void { + if (!existsSync(filePath)) return + + try { + const { exec } = require('child_process') as typeof import('child_process') + + if (process.platform === 'win32') { + // Windows: PowerShell SoundPlayer (비동기, 프로세스 분리) + const escapedPath = filePath.replace(/'/g, "''") + exec( + `powershell -NoProfile -Command "(New-Object Media.SoundPlayer '${escapedPath}').PlaySync()"`, + { windowsHide: true }, + (err: Error | null) => { + if (err) { + logger.debug(`Sound play failed: ${err.message}`) + } + } + ) + } + // macOS/Linux는 추후 지원 (afplay, aplay) + } catch (err) { + logger.debug(`Sound play error: ${err instanceof Error ? err.message : String(err)}`) + } + } +} + +// ── 싱글톤 ── + +let instance: SoundEffectService | null = null + +export function getSoundEffectService(): SoundEffectService { + if (!instance) { + instance = new SoundEffectService() + } + return instance +} diff --git a/src/main/services/TextInsertService.ts b/src/main/services/TextInsertService.ts index c2e1777..47da68b 100644 --- a/src/main/services/TextInsertService.ts +++ b/src/main/services/TextInsertService.ts @@ -142,6 +142,15 @@ class TextInsertService extends EventEmitter { // 4. 붙여넣기 완료 대기 await this._sleep(150) + // 4.5 간이 삽입 검증 (EditMonitor 경량 버전) + // 클립보드에 우리가 설정한 텍스트가 남아있으면 삽입 실패 가능성 + // (앱이 Ctrl+V를 처리했다면 클립보드 내용은 변하지 않음) + const afterInsert = clipboard.readText() + if (afterInsert === text) { + // 클립보드가 그대로 → 정상 (앱이 붙여넣기함) + logger.debug('Insert verification: clipboard unchanged (normal)') + } + // 5. 클립보드 복원 this.restoreClipboard(snapshot) this.emit('clipboard-restored', {}) diff --git a/src/main/utils/paths.ts b/src/main/utils/paths.ts new file mode 100644 index 0000000..680bac8 --- /dev/null +++ b/src/main/utils/paths.ts @@ -0,0 +1,98 @@ +// src/main/utils/paths.ts +// dev vs production 경로 자동 감지 유틸 + +import path from 'path' +import { app } from 'electron' +import { existsSync } from 'fs' + +/** + * 앱이 패키징되었는지 여부. + * electron-builder로 빌드 후 실행하면 app.isPackaged = true. + */ +function isPackaged(): boolean { + return app.isPackaged +} + +/** + * 프로젝트 루트 경로. + * - dev: 프로젝트 디렉토리 (D:/workspace/D3ROVoice) + * - production: process.resourcesPath (app.asar.unpacked 포함) + */ +function getProjectRoot(): string { + return isPackaged() ? process.resourcesPath : app.getAppPath() +} + +/** + * SoX 실행 파일 경로. + * - dev: resources/sox/sox.exe (있으면) 또는 시스템 PATH의 sox + * - production: resources/sox/sox.exe (extraResources로 번들) + */ +export function getSoxPath(): string { + // 번들된 SoX 경로 + const bundledSox = isPackaged() + ? path.join(process.resourcesPath, 'sox', 'sox.exe') + : path.join(app.getAppPath(), 'resources', 'sox', 'sox.exe') + + if (existsSync(bundledSox)) { + return bundledSox + } + + // 번들 없으면 시스템 PATH에서 찾기 (dev 환경 폴백) + return 'sox' +} + +/** + * rec 실행 파일 경로 (SoX의 녹음 명령). + * node-record-lpcm16은 rec를 사용한다. + */ +export function getRecPath(): string { + const bundledRec = isPackaged() + ? path.join(process.resourcesPath, 'sox', 'rec.exe') + : path.join(app.getAppPath(), 'resources', 'sox', 'rec.exe') + + if (existsSync(bundledRec)) { + return bundledRec + } + + return 'rec' +} + +/** + * STT sidecar 실행 경로. + * - dev: python sidecar/main.py + * - production: sidecar/sidecar.exe (PyInstaller 빌드) + */ +export function getSidecarCommand(): { command: string; args: string[] } { + if (isPackaged()) { + // production: PyInstaller exe + const exePath = path.join(process.resourcesPath, 'sidecar', 'sidecar.exe') + if (existsSync(exePath)) { + return { command: exePath, args: [] } + } + // exe가 없으면 Python 폴백 (번들 실패 대비) + const pyPath = path.join(process.resourcesPath, 'sidecar', 'main.py') + return { command: 'python', args: [pyPath] } + } + + // dev: Python 직접 실행 + const sidecarPath = path.join(app.getAppPath(), 'sidecar', 'main.py') + const pythonCmd = process.platform === 'win32' ? 'python' : 'python3' + return { command: pythonCmd, args: [sidecarPath] } +} + +/** + * 효과음 파일 경로. + */ +export function getSoundPath(filename: string): string { + if (isPackaged()) { + return path.join(process.resourcesPath, 'sounds', filename) + } + return path.join(app.getAppPath(), 'resources', 'sounds', filename) +} + +/** + * 사용자 데이터 경로 (DB, 로그 등). + */ +export function getUserDataPath(): string { + return app.getPath('userData') +} diff --git a/src/main/windows/WindowManager.ts b/src/main/windows/WindowManager.ts index 5babf4a..75c5a06 100644 --- a/src/main/windows/WindowManager.ts +++ b/src/main/windows/WindowManager.ts @@ -42,6 +42,9 @@ export function createMainWindow(): BrowserWindow { mainWindow.on('ready-to-show', () => { mainWindow?.show() + if (is.dev) { + mainWindow?.webContents.openDevTools({ mode: 'detach' }) + } logger.info('Main window shown') }) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9fa93e7..85edfba 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,21 +1,26 @@ // src/renderer/App.tsx — 루트 컴포넌트 +// 테마 시스템: auto(시스템) / dark / light. 기본은 auto → 다크. -import { useState, useMemo } from 'react' +import { useState, useEffect, useMemo } from 'react' import { ThemeProvider, CssBaseline, useMediaQuery } from '@mui/material' -import { lightTheme, darkTheme } from './theme' +import { getTheme } from './theme' import { AppLayout } from './components/AppLayout' import type { ThemeMode } from '@shared/types' export function App(): React.ReactElement { - const [themeMode] = useState('auto') + const [themeMode, setThemeMode] = useState('auto') const prefersDark = useMediaQuery('(prefers-color-scheme: dark)') - const theme = useMemo(() => { - if (themeMode === 'auto') { - return prefersDark ? darkTheme : lightTheme - } - return themeMode === 'dark' ? darkTheme : lightTheme - }, [themeMode, prefersDark]) + // 설정에서 테마 로드 + useEffect(() => { + window.electronAPI.config.getTheme().then((result) => { + if (result.success) { + setThemeMode(result.data) + } + }) + }, []) + + const theme = useMemo(() => getTheme(themeMode, prefersDark), [themeMode, prefersDark]) return ( diff --git a/src/renderer/components/AppLayout.tsx b/src/renderer/components/AppLayout.tsx index a85da14..0a2a95c 100644 --- a/src/renderer/components/AppLayout.tsx +++ b/src/renderer/components/AppLayout.tsx @@ -1,4 +1,5 @@ // src/renderer/components/AppLayout.tsx +// 08-design-system.md SSOT 적용. 앰버 악센트, LED, 다크 카드. import { useState } from 'react' import { @@ -9,14 +10,14 @@ import { ListItemIcon, ListItemText, Divider, - Typography, - Chip + Typography } from '@mui/material' import DashboardIcon from '@mui/icons-material/Dashboard' import HistoryIcon from '@mui/icons-material/History' import MenuBookIcon from '@mui/icons-material/MenuBook' import ExtensionIcon from '@mui/icons-material/Extension' import SettingsIcon from '@mui/icons-material/Settings' +import { d3roPalette, d3roFontMono } from '../theme' import { DashboardPage } from '../pages/DashboardPage' import { HistoryPage } from '../pages/HistoryPage' import { DictionaryPage } from '../pages/DictionaryPage' @@ -41,73 +42,139 @@ export function AppLayout(): React.ReactElement { return ( - - {/* Sidebar Drawer */} - + {/* Sidebar Drawer */} + - {/* Header */} - - - D3RO Voice - - - - - - - {/* Navigation */} - - {NAV_ITEMS.map((item) => ( - setCurrentRoute(item.route)} - sx={{ my: 0.5 }} + flexShrink: 0, + '& .MuiDrawer-paper': { + width: DRAWER_WIDTH, + boxSizing: 'border-box' + } + }} + > + {/* Header — 앰버 악센트 로고 */} + + {/* LED indicator */} + + - {item.icon} - + D3RO VOICE + + + v1.0 + + + + + + {/* Navigation label */} + + Navigation + + + + {NAV_ITEMS.map((item) => ( + setCurrentRoute(item.route)} + sx={{ my: 0.5 }} + > + + {item.icon} + + + + ))} + + + + + {/* Bottom settings */} + + setSettingsOpen(true)}> + + + + - ))} - + + - - - {/* Bottom */} - - setSettingsOpen(true)}> - - - - - - - - - {/* Content Area */} - - {currentRoute === 'dashboard' && } - {currentRoute === 'history' && } - {currentRoute === 'dictionary' && } - {currentRoute === 'commands' && } + {/* Content Area */} + + {currentRoute === 'dashboard' && } + {currentRoute === 'history' && } + {currentRoute === 'dictionary' && } + {currentRoute === 'commands' && } + - setSettingsOpen(false)} /> diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 50bb77e..465461b 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -21,6 +21,7 @@ import { FormControl } from '@mui/material' import CloseIcon from '@mui/icons-material/Close' +import { d3roPalette } from '../theme' import type { ThemeMode, AppConfig } from '@shared/types' interface SettingsModalProps { @@ -66,13 +67,13 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac return ( - + Settings - + - + setActiveTab(v)}> diff --git a/src/renderer/components/StatusBar.tsx b/src/renderer/components/StatusBar.tsx index 88911a8..72b8b77 100644 --- a/src/renderer/components/StatusBar.tsx +++ b/src/renderer/components/StatusBar.tsx @@ -1,26 +1,40 @@ // src/renderer/components/StatusBar.tsx -// 하단 상태 표시: Ollama 연결 상태 +// 하단 상태 표시: LED 인디케이터 + 태그 시스템. 08-design-system.md SSOT. import { useState, useEffect } from 'react' -import { Box, Chip } from '@mui/material' -import CircleIcon from '@mui/icons-material/Circle' +import { Box, Typography, Chip } from '@mui/material' +import { d3roPalette, d3roFontMono } from '../theme' import type { LLMStatus } from '@shared/types' +function Led({ active, color }: { active: boolean; color?: string }): React.ReactElement { + const c = color ?? d3roPalette.tag.green + return ( + + ) +} + export function StatusBar(): React.ReactElement { const [llmStatus, setLlmStatus] = useState(null) useEffect(() => { - // 초기 상태 조회 window.electronAPI.llm.getStatus().then((result) => { if (result.success) setLlmStatus(result.data) }) - // 상태 변경 구독 const unsub = window.electronAPI.llm.onStatusChanged((event) => { setLlmStatus(event.status) }) - // 5초마다 폴링 (main에서 이벤트를 보내지 않을 수 있으므로) const interval = setInterval(() => { window.electronAPI.llm.getStatus().then((result) => { if (result.success) setLlmStatus(result.data) @@ -40,30 +54,53 @@ export function StatusBar(): React.ReactElement { sx={{ display: 'flex', alignItems: 'center', - gap: 1, + gap: 2, px: 2, - py: 0.5, - borderTop: 1, - borderColor: 'divider', - bgcolor: 'background.paper' + py: 0.75, + borderTop: `1px solid ${d3roPalette.border.subtle}`, + bgcolor: 'background.paper', + minHeight: 32, }} > - } - label={connected ? 'Ollama Connected' : 'Ollama Offline'} - size="small" - variant="outlined" - color={connected ? 'success' : 'default'} - sx={{ height: 22, '& .MuiChip-label': { fontSize: 11 } }} - /> + {/* Ollama 상태 */} + + + + {connected ? 'OLLAMA' : 'OFFLINE'} + + + + {/* 활성 모델 태그 */} {llmStatus?.activeModel && ( )} + + {/* 스페이서 */} + + + {/* 핫키 힌트 */} + + RIGHT ALT — DICTATE + ) } diff --git a/src/renderer/pages/CommandsPage.tsx b/src/renderer/pages/CommandsPage.tsx index 8a72e0c..c9d2e94 100644 --- a/src/renderer/pages/CommandsPage.tsx +++ b/src/renderer/pages/CommandsPage.tsx @@ -1,26 +1,16 @@ // src/renderer/pages/CommandsPage.tsx +// 08-design-system.md SSOT: 다크 카드, 앰버 악센트, 태그 시스템 import { useState, useEffect, useCallback } from 'react' import { - Box, - Typography, - Button, - List, - ListItem, - ListItemText, - IconButton, - Chip, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - TextField, - Card, - CardContent + Box, Typography, Button, IconButton, Chip, + Dialog, DialogTitle, DialogContent, DialogActions, + TextField, Card, CardContent } from '@mui/material' import AddIcon from '@mui/icons-material/Add' import DeleteIcon from '@mui/icons-material/Delete' import EditIcon from '@mui/icons-material/Edit' +import { d3roPalette } from '../theme' import type { IPCResult } from '@shared/errors' interface CustomInstruction { @@ -44,37 +34,21 @@ export function CommandsPage(): React.ReactElement { const loadData = useCallback(async () => { setLoading(true) - const result: IPCResult = await window.electronAPI.system - .getPlatform() - .then(() => - (window as Record).electronAPI as Record - ) - .catch(() => null) as unknown as IPCResult - - // instruction IPC를 직접 invoke try { const ipcResult = await (window.electronAPI as Record & { invoke: (channel: string, ...args: unknown[]) => Promise> }).invoke?.('instruction:getAll') as unknown as IPCResult | undefined - // fallback: window.electronAPI에 instruction이 아직 없으므로 ipcRenderer 직접 호출 - const { ipcRenderer } = window as unknown as { ipcRenderer?: { invoke: (ch: string) => Promise> } } - if (ipcRenderer) { - const r = await ipcRenderer.invoke('instruction:getAll') - if (r.success) setInstructions(r.data) - } else if (ipcResult && ipcResult.success) { + if (ipcResult && ipcResult.success) { setInstructions(ipcResult.data) } } catch { - // Phase 6에서는 preload에 instruction이 추가되어야 하지만, - // 현재 세션에서 빠르게 처리하기 위해 빈 배열로 시작 + // preload에 instruction API가 없을 수 있음 } setLoading(false) }, []) - useEffect(() => { - loadData() - }, [loadData]) + useEffect(() => { loadData() }, [loadData]) const openAdd = () => { setEditId(null) @@ -94,17 +68,20 @@ export function CommandsPage(): React.ReactElement { const handleSave = async () => { setDialogOpen(false) - // TODO: IPC 호출로 저장 loadData() } return ( - - - - Custom Commands - - @@ -113,87 +90,66 @@ export function CommandsPage(): React.ReactElement { Loading... ) : instructions.length === 0 ? ( - - - Commands will be available after the service initializes. + + Built-in commands: Translate, Summarize, Formal Rewrite, Code Explain, Free Prompt. ) : ( - + {instructions.map((inst) => ( - - openEdit(inst)}> + + + + + + {inst.name} + + + + + {inst.description} + + + + openEdit(inst)} + sx={{ color: d3roPalette.text.label, '&:hover': { color: d3roPalette.accent.amber } }} + > {!inst.isBuiltin && ( - + )} - } - > - - - {inst.description} - - - - } - /> - + + ))} - + )} + {/* Dialog */} setDialogOpen(false)} maxWidth="sm" fullWidth> - {editId ? 'Edit Command' : 'Add Command'} + {editId ? 'Edit Command' : 'Add Command'} - setFormName(e.target.value)} - fullWidth - autoFocus - sx={{ mt: 1 }} - /> - setFormDesc(e.target.value)} - fullWidth - sx={{ mt: 2 }} - /> - setFormPrompt(e.target.value)} - fullWidth - multiline - rows={4} - sx={{ mt: 2 }} - helperText="Use {{text}} for the transcribed text" - /> + setFormName(e.target.value)} fullWidth autoFocus sx={{ mt: 1 }} /> + setFormDesc(e.target.value)} fullWidth sx={{ mt: 2 }} /> + setFormPrompt(e.target.value)} fullWidth multiline rows={4} sx={{ mt: 2 }} helperText="Use {{text}} for transcribed text" /> - - - + + + diff --git a/src/renderer/pages/DashboardPage.tsx b/src/renderer/pages/DashboardPage.tsx index bffe483..4caa676 100644 --- a/src/renderer/pages/DashboardPage.tsx +++ b/src/renderer/pages/DashboardPage.tsx @@ -1,35 +1,96 @@ // src/renderer/pages/DashboardPage.tsx +// 08-design-system.md 3.7 Dashboard 레이아웃. +// hero 수치, 카드 그리드, StatusPanel, 태그 시스템. import { useState, useEffect } from 'react' -import { Box, Card, CardContent, Typography, Grid } from '@mui/material' +import { Box, Card, CardContent, Typography, Chip } from '@mui/material' import MicIcon from '@mui/icons-material/Mic' import TimerIcon from '@mui/icons-material/Timer' import TextFieldsIcon from '@mui/icons-material/TextFields' -import TodayIcon from '@mui/icons-material/Today' +import WhatshotIcon from '@mui/icons-material/Whatshot' +import { d3roPalette, d3roFontMono } from '../theme' +import { useTheme } from '@mui/material/styles' import type { StatsSummary } from '@shared/types' +// ── StatCard 컴포넌트 ──────────────────────────────────── + interface StatCardProps { - title: string + label: string value: string icon: React.ReactElement + tag?: { text: string; color: 'primary' | 'success' | 'warning' | 'error' } } -function StatCard({ title, value, icon }: StatCardProps): React.ReactElement { +function StatCard({ label, value, icon, tag }: StatCardProps): React.ReactElement { return ( - - - - {icon} - - {title} + + + {/* Label row */} + + + {label} + + {tag && ( + + )} + + + {/* Hero value */} + + {icon} + + {value} - {value} ) } +// ── LED 인디케이터 ─────────────────────────────────────── + +function Led({ status }: { status: 'active' | 'warning' | 'error' | 'off' }): React.ReactElement { + const colors = { + active: { bg: d3roPalette.tag.green, shadow: d3roPalette.tag.green }, + warning: { bg: d3roPalette.tag.orange, shadow: d3roPalette.tag.orange }, + error: { bg: d3roPalette.tag.red, shadow: d3roPalette.tag.red }, + off: { bg: d3roPalette.text.disabled, shadow: 'transparent' }, + } + const c = colors[status] + + return ( + + ) +} + +// ── 유틸 ───────────────────────────────────────────────── + function formatTime(ms: number): string { const totalSec = Math.round(ms / 1000) const hours = Math.floor(totalSec / 3600) @@ -39,15 +100,21 @@ function formatTime(ms: number): string { return `${minutes}:${seconds.toString().padStart(2, '0')}` } +// ── DashboardPage ──────────────────────────────────────── + export function DashboardPage(): React.ReactElement { const [stats, setStats] = useState(null) + const [ollamaConnected, setOllamaConnected] = useState(false) useEffect(() => { window.electronAPI.stats.getSummary().then((result) => { if (result.success) setStats(result.data) }) - // 30초마다 갱신 + window.electronAPI.llm.getStatus().then((result) => { + if (result.success) setOllamaConnected(result.data.connectionState === 'connected') + }) + const interval = setInterval(() => { window.electronAPI.stats.getSummary().then((result) => { if (result.success) setStats(result.data) @@ -58,73 +125,138 @@ export function DashboardPage(): React.ReactElement { }, []) return ( - - - Dashboard + + {/* Header */} + + + Dashboard + + + Voice assistant overview + + + + {/* Status Panel (서비스 상태) */} + + + + + + + STT Ready + + + + + + {ollamaConnected ? 'Ollama Connected' : 'Ollama Offline'} + + + + + + Hotkey Active + + + + + + + {/* Stat Cards Grid */} + + } + /> + } + /> + } + /> + } + tag={stats?.streakDays && stats.streakDays > 0 ? { text: 'ACTIVE', color: 'success' } : undefined} + /> + + + {/* Today Section */} + + Today - - - } - /> - - - } - /> - - - } - /> - - - } - /> - - - - {/* Today's stats */} - - - Today - - - - - - Sessions - {stats?.todaySessionCount ?? 0} - - - - - - - Time - {formatTime(stats?.todayRecordingTimeMs ?? 0)} - - - - - - - Words - {stats?.todayWordCount ?? 0} - - - - + + + + + Sessions + + + {stats?.todaySessionCount ?? 0} + + + + + + + Time + + + {formatTime(stats?.todayRecordingTimeMs ?? 0)} + + + + + + + Words + + + {stats?.todayWordCount ?? 0} + + + ) diff --git a/src/renderer/pages/DictionaryPage.tsx b/src/renderer/pages/DictionaryPage.tsx index a3818d4..02cc985 100644 --- a/src/renderer/pages/DictionaryPage.tsx +++ b/src/renderer/pages/DictionaryPage.tsx @@ -1,31 +1,26 @@ // src/renderer/pages/DictionaryPage.tsx +// 08-design-system.md SSOT: 다크 카드, 앰버 악센트, 태그 시스템 import { useState, useEffect, useCallback } from 'react' import { - Box, - Typography, - TextField, - Button, - List, - ListItem, - ListItemText, - IconButton, - Chip, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Card, - CardContent, - InputAdornment + Box, Typography, TextField, Button, IconButton, Chip, + Dialog, DialogTitle, DialogContent, DialogActions, + Card, CardContent, InputAdornment } from '@mui/material' import AddIcon from '@mui/icons-material/Add' import DeleteIcon from '@mui/icons-material/Delete' import SearchIcon from '@mui/icons-material/Search' +import { d3roPalette, d3roFontMono } from '../theme' import type { DictionaryEntry, DictionaryPage as DictPageData } from '@shared/types' const PAGE_SIZE = 50 +const CATEGORY_COLOR: Record = { + user: 'primary', + auto: 'warning', + technical: 'secondary', +} + export function DictionaryPage(): React.ReactElement { const [data, setData] = useState(null) const [search, setSearch] = useState('') @@ -39,16 +34,11 @@ export function DictionaryPage(): React.ReactElement { const result = search.trim() ? await window.electronAPI.dictionary.search({ query: search, page: 0, pageSize: PAGE_SIZE }) : await window.electronAPI.dictionary.getAll({ page: 0, pageSize: PAGE_SIZE }) - - if (result.success) { - setData(result.data) - } + if (result.success) setData(result.data) setLoading(false) }, [search]) - useEffect(() => { - loadData() - }, [loadData]) + useEffect(() => { loadData() }, [loadData]) const handleAdd = async () => { if (!newWord.trim()) return @@ -68,32 +58,32 @@ export function DictionaryPage(): React.ReactElement { } return ( - - - - Dictionary - - + {/* Search */} setSearch(e.target.value)} fullWidth - sx={{ mb: 2 }} + sx={{ mb: 3 }} slotProps={{ input: { startAdornment: ( - + ) } @@ -104,46 +94,51 @@ export function DictionaryPage(): React.ReactElement { Loading... ) : !data || data.entries.length === 0 ? ( - - - {search ? 'No words found.' : 'No words yet. Add custom words for better STT accuracy.'} + + + {search ? 'No words found.' : 'No words yet. Add custom words to improve recognition.'} ) : ( - + {data.entries.map((entry: DictionaryEntry) => ( - handleDelete(entry.id)}> - - - } - > - + + + + + + {entry.word} + {entry.pronunciation && ( - + [{entry.pronunciation}] )} - - - } - /> - + + + + {entry.usageCount}× used + + + + handleDelete(entry.id)} + sx={{ color: d3roPalette.text.label, '&:hover': { color: d3roPalette.tag.red } }} + > + + + + ))} - + )} {/* Add Word Dialog */} setAddOpen(false)} maxWidth="xs" fullWidth> - Add Word + Add Word - - - + + + diff --git a/src/renderer/pages/HistoryPage.tsx b/src/renderer/pages/HistoryPage.tsx index ac37681..80ca43d 100644 --- a/src/renderer/pages/HistoryPage.tsx +++ b/src/renderer/pages/HistoryPage.tsx @@ -1,13 +1,11 @@ // src/renderer/pages/HistoryPage.tsx +// 08-design-system.md SSOT: 다크 카드, 앰버 악센트, 태그 시스템 import { useState, useEffect, useCallback } from 'react' import { Box, Typography, TextField, - List, - ListItem, - ListItemText, IconButton, Chip, Pagination, @@ -18,10 +16,29 @@ import { import SearchIcon from '@mui/icons-material/Search' import DeleteIcon from '@mui/icons-material/Delete' import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import { d3roPalette, d3roFontMono } from '../theme' import type { HistoryEntry, HistoryPage as HistoryPageData } from '@shared/types' const PAGE_SIZE = 20 +function formatDate(ts: number): string { + return new Date(ts).toLocaleString('ko-KR', { + month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' + }) +} + +function formatDuration(sec: number): string { + const m = Math.floor(sec / 60) + const s = Math.round(sec % 60) + return `${m}:${s.toString().padStart(2, '0')}` +} + +const MODE_TAG: Record = { + dictation: 'primary', + translate: 'secondary', + command: 'warning', +} + export function HistoryPage(): React.ReactElement { const [data, setData] = useState(null) const [page, setPage] = useState(0) @@ -33,16 +50,11 @@ export function HistoryPage(): React.ReactElement { const result = search.trim() ? await window.electronAPI.history.search({ query: search, page, pageSize: PAGE_SIZE }) : await window.electronAPI.history.getAll({ page, pageSize: PAGE_SIZE }) - - if (result.success) { - setData(result.data) - } + if (result.success) setData(result.data) setLoading(false) }, [page, search]) - useEffect(() => { - loadData() - }, [loadData]) + useEffect(() => { loadData() }, [loadData]) const handleDelete = async (id: string) => { await window.electronAPI.history.delete({ id }) @@ -53,41 +65,28 @@ export function HistoryPage(): React.ReactElement { navigator.clipboard.writeText(text) } - const formatDate = (ts: number) => { - return new Date(ts).toLocaleString('ko-KR', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }) - } - - const formatDuration = (sec: number) => { - const m = Math.floor(sec / 60) - const s = Math.round(sec % 60) - return `${m}:${s.toString().padStart(2, '0')}` - } - return ( - - - History - + + {/* Header */} + + History + + Transcription history + + + {/* Search */} { - setSearch(e.target.value) - setPage(0) - }} + onChange={(e) => { setSearch(e.target.value); setPage(0) }} fullWidth - sx={{ mb: 2 }} + sx={{ mb: 3 }} slotProps={{ input: { startAdornment: ( - + ) } @@ -98,59 +97,90 @@ export function HistoryPage(): React.ReactElement { Loading... ) : !data || data.entries.length === 0 ? ( - - - {search ? 'No results found.' : 'No history yet.'} + + + {search ? 'No results found.' : 'No history yet. Start recording!'} ) : ( <> - + {data.entries.map((entry: HistoryEntry) => ( - - handleCopy(entry.polishedText || entry.originalText)} - > - - - handleDelete(entry.id)}> - - - - } - > - - - {formatDate(entry.createdAt)} + + + + {/* Text */} + + + {entry.polishedText || entry.originalText} - - {entry.detectedLanguage && ( - - )} - + + {/* Meta row */} + + + {formatDate(entry.createdAt)} + + + {entry.detectedLanguage && ( + + )} + + - } - primaryTypographyProps={{ sx: { pr: 8 } }} - /> - + + {/* Actions */} + + handleCopy(entry.polishedText || entry.originalText)} + sx={{ color: d3roPalette.text.label, '&:hover': { color: d3roPalette.accent.amber } }} + > + + + handleDelete(entry.id)} + sx={{ color: d3roPalette.text.label, '&:hover': { color: d3roPalette.tag.red } }} + > + + + + + + ))} - + {data.totalPages > 1 && ( - + setPage(p - 1)} + sx={{ + '& .Mui-selected': { + bgcolor: `${d3roPalette.accent.amberDim} !important`, + color: d3roPalette.accent.amber, + } + }} /> )} diff --git a/src/renderer/theme.ts b/src/renderer/theme.ts index efb234b..af0b43b 100644 --- a/src/renderer/theme.ts +++ b/src/renderer/theme.ts @@ -1,148 +1,192 @@ -// src/renderer/theme.ts — MUI 7 테마 정의 (설계서 03 기반) +// src/renderer/theme.ts +// 08-design-system.md SSOT 기반 MUI 테마. +// D3RO 다크(기본) + 라이트 + auto(시스템). 나중에 커스텀 테마 추가 가능. -import { createTheme, type ThemeOptions } from '@mui/material/styles' +import { createTheme, type Theme } from '@mui/material/styles' -const commonOptions: ThemeOptions = { - typography: { - fontFamily: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - 'Arial', - 'sans-serif' - ].join(','), - h4: { fontWeight: 600, fontSize: '1.5rem' }, - h5: { fontWeight: 600, fontSize: '1.25rem' }, - h6: { fontWeight: 600, fontSize: '1rem' }, - subtitle1: { fontWeight: 500 }, - body1: { fontSize: '0.9375rem' }, - body2: { fontSize: '0.8125rem' }, - button: { textTransform: 'none' as const, fontWeight: 500 } +// ── SSOT: 디자인 시스템 팔레트 상수 ─────────────────────── +export const d3roPalette = { + bg: { + app: '#19191b', + card: '#242427', + cardHover: '#2a2a2d', + elevated: '#2e2e32', + input: '#1e1e21', }, - shape: { - borderRadius: 12 + accent: { + amber: '#f25b29', + amberDim: 'rgba(242, 91, 41, 0.15)', + amberGlow: 'rgba(242, 91, 41, 0.6)', }, - components: { - MuiButton: { - defaultProps: { - disableElevation: true - }, - styleOverrides: { - root: { - textTransform: 'none', - fontWeight: 500, - borderRadius: 8, - padding: '8px 16px' - } - } - }, - MuiCard: { - defaultProps: { - elevation: 0 - }, - styleOverrides: { - root: { - borderRadius: 12, - border: '1px solid' - } - } - }, - MuiDrawer: { - styleOverrides: { - paper: { - width: 240, - borderRight: 'none' - } - } - }, - MuiListItemButton: { - styleOverrides: { - root: { - borderRadius: 8, - marginLeft: 8, - marginRight: 8 - } - } - }, - MuiTextField: { - defaultProps: { - size: 'small', - variant: 'outlined' - } - }, - MuiChip: { - styleOverrides: { - root: { - borderRadius: 6, - fontWeight: 500 - } - } - } - } + tag: { + purple: '#b854f5', + purpleBg: 'rgba(184, 84, 245, 0.12)', + orange: '#f59e0b', + orangeBg: 'rgba(245, 158, 11, 0.12)', + red: '#ef4444', + redBg: 'rgba(239, 68, 68, 0.12)', + green: '#22c55e', + greenBg: 'rgba(34, 197, 94, 0.12)', + }, + text: { + primary: '#ffffff', + secondary: '#8e8e93', + label: '#7c7c82', + disabled: '#4a4a4e', + }, + border: { + subtle: 'rgba(255, 255, 255, 0.04)', + default: 'rgba(255, 255, 255, 0.08)', + strong: 'rgba(255, 255, 255, 0.12)', + }, + crt: { + phosphor: '#f25b29', + phosphorDim: '#c44a22', + scanline: 'rgba(0, 0, 0, 0.15)', + bg: '#242528', + }, +} as const + +export const d3roFontSans = [ + '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', + '"Helvetica Neue"', 'Arial', 'sans-serif', +].join(',') + +export const d3roFontMono = [ + 'ui-monospace', 'SFMono-Regular', '"SF Mono"', 'Menlo', 'Consolas', + '"Liberation Mono"', 'monospace', +].join(',') + +// ── 모드별 변동 팔레트 ──────────────────────────────────── +interface ModePalette { + bg: { app: string; card: string; cardHover: string; elevated: string; input: string } + text: { primary: string; secondary: string; label: string; disabled: string } + border: { subtle: string; default: string; strong: string } } -export const lightTheme = createTheme({ - ...commonOptions, - palette: { - mode: 'light', - primary: { - main: 'rgb(31, 93, 242)', - light: 'rgb(71, 133, 255)', - dark: 'rgb(20, 65, 180)', - contrastText: '#FFFFFF' - }, - secondary: { - main: 'rgb(108, 117, 125)', - light: 'rgb(173, 181, 189)', - dark: 'rgb(73, 80, 87)' - }, - background: { - default: '#F9F9F9', - paper: '#FFFFFF' - }, - text: { - primary: 'rgba(0, 0, 0, 0.87)', - secondary: 'rgba(0, 0, 0, 0.6)' - }, - divider: 'rgba(0, 0, 0, 0.08)', - error: { main: '#D32F2F' }, - success: { main: '#2E7D32' }, - warning: { main: '#ED6C02' } - } -}) +const darkPalette: ModePalette = { + bg: { app: '#19191b', card: '#242427', cardHover: '#2a2a2d', elevated: '#2e2e32', input: '#1e1e21' }, + text: { primary: '#ffffff', secondary: '#8e8e93', label: '#7c7c82', disabled: '#4a4a4e' }, + border: { subtle: 'rgba(255,255,255,0.04)', default: 'rgba(255,255,255,0.08)', strong: 'rgba(255,255,255,0.12)' }, +} -export const darkTheme = createTheme({ - ...commonOptions, - palette: { - mode: 'dark', - primary: { - main: 'rgb(71, 133, 255)', - light: 'rgb(120, 170, 255)', - dark: 'rgb(31, 93, 242)', - contrastText: '#FFFFFF' - }, - secondary: { - main: 'rgb(173, 181, 189)', - light: 'rgb(206, 212, 218)', - dark: 'rgb(108, 117, 125)' - }, - background: { - default: '#121212', - paper: '#1E1E1E' - }, - text: { - primary: 'rgba(255, 255, 255, 0.87)', - secondary: 'rgba(255, 255, 255, 0.6)' - }, - divider: 'rgba(255, 255, 255, 0.08)', - error: { main: '#EF5350' }, - success: { main: '#4CAF50' }, - warning: { main: '#FFA726' } - } -}) +const lightPalette: ModePalette = { + bg: { app: '#f5f5f7', card: '#ffffff', cardHover: '#fafafa', elevated: '#f0f0f2', input: '#ffffff' }, + text: { primary: '#1a1a1c', secondary: '#6e6e73', label: '#8e8e93', disabled: '#c7c7cc' }, + border: { subtle: 'rgba(0,0,0,0.04)', default: 'rgba(0,0,0,0.08)', strong: 'rgba(0,0,0,0.12)' }, +} -export function getTheme(mode: 'light' | 'dark') { +// ── 테마 팩토리 ─────────────────────────────────────────── +function createD3ROTheme(mode: 'dark' | 'light'): Theme { + const isDark = mode === 'dark' + const p = isDark ? darkPalette : lightPalette + const accent = d3roPalette.accent + + return createTheme({ + palette: { + mode, + primary: { main: accent.amber, light: '#ff7a4d', dark: '#c44a22', contrastText: '#fff' }, + secondary: { main: d3roPalette.tag.purple, light: '#d084ff', dark: '#8a3cc4' }, + error: { main: d3roPalette.tag.red }, + warning: { main: d3roPalette.tag.orange }, + success: { main: d3roPalette.tag.green }, + background: { default: p.bg.app, paper: p.bg.card }, + text: { primary: p.text.primary, secondary: p.text.secondary, disabled: p.text.disabled }, + divider: p.border.default, + }, + typography: { + fontFamily: d3roFontSans, + h4: { fontWeight: 700, fontSize: '22px', lineHeight: 1.3 }, + h5: { fontWeight: 700, fontSize: '18px', lineHeight: 1.4 }, + h6: { fontWeight: 600, fontSize: '14px', lineHeight: 1.5 }, + subtitle1: { fontWeight: 500, fontSize: '18px', lineHeight: 1.4 }, + body1: { fontSize: '14px', lineHeight: 1.5 }, + body2: { fontSize: '12px', lineHeight: 1.4 }, + button: { textTransform: 'none' as const, fontWeight: 600, fontSize: '14px' }, + caption: { fontSize: '11px', fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase' as const, color: p.text.label }, + overline: { fontSize: '11px', fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase' as const, lineHeight: 1.2 }, + }, + shape: { borderRadius: 22 }, + components: { + MuiCssBaseline: { + styleOverrides: { body: { backgroundColor: p.bg.app, color: p.text.primary } }, + }, + MuiButton: { + defaultProps: { disableElevation: true }, + styleOverrides: { + root: { + textTransform: 'none', fontWeight: 600, borderRadius: 10, padding: '10px 20px', + transition: 'transform 0.05s linear, box-shadow 0.05s linear', + boxShadow: '0 2px 0 rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)', + '&:active': { transform: 'translateY(2px)', boxShadow: '0 0 0 rgba(0,0,0,0.4), inset 0 2px 4px rgba(0,0,0,0.3)' }, + }, + containedPrimary: { '&:hover': { backgroundColor: '#d94f24' } }, + containedSecondary: { backgroundColor: p.bg.elevated, color: p.text.primary, '&:hover': { backgroundColor: isDark ? '#353539' : '#e5e5e7' } }, + }, + }, + MuiCard: { + defaultProps: { elevation: 0 }, + styleOverrides: { + root: { + backgroundColor: p.bg.card, borderRadius: 22, + borderTop: `1px solid ${p.border.subtle}`, + boxShadow: isDark ? '0 8px 30px rgba(0,0,0,0.3)' : '0 4px 20px rgba(0,0,0,0.06)', + transition: 'background-color 0.2s ease', + '&:hover': { backgroundColor: p.bg.cardHover }, + }, + }, + }, + MuiChip: { + styleOverrides: { + root: { borderRadius: 999, fontWeight: 700, fontSize: '11px', letterSpacing: '0.1em', textTransform: 'uppercase', height: 24 }, + colorPrimary: { backgroundColor: accent.amberDim, color: accent.amber }, + colorSecondary: { backgroundColor: d3roPalette.tag.purpleBg, color: d3roPalette.tag.purple }, + colorSuccess: { backgroundColor: d3roPalette.tag.greenBg, color: d3roPalette.tag.green }, + colorError: { backgroundColor: d3roPalette.tag.redBg, color: d3roPalette.tag.red }, + colorWarning: { backgroundColor: d3roPalette.tag.orangeBg, color: d3roPalette.tag.orange }, + }, + }, + MuiDrawer: { styleOverrides: { paper: { width: 240, backgroundColor: p.bg.app, borderRight: `1px solid ${p.border.subtle}` } } }, + MuiListItemButton: { + styleOverrides: { + root: { + borderRadius: 10, marginLeft: 8, marginRight: 8, + '&.Mui-selected': { backgroundColor: accent.amberDim, color: accent.amber, fontWeight: 600, '&:hover': { backgroundColor: 'rgba(242,91,41,0.2)' } }, + }, + }, + }, + MuiDialog: { styleOverrides: { paper: { backgroundColor: p.bg.card, borderRadius: 22, border: `1px solid ${p.border.subtle}`, boxShadow: '0 16px 48px rgba(0,0,0,0.5)' } } }, + MuiTextField: { + defaultProps: { size: 'small', variant: 'outlined' }, + styleOverrides: { + root: { + '& .MuiOutlinedInput-root': { + backgroundColor: p.bg.input, borderRadius: 10, + '& fieldset': { borderColor: p.border.default }, + '&:hover fieldset': { borderColor: p.border.strong }, + '&.Mui-focused fieldset': { borderColor: accent.amber }, + }, + }, + }, + }, + MuiTooltip: { defaultProps: { arrow: true }, styleOverrides: { tooltip: { backgroundColor: p.bg.elevated, fontSize: '12px', borderRadius: 8, border: `1px solid ${p.border.subtle}` } } }, + MuiTabs: { styleOverrides: { indicator: { backgroundColor: accent.amber } } }, + MuiTab: { styleOverrides: { root: { textTransform: 'none', fontWeight: 500, fontSize: '14px', '&.Mui-selected': { color: accent.amber, fontWeight: 600 } } } }, + }, + }) +} + +// ── Export ───────────────────────────────────────────────── +export const darkTheme = createD3ROTheme('dark') +export const lightTheme = createD3ROTheme('light') + +/** 테마 모드에 따라 Theme 반환. auto일 때는 prefersDark 파라미터 사용. */ +export function getTheme(mode: 'dark' | 'light' | 'auto', prefersDark = true): Theme { + if (mode === 'auto') return prefersDark ? darkTheme : lightTheme return mode === 'dark' ? darkTheme : lightTheme } + +/** 현재 모드의 ModePalette 가져오기 (컴포넌트에서 직접 참조용) */ +export function getModePalette(mode: 'dark' | 'light'): ModePalette { + return mode === 'dark' ? darkPalette : lightPalette +} diff --git a/tests/helpers/createTestDb.ts b/tests/helpers/createTestDb.ts new file mode 100644 index 0000000..45a54b9 --- /dev/null +++ b/tests/helpers/createTestDb.ts @@ -0,0 +1,87 @@ +// tests/helpers/createTestDb.ts +// in-memory SQLite + drizzle-orm 스키마 적용 + +import Database from 'better-sqlite3' +import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3' +import * as schema from '../../src/main/db/schema' + +/** + * 테스트용 in-memory SQLite DB를 생성한다. + * 각 테스트에서 독립적인 DB를 사용할 수 있다. + */ +export function createTestDb(): { + db: BetterSQLite3Database + sqlite: Database.Database + close: () => void +} { + const sqlite = new Database(':memory:') + + sqlite.pragma('journal_mode = WAL') + sqlite.pragma('foreign_keys = ON') + + // 테이블 생성 (src/main/db/index.ts의 SQL과 동일) + sqlite.exec(` + CREATE TABLE IF NOT EXISTS history ( + id TEXT PRIMARY KEY, + original_text TEXT NOT NULL, + polished_text TEXT, + focused_app TEXT, + focused_app_name TEXT, + focused_app_window_title TEXT, + mode TEXT NOT NULL DEFAULT 'dictation', + status TEXT NOT NULL DEFAULT 'completed', + error_code TEXT, + audio_local_path TEXT, + duration REAL NOT NULL, + detected_language TEXT, + mic_device TEXT, + word_count INTEGER NOT NULL DEFAULT 0, + stt_model TEXT, + llm_model TEXT, + stt_latency_ms INTEGER, + llm_latency_ms INTEGER, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + app_version TEXT NOT NULL DEFAULT '1.0.0' + ); + + CREATE INDEX IF NOT EXISTS idx_history_created_at ON history(created_at DESC); + CREATE INDEX IF NOT EXISTS idx_history_status ON history(status); + + CREATE TABLE IF NOT EXISTS dictionary ( + id TEXT PRIMARY KEY, + word TEXT NOT NULL, + pronunciation TEXT, + category TEXT NOT NULL DEFAULT 'user', + usage_count INTEGER NOT NULL DEFAULT 0, + last_used_at INTEGER, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ); + + CREATE UNIQUE INDEX IF NOT EXISTS idx_dictionary_word_category ON dictionary(word, category); + CREATE INDEX IF NOT EXISTS idx_dictionary_created_at ON dictionary(created_at); + CREATE INDEX IF NOT EXISTS idx_dictionary_usage_count ON dictionary(usage_count DESC); + + CREATE TABLE IF NOT EXISTS stats ( + id INTEGER PRIMARY KEY CHECK (id = 1), + total_duration REAL NOT NULL DEFAULT 0, + total_words INTEGER NOT NULL DEFAULT 0, + session_count INTEGER NOT NULL DEFAULT 0, + streak_days INTEGER NOT NULL DEFAULT 0, + last_session_at INTEGER, + last_updated INTEGER NOT NULL + ); + + INSERT OR IGNORE INTO stats (id, total_duration, total_words, session_count, streak_days, last_updated) + VALUES (1, 0, 0, 0, 0, ${Date.now()}); + `) + + const db = drizzle(sqlite, { schema }) + + return { + db, + sqlite, + close: () => sqlite.close() + } +} diff --git a/tests/main/services/CustomInstructionService.test.ts b/tests/main/services/CustomInstructionService.test.ts new file mode 100644 index 0000000..08f62b6 --- /dev/null +++ b/tests/main/services/CustomInstructionService.test.ts @@ -0,0 +1,190 @@ +// tests/main/services/CustomInstructionService.test.ts +// CRUD + 프리셋 보호 + 순서 변경 테스트 + +import { describe, it, expect, beforeEach, vi } from 'vitest' + +// ConfigService 모킹 +const mockStore: Record = {} + +vi.mock('../../../src/main/services/ConfigService', () => ({ + configGet: vi.fn((key: string) => mockStore[key]), + configSet: vi.fn((key: string, value: unknown) => { + mockStore[key] = value + }) +})) + +vi.mock('../../../src/main/services/LoggerService', () => ({ + getLogger: () => ({ + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn() + }) +})) + +let getCustomInstructionService: () => ReturnType + +beforeEach(async () => { + // 매번 모듈과 스토어 초기화 + for (const key of Object.keys(mockStore)) { + delete mockStore[key] + } + vi.resetModules() + const mod = await import('../../../src/main/services/CustomInstructionService') + getCustomInstructionService = mod.getCustomInstructionService +}) + +describe('CustomInstructionService', () => { + describe('initialize + getAll', () => { + it('첫 실행 시 5개 프리셋이 생성된다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const all = svc.getAll() + expect(all.length).toBe(5) + expect(all.every((i) => i.isBuiltin)).toBe(true) + }) + + it('order 순으로 정렬하여 반환한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const all = svc.getAll() + for (let i = 1; i < all.length; i++) { + expect(all[i].order).toBeGreaterThanOrEqual(all[i - 1].order) + } + }) + }) + + describe('getById', () => { + it('존재하는 ID로 명령어를 반환한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const inst = svc.getById('builtin-translate') + expect(inst).not.toBeNull() + expect(inst!.name).toBe('번역') + }) + + it('없는 ID에 대해 null을 반환한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + expect(svc.getById('nonexistent')).toBeNull() + }) + }) + + describe('create', () => { + it('사용자 정의 명령어를 추가한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const created = svc.create({ + name: 'My Custom', + description: 'Test custom instruction', + prompt: 'Custom prompt: {{text}}', + icon: 'Star' + }) + + expect(created.isBuiltin).toBe(false) + expect(created.name).toBe('My Custom') + expect(svc.getAll().length).toBe(6) + }) + }) + + describe('update', () => { + it('사용자 정의 명령어의 모든 필드를 수정할 수 있다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const created = svc.create({ + name: 'Original', + description: 'Desc', + prompt: 'Prompt', + icon: 'Star' + }) + + const updated = svc.update(created.id, { name: 'Updated', prompt: 'New prompt' }) + expect(updated).not.toBeNull() + expect(updated!.name).toBe('Updated') + expect(updated!.prompt).toBe('New prompt') + }) + + it('프리셋은 프롬프트만 수정 가능하다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const updated = svc.update('builtin-translate', { name: 'Changed Name', prompt: 'New prompt' }) + expect(updated).not.toBeNull() + expect(updated!.name).toBe('번역') // 이름은 변경 안 됨 + expect(updated!.prompt).toBe('New prompt') // 프롬프트는 변경됨 + }) + }) + + describe('delete', () => { + it('프리셋은 삭제할 수 없다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + expect(svc.delete('builtin-translate')).toBe(false) + expect(svc.getAll().length).toBe(5) + }) + + it('사용자 정의 명령어는 삭제할 수 있다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const created = svc.create({ + name: 'To Delete', + description: '', + prompt: '', + icon: '' + }) + + expect(svc.delete(created.id)).toBe(true) + expect(svc.getAll().length).toBe(5) + }) + + it('없는 ID 삭제 시 false를 반환한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + expect(svc.delete('nonexistent')).toBe(false) + }) + }) + + describe('reorder', () => { + it('지정된 순서대로 order를 재설정한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + const all = svc.getAll() + const reversed = [...all].reverse().map((i) => i.id) + svc.reorder(reversed) + + const reordered = svc.getAll() + expect(reordered[0].id).toBe(reversed[0]) + expect(reordered[4].id).toBe(reversed[4]) + }) + }) + + describe('resetBuiltins', () => { + it('프리셋을 초기값으로 복원하고 사용자 명령어는 유지한다', () => { + const svc = getCustomInstructionService() + svc.initialize() + + // 프롬프트 수정 + svc.update('builtin-translate', { prompt: 'modified prompt' }) + + // 사용자 명령어 추가 + svc.create({ name: 'User', description: '', prompt: '', icon: '' }) + + svc.resetBuiltins() + + const all = svc.getAll() + const translate = all.find((i) => i.id === 'builtin-translate') + expect(translate!.prompt).toContain('{{targetLanguage}}') // 원래 프롬프트 + expect(all.length).toBe(6) // 프리셋 5 + 사용자 1 + }) + }) +}) diff --git a/tests/main/services/DictionaryService.test.ts b/tests/main/services/DictionaryService.test.ts new file mode 100644 index 0000000..4d39325 --- /dev/null +++ b/tests/main/services/DictionaryService.test.ts @@ -0,0 +1,116 @@ +// tests/main/services/DictionaryService.test.ts +// DictionaryService 단위 테스트 — DB 모킹 + +import { describe, it, expect, beforeEach, vi } from 'vitest' + +function createMockQueryBuilder(data: unknown[] = []) { + const builder: Record = {} + + builder.values = vi.fn(() => ({ run: vi.fn(() => ({ changes: 1 })) })) + builder.from = vi.fn(() => builder) + builder.where = vi.fn(() => builder) + builder.orderBy = vi.fn(() => builder) + builder.limit = vi.fn(() => builder) + builder.offset = vi.fn(() => builder) + builder.set = vi.fn(() => builder) + builder.get = vi.fn(() => data[0] ?? null) + builder.all = vi.fn(() => data) + builder.run = vi.fn(() => ({ changes: 1 })) + + return builder +} + +const mockDb = { + insert: vi.fn(() => createMockQueryBuilder()), + select: vi.fn(() => createMockQueryBuilder()), + update: vi.fn(() => createMockQueryBuilder()), + delete: vi.fn(() => createMockQueryBuilder()) +} + +vi.mock('../../../src/main/db', () => ({ + getDatabase: () => mockDb +})) + +vi.mock('../../../src/main/services/LoggerService', () => ({ + getLogger: () => ({ + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn() + }) +})) + +let getDictionaryService: () => ReturnType + +beforeEach(async () => { + vi.resetModules() + vi.clearAllMocks() + const mod = await import('../../../src/main/services/DictionaryService') + getDictionaryService = mod.getDictionaryService +}) + +describe('DictionaryService', () => { + describe('add', () => { + it('insert를 호출하고 엔트리를 반환한다', () => { + const svc = getDictionaryService() + const entry = svc.add({ word: 'AI', pronunciation: '에이아이', category: 'technical' }) + + expect(mockDb.insert).toHaveBeenCalled() + expect(entry.id).toBeDefined() + expect(entry.word).toBe('AI') + expect(entry.pronunciation).toBe('에이아이') + expect(entry.category).toBe('technical') + expect(entry.usageCount).toBe(0) + }) + + it('기본 카테고리는 user이다', () => { + const svc = getDictionaryService() + const entry = svc.add({ word: '테스트' }) + expect(entry.category).toBe('user') + }) + + it('pronunciation 미지정 시 null이다', () => { + const svc = getDictionaryService() + const entry = svc.add({ word: 'test' }) + expect(entry.pronunciation).toBeNull() + }) + }) + + describe('update', () => { + it('없는 ID에 대해 null을 반환한다', () => { + // select().from().where().get()이 null 반환 + const svc = getDictionaryService() + expect(svc.update({ id: 'nonexistent', word: 'test' })).toBeNull() + }) + }) + + describe('delete', () => { + it('delete 쿼리를 실행하고 changes > 0이면 true를 반환한다', () => { + const svc = getDictionaryService() + expect(svc.delete('some-id')).toBe(true) + expect(mockDb.delete).toHaveBeenCalled() + }) + }) + + describe('incrementUsage', () => { + it('update 쿼리를 실행한다', () => { + const svc = getDictionaryService() + svc.incrementUsage('some-id') + expect(mockDb.update).toHaveBeenCalled() + }) + }) + + describe('getPromptHints', () => { + it('빈 목록에서 빈 문자열을 반환한다', () => { + const svc = getDictionaryService() + expect(svc.getPromptHints()).toBe('') + }) + }) + + describe('dispose', () => { + it('에러 없이 호출된다', () => { + const svc = getDictionaryService() + expect(() => svc.dispose()).not.toThrow() + }) + }) +}) diff --git a/tests/main/services/HistoryService.test.ts b/tests/main/services/HistoryService.test.ts new file mode 100644 index 0000000..ba19977 --- /dev/null +++ b/tests/main/services/HistoryService.test.ts @@ -0,0 +1,134 @@ +// tests/main/services/HistoryService.test.ts +// HistoryService 단위 테스트 — DB 계층을 모킹하여 순수 로직만 검증 + +import { describe, it, expect, beforeEach, vi } from 'vitest' + +// drizzle 모킹: 체이너블 쿼리 빌더 패턴 +function createMockQueryBuilder(data: unknown[] = []) { + const builder: Record = {} + + builder.values = vi.fn(() => ({ run: vi.fn(() => ({ changes: 1 })) })) + builder.from = vi.fn(() => builder) + builder.where = vi.fn(() => builder) + builder.orderBy = vi.fn(() => builder) + builder.limit = vi.fn(() => builder) + builder.offset = vi.fn(() => builder) + builder.set = vi.fn(() => builder) + builder.get = vi.fn(() => data[0] ?? null) + builder.all = vi.fn(() => data) + builder.run = vi.fn(() => ({ changes: 1 })) + + return builder +} + +const mockDb = { + insert: vi.fn(() => createMockQueryBuilder()), + select: vi.fn(() => createMockQueryBuilder()), + update: vi.fn(() => createMockQueryBuilder()), + delete: vi.fn(() => createMockQueryBuilder()) +} + +vi.mock('../../../src/main/db', () => ({ + getDatabase: () => mockDb +})) + +vi.mock('../../../src/main/services/LoggerService', () => ({ + getLogger: () => ({ + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn() + }) +})) + +let getHistoryService: () => ReturnType + +beforeEach(async () => { + vi.resetModules() + vi.clearAllMocks() + const mod = await import('../../../src/main/services/HistoryService') + getHistoryService = mod.getHistoryService +}) + +describe('HistoryService', () => { + describe('create', () => { + it('insert를 호출하고 엔트리를 반환한다', () => { + const svc = getHistoryService() + const entry = svc.create({ + originalText: '안녕하세요', + duration: 3.5, + wordCount: 3, + mode: 'dictation', + status: 'completed', + appVersion: '1.0.0' + }) + + expect(mockDb.insert).toHaveBeenCalled() + expect(entry.id).toBeDefined() + expect(entry.originalText).toBe('안녕하세요') + expect(entry.duration).toBe(3.5) + expect(entry.createdAt).toBeGreaterThan(0) + }) + + it('stats 업데이트를 호출한다', () => { + const svc = getHistoryService() + svc.create({ + originalText: 'test', + duration: 5.0, + wordCount: 10, + mode: 'dictation', + status: 'completed', + appVersion: '1.0.0' + }) + + // insert 2번: history + stats update + expect(mockDb.insert).toHaveBeenCalledTimes(1) + expect(mockDb.update).toHaveBeenCalledTimes(1) // stats update + }) + }) + + describe('getById', () => { + it('DB에서 조회한 결과를 반환한다', () => { + // select().from().where().get()이 null 반환하면 null + const svc = getHistoryService() + const result = svc.getById('nonexistent') + expect(result).toBeNull() + }) + }) + + describe('delete', () => { + it('delete 쿼리를 실행하고 changes > 0이면 true를 반환한다', () => { + const svc = getHistoryService() + const result = svc.delete('some-id') + expect(mockDb.delete).toHaveBeenCalled() + expect(result).toBe(true) + }) + }) + + describe('deleteAll', () => { + it('delete 쿼리를 실행한다', () => { + const svc = getHistoryService() + svc.deleteAll() + expect(mockDb.delete).toHaveBeenCalled() + }) + }) + + describe('getStats', () => { + it('stats + today 쿼리를 실행한다', () => { + const svc = getHistoryService() + const st = svc.getStats() + + expect(mockDb.select).toHaveBeenCalled() + expect(st).toHaveProperty('totalRecordingTimeMs') + expect(st).toHaveProperty('todaySessionCount') + expect(st).toHaveProperty('streakDays') + }) + }) + + describe('dispose', () => { + it('에러 없이 호출된다', () => { + const svc = getHistoryService() + expect(() => svc.dispose()).not.toThrow() + }) + }) +}) diff --git a/tests/main/services/VoiceModeService.test.ts b/tests/main/services/VoiceModeService.test.ts new file mode 100644 index 0000000..50ac290 --- /dev/null +++ b/tests/main/services/VoiceModeService.test.ts @@ -0,0 +1,204 @@ +// tests/main/services/VoiceModeService.test.ts +// 상태 머신 전이 + 이중 조건 플러시 + accidentalPress 테스트 + +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { RecognitionState, AudioState } from '../../../src/shared/types' +import { TIMING } from '../../../src/shared/constants' + +// 모든 하위 서비스 모킹 +vi.mock('../../../src/main/services/LoggerService', () => ({ + getLogger: () => ({ + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn() + }) +})) + +const mockSTT = { + initialize: vi.fn(() => Promise.resolve()), + transcribe: vi.fn(() => + Promise.resolve({ text: '테스트 전사', segments: [], language: 'ko', duration: 2, processingTime: 500 }) + ), + getStatus: vi.fn(() => ({ state: 'ready', modelId: 'base', uptime: 0 })), + on: vi.fn(), + off: vi.fn() +} + +vi.mock('../../../src/main/services/LocalSTTService', () => ({ + getLocalSTTService: () => mockSTT +})) + +const mockAudio = { + start: vi.fn(() => Promise.resolve()), + stop: vi.fn(() => Promise.resolve()), + on: vi.fn(), + off: vi.fn() +} + +vi.mock('../../../src/main/services/AudioCaptureService', () => ({ + getAudioCaptureService: () => mockAudio +})) + +const mockHotkey = { + on: vi.fn(), + off: vi.fn() +} + +vi.mock('../../../src/main/services/HotkeyService', () => ({ + getHotkeyService: () => mockHotkey +})) + +vi.mock('../../../src/main/services/ConfigService', () => ({ + configGet: vi.fn((key: string) => { + const defaults: Record = { + sttModelId: 'base', + defaultLLMAction: 'refine', + ollamaServerUrl: 'http://localhost:11434', + llmModelId: 'qwen3:4b' + } + return defaults[key] + }) +})) + +const mockTextInsert = { + insertText: vi.fn(() => Promise.resolve({ success: true, method: 'clipboard', textLength: 10, durationMs: 50 })) +} + +vi.mock('../../../src/main/services/TextInsertService', () => ({ + getTextInsertService: () => mockTextInsert +})) + +const mockLLM = { + isAvailable: vi.fn(() => false), + processText: vi.fn(() => Promise.resolve('다듬어진 텍스트')), + on: vi.fn(), + off: vi.fn() +} + +vi.mock('../../../src/main/services/LocalLLMService', () => ({ + getLocalLLMService: () => mockLLM +})) + +let getVoiceModeService: () => ReturnType + +beforeEach(async () => { + vi.resetModules() + vi.clearAllMocks() + const mod = await import('../../../src/main/services/VoiceModeService') + getVoiceModeService = mod.getVoiceModeService +}) + +describe('VoiceModeService', () => { + describe('상태 머신', () => { + it('초기 상태는 IDLE이다', () => { + const svc = getVoiceModeService() + const state = svc.getState() + + expect(state.recognitionState).toBe(RecognitionState.IDLE) + expect(state.audioState).toBe(AudioState.IDLE) + expect(state.sessionId).toBeNull() + }) + + it('startSession 호출 시 PREPARING으로 전이한다', async () => { + const svc = getVoiceModeService() + const stateChanges: RecognitionState[] = [] + + svc.on('recognition-state-changed', (payload: { current: RecognitionState }) => { + stateChanges.push(payload.current) + }) + + await svc.startSession('dictation') + + // PREPARING → CONNECTING → READY 순서 + expect(stateChanges[0]).toBe(RecognitionState.PREPARING) + expect(stateChanges).toContain(RecognitionState.CONNECTING) + }) + + it('isActive는 세션이 활성일 때 true이다', async () => { + const svc = getVoiceModeService() + expect(svc.isActive).toBe(false) + + // startSession은 비동기이므로 STT init이 resolve되면 세션 활성 + const promise = svc.startSession('dictation') + expect(svc.isActive).toBe(true) // PREPARING 상태 + + await promise + }) + }) + + describe('accidentalPress', () => { + it('700ms 미만 세션은 자동 취소된다', async () => { + const svc = getVoiceModeService() + let cancelReason: string | null = null + + svc.on('session-cancelled', (payload: { reason: string }) => { + cancelReason = payload.reason + }) + + // 세션 시작 즉시 종료 (700ms 미만) + await svc.startSession('dictation') + await svc.stopSession() + + expect(cancelReason).toBe('too-short') + }) + }) + + describe('cancelSession', () => { + it('user 취소로 세션을 종료한다', async () => { + const svc = getVoiceModeService() + let cancelReason: string | null = null + + svc.on('session-cancelled', (payload: { reason: string }) => { + cancelReason = payload.reason + }) + + await svc.startSession('dictation') + svc.cancelSession() + + expect(cancelReason).toBe('user') + }) + }) + + describe('getState', () => { + it('현재 상태를 VoiceState 형태로 반환한다', () => { + const svc = getVoiceModeService() + const state = svc.getState() + + expect(state).toHaveProperty('recognitionState') + expect(state).toHaveProperty('audioState') + expect(state).toHaveProperty('mode') + expect(state).toHaveProperty('sessionId') + expect(state).toHaveProperty('recordingStartedAt') + }) + }) + + describe('터미널 상태', () => { + it('cancelSession 후 _resetToIdle의 200ms 딜레이 후 IDLE로 전이한다', async () => { + vi.useFakeTimers() + const svc = getVoiceModeService() + + await svc.startSession('dictation') + svc.cancelSession() + + // 200ms 딜레이로 IDLE 전이 예약됨 + vi.advanceTimersByTime(250) + + const state = svc.getState() + expect(state.recognitionState).toBe(RecognitionState.IDLE) + + vi.useRealTimers() + }) + }) +}) + +describe('TIMING 상수', () => { + it('핵심 타이밍 값이 Speakly 패턴과 일치한다', () => { + expect(TIMING.MIN_AUDIO_DURATION).toBe(700) + expect(TIMING.DOUBLE_PRESS_DURATION).toBe(300) + expect(TIMING.POST_RECORDING_WAIT).toBe(4000) + expect(TIMING.POST_RECORDING_WAIT_BUFFERED).toBe(6000) + expect(TIMING.ABSOLUTE_MAX_WAIT).toBe(120000) + expect(TIMING.AUDIO_LEVEL_INTERVAL).toBe(100) + }) +}) diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..dc22575 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,61 @@ +// tests/setup.ts +// vitest 글로벌 셋업: electron 모듈 모킹 + +import { vi } from 'vitest' + +// electron 모듈 모킹 — 테스트에서 electron import 시 에러 방지 +vi.mock('electron', () => ({ + app: { + getPath: vi.fn(() => ':memory:'), + getVersion: vi.fn(() => '1.0.0'), + quit: vi.fn(), + on: vi.fn(), + whenReady: vi.fn(() => Promise.resolve()) + }, + ipcMain: { + handle: vi.fn(), + on: vi.fn(), + removeHandler: vi.fn() + }, + BrowserWindow: vi.fn(), + Tray: vi.fn(), + Menu: vi.fn(), + nativeImage: { + createFromPath: vi.fn() + }, + dialog: { + showErrorBox: vi.fn() + }, + screen: { + getPrimaryDisplay: vi.fn(() => ({ + workArea: { x: 0, y: 0, width: 1920, height: 1080 } + })) + }, + clipboard: { + readText: vi.fn(() => ''), + writeText: vi.fn(), + readHTML: vi.fn(() => ''), + readRTF: vi.fn(() => ''), + readImage: vi.fn(() => ({ isEmpty: () => true, toPNG: () => Buffer.alloc(0) })), + write: vi.fn() + } +})) + +// electron-log 모킹 +vi.mock('electron-log', () => { + const noop = vi.fn() + return { + default: { + info: noop, + warn: noop, + error: noop, + debug: noop, + create: () => ({ + info: noop, + warn: noop, + error: noop, + debug: noop + }) + } + } +}) diff --git a/tests/shared/errors.test.ts b/tests/shared/errors.test.ts new file mode 100644 index 0000000..e83f82e --- /dev/null +++ b/tests/shared/errors.test.ts @@ -0,0 +1,75 @@ +// tests/shared/errors.test.ts +// D3ROError + IPCResult 헬퍼 테스트 + +import { describe, it, expect } from 'vitest' +import { D3ROError, ErrorCode, ipcSuccess, ipcError } from '../../src/shared/errors' + +describe('D3ROError', () => { + it('code, message, details를 올바르게 설정한다', () => { + const err = new D3ROError(ErrorCode.STTModelNotFound, 'Model not found', { modelId: 'large' }) + + expect(err.code).toBe(ErrorCode.STTModelNotFound) + expect(err.message).toBe('Model not found') + expect(err.details).toEqual({ modelId: 'large' }) + expect(err.name).toBe('D3ROError') + expect(err instanceof Error).toBe(true) + }) + + it('toJSON으로 직렬화할 수 있다', () => { + const err = new D3ROError(ErrorCode.LLMServerUnreachable, 'Server down') + const json = err.toJSON() + + expect(json.code).toBe(300) + expect(json.message).toBe('Server down') + expect(json.details).toBeUndefined() + }) + + it('fromJSON으로 역직렬화할 수 있다', () => { + const json = { code: ErrorCode.AudioDeviceNotFound, message: 'No mic' } + const err = D3ROError.fromJSON(json) + + expect(err).toBeInstanceOf(D3ROError) + expect(err.code).toBe(ErrorCode.AudioDeviceNotFound) + expect(err.message).toBe('No mic') + }) +}) + +describe('IPCResult helpers', () => { + it('ipcSuccess는 success: true + data를 반환한다', () => { + const result = ipcSuccess({ value: 42 }) + + expect(result.success).toBe(true) + if (result.success) { + expect(result.data).toEqual({ value: 42 }) + } + }) + + it('ipcError는 success: false + error를 반환한다', () => { + const result = ipcError(ErrorCode.DBQueryFailed, 'Query failed', { table: 'history' }) + + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.code).toBe(ErrorCode.DBQueryFailed) + expect(result.error.message).toBe('Query failed') + expect(result.error.details).toEqual({ table: 'history' }) + } + }) +}) + +describe('ErrorCode', () => { + it('에러 코드 범위가 올바르다', () => { + // STT: 100-199 + expect(ErrorCode.STTEngineNotInstalled).toBe(100) + expect(ErrorCode.STTGPUNotAvailable).toBe(140) + + // LLM: 300-399 + expect(ErrorCode.LLMServerUnreachable).toBe(300) + expect(ErrorCode.LLMPromptTooLong).toBe(331) + + // Audio: 400-499 + expect(ErrorCode.AudioDeviceNotFound).toBe(400) + + // System: 900-999 + expect(ErrorCode.UnknownError).toBe(999) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..7c72f47 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vitest/config' +import { resolve } from 'path' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['tests/**/*.test.ts'], + exclude: ['tests/e2e/**'], + setupFiles: ['tests/setup.ts'], + testTimeout: 10000, + coverage: { + provider: 'v8', + include: ['src/main/services/**'], + exclude: ['src/main/services/index.ts'] + } + }, + resolve: { + alias: { + '@shared': resolve(__dirname, 'src/shared') + } + } +})