Compare commits

...
10 Commits
Author SHA1 Message Date
jackyu66git c021003861 feat: 接入 Google Analytics (G-LVVXH3TL04) 2026-07-02 15:57:26 +08:00
jackyu66git 23f3c61c9f misc: binance momentum, telegram, frontend updates 2026-06-03 09:39:54 +08:00
jackyu66git b2ff322ef3 misc updates 2026-05-21 14:37:49 +08:00
jackyu66gitandClaude Opus 4.6 d38782490c feat: 重构为三所价差异动监控系统
删除 HyperLiquid + 全部交易功能,构建自适应 surge 检测器。
- 新增 surge_detector.go: 每币独立滚动窗口基线,检测三所价差异常飙升
- 新增 SpreadCard/SurgeCard 前端组件
- 保留 momentum/trend/cumulative/trend_filter 扫描功能
- 更新文档和配置以反映新系统

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 02:01:18 +08:00
jackyu66gitandClaude Opus 4.6 559d7bb870 feat: 趋势过滤信号记录系统 + 实时涨跌方向判断
- 新增 TrendFilter 信号记录(enter/exit),按完整信号和高分信号两档分类
- 信号持久化到 data/trend_signals_cache.json,开机自动恢复
- 新增 /api/trend-signals API + SSE trend_signal 实时广播
- 前端新增完整信号卡片和高分信号卡片,移除旧趋势检测卡片
- 评分加入 1h 涨跌方向和实时 drift 惩罚,下跌币不触发信号
- OKX 交易所支持(累积变动、动量、趋势检测)
- 修复 trend_filter.go 编译错误

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 22:24:59 +08:00
jackyu66gitandClaude Opus 4.6 73dac50a36 chore: 清理git跟踪的node_modules + 添加node_modules/.DS_Store到gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 13:29:36 +08:00
jackyu66gitandClaude Opus 4.6 b7767c95ae feat: 添加OKX行情接入+趋势检测+累积变动系统+界面重构
- 新增OKX WebSocket行情连接器,扩展4交易所价格监控
- 新增z-score趋势检测引擎(TrendDetector),识别价格异动/趋势启动
- 新增累积变动跟踪(CumulativeTracker),基于1min/5min多交易所共识
- 趋势事件和累积变动事件持久化到SQLite
- 新增Binance/OKX动量检测字段,扩展前端动量卡片至15列
- 迁移至macOS(darwin-arm64),更新前端依赖
- Dashboard网格重构:非交易卡片置顶,交易卡片置底
- TrackedCoin添加OK字段,添加ExBinance/ExOKX常量
- 前端新增趋势检测卡片、趋势历史卡片、累积变动卡片

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 13:26:05 +08:00
jackyu66gitandClaude Opus 4.7 047571921e fix: 重启时查交易所验证持仓是否存在,幽灵仓位自动标记 closed
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 03:23:44 +08:00
jackyu66gitandClaude Opus 4.7 2f4d7869a9 fix: 重启后加仓数据恢复 + scale_count/amount_usd 实时持久化
- restoreOpenPositions 从 orders 表 count 加仓次数,正确设置 ScaleLevels 和 AmountUSD
- 加仓时立即持久化 amount_usd 和 scale_count 到 trades 表(不等到平仓)
- PnL 图表支持单笔交易显示(原点+累计PnL连线)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 03:19:26 +08:00
jackyu66gitandClaude Opus 4.7 f97ac16b1c feat: 实际成交价追踪 + reduceOnly保护 + 数据库增强
- Bitget GetTradeFee 返回实际成交均价(weighted avg fill price)及手续费, 支持最多5次重试
- HyperLiquid ParseFillFromResponse 提取订单成交均价, 新增 GetSize/GetBalance 方法
- 开仓/加仓/平仓均从交易所获取实际成交价替代WS估算价
- HL 平仓使用 reduceOnly 防止反向开仓
- 所有 OrderID 保存到数据库 orders 表
- 加仓零成交检测及实际手续费获取
- PnL 计算修正为按USD计算手续费
- 新增 GetAllClosedTrades / GetClosedStats 数据库查询
- 服务器重启 restore 未完成交易修复(DBTradeID 缺失)
- 黑名单强平添加 USD PnL/手续费预计算

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 02:59:18 +08:00
2273 changed files with 5663 additions and 439197 deletions
+15 -28
View File
@@ -1,39 +1,26 @@
# 交易所监控 + 自动套利 # 三所价差异动监控
# 复制为 .env 并填入实际值 # 复制为 .env 并填入实际值
# Telegram 推送 # Telegram 推送 (可选)
TELEGRAM_BOT_TOKEN=*** TELEGRAM_BOT_TOKEN=***
TELEGRAM_CHAT_ID=你的聊天ID TELEGRAM_CHAT_ID=你的聊天ID
# ============================================ # Bitget API (WS 行情)
# 自动交易开关 (设置为 1 启用)
TRADE_ENABLED=0
# 交易参数
TRADE_THRESHOLD=0.15 # 最低套利利润率 (%)
TRADE_AMOUNT_USD=10 # 每腿金额 (USDT)
TRADE_COOLDOWN_MS=30000 # 同一币种套利冷却 (毫秒)
# Bitget API (需开通合约API)
BITGET_API_KEY=*** BITGET_API_KEY=***
BITGET_API_SECRET=*** BITGET_API_SECRET=***
BITGET_PASSPHRASE=你的密码短语 BITGET_PASSPHRASE=你的密码短语
# HyperLiquid API (钱包私钥) # Binance API (WS 行情 + K线 REST)
HL_PRIVATE_KEY=你的ed25519私钥(hex) BINANCE_API_KEY=***
HL_ADDRESS=你的钱包地址 BINANCE_API_SECRET=***
# OKX 行情为公开 WS,无需 API Key
# 网络代理 (国内环境需要)
HTTPS_PROXY=http://127.0.0.1:7890
# ============================================ # ============================================
# 以下为 ema-monitor 使用的参数 (保持不变) # 扫描参数 (可选,config.json 已有默认值)
DATA_API_BASE=http://localhost:80 # SURGE_ENABLED=true
SYMBOL=BTC/USDT:USDT # MOMENTUM_ENABLED=true
FETCH_LIMIT_BASE=8000 # TREND_ENABLED=true
POLL_INTERVAL=10
PROXIMITY_THRESHOLD_PCT=0.15
ALERT_COOLDOWN=3600
# ============================================
# 测试模式 (模拟交易,不需要真实 API Key)
# TEST_MODE=true 时,TRADE_ENABLED 被忽略
TEST_MODE=false
MOCK_SLIPPAGE_PCT=0.005 # 每腿模拟滑点 (%)
+2
View File
@@ -1,3 +1,5 @@
node_modules/
.DS_Store
hl_helper/node_modules/ hl_helper/node_modules/
.env .env
exchange-monitor exchange-monitor
+121
View File
@@ -0,0 +1,121 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
3-exchange spread surge detection system using Binance, OKX, and Bitget. Scans ~150 coins for inter-exchange price spread anomalies, detects surge events with per-coin adaptive baselines, and displays real-time data on a React dashboard.
## Build & Run Commands
```bash
# Build Go binary
go build -o exchange-monitor .
# Start (kills old process + builds if needed + runs)
bash start.sh
# Options: --clean (delete DB), --rebuild (force recompile)
bash start.sh --clean --rebuild
# Frontend dev (hot reload on :5173, proxies /api to :8888)
cd frontend && npm run dev
# Frontend production build
cd frontend && npm run build
```
## Architecture
### Data Flow
```
Exchange WS (Bitget + Binance + OKX) → PriceStore (in-memory)
scanner (Scan3Ex)
┌───────────────┼───────────────┐
↓ ↓ ↓
surge_detector momentum.go trend.go
cumulative.go trend_filter.go
↓ ↓ ↓
dashboard (SSE hub, :8888)
React frontend (SSE events)
```
### Main Loop (main.go)
Fixed 50ms tick: reads snap from PriceStore → Scan3Ex() → surgeDetector.Tick() → momentum.Tick() etc. Every 30s: status log. Hourly: Telegram summary.
### Package Layout
| Package | Files | Responsibility |
|---------|-------|---------------|
| `main` (root) | `main.go`, `scanner.go`, `dashboard.go`, `config.go`, `types.go`, `surge_detector.go`, `momentum.go`, `trend.go`, `cumulative.go`, `trend_filter.go` | All core logic in a single flat package |
| `exchange/` | `connector.go`, `bitget.go`, `binance.go`, `okx.go`, `helpers.go` | WS reconnector + exchange-specific REST/WS APIs |
| `db/` | `db.go`, `surge_event_repo.go` | SQLite persistence (surge_events, cm_events, trend_events, trend_signals) |
| `frontend/` | Vite + React | Real-time dashboard consuming SSE from backend |
### Key Types
- **PriceStore** — Thread-safe in-memory map of coin→exchange→price, with bid/ask spread tracking
- **ThreeExSpread** — 3-exchange scan result: coin, prices, spread %, max/min exchange
- **SurgeDetector** — Per-coin adaptive baseline surge detection with rolling window median
- **SurgeEvent** — Detected surge: coin, prices, spread, baseline, direction, leading exchange
- **MomentumTracker** — Multi-window (1s/5s/15s/60s) price change tracking per exchange
- **TrendDetector** — Cross-exchange trend state machine (idle→alert→confirmed→exhausting)
- **CumulativeTracker** — 1m/5m/1h consensus change tracking across exchanges
- **TrendFilter** — K-line based quiet detection + EMA52 trend filtering
### Exchange Connector
`PriceConnector` (exchange/connector.go) is a reusable WebSocket reconnector with exponential backoff (1s-30s), configurable ping interval, and read deadline. Bitget uses text ping frames; Binance and OKX use standard ping/pong.
### Surge Detection Logic
- **Adaptive baseline**: Per-coin rolling window (600 samples, ~30s at 50ms tick) of 3-exchange max spreads
- **Threshold**: median(spreads) × multiplier (default 3.0), with min floor (0.05%)
- **Trigger**: currentSpread > threshold AND cooldown (60s) passed
- **Direction**: Compare highest exchange deviation from median vs lowest exchange deviation
- **Leading exchange**: The exchange furthest from median price (first to reflect price move)
### Configuration Priority
`.env` vars > `config.json` > code defaults. Config struct in `config.go`.
Key env vars: `BITGET_API_KEY`, `BITGET_API_SECRET`, `BITGET_PASSPHRASE`, `BINANCE_API_KEY`, `BINANCE_API_SECRET`, `HTTPS_PROXY`, `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`.
### Dashboard API
| Endpoint | Description |
|----------|-------------|
| `GET /` | Serves index.html (disk first, fallback embed) |
| `GET /api/status` | Current prices snapshot |
| `GET /events` | SSE stream (prices, spread_3ex, momentum, trend, cumulative, trend_filter, surge, status) |
| `GET /api/history?coin=&exchange=` | Price history ring buffer (500 pts) |
| `GET /api/spread-history?coin=` | 3-exchange spread history |
| `GET /api/surge-events?limit=` | Surge event history from DB or memory |
| `GET /api/cm-history` | Cumulative change event history |
| `GET /api/trend-signals` | Trend filter signal history |
| `GET /api/connections` | Exchange WS health (online/stale/offline) |
### SSE Events
| Event | Data | Frequency |
|-------|------|-----------|
| `prices` | All coin prices + 3-ex spread | Every tick |
| `spread_3ex` | Top 3-ex spreads scan results | Every tick |
| `momentum` | Multi-window price change % | Every tick |
| `trend` | Trend state machine snapshots | Every tick |
| `cumulative` | Cumulative consensus changes | Every tick |
| `trend_filter` | K-line filter states | Every tick |
| `trend_signal` | Individual trend signal (enter/exit) | On event |
| `surge` | Current spread/baseline snapshots | Every tick |
| `surge_event` | New surge detection event | On detection |
| `status` | Connection health + coin count | Every tick |
### Database
SQLite at `~/Project/exchange-monitor-go/data/trades.db` (single-writer mode). Tables: `surge_events`, `cm_events`, `trend_events`, `trend_signals`.
### Coin Tracking
~150 coins in `TrackedCoins` slice (scanner.go). Each entry has Name, BN (Binance symbol), BG (Bitget symbol), OKX (OKX symbol). Active WebSocket connections: Bitget + Binance + OKX.
+55 -69
View File
@@ -1,29 +1,34 @@
# ⚡ 跨交易所永续合约套利监控 # 三所价差异动监控
Bitget ↔ HyperLiquid 跨交易所永续合约价差套利系统。支持模拟盘/实盘交易、价差监控、自动开仓/加仓/平仓、Web 仪表盘 Binance + OKX + Bitget 三交易所价差异动实时检测系统。通过追踪不同交易所之间的价差异常,捕捉币价「启动」的瞬间
## 功能特点 ## 功能特点
- **实时价差监控** — 200ms 扫描间隔,追踪 DOGE/LINK/ONDO/OP/WIF/ARB 六个币种 - **三所价差扫描** — 实时计算 Binance/OKX/Bitget 之间的最大价差,按价差排序展示
- **自动套利交易** — 价差超过阈值自动开仓,收敛自动平仓,支持多级加仓 - **自适应 Surge 检测** — 每个币维护独立的滚动窗口基线,检测价差异常飙升
- **模拟/实盘双模式** — `TestMode` 控制,模拟模式无需真实 API Key - **方向判断** — 根据领先交易所判断上涨/下跌启动方向
- **动量扫描** — 多时间窗口 (1s/5s/15s/60s) 价格变动率追踪
- **趋势检测** — 跨交易所一致性确认的趋势状态机 (idle→alert→confirmed→exhausting)
- **累积变动** — 1m/5m/1h 多交易所共识变动追踪
- **趋势过滤** — K 线数据 + EMA52 趋势过滤,识别安静后的异动
- **Web 仪表盘** — Go 内置 HTTP Server + Vite React 前端,SSE 实时推送 - **Web 仪表盘** — Go 内置 HTTP Server + Vite React 前端,SSE 实时推送
- **SQLite 持久化** — 交易记录、订单明细、手续费明细全量存储 - **SQLite 持久化** — Surge 事件、累积变动事件全量存储
- **手续费精确计算** — 逐笔累加实际 USD 手续费(开仓费+平仓费),非百分比估算
- **交易所独立资金管理** — 每交易所 $500 初始资金,开仓前检查两边余额充足
- **模拟滑点** — 可配置 `mock_slippage_pct`,模拟真实滑点对净利的影响
- **持仓详情弹窗** — 点击持仓卡片查看完整交易详情(价差、PnL、手续费分腿)
- **Telegram 通知** — 开仓/平仓/异常实时推送
## 架构 ## 架构
``` ```
┌─────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────┐
scanner.go ← 每 200ms 扫描价差 Exchange WS (Bitget + Binance + OKX)
│ ↓ 发现机会 (NetProfit > 阈值) │ ↓ 价格推送
trader.go ← 开仓/加仓/平仓逻辑 PriceStore ← 内存价格存储
│ ↓ 持久化 │ ↓
db/ ← SQLite (trades / orders / system) scanner.go ← 三所价差扫描
│ ↓ │
│ surge_detector.go ← 自适应Surge检测 │
│ momentum.go ← 动量扫描 │
│ trend.go ← 趋势检测 │
│ cumulative.go ← 累积变动追踪 │
│ trend_filter.go ← K线趋势过滤 │
│ ↓ SSE 推送 │ │ ↓ SSE 推送 │
│ dashboard.go ← HTTP Server :8888 │ │ dashboard.go ← HTTP Server :8888 │
│ ↓ │ │ ↓ │
@@ -39,22 +44,29 @@ Bitget ↔ HyperLiquid 跨交易所永续合约价差套利系统。支持模拟
```json ```json
{ {
"arb_threshold": 0.3,
"scan_interval_ms": 200, "scan_interval_ms": 200,
"trade_enabled": true, "alert_cooldown_sec": 300,
"test_mode": true, "surge_enabled": true,
"trade_threshold": 0.20, "surge_window_size": 600,
"take_profit_pct": 0.20, "surge_baseline_multiplier": 3.0,
"mock_slippage_pct": 0.05, "surge_min_abs_spread_pct": 0.05,
"trade_amount_usd": 5, "surge_cooldown_sec": 60,
"max_positions": 5, "momentum_enabled": true,
"initial_capital": 500, "momentum_threshold_pct": 0.25,
"taker_fee_bitget": 0.060, "trend_enabled": true,
"taker_fee_hyperliquid": 0.045, "trend_baseline_window": 600,
"telegram_bot_token": "xxx", "trend_anomaly_mul": 3.0,
"telegram_chat_id": "xxx" "trend_confirm_ticks": 3,
"trend_alert_cooldown_ms": 60000
} }
``` ```
环境变量 (`.env`):
- `BITGET_API_KEY`, `BITGET_API_SECRET`, `BITGET_PASSPHRASE` — Bitget API
- `BINANCE_API_KEY`, `BINANCE_API_SECRET` — Binance API (可选,用于K线)
- `HTTPS_PROXY` — 网络代理 (国内环境需要)
### 2. 启动 ### 2. 启动
```bash ```bash
@@ -79,54 +91,28 @@ npm run dev # 开发模式 (Vite HMR :5173)
npm run build # 构建生产版本 npm run build # 构建生产版本
``` ```
后端优先从 `frontend/dist/` 读取静态文件(热加载),回退到 Go embed。
## 配置参数 ## 配置参数
| 参数 | 说明 | 默认 | | 参数 | 说明 | 默认 |
|------|------|------| |------|------|------|
| `scan_interval_ms` | 扫描间隔 (ms) | 200 | | `surge_enabled` | 启用 Surge 检测 | true |
| `trade_threshold` | 开仓净利阈值 (%) | 0.20 | | `surge_window_size` | 滚动窗口样本数 | 600 |
| `take_profit_pct` | 止盈净利 (%) | 0.20 | | `surge_baseline_multiplier` | 基线倍数(阈值 = 基线 × N) | 3.0 |
| `mock_slippage_pct` | 模拟滑点 (%) | 0.05 | | `surge_min_abs_spread_pct` | 最小绝对价差 % | 0.05 |
| `trade_amount_usd` | 每腿交易额 ($) | 5 | | `surge_cooldown_sec` | 同币冷却秒数 | 60 |
| `max_positions` | 最大并行持仓 | 5 | | `momentum_enabled` | 启用动量扫描 | true |
| `taker_fee_bitget` | Bitget 吃单费率 (%) | 0.060 | | `trend_enabled` | 启用趋势检测 | true |
| `taker_fee_hyperliquid` | HyperLiquid 吃单费率 (%) | 0.045 | | `trend_baseline_window` | 趋势基线窗口 | 600 |
| `scale_step_pct` | 加仓步长 (%) | 0.10 | | `trend_anomaly_mul` | 异常检测倍数 | 3.0 |
| `position_timeout` | 最长持仓时间 | 10m | | `trend_confirm_ticks` | 确认所需次数 | 3 |
| `leg_delay` | 两腿下单间隔 | 300ms |
| `initial_capital` | 每交易所初始资金 ($) | 500 |
## 数据库 ## 数据库
SQLite (`data/trades.db`)三张核心表: SQLite (`data/trades.db`),核心表:
| 表 | 说明 | | 表 | 说明 |
|----|------| |----|------|
| `trades` | 交易主表 — 价差、PnL、手续费 ($) | | `surge_events` | Surge 异动事件 — 币种、价格、价差、方向、领先交易所 |
| `orders` | 订单明细 — 每腿的开仓/加仓/平仓、手续费 ($) | | `cm_events` | 累积变动事件 — 1m/5m/1h 多所共识变动 |
| `system_orders` | 系统订单 — 双向关联 long↔short 订单 | | `trend_events` | 趋势状态变迁 — idle→alert→confirmed→exhausting 全生命周期 |
| `trend_signals` | 趋势过滤信号 — K 线安静 + EMA 异动信号记录 |
## 版本历史
### v1.3 (当前)
-**交易所独立资金管理** — 每交易所 $500 初始资金,开仓前检查余额
-**持仓点击详情弹窗** — 点击持仓卡片弹出完整交易详情
-**模拟滑点** — 新增 `mock_slippage_pct` 配置,模拟真实成交滑点
-**止损硬编码** — 取消 `SpreadReverseExitPct` 配置,价差 <= 0 硬止损
- 🐛 修复 DB 迁移 tab 字符损坏导致历史交易数据不显示
- 📊 PnL 统计分腿存储(`pnl_long_usd`/`pnl_short_usd`/`fee_long_usd`/`fee_short_usd`
### v1.2.1
- ✨ scale-in 后 leg.EntryPrice 更新为加权平均(`weightedAvgPrice`
- ✨ 进程重启恢复仓位时,从 `orders` 表加载 scale prices,重建完整价格切片
- ✨ 新增 `GetScalePrices()` DB 方法
### v1.2
-`system_orders` 表,记录系统级开仓/加仓/平仓
- ✨ 手续费改为逐笔累加 USD,不再用百分比估算
- ✨ Vite + React 前端,支持热加载
- ✨ Web 仪表盘持仓 PnL 美元化显示
- 🐛 修复 `persistTrade` 费用在 `SaveTrade` 后才累加导致 fee=0 的 bug
- 🗑 移除老版 Chart.js 图表
+220
View File
@@ -0,0 +1,220 @@
package main
import (
"math"
"sync"
"time"
)
// bmBuffer is a per-coin ring buffer for Binance prices.
type bmBuffer struct {
prices []float64
head int
count int
}
func newBmBuffer(capacity int) *bmBuffer {
return &bmBuffer{
prices: make([]float64, capacity),
}
}
func (b *bmBuffer) push(price float64) {
b.prices[b.head] = price
b.head = (b.head + 1) % len(b.prices)
if b.count < len(b.prices) {
b.count++
}
}
func (b *bmBuffer) isFull() bool {
return b.count == len(b.prices)
}
// oldest returns the price written exactly `capacity` ticks ago.
func (b *bmBuffer) oldest() float64 {
if !b.isFull() {
return 0
}
return b.prices[b.head]
}
// newest returns the most recently written price.
func (b *bmBuffer) newest() float64 {
if b.count == 0 {
return 0
}
idx := b.head - 1
if idx < 0 {
idx = len(b.prices) - 1
}
return b.prices[idx]
}
// BinanceAlert is emitted when a coin's 1-minute Binance change exceeds threshold.
type BinanceAlert struct {
Coin string `json:"coin"`
Price float64 `json:"price"`
OldPrice float64 `json:"old_price"`
ChangePct float64 `json:"change_pct"`
Direction string `json:"direction"`
ThresholdPct float64 `json:"threshold_pct"`
Timestamp time.Time `json:"timestamp"`
}
// BinanceMomentumSnapshot is the current state of a coin for SSE push.
type BinanceMomentumSnapshot struct {
Coin string `json:"coin"`
Price float64 `json:"price"`
ChangePct float64 `json:"change_pct"`
Direction string `json:"direction"`
BufferFull bool `json:"buffer_full"`
}
// BinanceMomentumDetector tracks Binance price changes over a configurable window.
type BinanceMomentumDetector struct {
mu sync.Mutex
windowTicks int
thresholdPct float64
cooldownSec int
buffers map[string]*bmBuffer
lastAlertAt map[string]time.Time
recentAlerts []BinanceAlert
maxAlerts int
}
// NewBinanceMomentumDetector creates a detector for all TrackedCoins.
func NewBinanceMomentumDetector(windowSec int, tickMs int, thresholdPct float64, cooldownSec int) *BinanceMomentumDetector {
windowTicks := windowSec * 1000 / tickMs
if windowTicks < 1 {
windowTicks = 1
}
d := &BinanceMomentumDetector{
windowTicks: windowTicks,
thresholdPct: thresholdPct,
cooldownSec: cooldownSec,
buffers: make(map[string]*bmBuffer, len(TrackedCoins)),
lastAlertAt: make(map[string]time.Time),
maxAlerts: 200,
}
for _, tc := range TrackedCoins {
d.buffers[tc.Name] = newBmBuffer(windowTicks)
}
return d
}
// Record appends the current Binance price for a coin into its ring buffer.
func (d *BinanceMomentumDetector) Record(coin string, price float64) {
d.mu.Lock()
buf, ok := d.buffers[coin]
if ok {
buf.push(price)
}
d.mu.Unlock()
}
// Detect checks all coins for threshold breaches. Returns new alerts.
func (d *BinanceMomentumDetector) Detect() []BinanceAlert {
d.mu.Lock()
defer d.mu.Unlock()
now := time.Now()
var alerts []BinanceAlert
for coin, buf := range d.buffers {
if !buf.isFull() {
continue
}
old := buf.oldest()
current := buf.newest()
if old <= 0 || current <= 0 {
continue
}
changePct := (current - old) / old * 100
if math.Abs(changePct) < d.thresholdPct {
continue
}
lastAt, exists := d.lastAlertAt[coin]
if exists && now.Sub(lastAt).Seconds() < float64(d.cooldownSec) {
continue
}
dir := "up"
if changePct < 0 {
dir = "down"
}
alert := BinanceAlert{
Coin: coin,
Price: current,
OldPrice: old,
ChangePct: changePct,
Direction: dir,
ThresholdPct: d.thresholdPct,
Timestamp: now,
}
alerts = append(alerts, alert)
d.lastAlertAt[coin] = now
// Store in recent alerts ring buffer
d.recentAlerts = append(d.recentAlerts, alert)
if len(d.recentAlerts) > d.maxAlerts {
d.recentAlerts = d.recentAlerts[len(d.recentAlerts)-d.maxAlerts:]
}
}
return alerts
}
// Snapshot returns the current momentum state for all coins.
func (d *BinanceMomentumDetector) Snapshot() []BinanceMomentumSnapshot {
d.mu.Lock()
defer d.mu.Unlock()
snapshots := make([]BinanceMomentumSnapshot, 0, len(d.buffers))
for coin, buf := range d.buffers {
s := BinanceMomentumSnapshot{
Coin: coin,
BufferFull: buf.isFull(),
}
if buf.isFull() {
old := buf.oldest()
current := buf.newest()
s.Price = current
if old > 0 {
s.ChangePct = (current - old) / old * 100
if s.ChangePct >= 0 {
s.Direction = "up"
} else {
s.Direction = "down"
}
}
} else if buf.count > 0 {
s.Price = buf.newest()
s.Direction = "flat"
}
snapshots = append(snapshots, s)
}
return snapshots
}
// RecentAlerts returns the last N alerts.
func (d *BinanceMomentumDetector) RecentAlerts(limit int) []BinanceAlert {
d.mu.Lock()
defer d.mu.Unlock()
if limit <= 0 || limit > len(d.recentAlerts) {
limit = len(d.recentAlerts)
}
if limit == 0 {
return nil
}
start := len(d.recentAlerts) - limit
r := make([]BinanceAlert, limit)
copy(r, d.recentAlerts[start:])
// Reverse so newest is first
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return r
}
+249
View File
@@ -0,0 +1,249 @@
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func main() {
log.SetFlags(log.Ltime | log.Lmicroseconds)
log.Println("=== Bitget 开仓/平仓测试 v2 ===")
apiKey := os.Getenv("BITGET_API_KEY")
apiSecret := os.Getenv("BITGET_API_SECRET")
passphrase := os.Getenv("BITGET_PASSPHRASE")
if apiKey == "" || apiSecret == "" || passphrase == "" {
log.Fatal("环境变量: BITGET_API_KEY, BITGET_API_SECRET, BITGET_PASSPHRASE")
}
client := &http.Client{Timeout: 10 * time.Second}
headers := func(method, path, body string) map[string]string {
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
raw := ts + method + path + body
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(raw))
sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return map[string]string{
"ACCESS-KEY": apiKey,
"ACCESS-SIGN": sign,
"ACCESS-TIMESTAMP": ts,
"ACCESS-PASSPHRASE": passphrase,
}
}
symbol := "MEMEUSDT"
mode := "open"
if len(os.Args) > 1 {
mode = os.Args[1]
}
if mode == "close" {
// === 只平仓 (手动) ===
log.Println("--- 只用 holdSide 测试平仓 ---")
closeWithHoldSide(client, headers, symbol, "17280.1105", "long")
pos := checkPos(client, headers, symbol)
if pos != "" {
log.Printf("⚠️ 仍有持仓: %s", pos)
} else {
log.Println("✅ 已清仓")
}
return
}
// === 开仓 + 平仓 (自动) ===
price := getPrice(client)
log.Printf("当前价格: $%.6f", price)
sz := flooredSize(price, 10, 4)
log.Printf("目标开仓数量: %s (for $10)", sz)
// 开仓
log.Println("\n--- 开仓: BUY (Long, open) ---")
openOID := doPlace(client, headers, map[string]interface{}{
"marginCoin": "USDT", "symbol": symbol,
"productType": "USDT-FUTURES", "side": "buy",
"orderType": "market", "timeInForce": "IOC",
"marginMode": "crossed", "tradeSide": "open",
"size": sz,
})
log.Printf("开仓 orderID=%s", openOID)
// 等 + 查持仓
time.Sleep(3 * time.Second)
pos := checkPos(client, headers, symbol)
log.Printf("开仓后持仓: %s", pos)
if pos != "" {
// 有仓 → 尝试平仓 (holdSide)
log.Println("\n--- 平仓: SELL close holdSide=long ---")
closeWithHoldSide(client, headers, symbol, sz, "long")
time.Sleep(1 * time.Second)
pos2 := checkPos(client, headers, symbol)
if pos2 != "" {
// 试另一种方式: 不带 holdSide
log.Println("\n--- 再试: SELL close 不带holdSide ---")
_, err := doPlaceRaw(client, headers, map[string]interface{}{
"marginCoin": "USDT", "symbol": symbol,
"productType": "USDT-FUTURES", "side": "sell",
"orderType": "market", "timeInForce": "IOC",
"marginMode": "crossed", "tradeSide": "close",
"size": sz,
})
if err != nil {
log.Printf("❌ 无 holdSide 也失败: %v", err)
} else {
log.Println("✅ 无holdSide平仓成功!")
}
}
time.Sleep(1 * time.Second)
log.Printf("最终持仓: %s", checkPos(client, headers, symbol))
} else {
log.Println("ℹ️ 无持仓,可能开仓未成交")
}
log.Println("\n=== 测试完成 ===")
}
func closeWithHoldSide(client *http.Client, hdr func(m, p, b string) map[string]string, symbol, size, holdSide string) {
oid, err := doPlaceRaw(client, hdr, map[string]interface{}{
"marginCoin": "USDT", "symbol": symbol,
"productType": "USDT-FUTURES", "side": "sell",
"orderType": "market", "timeInForce": "IOC",
"marginMode": "crossed", "tradeSide": "close",
"holdSide": holdSide, "size": size,
})
if err != nil {
if strings.Contains(err.Error(), "22002") {
log.Printf("❌ holdSide=%s 返回 22002(无仓位可平)", holdSide)
} else {
log.Printf("❌ holdSide=%s 失败: %v", holdSide, err)
}
} else {
log.Printf("✅ holdSide=%s 平仓成功 orderID=%s", holdSide, oid)
}
}
// --- helpers 跟之前一样 ---
func doPlace(client *http.Client, hdr func(m, p, b string) map[string]string, body map[string]interface{}) string {
oid, err := doPlaceRaw(client, hdr, body)
if err != nil {
log.Fatalf("下单失败: %v", err)
}
return oid
}
func doPlaceRaw(client *http.Client, hdr func(m, p, b string) map[string]string, body map[string]interface{}) (string, error) {
method := "POST"
path := "/api/v2/mix/order/place-order"
bodyJSON, _ := json.Marshal(body)
h := hdr(method, path, string(bodyJSON))
req, _ := http.NewRequest(method, "https://api.bitget.com"+path, strings.NewReader(string(bodyJSON)))
req.Header.Set("Content-Type", "application/json")
for k, v := range h {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("http: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
OrderID string `json:"orderId"`
} `json:"data"`
}
json.Unmarshal(respBody, &result)
log.Printf(" → 返回: code=%s msg=%s orderID=%s", result.Code, result.Msg, result.Data.OrderID)
if result.Code != "00000" {
return "", fmt.Errorf("%s - %s", result.Code, result.Msg)
}
return result.Data.OrderID, nil
}
func checkPos(client *http.Client, hdr func(m, p, b string) map[string]string, symbol string) string {
method := "GET"
path := "/api/v2/mix/position/single-position?symbol=" + symbol + "&productType=USDT-FUTURES&marginCoin=USDT"
h := hdr(method, path, "")
req, _ := http.NewRequest(method, "https://api.bitget.com"+path, nil)
for k, v := range h {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
return ""
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
log.Printf(" → 持仓API: %s", string(respBody))
var raw struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data []struct {
Symbol string `json:"symbol"`
HoldSide string `json:"holdSide"`
Total string `json:"total"`
Available string `json:"available"`
} `json:"data"`
}
json.Unmarshal(respBody, &raw)
if raw.Code != "00000" {
return ""
}
if len(raw.Data) > 0 {
d := raw.Data[0]
return fmt.Sprintf("%s %s total=%s avai=%s", d.Symbol, d.HoldSide, d.Total, d.Available)
}
return ""
}
func getPrice(client *http.Client) float64 {
resp, err := client.Get("https://api.bitget.com/api/v2/mix/market/tickers?productType=USDT-FUTURES")
if err != nil {
return 0
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var raw struct {
Code string `json:"code"`
Data []struct {
Symbol string `json:"symbol"`
Last string `json:"lastPr"`
} `json:"data"`
}
json.Unmarshal(body, &raw)
for _, d := range raw.Data {
if d.Symbol == "MEMEUSDT" {
p, _ := strconv.ParseFloat(d.Last, 64)
return p
}
}
return 0
}
func flooredSize(price float64, usd, decimals int) string {
sz := float64(usd) / price
div := 1
for i := 0; i < decimals; i++ {
div *= 10
}
f := float64(div)
floored := float64(int64(sz*f)) / f
return fmt.Sprintf("%."+fmt.Sprintf("%d", decimals)+"f", floored)
}
+116 -156
View File
@@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"strconv" "strconv"
"time"
) )
// Config holds all system configuration. // Config holds all system configuration.
@@ -13,83 +12,63 @@ type Config struct {
TelegramBotToken string TelegramBotToken string
TelegramChatID string TelegramChatID string
AlertCooldownSec int // seconds between alerts for same coin AlertCooldownSec int // seconds between alerts for same coin
ArbThreshold float64 // minimum net profit % to trigger alert ArbThreshold float64 // minimum spread % to trigger alert
ScanIntervalMs int // how often scanner runs (milliseconds) ScanIntervalMs int // how often scanner runs (milliseconds)
// Automated trading // Surge detection
TradeEnabled bool SurgeEnabled bool
TradeThreshold float64 // minimum profit % to execute trade SurgeWindowSize int // rolling window samples (default: 600 = ~30s)
TradeAmountUSD float64 // amount per leg in USDT SurgeBaselineMultiplier float64 // baseline * N = threshold (default: 3.0)
TradeCooldownMs int // ms between trades of same coin SurgeMinAbsSpreadPct float64 // minimum absolute spread % (default: 0.05)
MaxPositions int // max concurrent open positions (0 = unlimited) SurgeCooldownSec int // cooldown seconds per coin (default: 60)
// Capital // Momentum scanning mode
InitialCapital float64 // starting capital in USD (for PnL % calculation) MomentumEnabled bool
MomentumThresholdPct float64
// Blacklist — stale spread observation // Trend detection mode
BlacklistDuration time.Duration // how long a coin stays blacklisted (0 = permanent) TrendEnabled bool
TrendBaselineWindow int // ticks for EMA volatility baseline (default: 600 = 30s)
TrendAnomalyMul float64 // z-score multiplier for alert threshold (default: 3.0)
TrendConfirmTicks int // ticks needed for state confirmation (default: 3)
TrendAlertCooldown int64 // ms cooldown between alerts for same coin (default: 60000)
// ExcludedCoins — coins to never trade (hard block) // Binance momentum detection (1-minute Binance-only price change)
ExcludedCoins []string BinanceMomentumEnabled bool
BinanceMomentumThresholdPct float64 // default: 10.0
// Test mode (no real API keys needed) BinanceMomentumCooldownSec int // default: 300 (5 min)
TestMode bool BinanceMomentumWindowSec int // default: 60
MockSlippagePct float64 // simulated slippage per order (e.g. 0.01 = 0.01%)
// Exchange fee rates (% per order)
TakerFeeBitget float64
TakerFeeHyperLiquid float64
// Exit/risk parameters
TakeProfitPct float64 // net profit % threshold for take-profit
PositionTimeout time.Duration // max position hold time before auto-close
LegDelay time.Duration // delay between placing long and short legs
// Scale-in parameters
ScaleStepPct float64 // spread widening % trigger for each scale level
ScaleCooldown time.Duration // minimum time between scale-ins
// Entry sanity check: reject if price moved beyond this % in the wrong direction
ReversalTolerancePct float64
// Bitget API
BitgetAPIKey string
BitgetAPISecret string
BitgetPassphrase string
// HyperLiquid API
HLPrivateKey string // ed25519 private key hex
HLAddress string // main account address
HLAPIAddress string // API wallet address (signer, auto-derived if empty)
} }
// jsonConfig maps config.json fields (non-secret defaults checked into git). // jsonConfig maps config.json fields (non-secret defaults checked into git).
type jsonConfig struct { type jsonConfig struct {
TestMode bool `json:"test_mode"`
TradeEnabled bool `json:"trade_enabled"`
ArbThreshold float64 `json:"arb_threshold"` ArbThreshold float64 `json:"arb_threshold"`
ScanIntervalMs int `json:"scan_interval_ms"` ScanIntervalMs int `json:"scan_interval_ms"`
TradeThreshold float64 `json:"trade_threshold"`
TradeAmountUSD float64 `json:"trade_amount_usd"`
TradeCooldownMs int `json:"trade_cooldown_ms"`
AlertCooldownSec int `json:"alert_cooldown_sec"` AlertCooldownSec int `json:"alert_cooldown_sec"`
MockSlippagePct float64 `json:"mock_slippage_pct"`
MaxPositions int `json:"max_positions"`
BlacklistDuration int `json:"blacklist_duration_sec"`
InitialCapital float64 `json:"initial_capital"`
ExcludedCoins []string `json:"excluded_coins"`
// New: exchange fees // Surge detection
TakerFeeBitget float64 `json:"taker_fee_bitget"` SurgeEnabled bool `json:"surge_enabled"`
TakerFeeHyperLiquid float64 `json:"taker_fee_hyperliquid"` SurgeWindowSize int `json:"surge_window_size"`
SurgeBaselineMultiplier float64 `json:"surge_baseline_multiplier"`
SurgeMinAbsSpreadPct float64 `json:"surge_min_abs_spread_pct"`
SurgeCooldownSec int `json:"surge_cooldown_sec"`
// New: exit/risk parameters // Momentum scanning
TakeProfitPct float64 `json:"take_profit_pct"` MomentumEnabled bool `json:"momentum_enabled"`
PositionTimeoutSec int `json:"position_timeout_sec"` MomentumThresholdPct float64 `json:"momentum_threshold_pct"`
LegDelayMs int `json:"leg_delay_ms"`
ReversalTolerancePct float64 `json:"reversal_tolerance_pct"` // Trend detection
ScaleStepPct float64 `json:"scale_step_pct"` TrendEnabled bool `json:"trend_enabled"`
ScaleCooldownSec int `json:"scale_cooldown_sec"` TrendBaselineWindow int `json:"trend_baseline_window"`
TrendAnomalyMul float64 `json:"trend_anomaly_mul"`
TrendConfirmTicks int `json:"trend_confirm_ticks"`
TrendAlertCooldown int64 `json:"trend_alert_cooldown_ms"`
// Binance momentum detection
BinanceMomentumEnabled bool `json:"binance_momentum_enabled"`
BinanceMomentumThresholdPct float64 `json:"binance_momentum_threshold_pct"`
BinanceMomentumCooldownSec int `json:"binance_momentum_cooldown_sec"`
BinanceMomentumWindowSec int `json:"binance_momentum_window_sec"`
} }
func LoadConfig() *Config { func LoadConfig() *Config {
@@ -129,42 +108,29 @@ func LoadConfig() *Config {
ArbThreshold: getFloat("ARB_THRESHOLD", jsonCfg.ArbThreshold), ArbThreshold: getFloat("ARB_THRESHOLD", jsonCfg.ArbThreshold),
ScanIntervalMs: int(getFloat("SCAN_INTERVAL_MS", float64(jsonCfg.ScanIntervalMs))), ScanIntervalMs: int(getFloat("SCAN_INTERVAL_MS", float64(jsonCfg.ScanIntervalMs))),
TradeEnabled: getBool("TRADE_ENABLED", jsonCfg.TradeEnabled), // Surge detection
TradeThreshold: getFloat("TRADE_THRESHOLD", jsonCfg.TradeThreshold), SurgeEnabled: getBool("SURGE_ENABLED", jsonCfg.SurgeEnabled),
TradeAmountUSD: getFloat("TRADE_AMOUNT_USD", jsonCfg.TradeAmountUSD), SurgeWindowSize: int(getFloat("SURGE_WINDOW_SIZE", float64(jsonCfg.SurgeWindowSize))),
TradeCooldownMs: int(getFloat("TRADE_COOLDOWN_MS", float64(jsonCfg.TradeCooldownMs))), SurgeBaselineMultiplier: getFloat("SURGE_BASELINE_MULTIPLIER", jsonCfg.SurgeBaselineMultiplier),
MaxPositions: int(getFloat("MAX_POSITIONS", float64(jsonCfg.MaxPositions))), SurgeMinAbsSpreadPct: getFloat("SURGE_MIN_ABS_SPREAD_PCT", jsonCfg.SurgeMinAbsSpreadPct),
SurgeCooldownSec: int(getFloat("SURGE_COOLDOWN_SEC", float64(jsonCfg.SurgeCooldownSec))),
InitialCapital: getFloat("INITIAL_CAPITAL", jsonCfg.InitialCapital), // Momentum scanning
MomentumEnabled: getBool("MOMENTUM_ENABLED", jsonCfg.MomentumEnabled),
MomentumThresholdPct: getFloat("MOMENTUM_THRESHOLD_PCT", jsonCfg.MomentumThresholdPct),
BlacklistDuration: time.Duration(getFloat("BLACKLIST_DURATION_SEC", float64(jsonCfg.BlacklistDuration))) * time.Second, // Trend detection
TrendEnabled: getBool("TREND_ENABLED", jsonCfg.TrendEnabled),
TrendBaselineWindow: int(getFloat("TREND_BASELINE_WINDOW", float64(jsonCfg.TrendBaselineWindow))),
TrendAnomalyMul: getFloat("TREND_ANOMALY_MUL", jsonCfg.TrendAnomalyMul),
TrendConfirmTicks: int(getFloat("TREND_CONFIRM_TICKS", float64(jsonCfg.TrendConfirmTicks))),
TrendAlertCooldown: int64(getFloat("TREND_ALERT_COOLDOWN_MS", float64(jsonCfg.TrendAlertCooldown))),
TestMode: getBool("TEST_MODE", jsonCfg.TestMode), // Binance momentum detection
MockSlippagePct: getFloat("MOCK_SLIPPAGE_PCT", jsonCfg.MockSlippagePct), BinanceMomentumEnabled: getBool("BINANCE_MOMENTUM_ENABLED", jsonCfg.BinanceMomentumEnabled),
BinanceMomentumThresholdPct: getFloat("BINANCE_MOMENTUM_THRESHOLD_PCT", jsonCfg.BinanceMomentumThresholdPct),
// Exchange fee rates BinanceMomentumCooldownSec: int(getFloat("BINANCE_MOMENTUM_COOLDOWN_SEC", float64(jsonCfg.BinanceMomentumCooldownSec))),
TakerFeeBitget: getFloat("TAKER_FEE_BITGET", jsonCfg.TakerFeeBitget), BinanceMomentumWindowSec: int(getFloat("BINANCE_MOMENTUM_WINDOW_SEC", float64(jsonCfg.BinanceMomentumWindowSec))),
TakerFeeHyperLiquid: getFloat("TAKER_FEE_HYPERLIQUID", jsonCfg.TakerFeeHyperLiquid),
// Exit/risk parameters
TakeProfitPct: getFloat("TAKE_PROFIT_PCT", jsonCfg.TakeProfitPct),
PositionTimeout: time.Duration(getFloat("POSITION_TIMEOUT_SEC", float64(jsonCfg.PositionTimeoutSec))) * time.Second,
LegDelay: time.Duration(getFloat("LEG_DELAY_MS", float64(jsonCfg.LegDelayMs))) * time.Millisecond,
ReversalTolerancePct: getFloat("REVERSAL_TOLERANCE_PCT", jsonCfg.ReversalTolerancePct),
// Scale-in parameters
ScaleStepPct: getFloat("SCALE_STEP_PCT", jsonCfg.ScaleStepPct),
ScaleCooldown: time.Duration(getFloat("SCALE_COOLDOWN_SEC", float64(jsonCfg.ScaleCooldownSec))) * time.Second,
ExcludedCoins: jsonCfg.ExcludedCoins,
BitgetAPIKey: getEnv("BITGET_API_KEY", ""),
BitgetAPISecret: getEnv("BITGET_API_SECRET", ""),
BitgetPassphrase: getEnv("BITGET_PASSPHRASE", ""),
HLPrivateKey: getEnv("HL_PRIVATE_KEY", ""),
HLAddress: getEnv("HL_ADDRESS", ""),
HLAPIAddress: getEnv("HL_API_ADDRESS", ""),
} }
} }
@@ -172,28 +138,29 @@ func loadJSONConfig() jsonConfig {
def := jsonConfig{ def := jsonConfig{
ArbThreshold: 0.03, ArbThreshold: 0.03,
ScanIntervalMs: 500, ScanIntervalMs: 500,
TradeThreshold: 0.15,
TradeAmountUSD: 10,
TradeCooldownMs: 30000,
AlertCooldownSec: 300, AlertCooldownSec: 300,
MockSlippagePct: 0.005,
MaxPositions: 5, // default max 5 concurrent positions
BlacklistDuration: 3600, // default 1 hour blacklist observation
InitialCapital: 1000, // default $1000 starting capital
// Exchange fee rates // Surge detection
TakerFeeBitget: 0.060, // 0.060% SurgeEnabled: true,
TakerFeeHyperLiquid: 0.045, // 0.045% SurgeWindowSize: 600, // ~30s at 50ms tick
SurgeBaselineMultiplier: 3.0, // baseline * N = threshold
SurgeMinAbsSpreadPct: 0.05, // minimum absolute spread %
SurgeCooldownSec: 60, // seconds between alerts for same coin
// Exit/risk parameters // Momentum scanning
TakeProfitPct: 0.20, // 0.20% net profit take-profit MomentumThresholdPct: 0.25, // 0.25% change flags momentum
PositionTimeoutSec: 1800, // 30 minutes
LegDelayMs: 300, // 300ms between legs
ReversalTolerancePct: 0.1, // 0.1% tolerance for entry sanity check
// Scale-in parameters // Trend detection
ScaleStepPct: 0.10, // 0.10% spread widening per scale level TrendBaselineWindow: 600, // ~30s at 50ms tick
ScaleCooldownSec: 5, // 5 seconds between scales TrendAnomalyMul: 3.0, // 3 sigma z-score threshold
TrendConfirmTicks: 3, // 3 consecutive ticks for confirmation
TrendAlertCooldown: 60000, // 1 min cooldown
// Binance momentum detection
BinanceMomentumEnabled: true,
BinanceMomentumThresholdPct: 10.0,
BinanceMomentumCooldownSec: 300, // 5 minutes between alerts per coin
BinanceMomentumWindowSec: 60, // 1-minute lookback
} }
data, err := os.ReadFile("config.json") data, err := os.ReadFile("config.json")
@@ -213,65 +180,58 @@ func loadJSONConfig() jsonConfig {
if cfg.ScanIntervalMs != 0 { if cfg.ScanIntervalMs != 0 {
def.ScanIntervalMs = cfg.ScanIntervalMs def.ScanIntervalMs = cfg.ScanIntervalMs
} }
if cfg.TradeThreshold != 0 {
def.TradeThreshold = cfg.TradeThreshold
}
if cfg.TradeAmountUSD != 0 {
def.TradeAmountUSD = cfg.TradeAmountUSD
}
if cfg.TradeCooldownMs != 0 {
def.TradeCooldownMs = cfg.TradeCooldownMs
}
if cfg.AlertCooldownSec != 0 { if cfg.AlertCooldownSec != 0 {
def.AlertCooldownSec = cfg.AlertCooldownSec def.AlertCooldownSec = cfg.AlertCooldownSec
} }
if cfg.MockSlippagePct != 0 {
def.MockSlippagePct = cfg.MockSlippagePct // Surge detection JSON overrides
if cfg.SurgeWindowSize != 0 {
def.SurgeWindowSize = cfg.SurgeWindowSize
} }
if cfg.MaxPositions != 0 { if cfg.SurgeBaselineMultiplier != 0 {
def.MaxPositions = cfg.MaxPositions def.SurgeBaselineMultiplier = cfg.SurgeBaselineMultiplier
} }
if cfg.BlacklistDuration != 0 { if cfg.SurgeMinAbsSpreadPct != 0 {
def.BlacklistDuration = cfg.BlacklistDuration def.SurgeMinAbsSpreadPct = cfg.SurgeMinAbsSpreadPct
} }
if cfg.InitialCapital != 0 { if cfg.SurgeCooldownSec != 0 {
def.InitialCapital = cfg.InitialCapital def.SurgeCooldownSec = cfg.SurgeCooldownSec
} }
// New config fields if cfg.MomentumThresholdPct != 0 {
if cfg.TakerFeeBitget != 0 { def.MomentumThresholdPct = cfg.MomentumThresholdPct
def.TakerFeeBitget = cfg.TakerFeeBitget
} }
if cfg.TakerFeeHyperLiquid != 0 {
def.TakerFeeHyperLiquid = cfg.TakerFeeHyperLiquid // Trend detection JSON overrides
if cfg.TrendBaselineWindow != 0 {
def.TrendBaselineWindow = cfg.TrendBaselineWindow
} }
if cfg.TakeProfitPct != 0 { if cfg.TrendAnomalyMul != 0 {
def.TakeProfitPct = cfg.TakeProfitPct def.TrendAnomalyMul = cfg.TrendAnomalyMul
} }
if cfg.PositionTimeoutSec != 0 { if cfg.TrendConfirmTicks != 0 {
def.PositionTimeoutSec = cfg.PositionTimeoutSec def.TrendConfirmTicks = cfg.TrendConfirmTicks
} }
if cfg.LegDelayMs != 0 { if cfg.TrendAlertCooldown != 0 {
def.LegDelayMs = cfg.LegDelayMs def.TrendAlertCooldown = cfg.TrendAlertCooldown
} }
if cfg.ReversalTolerancePct != 0 {
def.ReversalTolerancePct = cfg.ReversalTolerancePct // Binance momentum JSON overrides
if cfg.BinanceMomentumThresholdPct != 0 {
def.BinanceMomentumThresholdPct = cfg.BinanceMomentumThresholdPct
} }
if cfg.ScaleStepPct != 0 { if cfg.BinanceMomentumCooldownSec != 0 {
def.ScaleStepPct = cfg.ScaleStepPct def.BinanceMomentumCooldownSec = cfg.BinanceMomentumCooldownSec
} }
if cfg.ScaleCooldownSec != 0 { if cfg.BinanceMomentumWindowSec != 0 {
def.ScaleCooldownSec = cfg.ScaleCooldownSec def.BinanceMomentumWindowSec = cfg.BinanceMomentumWindowSec
}
if len(cfg.ExcludedCoins) > 0 {
def.ExcludedCoins = cfg.ExcludedCoins
} }
// Boolean fields: zero default is false, so use OR logic // Boolean fields: zero default is false, so use OR logic
// When JSON has true → true || false = true (override) def.MomentumEnabled = cfg.MomentumEnabled || def.MomentumEnabled
// When JSON has false → false || false = false (keep default) def.TrendEnabled = cfg.TrendEnabled || def.TrendEnabled
def.TestMode = cfg.TestMode || def.TestMode def.SurgeEnabled = cfg.SurgeEnabled || def.SurgeEnabled
def.TradeEnabled = cfg.TradeEnabled || def.TradeEnabled def.BinanceMomentumEnabled = cfg.BinanceMomentumEnabled || def.BinanceMomentumEnabled
return def return def
} }
+16 -18
View File
@@ -1,23 +1,21 @@
{ {
"test_mode": false,
"trade_enabled": true,
"arb_threshold": 0.3, "arb_threshold": 0.3,
"scan_interval_ms": 200, "scan_interval_ms": 200,
"trade_threshold": 0.3,
"trade_amount_usd": 20,
"trade_cooldown_ms": 30000,
"alert_cooldown_sec": 300, "alert_cooldown_sec": 300,
"mock_slippage_pct": 0.05, "surge_enabled": true,
"max_positions": 1, "surge_window_size": 600,
"blacklist_duration_sec": 3600, "surge_baseline_multiplier": 3.0,
"initial_capital": 1000, "surge_min_abs_spread_pct": 0.05,
"taker_fee_bitget": 0.060, "surge_cooldown_sec": 60,
"taker_fee_hyperliquid": 0.045, "momentum_enabled": true,
"take_profit_pct": 0.3, "momentum_threshold_pct": 0.25,
"spread_reverse_exit_pct": 0, "trend_enabled": true,
"position_timeout_sec": 1800, "trend_baseline_window": 600,
"leg_delay_ms": 300, "trend_anomaly_mul": 3.0,
"reversal_tolerance_pct": 0.1, "trend_confirm_ticks": 3,
"scale_step_pct": 0.3, "trend_alert_cooldown_ms": 60000,
"scale_cooldown_sec": 5 "binance_momentum_enabled": true,
"binance_momentum_threshold_pct": 10.0,
"binance_momentum_cooldown_sec": 300,
"binance_momentum_window_sec": 60
} }
+506
View File
@@ -0,0 +1,506 @@
package main
import (
"math"
"sort"
"sync"
"time"
)
// CmState represents a coin's cumulative move state.
type CmState string
const (
CmNeutral CmState = "neutral"
CmRising CmState = "rising" // strong upward consensus across exchanges
CmFalling CmState = "falling" // strong downward consensus across exchanges
)
// exChange holds a per-exchange price change percentage.
type exChange struct {
name string
change float64
}
// shortExName maps full exchange names to short prefixes for JSON keys.
func shortExName(name string) string {
switch name {
case ExBitget:
return "bg"
case ExBinance:
return "bn"
case ExOKX:
return "okx"
}
return name
}
// CmEvent records a cumulative move state transition, persisted to DB.
type CmEvent struct {
Coin string `json:"coin"`
PrevState string `json:"prev_state"`
NewState string `json:"new_state"`
Direction string `json:"direction"`
Score float64 `json:"score"` // avg_change% × ex_agree
AvgChange float64 `json:"avg_change"` // average change% across all exchanges
ExAgree int `json:"ex_agree"`
ExTotal int `json:"ex_total"`
BGChange1m float64 `json:"bg_1m"`
HLChange1m float64 `json:"hl_1m"`
BNChange1m float64 `json:"bn_1m"`
OKXChange1m float64 `json:"okx_1m"`
BGChange5m float64 `json:"bg_5m"`
HLChange5m float64 `json:"hl_5m"`
BNChange5m float64 `json:"bn_5m"`
OKXChange5m float64 `json:"okx_5m"`
Timestamp int64 `json:"timestamp"`
}
// CmSnapshot is a point-in-time price snapshot for all exchanges for one coin.
type cmSnapshot struct {
time int64
prices map[string]float64 // exchange → price
}
// CumulativeTracker monitors multi-exchange cumulative price changes.
// Takes 1-second snapshots, computes 1m/5m/1h changes, detects consensus surges.
type CumulativeTracker struct {
mu sync.RWMutex
coins map[string][]cmSnapshot // coin → ring buffer of snapshots
heads map[string]int
counts map[string]int
// Per-coin state
states map[string]CmState
prevState map[string]CmState
// Ring buffer config
maxSnapshots int // 5min worth at 1s = 300
// Thresholds
minExchanges int // need at least this many exchanges with data (default: 3)
surgePct1m float64 // 1m change% threshold to trigger (default: 0.5%)
surgePct5m float64 // 5m change% threshold to trigger (default: 1.0%)
surgePct1h float64 // 1h change% threshold to trigger (default: 2.0%)
// Event history (in-memory ring buffer)
events [maxTrendEvents]CmEvent
eventsHead int
eventsLen int
// Callback for DB persistence
OnEvent func(CmEvent)
}
// NewCumulativeTracker creates a tracker with default thresholds.
func NewCumulativeTracker() *CumulativeTracker {
return &CumulativeTracker{
coins: make(map[string][]cmSnapshot),
heads: make(map[string]int),
counts: make(map[string]int),
states: make(map[string]CmState),
prevState: make(map[string]CmState),
maxSnapshots: 3600, // 1h at 1s
minExchanges: 3,
surgePct1m: 0.5, // 0.5% in 1min
surgePct5m: 1.0, // 1.0% in 5min
surgePct1h: 2.0, // 2.0% in 1h
}
}
// Record stores a price snapshot for a coin at the current time.
// Call this once per second with all exchange prices for each coin.
func (ct *CumulativeTracker) Record(coin string, prices map[string]float64) {
ct.mu.Lock()
defer ct.mu.Unlock()
now := time.Now().UnixMilli()
// Initialize buffer if needed — pre-fill entire ring buffer with this price
// so 1m/5m/1h windows show 0% immediately instead of waiting for data.
if ct.coins[coin] == nil {
ct.coins[coin] = make([]cmSnapshot, ct.maxSnapshots)
ct.heads[coin] = 0
ct.counts[coin] = ct.maxSnapshots // mark as full
ct.states[coin] = CmNeutral
ct.prevState[coin] = CmNeutral
startTime := now - int64(ct.maxSnapshots-1)*1000
for i := 0; i < ct.maxSnapshots; i++ {
ct.coins[coin][i] = cmSnapshot{
time: startTime + int64(i)*1000,
prices: prices,
}
}
return
}
// Deduplicate: skip if last snapshot is less than 1 second old
buf := ct.coins[coin]
head := ct.heads[coin]
prevIdx := (head - 1 + ct.maxSnapshots) % ct.maxSnapshots
if buf[prevIdx].time > now-1000 {
return
}
buf[head] = cmSnapshot{
time: now,
prices: prices,
}
ct.heads[coin] = (head + 1) % ct.maxSnapshots
}
// GetCurrent returns current cumulative change info for all coins, sorted by score desc.
func (ct *CumulativeTracker) GetCurrent() []map[string]interface{} {
ct.mu.RLock()
defer ct.mu.RUnlock()
var results []map[string]interface{}
for coin, buf := range ct.coins {
count := ct.counts[coin]
if count < 10 {
continue // not enough data
}
head := ct.heads[coin]
// Get current snapshot (most recent)
currentIdx := (head - 1 + ct.maxSnapshots) % ct.maxSnapshots
current := buf[currentIdx]
if current.time == 0 {
continue
}
if len(current.prices) < ct.minExchanges {
continue
}
// Find snapshots from ~60s ago, ~300s ago, and ~3600s ago
now := current.time
oneMinAgo := now - 60000
fiveMinAgo := now - 300000
oneHourAgo := now - 3600000
var snap1m, snap5m, snap1h *cmSnapshot
var found1m, found5m, found1h bool
// Walk backwards from current to find closest snapshots
for i := 0; i < count && i < ct.maxSnapshots; i++ {
idx := (currentIdx - i + ct.maxSnapshots) % ct.maxSnapshots
s := &buf[idx]
if s.time == 0 {
continue
}
if !found1m && s.time <= oneMinAgo {
snap1m = s
found1m = true
}
if !found5m && s.time <= fiveMinAgo {
snap5m = s
found5m = true
}
if !found1h && s.time <= oneHourAgo {
snap1h = s
found1h = true
}
}
if !found1m {
// Use oldest available as 1m approximation
continue
}
// Compute 1m/5m/1h changes per exchange
var changes1m, changes5m, changes1h []exChange
for ex, curP := range current.prices {
if curP <= 0 {
continue
}
if oldP, ok := snap1m.prices[ex]; ok && oldP > 0 {
chg := (curP - oldP) / oldP * 100
changes1m = append(changes1m, exChange{name: ex, change: chg})
}
if found5m && snap5m != nil {
if oldP, ok := snap5m.prices[ex]; ok && oldP > 0 {
chg := (curP - oldP) / oldP * 100
changes5m = append(changes5m, exChange{name: ex, change: chg})
}
}
if found1h && snap1h != nil {
if oldP, ok := snap1h.prices[ex]; ok && oldP > 0 {
chg := (curP - oldP) / oldP * 100
changes1h = append(changes1h, exChange{name: ex, change: chg})
}
}
}
if len(changes1m) < ct.minExchanges {
continue
}
// Compute averages and agreement
var sum1m, sum5m, sum1h float64
agreeUp1m, agreeDown1m := 0, 0
agreeUp5m, agreeDown5m := 0, 0
agreeUp1h, agreeDown1h := 0, 0
for _, c := range changes1m {
sum1m += c.change
if c.change > 0.001 {
agreeUp1m++
} else if c.change < -0.001 {
agreeDown1m++
}
}
for _, c := range changes5m {
sum5m += c.change
if c.change > 0.005 {
agreeUp5m++
} else if c.change < -0.005 {
agreeDown5m++
}
}
for _, c := range changes1h {
sum1h += c.change
if c.change > 0.01 {
agreeUp1h++
} else if c.change < -0.01 {
agreeDown1h++
}
}
avg1m := sum1m / float64(len(changes1m))
var avg5m float64
if len(changes5m) >= ct.minExchanges {
avg5m = sum5m / float64(len(changes5m))
}
var avg1h float64
if len(changes1h) >= ct.minExchanges {
avg1h = sum1h / float64(len(changes1h))
}
// Determine direction and agreement
majorityDir := "up"
majority := agreeUp1m
if agreeDown1m > agreeUp1m {
majorityDir = "down"
majority = agreeDown1m
}
// Score: abs(avg1m) × agreement (weighted by magnitude)
absAvg := math.Abs(avg1m)
score := absAvg * float64(majority)
entry := map[string]interface{}{
"coin": coin,
"avg_1m": math.Round(avg1m*10000) / 10000,
"avg_5m": math.Round(avg5m*10000) / 10000,
"avg_1h": math.Round(avg1h*10000) / 10000,
"score": math.Round(score*100) / 100,
"direction": majorityDir,
"ex_agree": majority,
"ex_total": len(changes1m),
}
// Individual exchange changes (using short names: bg, hl, bn, okx)
for _, c := range changes1m {
entry[shortExName(c.name)+"_1m"] = math.Round(c.change*10000) / 10000
}
if len(changes5m) >= ct.minExchanges {
for _, c := range changes5m {
entry[shortExName(c.name)+"_5m"] = math.Round(c.change*10000) / 10000
}
}
if len(changes1h) >= ct.minExchanges {
for _, c := range changes1h {
entry[shortExName(c.name)+"_1h"] = math.Round(c.change*10000) / 10000
}
}
// Current state
entry["state"] = string(ct.states[coin])
results = append(results, entry)
}
// Sort by score descending
sort.Slice(results, func(i, j int) bool {
si, _ := results[i]["score"].(float64)
sj, _ := results[j]["score"].(float64)
return si > sj
})
if len(results) > 100 {
results = results[:100]
}
return results
}
// Tick runs one detection cycle: updates state machines, fires events.
func (ct *CumulativeTracker) Tick() {
ct.mu.Lock()
defer ct.mu.Unlock()
for coin, buf := range ct.coins {
count := ct.counts[coin]
if count < 60 {
continue // need at least 1min of data
}
head := ct.heads[coin]
currentIdx := (head - 1 + ct.maxSnapshots) % ct.maxSnapshots
current := buf[currentIdx]
if current.time == 0 || len(current.prices) < ct.minExchanges {
continue
}
// Find 1min ago snapshot
oneMinAgo := current.time - 60000
var snap1m *cmSnapshot
for i := 0; i < count && i < ct.maxSnapshots; i++ {
idx := (currentIdx - i + ct.maxSnapshots) % ct.maxSnapshots
s := &buf[idx]
if s.time > 0 && s.time <= oneMinAgo {
snap1m = s
break
}
}
if snap1m == nil {
continue
}
// Compute 1m changes
var changes []exChange
for ex, curP := range current.prices {
if curP <= 0 {
continue
}
if oldP, ok := snap1m.prices[ex]; ok && oldP > 0 {
chg := (curP - oldP) / oldP * 100
changes = append(changes, exChange{name: ex, change: chg})
}
}
if len(changes) < ct.minExchanges {
continue
}
var sum float64
agreeUp, agreeDown := 0, 0
for _, c := range changes {
sum += c.change
if c.change > 0.001 {
agreeUp++
} else if c.change < -0.001 {
agreeDown++
}
}
avg := sum / float64(len(changes))
majority := agreeUp
majorityDir := "up"
if agreeDown > agreeUp {
majority = agreeDown
majorityDir = "down"
}
// Determine new state
absAvg := math.Abs(avg)
newState := ct.states[coin]
// Map exchange changes for individual values
exMap := make(map[string]float64)
for _, c := range changes {
exMap[c.name] = c.change
}
if absAvg >= ct.surgePct1m && majority >= ct.minExchanges {
if majorityDir == "up" {
if ct.states[coin] == CmNeutral || ct.states[coin] == CmFalling {
ct.prevState[coin] = ct.states[coin]
ct.states[coin] = CmRising
newState = CmRising
// Fire event
ev := ct.makeEvent(coin, string(ct.prevState[coin]), "rising", majorityDir,
absAvg*float64(majority), avg, majority, len(changes), exMap)
ct.storeEvent(ev)
}
} else {
if ct.states[coin] == CmNeutral || ct.states[coin] == CmRising {
ct.prevState[coin] = ct.states[coin]
ct.states[coin] = CmFalling
newState = CmFalling
ev := ct.makeEvent(coin, string(ct.prevState[coin]), "falling", majorityDir,
absAvg*float64(majority), avg, majority, len(changes), exMap)
ct.storeEvent(ev)
}
}
} else if absAvg < ct.surgePct1m*0.3 || majority < 2 {
if ct.states[coin] != CmNeutral {
ct.prevState[coin] = ct.states[coin]
ct.states[coin] = CmNeutral
ev := ct.makeEvent(coin, string(ct.prevState[coin]), "neutral", majorityDir,
absAvg*float64(majority), avg, majority, len(changes), exMap)
ct.storeEvent(ev)
}
}
_ = newState
}
}
// makeEvent builds a CmEvent struct with 1m and 5m data.
func (ct *CumulativeTracker) makeEvent(coin, prevState, newState, direction string, score, avgChange float64, exAgree, exTotal int, exChanges map[string]float64) CmEvent {
return CmEvent{
Coin: coin,
PrevState: prevState,
NewState: newState,
Direction: direction,
Score: math.Round(score*100) / 100,
AvgChange: math.Round(avgChange*10000) / 10000,
ExAgree: exAgree,
ExTotal: exTotal,
BGChange1m: exChanges[ExBitget],
HLChange1m: 0,
BNChange1m: exChanges[ExBinance],
OKXChange1m: exChanges[ExOKX],
Timestamp: time.Now().UnixMilli(),
}
}
// storeEvent adds to ring buffer and fires callback.
func (ct *CumulativeTracker) storeEvent(ev CmEvent) {
ct.events[ct.eventsHead] = ev
ct.eventsHead = (ct.eventsHead + 1) % maxTrendEvents
if ct.eventsLen < maxTrendEvents {
ct.eventsLen++
}
if ct.OnEvent != nil {
ct.OnEvent(ev)
}
}
// GetEvents returns stored events, newest first.
func (ct *CumulativeTracker) GetEvents(limit int) []CmEvent {
ct.mu.RLock()
defer ct.mu.RUnlock()
n := ct.eventsLen
if limit > 0 && limit < n {
n = limit
}
result := make([]CmEvent, 0, n)
for i := 0; i < n; i++ {
idx := (ct.eventsHead - 1 - i + maxTrendEvents) % maxTrendEvents
if ct.events[idx].Timestamp == 0 {
continue
}
result = append(result, ct.events[idx])
}
return result
}
// GetTopCoins returns top surging coins by score.
func (ct *CumulativeTracker) GetTopCoins(limit int) []map[string]interface{} {
all := ct.GetCurrent()
if limit > 0 && limit < len(all) {
return all[:limit]
}
return all
}
+236 -297
View File
@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"log" "log"
"math"
"net/http" "net/http"
"os" "os"
"sync" "sync"
@@ -132,17 +131,17 @@ func (ph *priceHistory) GetHistory(coin, exchange string, limit int) []pricePoin
} }
// ============================================================ // ============================================================
// Spread History — tracks BG↔HL spread % per coin (P3-2) // Spread History — tracks 3-exchange max spread % per coin
// ============================================================ // ============================================================
type spreadPoint struct { type spreadPoint struct {
T int64 `json:"t"` T int64 `json:"t"`
Spread float64 `json:"s"` // spread % (positive = BG cheaper than HL for BG->HL direction) Spread float64 `json:"s"` // 3-exchange max spread %
} }
type spreadHistory struct { type spreadHistory struct {
mu sync.RWMutex mu sync.RWMutex
buffers map[string][]spreadPoint // coin -> spread points buffers map[string][]spreadPoint
} }
func newSpreadHistory() *spreadHistory { func newSpreadHistory() *spreadHistory {
@@ -186,31 +185,83 @@ type Dashboard struct {
history *priceHistory history *priceHistory
spreads *spreadHistory spreads *spreadHistory
store *PriceStore store *PriceStore
trader *Trader
db *db.DB db *db.DB
addr string addr string
cfg *Config
// cached arb scan results // cached scan results
mu sync.RWMutex mu sync.RWMutex
lastScan []*ArbOpportunity lastScan []ThreeExSpread
scanTime time.Time scanTime time.Time
// P3-5: connection status — exchange -> last update time // connection status — exchange -> last update time
connMu sync.RWMutex connMu sync.RWMutex
connMap map[string]time.Time // exchange name -> last price timestamp connMap map[string]time.Time
// Momentum tracker
momentumTracker *MomentumTracker
// Trend detector
trendDetector *TrendDetector
// Cumulative tracker (1m/5m multi-exchange consensus)
cumulativeTracker *CumulativeTracker
// Trend filter (K-line based quiet + EMA filter)
trendFilter *TrendFilter
// Surge detector
surgeDetector *SurgeDetector
// Binance momentum detector
binanceMomentumDetector *BinanceMomentumDetector
} }
func NewDashboard(store *PriceStore, trader *Trader, database *db.DB, addr string) *Dashboard { func NewDashboard(store *PriceStore, database *db.DB, addr string, cfg *Config, momentumTracker *MomentumTracker, trendDetector *TrendDetector, cumulativeTracker *CumulativeTracker, trendFilter *TrendFilter, surgeDetector *SurgeDetector, binanceMomentumDetector *BinanceMomentumDetector) *Dashboard {
return &Dashboard{ d := &Dashboard{
hub: NewSSEHub(), hub: NewSSEHub(),
history: newPriceHistory(), history: newPriceHistory(),
spreads: newSpreadHistory(), spreads: newSpreadHistory(),
store: store, store: store,
trader: trader,
db: database, db: database,
addr: addr, addr: addr,
cfg: cfg,
connMap: make(map[string]time.Time), connMap: make(map[string]time.Time),
momentumTracker: momentumTracker,
trendDetector: trendDetector,
cumulativeTracker: cumulativeTracker,
trendFilter: trendFilter,
surgeDetector: surgeDetector,
binanceMomentumDetector: binanceMomentumDetector,
} }
// Wire trend event persistence to SQLite
if trendDetector != nil && database != nil {
trendDetector.OnEvent = func(ev TrendEvent) {
database.InsertTrendEvent(ev.Coin, ev.PrevState, ev.NewState, ev.Direction,
ev.ZScore, ev.Volatility, ev.BGChange, ev.HLChange, ev.BNChange, ev.OKXChange,
ev.ExAgree, ev.ExTotal)
}
}
// Wire trend filter signal broadcast via SSE
if trendFilter != nil {
trendFilter.OnNewSignal = func(sig TrendSignal) {
d.hub.Broadcast("trend_signal", sig)
}
}
// Wire cumulative event persistence to SQLite
if cumulativeTracker != nil && database != nil {
cumulativeTracker.OnEvent = func(ev CmEvent) {
database.InsertCmEvent(ev.Coin, ev.PrevState, ev.NewState, ev.Direction,
ev.Score, ev.AvgChange, ev.ExAgree, ev.ExTotal,
ev.BGChange1m, ev.HLChange1m, ev.BNChange1m, ev.OKXChange1m,
ev.BGChange5m, ev.HLChange5m, ev.BNChange5m, ev.OKXChange5m)
}
}
return d
} }
func (d *Dashboard) Run() { func (d *Dashboard) Run() {
@@ -223,25 +274,24 @@ func (d *Dashboard) Run() {
if diskFS := os.DirFS("frontend/dist"); true { if diskFS := os.DirFS("frontend/dist"); true {
if _, diskErr := fs.Stat(diskFS, "index.html"); diskErr == nil { if _, diskErr := fs.Stat(diskFS, "index.html"); diskErr == nil {
staticSub = diskFS staticSub = diskFS
log.Printf("[Web] Serving from disk: frontend/dist/ (hot reload enabled)")
} }
} }
if err != nil && staticSub == nil { if err == nil && staticSub != nil {
log.Printf("[Web] Failed to create static sub-fs: %v", err)
} else {
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub)))) mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub))))
} }
mux.HandleFunc("GET /", d.handleIndex) mux.HandleFunc("GET /", d.handleIndex)
mux.HandleFunc("GET /api/status", d.handleStatus) mux.HandleFunc("GET /api/status", d.handleStatus)
mux.HandleFunc("GET /api/history", d.handleHistory) mux.HandleFunc("GET /api/history", d.handleHistory)
mux.HandleFunc("GET /api/spread-history", d.handleSpreadHistory) // P3-2 mux.HandleFunc("GET /api/spread-history", d.handleSpreadHistory)
mux.HandleFunc("GET /api/trades", d.handleTrades) mux.HandleFunc("GET /api/connections", d.handleConnStatus)
mux.HandleFunc("GET /api/trade/", d.handleTradeDetail) mux.HandleFunc("GET /api/trend-history", d.handleTrendHistory)
mux.HandleFunc("GET /api/connections", d.handleConnStatus) // P3-5 mux.HandleFunc("GET /api/cm-history", d.handleCmHistory)
mux.HandleFunc("GET /api/trend-signals", d.handleTrendSignals)
mux.HandleFunc("GET /api/surge-events", d.handleSurgeEvents)
mux.HandleFunc("GET /events", d.handleSSE) mux.HandleFunc("GET /events", d.handleSSE)
mux.HandleFunc("POST /api/stop", d.handleStop) mux.HandleFunc("GET /binance", d.handleBinanceIndex)
mux.HandleFunc("POST /api/start", d.handleStart) mux.HandleFunc("GET /api/binance-alerts", d.handleBinanceAlerts)
server := &http.Server{ server := &http.Server{
Addr: d.addr, Addr: d.addr,
@@ -256,68 +306,6 @@ func (d *Dashboard) Run() {
} }
} }
// ============================================================
// Stats computation — kept separate from trading logic
// ============================================================
// DetailedStats holds aggregated PnL and duration statistics.
type DetailedStats struct {
TotalTrades int `json:"total_trades"`
TotalPnlUSD float64 `json:"total_pnl_usd"` // sum of all trade PnL in USD
CapitalPnlPct float64 `json:"capital_pnl_pct"` // TotalPnlUSD / InitialCapital * 100
AvgPnlPct float64 `json:"avg_pnl_pct"`
MaxProfitPct float64 `json:"max_profit_pct"`
MaxLossPct float64 `json:"max_loss_pct"`
AvgDuration string `json:"avg_duration"`
TotalDuration string `json:"total_duration"`
WinningTrades int `json:"winning_trades"`
LosingTrades int `json:"losing_trades"`
WinRate float64 `json:"win_rate"`
}
// calcDetailedStats computes trading statistics from a slice of closed trades.
// This is a pure function — no dependency on Trader internals.
func calcDetailedStats(trades []TradeRecord, initialCapital float64) DetailedStats {
ds := DetailedStats{}
if len(trades) == 0 {
return ds
}
var totalDur time.Duration
ds.MaxLossPct = 1e9 // sentinel
for _, tr := range trades {
ds.TotalTrades++
ds.TotalPnlUSD += tr.PnlUSD
if tr.PnlPct >= 0 {
ds.WinningTrades++
if tr.PnlPct > ds.MaxProfitPct {
ds.MaxProfitPct = tr.PnlPct
}
} else {
ds.LosingTrades++
if tr.PnlPct < ds.MaxLossPct {
ds.MaxLossPct = tr.PnlPct
}
}
if !tr.ClosedAt.IsZero() && !tr.OpenedAt.IsZero() {
totalDur += tr.ClosedAt.Sub(tr.OpenedAt)
}
}
if ds.MaxLossPct == 1e9 {
ds.MaxLossPct = 0
}
if ds.TotalTrades > 0 {
ds.CapitalPnlPct = ds.TotalPnlUSD / initialCapital * 100
ds.AvgPnlPct = ds.TotalPnlUSD / float64(ds.TotalTrades) / initialCapital * 100
ds.WinRate = float64(ds.WinningTrades) / float64(ds.TotalTrades) * 100
}
if totalDur > 0 {
avgDur := totalDur / time.Duration(ds.TotalTrades)
ds.AvgDuration = avgDur.Round(time.Second).String()
ds.TotalDuration = totalDur.Round(time.Second).String()
}
return ds
}
// broadcastLoop pushes data to SSE clients every 1 second. // broadcastLoop pushes data to SSE clients every 1 second.
func (d *Dashboard) broadcastLoop() { func (d *Dashboard) broadcastLoop() {
tick := time.NewTicker(1 * time.Second) tick := time.NewTicker(1 * time.Second)
@@ -329,7 +317,7 @@ func (d *Dashboard) broadcastLoop() {
continue continue
} }
// 1. Prices + spreads + connection status // 1. Prices + 3-exchange spreads
var prices []map[string]interface{} var prices []map[string]interface{}
for _, coin := range TrackedCoins { for _, coin := range TrackedCoins {
exMap := snap[coin.Name] exMap := snap[coin.Name]
@@ -349,135 +337,48 @@ func (d *Dashboard) broadcastLoop() {
} }
} }
// P3-2: Calculate BG↔HL spread and record // Calculate 3-exchange max spread
bnP := exMap[ExBinance]
okxP := exMap[ExOKX]
bgP := exMap[ExBitget] bgP := exMap[ExBitget]
hlP := exMap[ExHyperLiquid] if bnP > 0 && okxP > 0 && bgP > 0 {
if bgP > 0 && hlP > 0 { prices_ := []float64{bnP, okxP, bgP}
spreadPct := (hlP - bgP) / bgP * 100 minP, maxP := prices_[0], prices_[0]
entry["bg_hl_spread"] = spreadPct for _, p := range prices_[1:] {
if p < minP { minP = p }
if p > maxP { maxP = p }
}
spreadPct := (maxP - minP) / minP * 100
entry["spread_3ex"] = spreadPct
d.spreads.Record(coin.Name, spreadPct) d.spreads.Record(coin.Name, spreadPct)
// Both directions net profit after fees (4 taker fees: 2 entry + 2 exit)
cost := bgP * (1 + takerFees[ExBitget]/100)
revenue := hlP * (1 - takerFees[ExHyperLiquid]/100)
netBG := (revenue/cost-1)*100 - 2*(takerFees[ExBitget]+takerFees[ExHyperLiquid])
cost = hlP * (1 + takerFees[ExHyperLiquid]/100)
revenue = bgP * (1 - takerFees[ExBitget]/100)
netHL := (revenue/cost-1)*100 - 2*(takerFees[ExHyperLiquid]+takerFees[ExBitget])
entry["net_bg_to_hl"] = math.Round(netBG*10000) / 10000
entry["net_hl_to_bg"] = math.Round(netHL*10000) / 10000
} }
prices = append(prices, entry) prices = append(prices, entry)
} }
d.hub.Broadcast("prices", prices) d.hub.Broadcast("prices", prices)
// 2. Open positions with live PnL (P3-3) — read from decoupled snapshot, never blocks trader // 2. 3-exchange scan results
positions := d.trader.ReadSnapshot()
posList := make([]map[string]interface{}, 0, len(positions))
for _, pos := range positions {
posEntry := map[string]interface{}{
"coin": pos.Coin,
"direction": pos.Direction,
"amount_usd": pos.AmountUSD,
"entry_spread": pos.EntrySpread,
"scales": pos.ScaleLevels,
"duration": time.Since(pos.StartedAt).Round(time.Second).String(),
"started_at": pos.StartedAt.Format("15:04:05"),
"started_ts": pos.StartedAt.UnixMilli(),
"long_exchange": pos.LongLeg.Exchange,
"short_exchange": pos.ShortLeg.Exchange,
"long_entry": pos.LongLeg.EntryPrice,
"short_entry": pos.ShortLeg.EntryPrice,
"db_trade_id": pos.DBTradeID,
}
// Calculate live PnL from current prices — use weighted average for scale-ins
if exMap := snap[pos.Coin]; exMap != nil {
bgP := exMap[ExBitget]
hlP := exMap[ExHyperLiquid]
if bgP > 0 && hlP > 0 {
var longCurrent, shortCurrent float64
if pos.LongLeg.Exchange == ExBitget {
longCurrent, shortCurrent = bgP, hlP
} else {
longCurrent, shortCurrent = hlP, bgP
}
longAvg := weightedAvgPrice(pos.LongEntryPrices, pos.AmountUSD/float64(max(1, len(pos.LongEntryPrices))))
shortAvg := weightedAvgPrice(pos.ShortEntryPrices, pos.AmountUSD/float64(max(1, len(pos.ShortEntryPrices))))
longPnl := (longCurrent - longAvg) / longAvg * 100
shortPnl := (shortAvg - shortCurrent) / shortAvg * 100
feeEntryUSD := float64(1+pos.ScaleLevels) * (pos.AmountUSD / float64(max(1, 1+pos.ScaleLevels))) * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) / 100
feeExitUSD := pos.AmountUSD * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) / 100
pricePnLUSD := pos.AmountUSD * (longPnl + shortPnl) / 100
netPnLUSD := pricePnLUSD - feeEntryUSD - feeExitUSD
currentSpread := (hlP - bgP) / bgP * 100
if pos.LongLeg.Exchange == ExHyperLiquid {
// HL→BG: spread positive when bgP > hlP
currentSpread = (bgP - hlP) / hlP * 100
}
posEntry["current_spread"] = math.Round(currentSpread*10000) / 10000
posEntry["pnl_est"] = math.Round(netPnLUSD*10000) / 10000
}
}
posList = append(posList, posEntry)
}
d.hub.Broadcast("positions", posList)
// 3. Arb scan results
d.mu.RLock() d.mu.RLock()
scanCopy := d.lastScan scanCopy := d.lastScan
d.mu.RUnlock() d.mu.RUnlock()
if len(scanCopy) > 0 { if len(scanCopy) > 0 {
scanList := make([]map[string]interface{}, 0, len(scanCopy)) scanList := make([]map[string]interface{}, 0, len(scanCopy))
for _, opp := range scanCopy { for _, s := range scanCopy {
scanList = append(scanList, map[string]interface{}{ scanList = append(scanList, map[string]interface{}{
"coin": opp.Coin, "coin": s.Coin,
"direction": opp.Direction, "spread_pct": s.SpreadPct,
"buy_ex": opp.BuyEx, "bn_price": s.BnPrice,
"sell_ex": opp.SellEx, "okx_price": s.OkxPrice,
"buy_price": opp.BuyPrice, "bg_price": s.BgPrice,
"sell_price": opp.SellPrice, "max_ex": s.MaxEx,
"net_profit": opp.NetProfit, "min_ex": s.MinEx,
"gross": opp.GrossBasis,
}) })
} }
d.hub.Broadcast("arb", scanList) d.hub.Broadcast("spread_3ex", scanList)
} }
// 4. Stats + connection status (P3-5) // 3. Connection status
converged, diverged, flat, total := d.trader.GetClosedStats()
detail := calcDetailedStats(d.trader.GetClosedTrades(), d.trader.cfg.InitialCapital)
stats := map[string]interface{}{
"total_trades": total,
"converged": converged,
"diverged": diverged,
"flat": flat,
"open_positions": len(positions),
"coins": len(prices),
"capital": d.trader.cfg.InitialCapital,
// Detailed PnL & duration stats (session only)
"detail": map[string]interface{}{
"total_pnl_usd": math.Round(detail.TotalPnlUSD*100) / 100,
"capital_pnl": math.Round(detail.CapitalPnlPct*10000) / 10000,
"avg_pnl": detail.AvgPnlPct,
"max_profit": detail.MaxProfitPct,
"max_loss": detail.MaxLossPct,
"avg_dur": detail.AvgDuration,
"win_rate": detail.WinRate,
"wins": detail.WinningTrades,
"losses": detail.LosingTrades,
"total_dur": detail.TotalDuration,
},
}
// Connection status
d.connMu.RLock() d.connMu.RLock()
connInfo := make(map[string]string) connInfo := make(map[string]string)
for ex, lastTime := range d.connMap { for ex, lastTime := range d.connMap {
@@ -491,59 +392,73 @@ func (d *Dashboard) broadcastLoop() {
} }
} }
d.connMu.RUnlock() d.connMu.RUnlock()
stats["connections"] = connInfo
// Trading status status := map[string]interface{}{
stats["trading"] = map[string]interface{}{ "coins": len(prices),
"active": !d.trader.IsShuttingDown(), "connections": connInfo,
"mode": d.trader.ModeLabel(), }
"test": d.trader.cfg.TestMode, d.hub.Broadcast("status", status)
"target": d.trader.realTradesTarget,
"done": d.trader.realTradesDone, // 4. Momentum data (if enabled)
if d.momentumTracker != nil && d.cfg.MomentumEnabled {
momentumData := d.momentumTracker.Snapshot(d.cfg.MomentumThresholdPct)
if len(momentumData) > 0 {
d.hub.Broadcast("momentum", momentumData)
}
} }
// Per-exchange fund tracking // 5. Trend detection (if enabled)
exFunds := d.trader.GetExchangeFunds() if d.trendDetector != nil && d.cfg.TrendEnabled {
exFundsMap := make(map[string]map[string]float64, len(exFunds)) d.trendDetector.Tick()
for ex, ef := range exFunds { trendData := d.trendDetector.Snapshot()
exFundsMap[ex] = map[string]float64{ if len(trendData) > 0 {
"balance": math.Round(ef.Balance*100) / 100, d.hub.Broadcast("trend", trendData)
"total_fee": math.Round(ef.TotalFee*100) / 100,
"total_pnl": math.Round(ef.TotalPnl*100) / 100,
} }
} }
stats["exchange_funds"] = exFundsMap
// Blacklist — stale spread coins // 6. Cumulative change tracking
bl := d.trader.GetBlacklist() if d.cumulativeTracker != nil {
blList := make([]map[string]interface{}, 0, len(bl)) d.cumulativeTracker.Tick()
for coin, t := range bl { cmData := d.cumulativeTracker.GetTopCoins(30)
if d.trader.cfg.BlacklistDuration > 0 && time.Since(t) >= d.trader.cfg.BlacklistDuration { if len(cmData) > 0 {
continue // expired, will be cleaned up on next check d.hub.Broadcast("cumulative", cmData)
} }
remaining := time.Duration(0)
if d.trader.cfg.BlacklistDuration > 0 {
remaining = d.trader.cfg.BlacklistDuration - time.Since(t)
} }
blList = append(blList, map[string]interface{}{
"coin": coin,
"since": t.Format("15:04:05"),
"remaining_sec": int(remaining.Seconds()),
})
}
stats["blacklist"] = blList
d.hub.Broadcast("stats", stats) // 7. Trend filter (K-line based quiet + EMA)
if d.trendFilter != nil {
d.trendFilter.Tick()
filterData := d.trendFilter.Snapshot(0)
if len(filterData) > 0 {
d.hub.Broadcast("trend_filter", filterData)
}
}
// 8. Surge status (current spread/baseline for all coins)
if d.surgeDetector != nil && d.cfg.SurgeEnabled {
surgeSnap := d.surgeDetector.Snapshot()
if len(surgeSnap) > 0 {
d.hub.Broadcast("surge", surgeSnap)
}
}
// 9. Binance momentum snapshot (1-minute change for all coins)
if d.binanceMomentumDetector != nil && d.cfg.BinanceMomentumEnabled {
bmSnap := d.binanceMomentumDetector.Snapshot()
if len(bmSnap) > 0 {
d.hub.Broadcast("binance_momentum", bmSnap)
}
}
} }
} }
// ============================================================ // ============================================================
// Public methods called from main.go / trader // Public methods called from main.go
// ============================================================ // ============================================================
func (d *Dashboard) UpdateScan(opps []*ArbOpportunity) { func (d *Dashboard) UpdateScan(spreads []ThreeExSpread) {
d.mu.Lock() d.mu.Lock()
d.lastScan = opps d.lastScan = spreads
d.scanTime = time.Now() d.scanTime = time.Now()
d.mu.Unlock() d.mu.Unlock()
} }
@@ -552,14 +467,14 @@ func (d *Dashboard) RecordPrice(coin, exchange string, price float64) {
d.history.Record(coin, exchange, price) d.history.Record(coin, exchange, price)
} }
// RecordConnStatus updates the last-seen time for an exchange (P3-5). // RecordConnStatus updates the last-seen time for an exchange.
func (d *Dashboard) RecordConnStatus(exchange string) { func (d *Dashboard) RecordConnStatus(exchange string) {
d.connMu.Lock() d.connMu.Lock()
d.connMap[exchange] = time.Now() d.connMap[exchange] = time.Now()
d.connMu.Unlock() d.connMu.Unlock()
} }
// BroadcastEvent sends an immediate SSE event (P3-4). // BroadcastEvent sends an immediate SSE event.
func (d *Dashboard) BroadcastEvent(event string, data interface{}) { func (d *Dashboard) BroadcastEvent(event string, data interface{}) {
d.hub.Broadcast(event, data) d.hub.Broadcast(event, data)
} }
@@ -575,7 +490,6 @@ func (d *Dashboard) handleIndex(w http.ResponseWriter, r *http.Request) {
// Try disk first (hot reload) // Try disk first (hot reload)
data, err = os.ReadFile("frontend/dist/index.html") data, err = os.ReadFile("frontend/dist/index.html")
if err != nil { if err != nil {
// Fall back to embed
data, err = staticFS.ReadFile("frontend/dist/index.html") data, err = staticFS.ReadFile("frontend/dist/index.html")
} }
if err != nil { if err != nil {
@@ -588,25 +502,9 @@ func (d *Dashboard) handleIndex(w http.ResponseWriter, r *http.Request) {
func (d *Dashboard) handleStatus(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleStatus(w http.ResponseWriter, r *http.Request) {
snap := d.store.GetAll() snap := d.store.GetAll()
positions := d.trader.ReadSnapshot()
converged, diverged, flat, total := d.trader.GetClosedStats()
// Format exchange funds (snake_case, like SSE)
exFunds := d.trader.GetExchangeFunds()
exFundsMap := make(map[string]map[string]float64, len(exFunds))
for ex, ef := range exFunds {
exFundsMap[ex] = map[string]float64{
"balance": math.Round(ef.Balance*100) / 100,
"total_fee": math.Round(ef.TotalFee*100) / 100,
"total_pnl": math.Round(ef.TotalPnl*100) / 100,
}
}
resp := map[string]interface{}{ resp := map[string]interface{}{
"prices": snap, "prices": snap,
"positions": len(positions), "coins": len(snap),
"stats": map[string]int{"total": total, "converged": converged, "diverged": diverged, "flat": flat},
"exchange_funds": exFundsMap,
} }
writeJSON(w, resp) writeJSON(w, resp)
} }
@@ -620,7 +518,7 @@ func (d *Dashboard) handleHistory(w http.ResponseWriter, r *http.Request) {
for c := range snap { for c := range snap {
coins = append(coins, c) coins = append(coins, c)
} }
writeJSON(w, map[string]interface{}{"coins": coins, "exchanges": []string{"Binance", "HyperLiquid", "Bitget", "dYdX"}}) writeJSON(w, map[string]interface{}{"coins": coins, "exchanges": []string{ExBinance, ExOKX, ExBitget}})
return return
} }
points := d.history.GetHistory(coin, exchange, 300) points := d.history.GetHistory(coin, exchange, 300)
@@ -631,7 +529,7 @@ func (d *Dashboard) handleHistory(w http.ResponseWriter, r *http.Request) {
}) })
} }
// handleSpreadHistory returns BG↔HL spread history for a coin (P3-2). // handleSpreadHistory returns 3-exchange max spread history for a coin.
func (d *Dashboard) handleSpreadHistory(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleSpreadHistory(w http.ResponseWriter, r *http.Request) {
coin := r.URL.Query().Get("coin") coin := r.URL.Query().Get("coin")
if coin == "" { if coin == "" {
@@ -645,7 +543,7 @@ func (d *Dashboard) handleSpreadHistory(w http.ResponseWriter, r *http.Request)
}) })
} }
// handleConnStatus returns connection health for all exchanges (P3-5). // handleConnStatus returns connection health for all exchanges.
func (d *Dashboard) handleConnStatus(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleConnStatus(w http.ResponseWriter, r *http.Request) {
d.connMu.RLock() d.connMu.RLock()
conns := make(map[string]string) conns := make(map[string]string)
@@ -664,51 +562,72 @@ func (d *Dashboard) handleConnStatus(w http.ResponseWriter, r *http.Request) {
writeJSON(w, conns) writeJSON(w, conns)
} }
func (d *Dashboard) handleTrades(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleTrendHistory(w http.ResponseWriter, r *http.Request) {
if d.db == nil { var events interface{}
writeJSON(w, map[string]interface{}{"trades": []interface{}{}, "total": 0}) if d.db != nil {
return records, err := d.db.GetTrendEvents(200)
} if err == nil {
page := 1 events = records
limit := 20
coin := r.URL.Query().Get("coin")
if l := r.URL.Query().Get("limit"); l != "" {
if n, err := fmt.Sscanf(l, "%d", &limit); err != nil || n != 1 {
limit = 20
} }
} }
trades, total, err := d.db.GetTrades(page, limit, coin) if events == nil {
if err != nil { if d.trendDetector != nil {
http.Error(w, err.Error(), 500) events = d.trendDetector.GetEvents(200)
return } else {
events = []interface{}{}
} }
writeJSON(w, map[string]interface{}{ }
"trades": trades, writeJSON(w, map[string]interface{}{"events": events})
"total": total,
"page": page,
"limit": limit,
})
} }
func (d *Dashboard) handleTradeDetail(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleCmHistory(w http.ResponseWriter, r *http.Request) {
if d.db == nil { var events interface{}
http.Error(w, "DB not available", 503) if d.db != nil {
records, err := d.db.GetCmEvents(200)
if err == nil {
events = records
}
}
if events == nil {
if d.cumulativeTracker != nil {
events = d.cumulativeTracker.GetEvents(200)
} else {
events = []interface{}{}
}
}
writeJSON(w, map[string]interface{}{"events": events})
}
func (d *Dashboard) handleTrendSignals(w http.ResponseWriter, r *http.Request) {
var signals []TrendSignal
if d.trendFilter != nil {
signals = d.trendFilter.GetSignals(100)
}
if signals == nil {
signals = []TrendSignal{}
}
writeJSON(w, map[string]interface{}{"signals": signals})
}
func (d *Dashboard) handleSurgeEvents(w http.ResponseWriter, r *http.Request) {
limit := 100
// Try DB first
if d.db != nil {
events, err := d.db.GetSurgeEvents(limit)
if err == nil {
writeJSON(w, map[string]interface{}{"events": events, "total": len(events)})
return return
} }
var id int64
if _, err := fmt.Sscanf(r.URL.Path, "/api/trade/%d", &id); err != nil {
http.Error(w, "Invalid trade ID", 400)
return
} }
trade, orders, err := d.db.GetTradeByID(id)
if err != nil { // Fallback to in-memory
http.Error(w, err.Error(), 404) if d.surgeDetector != nil {
return events := d.surgeDetector.GetRecentEvents(limit)
writeJSON(w, map[string]interface{}{"events": events, "total": len(events)})
} else {
writeJSON(w, map[string]interface{}{"events": []interface{}{}, "total": 0})
} }
writeJSON(w, map[string]interface{}{
"trade": trade,
"orders": orders,
})
} }
func (d *Dashboard) handleSSE(w http.ResponseWriter, r *http.Request) { func (d *Dashboard) handleSSE(w http.ResponseWriter, r *http.Request) {
@@ -743,14 +662,34 @@ func (d *Dashboard) handleSSE(w http.ResponseWriter, r *http.Request) {
} }
} }
func (d *Dashboard) handleStop(w http.ResponseWriter, r *http.Request) { // handleBinanceIndex serves the Binance momentum monitor page.
d.trader.Stop() func (d *Dashboard) handleBinanceIndex(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]string{"status": "stopped", "message": "Trading stopped, positions closing"}) var data []byte
var err error
data, err = os.ReadFile("frontend/dist/binance.html")
if err != nil {
data, err = staticFS.ReadFile("frontend/dist/binance.html")
}
if err != nil {
http.Error(w, "Not found", 404)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(data)
} }
func (d *Dashboard) handleStart(w http.ResponseWriter, r *http.Request) { // handleBinanceAlerts returns recent Binance momentum alerts.
d.trader.Start() func (d *Dashboard) handleBinanceAlerts(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]string{"status": "started", "message": "Trading resumed"}) if d.binanceMomentumDetector == nil {
writeJSON(w, map[string]interface{}{"alerts": []interface{}{}})
return
}
alerts := d.binanceMomentumDetector.RecentAlerts(50)
if alerts == nil {
alerts = []BinanceAlert{}
}
writeJSON(w, map[string]interface{}{"alerts": alerts})
} }
func writeJSON(w http.ResponseWriter, v interface{}) { func writeJSON(w http.ResponseWriter, v interface{}) {
+65
View File
@@ -0,0 +1,65 @@
package db
import (
"time"
)
// CmEventRecord represents a persisted cumulative move state transition.
type CmEventRecord struct {
ID int64 `json:"id"`
Coin string `json:"coin"`
PrevState string `json:"prev_state"`
NewState string `json:"new_state"`
Direction string `json:"direction"`
Score float64 `json:"score"`
AvgChange float64 `json:"avg_change"`
ExAgree int `json:"ex_agree"`
ExTotal int `json:"ex_total"`
BG1m float64 `json:"bg_1m"`
HL1m float64 `json:"hl_1m"`
BN1m float64 `json:"bn_1m"`
OKX1m float64 `json:"okx_1m"`
BG5m float64 `json:"bg_5m"`
HL5m float64 `json:"hl_5m"`
BN5m float64 `json:"bn_5m"`
OKX5m float64 `json:"okx_5m"`
CreatedAt string `json:"created_at"`
}
// InsertCmEvent saves a cumulative move event to the database.
func (d *DB) InsertCmEvent(coin, prevState, newState, direction string, score, avgChange float64, exAgree, exTotal int, bg1m, hl1m, bn1m, okx1m, bg5m, hl5m, bn5m, okx5m float64) error {
_, err := d.Exec(`
INSERT INTO cm_events (coin, prev_state, new_state, direction, score, avg_change, ex_agree, ex_total, bg_1m, hl_1m, bn_1m, okx_1m, bg_5m, hl_5m, bn_5m, okx_5m, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
coin, prevState, newState, direction, score, avgChange, exAgree, exTotal, bg1m, hl1m, bn1m, okx1m, bg5m, hl5m, bn5m, okx5m, Now().Format(time.RFC3339))
return err
}
// GetCmEvents returns cumulative move events ordered by creation time descending.
func (d *DB) GetCmEvents(limit int) ([]CmEventRecord, error) {
if limit <= 0 {
limit = 100
}
rows, err := d.Query(`
SELECT id, coin, prev_state, new_state, direction, score, avg_change, ex_agree, ex_total, bg_1m, hl_1m, bn_1m, okx_1m, bg_5m, hl_5m, bn_5m, okx_5m, created_at
FROM cm_events
ORDER BY created_at DESC
LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var result []CmEventRecord
for rows.Next() {
var r CmEventRecord
if err := rows.Scan(&r.ID, &r.Coin, &r.PrevState, &r.NewState, &r.Direction,
&r.Score, &r.AvgChange, &r.ExAgree, &r.ExTotal,
&r.BG1m, &r.HL1m, &r.BN1m, &r.OKX1m,
&r.BG5m, &r.HL5m, &r.BN5m, &r.OKX5m, &r.CreatedAt); err != nil {
return nil, err
}
result = append(result, r)
}
return result, rows.Err()
}
+79
View File
@@ -108,6 +108,85 @@ func (d *DB) migrate() error {
created_at DATETIME NOT NULL created_at DATETIME NOT NULL
); );
CREATE INDEX IF NOT EXISTS idx_system_orders_trade ON system_orders(trade_id); CREATE INDEX IF NOT EXISTS idx_system_orders_trade ON system_orders(trade_id);
CREATE TABLE IF NOT EXISTS trend_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
coin TEXT NOT NULL,
prev_state TEXT NOT NULL,
new_state TEXT NOT NULL,
direction TEXT NOT NULL,
z_score REAL,
volatility REAL,
bg_change REAL,
hl_change REAL,
bn_change REAL,
okx_change REAL,
ex_agree INTEGER,
ex_total INTEGER,
created_at DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_trend_events_coin ON trend_events(coin);
CREATE INDEX IF NOT EXISTS idx_trend_events_created ON trend_events(created_at);
CREATE TABLE IF NOT EXISTS cm_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
coin TEXT NOT NULL,
prev_state TEXT NOT NULL,
new_state TEXT NOT NULL,
direction TEXT NOT NULL,
score REAL,
avg_change REAL,
ex_agree INTEGER,
ex_total INTEGER,
bg_1m REAL,
hl_1m REAL,
bn_1m REAL,
okx_1m REAL,
bg_5m REAL,
hl_5m REAL,
bn_5m REAL,
okx_5m REAL,
created_at DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_cm_events_coin ON cm_events(coin);
CREATE INDEX IF NOT EXISTS idx_cm_events_created ON cm_events(created_at);
CREATE TABLE IF NOT EXISTS trend_signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
coin TEXT NOT NULL,
type TEXT NOT NULL,
signal_score REAL,
price REAL,
ema_52 REAL,
ema_slope REAL,
volume_ratio REAL,
range_24h REAL,
vol_baseline REAL,
price_above_ema INTEGER,
state TEXT,
created_at DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_trend_signals_coin ON trend_signals(coin);
CREATE INDEX IF NOT EXISTS idx_trend_signals_created ON trend_signals(created_at);
CREATE TABLE IF NOT EXISTS surge_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
coin TEXT NOT NULL,
timestamp DATETIME NOT NULL,
bn_price REAL,
okx_price REAL,
bg_price REAL,
spread_pct REAL NOT NULL,
baseline_pct REAL,
threshold_pct REAL,
ratio REAL,
direction TEXT NOT NULL,
leading_exchange TEXT NOT NULL,
mid_price REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_surge_events_coin ON surge_events(coin);
CREATE INDEX IF NOT EXISTS idx_surge_events_created ON surge_events(created_at);
` `
_, err := d.Exec(schema) _, err := d.Exec(schema)
if err != nil { if err != nil {
+58
View File
@@ -0,0 +1,58 @@
package db
import "time"
// SurgeEventRecord represents a persisted surge detection event.
type SurgeEventRecord struct {
ID int64 `json:"id"`
Coin string `json:"coin"`
Timestamp string `json:"timestamp"`
BnPrice float64 `json:"bn_price"`
OkxPrice float64 `json:"okx_price"`
BgPrice float64 `json:"bg_price"`
SpreadPct float64 `json:"spread_pct"`
BaselinePct float64 `json:"baseline_pct"`
ThresholdPct float64 `json:"threshold_pct"`
Ratio float64 `json:"ratio"`
Direction string `json:"direction"`
LeadingExchange string `json:"leading_exchange"`
MidPrice float64 `json:"mid_price"`
CreatedAt string `json:"created_at"`
}
// InsertSurgeEvent saves a surge event to the database.
func (d *DB) InsertSurgeEvent(coin string, ts time.Time, bnPrice, okxPrice, bgPrice, spreadPct, baselinePct, thresholdPct, ratio float64, direction, leadingExchange string, midPrice float64) error {
_, err := d.Exec(`
INSERT INTO surge_events (coin, timestamp, bn_price, okx_price, bg_price, spread_pct, baseline_pct, threshold_pct, ratio, direction, leading_exchange, mid_price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
coin, ts.Format(time.RFC3339), bnPrice, okxPrice, bgPrice, spreadPct, baselinePct, thresholdPct, ratio, direction, leadingExchange, midPrice, Now().Format(time.RFC3339))
return err
}
// GetSurgeEvents returns surge events ordered by creation time descending.
func (d *DB) GetSurgeEvents(limit int) ([]SurgeEventRecord, error) {
if limit <= 0 {
limit = 100
}
rows, err := d.Query(`
SELECT id, coin, timestamp, bn_price, okx_price, bg_price, spread_pct, baseline_pct, threshold_pct, ratio, direction, leading_exchange, mid_price, created_at
FROM surge_events
ORDER BY created_at DESC
LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var result []SurgeEventRecord
for rows.Next() {
var r SurgeEventRecord
if err := rows.Scan(&r.ID, &r.Coin, &r.Timestamp, &r.BnPrice, &r.OkxPrice, &r.BgPrice,
&r.SpreadPct, &r.BaselinePct, &r.ThresholdPct, &r.Ratio, &r.Direction,
&r.LeadingExchange, &r.MidPrice, &r.CreatedAt); err != nil {
return nil, err
}
result = append(result, r)
}
return result, rows.Err()
}
-312
View File
@@ -1,312 +0,0 @@
package db
import (
"database/sql"
"time"
)
// TradeRecord mirrors the database row for trades table.
type TradeRecord struct {
ID int64
Coin string
Direction string
Status string // open / closed
EntrySpread *float64
ExitSpread *float64
LongExchange string
ShortExchange string
LongEntry *float64
LongExit *float64
ShortEntry *float64
ShortExit *float64
LongPnl *float64
ShortPnl *float64
FeeEntry *float64
FeeExit *float64
NetPnl *float64
AmountUSD float64
ScaleCount int
ExitReason *string
Convergence *string
OpenedAt time.Time
ClosedAt *time.Time
PnlLongUSD *float64 // per-exchange PnL in USD
PnlShortUSD *float64
FeeLongUSD *float64 // per-exchange fee in USD
FeeShortUSD *float64
}
// OrderRecord mirrors the database row for orders table.
type OrderRecord struct {
ID int64
TradeID int64
Leg string // long / short
Type string // entry / exit / scale
Exchange string
Side string // buy / sell
Price *float64
Size *float64
Fee *float64
OrderID *string
Status *string
CreatedAt time.Time
}
// SystemOrderRecord represents one system-level arbitrage action (entry/scale/exit).
type SystemOrderRecord struct {
ID int64
TradeID int64
Type string // entry / scale / exit
Status string // filled / failed
Spread *float64
LongPrice *float64
ShortPrice *float64
LongOrderID *int64
ShortOrderID *int64
CreatedAt time.Time
}
// SaveTrade inserts a new trade and returns its ID.
func (d *DB) SaveTrade(t *TradeRecord) (int64, error) {
res, err := d.Exec(`INSERT INTO trades (
coin, direction, status, entry_spread, exit_spread,
long_exchange, short_exchange, long_entry, long_exit, short_entry, short_exit,
long_pnl, short_pnl, fee_entry, fee_exit, net_pnl,
amount_usd, scale_count, exit_reason, convergence, opened_at, closed_at,
pnl_long_usd, pnl_short_usd, fee_long_usd, fee_short_usd
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
t.Coin, t.Direction, t.Status, t.EntrySpread, t.ExitSpread,
t.LongExchange, t.ShortExchange, t.LongEntry, t.LongExit, t.ShortEntry, t.ShortExit,
t.LongPnl, t.ShortPnl, t.FeeEntry, t.FeeExit, t.NetPnl,
t.AmountUSD, t.ScaleCount, t.ExitReason, t.Convergence, t.OpenedAt, t.ClosedAt,
t.PnlLongUSD, t.PnlShortUSD, t.FeeLongUSD, t.FeeShortUSD,
)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// UpdateTradeStatus updates an existing trade's close data.
func (d *DB) UpdateTradeStatus(id int64, t *TradeRecord) error {
_, err := d.Exec(`UPDATE trades SET
status=?, exit_spread=?, long_exit=?, short_exit=?,
long_pnl=?, short_pnl=?, fee_entry=?, fee_exit=?, net_pnl=?,
amount_usd=?, scale_count=?, exit_reason=?, convergence=?, closed_at=?,
pnl_long_usd=?, pnl_short_usd=?, fee_long_usd=?, fee_short_usd=?
WHERE id=?`,
t.Status, t.ExitSpread,
t.LongExit, t.ShortExit,
t.LongPnl, t.ShortPnl, t.FeeEntry, t.FeeExit, t.NetPnl,
t.AmountUSD, t.ScaleCount, t.ExitReason, t.Convergence, t.ClosedAt,
t.PnlLongUSD, t.PnlShortUSD, t.FeeLongUSD, t.FeeShortUSD,
id,
)
return err
}
// SetTradeStatus updates only the status field of a trade.
func (d *DB) SetTradeStatus(id int64, status string) error {
_, err := d.Exec("UPDATE trades SET status=? WHERE id=?", status, id)
return err
}
// GetOpenTrades returns all non-closed trades (status='open' or status='entering').
func (d *DB) GetOpenTrades() ([]TradeRecord, error) {
rows, err := d.Query(`SELECT id, coin, direction, status, entry_spread, exit_spread,
long_exchange, short_exchange, long_entry, long_exit, short_entry, short_exit,
long_pnl, short_pnl, fee_entry, fee_exit, net_pnl,
amount_usd, scale_count, exit_reason, convergence, opened_at, closed_at,
pnl_long_usd, pnl_short_usd, fee_long_usd, fee_short_usd
FROM trades WHERE status IN ('open','entering')`)
if err != nil {
return nil, err
}
defer rows.Close()
return scanTrades(rows)
}
// GetTrades returns paginated closed trades.
func (d *DB) GetTrades(page, limit int, coin string) ([]TradeRecord, int, error) {
// Count total
var total int
countSQL := "SELECT COUNT(*) FROM trades WHERE status='closed'"
args := []interface{}{}
if coin != "" {
countSQL += " AND coin=?"
args = append(args, coin)
}
if err := d.QueryRow(countSQL, args...).Scan(&total); err != nil {
return nil, 0, err
}
// Fetch page
offset := (page - 1) * limit
query := `SELECT id, coin, direction, status, entry_spread, exit_spread,
long_exchange, short_exchange, long_entry, long_exit, short_entry, short_exit,
long_pnl, short_pnl, fee_entry, fee_exit, net_pnl,
amount_usd, scale_count, exit_reason, convergence, opened_at, closed_at,
pnl_long_usd, pnl_short_usd, fee_long_usd, fee_short_usd
FROM trades WHERE status='closed'`
if coin != "" {
query += " AND coin=?"
}
query += " ORDER BY closed_at DESC LIMIT ? OFFSET ?"
allArgs := args
allArgs = append(allArgs, limit, offset)
rows, err := d.Query(query, allArgs...)
if err != nil {
return nil, 0, err
}
defer rows.Close()
trades, err := scanTrades(rows)
return trades, total, err
}
// SaveOrder inserts an order record.
func (d *DB) SaveOrder(o *OrderRecord) (int64, error) {
res, err := d.Exec(`INSERT INTO orders
(trade_id, leg, type, exchange, side, price, size, fee, order_id, status, created_at)
VALUES (?,?,?,?,?, ?,?,?,?,?, ?)`,
o.TradeID, o.Leg, o.Type, o.Exchange, o.Side,
o.Price, o.Size, o.Fee, o.OrderID, o.Status, o.CreatedAt,
)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// SaveSystemOrder inserts a system-level order record.
func (d *DB) SaveSystemOrder(o *SystemOrderRecord) (int64, error) {
res, err := d.Exec(`INSERT INTO system_orders
(trade_id, type, status, spread, long_price, short_price, long_order_id, short_order_id, created_at)
VALUES (?,?,?,?,?, ?,?,?,?)`,
o.TradeID, o.Type, o.Status, o.Spread,
o.LongPrice, o.ShortPrice, o.LongOrderID, o.ShortOrderID, o.CreatedAt,
)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// GetTradeByID returns a single trade with its orders.
func (d *DB) GetTradeByID(id int64) (*TradeRecord, []OrderRecord, error) {
row := d.QueryRow(`SELECT id, coin, direction, status, entry_spread, exit_spread,
long_exchange, short_exchange, long_entry, long_exit, short_entry, short_exit,
long_pnl, short_pnl, fee_entry, fee_exit, net_pnl,
amount_usd, scale_count, exit_reason, convergence, opened_at, closed_at,
pnl_long_usd, pnl_short_usd, fee_long_usd, fee_short_usd
FROM trades WHERE id=?`, id)
var t TradeRecord
err := row.Scan(
&t.ID, &t.Coin, &t.Direction, &t.Status, &t.EntrySpread, &t.ExitSpread,
&t.LongExchange, &t.ShortExchange, &t.LongEntry, &t.LongExit, &t.ShortEntry, &t.ShortExit,
&t.LongPnl, &t.ShortPnl, &t.FeeEntry, &t.FeeExit, &t.NetPnl,
&t.AmountUSD, &t.ScaleCount, &t.ExitReason, &t.Convergence, &t.OpenedAt, &t.ClosedAt,
&t.PnlLongUSD, &t.PnlShortUSD, &t.FeeLongUSD, &t.FeeShortUSD,
)
if err != nil {
return nil, nil, err
}
// Fetch orders
oRows, err := d.Query(`SELECT id, trade_id, leg, type, exchange, side,
price, size, fee, order_id, status, created_at
FROM orders WHERE trade_id=? ORDER BY id`, id)
if err != nil {
return nil, nil, err
}
defer oRows.Close()
var orders []OrderRecord
for oRows.Next() {
var o OrderRecord
if err := oRows.Scan(&o.ID, &o.TradeID, &o.Leg, &o.Type, &o.Exchange, &o.Side,
&o.Price, &o.Size, &o.Fee, &o.OrderID, &o.Status, &o.CreatedAt); err != nil {
return nil, nil, err
}
orders = append(orders, o)
}
return &t, orders, nil
}
func scanTrades(rows *sql.Rows) ([]TradeRecord, error) {
var trades []TradeRecord
for rows.Next() {
var t TradeRecord
if err := rows.Scan(
&t.ID, &t.Coin, &t.Direction, &t.Status, &t.EntrySpread, &t.ExitSpread,
&t.LongExchange, &t.ShortExchange, &t.LongEntry, &t.LongExit, &t.ShortEntry, &t.ShortExit,
&t.LongPnl, &t.ShortPnl, &t.FeeEntry, &t.FeeExit, &t.NetPnl,
&t.AmountUSD, &t.ScaleCount, &t.ExitReason, &t.Convergence, &t.OpenedAt, &t.ClosedAt,
&t.PnlLongUSD, &t.PnlShortUSD, &t.FeeLongUSD, &t.FeeShortUSD,
); err != nil {
return nil, err
}
trades = append(trades, t)
}
return trades, rows.Err()
}
// GetScalePrices returns scale-in order prices for a trade, grouped by leg.
func (d *DB) GetScalePrices(tradeID int64) (longPrices, shortPrices []float64, err error) {
rows, err := d.Query(`SELECT leg, price FROM orders
WHERE trade_id=? AND type='scale' AND price IS NOT NULL
ORDER BY id`, tradeID)
if err != nil {
return nil, nil, err
}
defer rows.Close()
for rows.Next() {
var leg string
var price float64
if err := rows.Scan(&leg, &price); err != nil {
return nil, nil, err
}
switch leg {
case "long":
longPrices = append(longPrices, price)
case "short":
shortPrices = append(shortPrices, price)
}
}
return longPrices, shortPrices, rows.Err()
}
// GetAllClosedTrades returns all closed trades for PnL history restoration.
func (d *DB) GetAllClosedTrades() ([]TradeRecord, error) {
rows, err := d.Query(`SELECT id, coin, direction, status, entry_spread, exit_spread,
long_exchange, short_exchange, long_entry, long_exit, short_entry, short_exit,
long_pnl, short_pnl, fee_entry, fee_exit, net_pnl,
amount_usd, scale_count, exit_reason, convergence, opened_at, closed_at,
pnl_long_usd, pnl_short_usd, fee_long_usd, fee_short_usd
FROM trades WHERE status='closed' ORDER BY id`)
if err != nil {
return nil, err
}
defer rows.Close()
return scanTrades(rows)
}
// GetClosedStats returns convergence counts from the database.
func (d *DB) GetClosedStats() (converged, diverged, flat, total int, err error) {
if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed'").Scan(&total); err != nil {
return
}
if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed' AND convergence='价差收敛'").Scan(&converged); err != nil {
return
}
if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed' AND convergence='价差发散'").Scan(&diverged); err != nil {
return
}
if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed' AND (convergence IS NULL OR convergence NOT IN ('价差收敛','价差发散'))").Scan(&flat); err != nil {
return
}
return
}
+60
View File
@@ -0,0 +1,60 @@
package db
import (
"time"
)
// TrendEventRecord represents a persisted trend state transition.
type TrendEventRecord struct {
ID int64 `json:"id"`
Coin string `json:"coin"`
PrevState string `json:"prev_state"`
NewState string `json:"new_state"`
Direction string `json:"direction"`
ZScore float64 `json:"z_score"`
Volatility float64 `json:"volatility"`
BGChange float64 `json:"bg_change"`
HLChange float64 `json:"hl_change"`
BNChange float64 `json:"bn_change"`
OKXChange float64 `json:"okx_change"`
ExAgree int `json:"ex_agree"`
ExTotal int `json:"ex_total"`
CreatedAt string `json:"created_at"`
}
// InsertTrendEvent saves a trend event to the database.
func (d *DB) InsertTrendEvent(coin, prevState, newState, direction string, zScore, volatility, bgChange, hlChange, bnChange, okxChange float64, exAgree, exTotal int) error {
_, err := d.Exec(`
INSERT INTO trend_events (coin, prev_state, new_state, direction, z_score, volatility, bg_change, hl_change, bn_change, okx_change, ex_agree, ex_total, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
coin, prevState, newState, direction, zScore, volatility, bgChange, hlChange, bnChange, okxChange, exAgree, exTotal, Now().Format(time.RFC3339))
return err
}
// GetTrendEvents returns trend events ordered by creation time descending.
func (d *DB) GetTrendEvents(limit int) ([]TrendEventRecord, error) {
if limit <= 0 {
limit = 100
}
rows, err := d.Query(`
SELECT id, coin, prev_state, new_state, direction, z_score, volatility, bg_change, hl_change, bn_change, okx_change, ex_agree, ex_total, created_at
FROM trend_events
ORDER BY created_at DESC
LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var result []TrendEventRecord
for rows.Next() {
var r TrendEventRecord
if err := rows.Scan(&r.ID, &r.Coin, &r.PrevState, &r.NewState, &r.Direction,
&r.ZScore, &r.Volatility, &r.BGChange, &r.HLChange, &r.BNChange, &r.OKXChange,
&r.ExAgree, &r.ExTotal, &r.CreatedAt); err != nil {
return nil, err
}
result = append(result, r)
}
return result, rows.Err()
}
+64
View File
@@ -0,0 +1,64 @@
package db
import "time"
// TrendSignalRecord mirrors the trend_signals table row.
type TrendSignalRecord struct {
ID int64
Coin string
Type string // "enter" or "exit"
SignalScore *float64
Price *float64
EMA52 *float64
EMASlope *float64
VolumeRatio *float64
Range24h *float64
VolBaseline *float64
PriceAboveEMA bool
State *string
CreatedAt time.Time
}
// SaveTrendSignal inserts a new trend signal record.
func (d *DB) SaveTrendSignal(s *TrendSignalRecord) (int64, error) {
pa := 0
if s.PriceAboveEMA {
pa = 1
}
res, err := d.Exec(`INSERT INTO trend_signals
(coin, type, signal_score, price, ema_52, ema_slope, volume_ratio,
range_24h, vol_baseline, price_above_ema, state, created_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`,
s.Coin, s.Type, s.SignalScore, s.Price, s.EMA52, s.EMASlope, s.VolumeRatio,
s.Range24h, s.VolBaseline, pa, s.State, s.CreatedAt,
)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// GetTrendSignals returns the most recent N trend signal records.
func (d *DB) GetTrendSignals(limit int) ([]TrendSignalRecord, error) {
rows, err := d.Query(`SELECT id, coin, type, signal_score, price, ema_52, ema_slope,
volume_ratio, range_24h, vol_baseline, price_above_ema, state, created_at
FROM trend_signals ORDER BY id DESC LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var signals []TrendSignalRecord
for rows.Next() {
var s TrendSignalRecord
var pa int
if err := rows.Scan(&s.ID, &s.Coin, &s.Type, &s.SignalScore, &s.Price,
&s.EMA52, &s.EMASlope, &s.VolumeRatio, &s.Range24h, &s.VolBaseline,
&pa, &s.State, &s.CreatedAt); err != nil {
return nil, err
}
s.PriceAboveEMA = pa == 1
signals = append(signals, s)
}
return signals, rows.Err()
}
+55 -17
View File
@@ -4,39 +4,50 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"strconv" "math"
"strings" "strings"
"time" "time"
) )
// BinanceWS connects to Binance WS for ticker data.
// Splits symbols across multiple combined-stream connections.
type BinanceWS struct { type BinanceWS struct {
Tracked []string Tracked []string
connections int
} }
func NewBinanceWS(tracked []string) *BinanceWS { func NewBinanceWS(tracked []string) *BinanceWS {
return &BinanceWS{Tracked: tracked} conns := int(math.Ceil(float64(len(tracked)) / 60))
if conns < 1 {
conns = 1
} }
// Run connects to Binance WS and streams bookTicker data. if conns > 10 {
func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) error { conns = 10
}
return &BinanceWS{Tracked: tracked, connections: conns}
}
func (b *BinanceWS) runSingle(symbols []string, connIdx int, updateFn func(coin string, price, bid, ask float64)) error {
streams := "" streams := ""
for i, sym := range b.Tracked { for i, sym := range symbols {
if i > 0 { if i > 0 {
streams += "/" streams += "/"
} }
streams += fmt.Sprintf("%s@bookTicker", strings.ToLower(sym)) streams += fmt.Sprintf("%s@bookTicker", strings.ToLower(sym))
} }
url := fmt.Sprintf("wss://fstream.binance.com/stream?streams=%s", streams)
conn := NewPriceConnector(url, "Binance", 120*time.Second, 30*time.Second) url := fmt.Sprintf("wss://fstream.binance.com/stream?streams=%s", streams)
conn.PingInterval = 45 * time.Second name := fmt.Sprintf("Binance-%d", connIdx)
conn := NewPriceConnector(url, name, 60*time.Second, 15*time.Second)
// No client-side pings — let the proxy handle keepalive
conn.PingInterval = 0
conn.OnConnect = func() { conn.OnConnect = func() {
log.Printf("[Binance WS] Connected") log.Printf("[%s] Connected (%d symbols)", name, len(symbols))
} }
conn.OnMessage = func(msg []byte) { conn.OnMessage = func(msg []byte) {
// Combined stream: {"stream":"...","data":{...}}
// Navigate through "data" using map to avoid field name conflicts
var raw map[string]json.RawMessage var raw map[string]json.RawMessage
if err := json.Unmarshal(msg, &raw); err != nil { if err := json.Unmarshal(msg, &raw); err != nil {
return return
@@ -46,12 +57,12 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
return return
} }
// Parse data object as flat map to extract fields by exact name // Parse data as a generic map to avoid field name conflicts
// (bookTicker has both "b" bid price and "B" bid quantity)
var dataMap map[string]interface{} var dataMap map[string]interface{}
if err := json.Unmarshal(dataRaw, &dataMap); err != nil { if err := json.Unmarshal(dataRaw, &dataMap); err != nil {
return return
} }
symbol, _ := dataMap["s"].(string) symbol, _ := dataMap["s"].(string)
bidStr, _ := dataMap["b"].(string) bidStr, _ := dataMap["b"].(string)
askStr, _ := dataMap["a"].(string) askStr, _ := dataMap["a"].(string)
@@ -59,13 +70,12 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
return return
} }
bid, err1 := strconv.ParseFloat(bidStr, 64) bid := parseFloat(bidStr)
ask, err2 := strconv.ParseFloat(askStr, 64) ask := parseFloat(askStr)
if err1 != nil || err2 != nil || bid <= 0 || ask <= 0 { if bid <= 0 || ask <= 0 {
return return
} }
// Extract coin name (e.g., "BTCUSDT" -> "BTC")
coin := symbolToCoin(symbol, "USDT") coin := symbolToCoin(symbol, "USDT")
if coin == "" { if coin == "" {
return return
@@ -77,3 +87,31 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
return conn.Run() return conn.Run()
} }
func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) error {
if len(b.Tracked) == 0 {
return nil
}
n := b.connections
perConn := (len(b.Tracked) + n - 1) / n
errCh := make(chan error, n)
for i := 0; i < n; i++ {
start := i * perConn
end := start + perConn
if end > len(b.Tracked) {
end = len(b.Tracked)
}
if start >= end {
errCh <- nil
continue
}
batch := b.Tracked[start:end]
go func(idx int, syms []string) {
errCh <- b.runSingle(syms, idx, updateFn)
}(i+1, batch)
}
return <-errCh
}
-230
View File
@@ -1,230 +0,0 @@
package exchange
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"strconv"
"strings"
"time"
)
type BitgetTrade struct {
APIKey string
APISecret string
Passphrase string
client *http.Client
}
func NewBitgetTrade(apiKey, apiSecret, passphrase string) *BitgetTrade {
return &BitgetTrade{
APIKey: apiKey,
APISecret: apiSecret,
Passphrase: passphrase,
client: &http.Client{Timeout: 10 * time.Second},
}
}
func (b *BitgetTrade) PlaceMarketOrder(side, symbol, size, tradeSide, holdSide string) (string, error) {
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
method := "POST"
requestPath := "/api/v2/mix/order/place-order"
host := "https://api.bitget.com"
body := map[string]interface{}{
"marginCoin": "USDT",
"symbol": symbol,
"productType": "USDT-FUTURES",
"side": side,
"orderType": "market",
"timeInForce": "IOC",
"marginMode": "crossed",
"tradeSide": tradeSide,
"size": size,
}
// When closing, Bitget requires holdSide to identify which position to close
if tradeSide == "close" && holdSide != "" {
body["holdSide"] = holdSide
}
bodyJSON, _ := json.Marshal(body)
sign := b.sign(method, requestPath, ts, string(bodyJSON))
url := host + requestPath
req, _ := http.NewRequest(method, url, strings.NewReader(string(bodyJSON)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("ACCESS-KEY", b.APIKey)
req.Header.Set("ACCESS-SIGN", sign)
req.Header.Set("ACCESS-TIMESTAMP", ts)
req.Header.Set("ACCESS-PASSPHRASE", b.Passphrase)
resp, err := b.client.Do(req)
if err != nil {
return "", fmt.Errorf("http request: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var result struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
OrderID string `json:"orderId"`
} `json:"data"`
}
if err := json.Unmarshal(respBody, &result); err != nil {
return "", fmt.Errorf("parse: %s", string(respBody))
}
if result.Code != "00000" {
return "", fmt.Errorf("bitget error: %s - %s", result.Code, result.Msg)
}
return result.Data.OrderID, nil
}
// GetTradeFee queries the fills endpoint for actual fee charged.
// Waits 1s before querying because Bitget's fills API may lag behind
// the place-order response. Returns 0 if no fills yet (caller uses
// estimated fee from config as fallback).
func (b *BitgetTrade) GetTradeFee(symbol, orderID string) (feeUSD float64, err error) {
time.Sleep(1 * time.Second)
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
method := "GET"
requestPath := "/api/v2/mix/order/fills?symbol=" + symbol + "&orderId=" + orderID + "&productType=USDT-FUTURES"
host := "https://api.bitget.com"
sign := b.sign(method, requestPath, ts, "")
url := host + requestPath
req, _ := http.NewRequest(method, url, nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("ACCESS-KEY", b.APIKey)
req.Header.Set("ACCESS-SIGN", sign)
req.Header.Set("ACCESS-TIMESTAMP", ts)
req.Header.Set("ACCESS-PASSPHRASE", b.Passphrase)
resp, err := b.client.Do(req)
if err != nil {
return 0, fmt.Errorf("http: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var raw struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data struct {
FillList []json.RawMessage `json:"fillList"`
} `json:"data"`
}
if err := json.Unmarshal(respBody, &raw); err != nil {
return 0, fmt.Errorf("parse: %s", string(respBody))
}
if raw.Code != "00000" {
return 0, fmt.Errorf("bitget error: %s - %s", raw.Code, raw.Msg)
}
var totalFee float64
for _, item := range raw.Data.FillList {
var fill struct {
FillFee string `json:"fillFee"`
}
if err := json.Unmarshal(item, &fill); err != nil {
continue
}
f, _ := strconv.ParseFloat(fill.FillFee, 64)
totalFee += math.Abs(f)
}
return totalFee, nil
}
func (b *BitgetTrade) sign(method, requestPath, timestamp, body string) string {
raw := timestamp + method + requestPath + body
mac := hmac.New(sha256.New, []byte(b.APISecret))
mac.Write([]byte(raw))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
func (b *BitgetTrade) GetBalance() (float64, error) {
ts := fmt.Sprintf("%d", time.Now().UnixMilli())
method := "GET"
host := "https://api.bitget.com"
requestPath := "/api/v2/mix/account/accounts?productType=USDT-FUTURES"
sign := b.sign(method, requestPath, ts, "")
url := host + requestPath
req, _ := http.NewRequest(method, url, nil)
req.Header.Set("ACCESS-KEY", b.APIKey)
req.Header.Set("ACCESS-SIGN", sign)
req.Header.Set("ACCESS-TIMESTAMP", ts)
req.Header.Set("ACCESS-PASSPHRASE", b.Passphrase)
resp, err := b.client.Do(req)
if err != nil {
return 0, fmt.Errorf("http: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var raw map[string]interface{}
if err := json.Unmarshal(respBody, &raw); err != nil {
return 0, fmt.Errorf("parse: %s", string(respBody))
}
code, _ := raw["code"].(string)
if code != "00000" && code != "" {
msg, _ := raw["msg"].(string)
return 0, fmt.Errorf("bitget error: %s - %s", code, msg)
}
dataRaw, ok := raw["data"]
if !ok || dataRaw == nil {
return 0, fmt.Errorf("no data in response")
}
dataArr, ok := dataRaw.([]interface{})
if !ok {
return 0, fmt.Errorf("unexpected data format")
}
for _, item := range dataArr {
acct, ok := item.(map[string]interface{})
if !ok {
continue
}
if acct["marginCoin"] == "USDT" {
bal, _ := strconv.ParseFloat(fmt.Sprint(acct["available"]), 64)
return bal, nil
}
}
return 0, fmt.Errorf("no USDT account found")
}
func GetBitgetSize(symbol string, amountUSD, price float64) string {
if amountUSD < 5 {
amountUSD = 5
}
sz := amountUSD / price
switch symbol {
case "DOGEUSDT":
if sz < 1 { sz = 1 }
return fmt.Sprintf("%.0f", math.Floor(sz))
case "ONDOUSDT":
sz = math.Floor(sz*10)/10
if sz < 0.1 { sz = 0.1 }
return fmt.Sprintf("%.1f", sz)
case "OPUSDT":
sz = math.Floor(sz*10)/10
if sz < 0.1 { sz = 0.1 }
return fmt.Sprintf("%.1f", sz)
case "WIFUSDT":
sz = math.Floor(sz*10)/10
if sz < 0.1 { sz = 0.1 }
return fmt.Sprintf("%.1f", sz)
case "ARBUSDT":
sz = math.Floor(sz*100)/100
if sz < 0.01 { sz = 0.01 }
return fmt.Sprintf("%.2f", sz)
default:
return fmt.Sprintf("%.4f", sz)
}
}
-66
View File
@@ -1,66 +0,0 @@
package exchange
import (
"encoding/json"
"log"
"strconv"
"time"
)
type HyperLiquidWS struct {
Tracked []string
}
type hlAllMidsMsg struct {
Channel string `json:"channel"`
Data json.RawMessage `json:"data"`
}
type hlMidsData struct {
Mids map[string]string `json:"mids"`
}
func NewHyperLiquidWS(tracked []string) *HyperLiquidWS {
return &HyperLiquidWS{Tracked: tracked}
}
// Run connects to HyperLiquid WS and streams mid prices.
func (h *HyperLiquidWS) Run(updateFn func(coin string, price, bid, ask float64)) error {
conn := NewPriceConnector("wss://api.hyperliquid.xyz/ws", "HyperLiquid", 120*time.Second, 30*time.Second)
conn.PingInterval = 45 * time.Second
conn.OnConnect = func() {
log.Printf("[HL WS] Connected")
sub := map[string]interface{}{
"method": "subscribe",
"subscription": map[string]string{
"type": "allMids",
},
}
if err := conn.SendJSON(sub); err != nil {
log.Printf("[HL WS] Subscribe error: %v", err)
}
}
conn.OnMessage = func(msg []byte) {
var raw hlAllMidsMsg
if err := json.Unmarshal(msg, &raw); err != nil {
return
}
if raw.Channel != "allMids" {
return
}
var data hlMidsData
if err := json.Unmarshal(raw.Data, &data); err != nil {
return
}
for coin, priceStr := range data.Mids {
price, err := strconv.ParseFloat(priceStr, 64)
if err != nil || price <= 0 {
continue
}
updateFn(coin, price, 0, 0)
}
}
return conn.Run()
}
-221
View File
@@ -1,221 +0,0 @@
package exchange
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"fmt"
"log"
"math"
"strconv"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/crypto"
hl "github.com/sonirico/go-hyperliquid"
)
type HyperLiquidTrade struct {
exchange *hl.Exchange
info *hl.Info
privateKey *ecdsa.PrivateKey
mainAddress string
nonceMu sync.Mutex
lastNonce int64
configured bool
// szDecimals maps coin name -> decimal places for size formatting
// Populated from HL Meta on initExchange()
szDecimals map[string]int
}
func NewHyperLiquidTrade(privateKeyHex, mainAddress, apiAddress string) (*HyperLiquidTrade, error) {
if privateKeyHex == "" {
return &HyperLiquidTrade{}, nil
}
keyHex := strings.TrimPrefix(privateKeyHex, "0x")
keyBytes, err := hex.DecodeString(keyHex)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
privKey, err := crypto.ToECDSA(keyBytes)
if err != nil {
return nil, fmt.Errorf("to ECDSA: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
info := hl.NewInfo(ctx, hl.MainnetAPIURL, true, nil, nil, nil)
return &HyperLiquidTrade{
privateKey: privKey,
mainAddress: mainAddress,
info: info,
configured: true,
}, nil
}
// InitExchange ensures the HL exchange is initialized (fetches metadata, szDecimals, etc.).
// Safe to call multiple times — no-op after first initialization.
// Must be called before GetSize or PlaceMarketOrder for accurate size formatting.
func (h *HyperLiquidTrade) InitExchange() error {
return h.initExchange()
}
func (h *HyperLiquidTrade) initExchange() error {
if h.exchange != nil {
return nil
}
if !h.configured {
return fmt.Errorf("HL not configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
meta, err := h.info.Meta(ctx)
if err != nil {
return fmt.Errorf("meta: %w", err)
}
spotMeta, err := h.info.SpotMeta(ctx)
if err != nil {
return fmt.Errorf("spot meta: %w", err)
}
h.exchange = hl.NewExchange(ctx, h.privateKey, hl.MainnetAPIURL, meta, "", h.mainAddress, spotMeta, nil)
// Build szDecimals map from HL Meta for correct size formatting
h.szDecimals = make(map[string]int, len(meta.Universe))
for _, asset := range meta.Universe {
h.szDecimals[asset.Name] = asset.SzDecimals
}
return nil
}
// GetSize returns a formatted size string for HL orders using the correct szDecimals.
func (h *HyperLiquidTrade) GetSize(coin string, amountUSD, price float64) string {
sz := amountUSD / price
decimals, ok := h.szDecimals[coin]
if !ok {
// Fallback: 4 decimal places
return fmt.Sprintf("%.4f", math.Floor(sz*10000)/10000)
}
switch decimals {
case 0:
sz = math.Floor(sz)
if sz < 1 {
sz = 1
}
return fmt.Sprintf("%.0f", sz)
case 1:
sz = math.Floor(sz*10) / 10
if sz < 0.1 {
sz = 0.1
}
return fmt.Sprintf("%.1f", sz)
case 2:
sz = math.Floor(sz*100) / 100
if sz < 0.01 {
sz = 0.01
}
return fmt.Sprintf("%.2f", sz)
default:
mult := math.Pow10(decimals)
sz = math.Floor(sz*mult) / mult
if sz < 1/mult {
sz = 1 / mult
}
return fmt.Sprintf("%."+strconv.Itoa(decimals)+"f", sz)
}
}
func (h *HyperLiquidTrade) IsConfigured() bool {
return h.configured
}
// PlaceMarketOrder places a market order and returns the raw JSON response.
func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, error) {
if !h.configured {
return "", fmt.Errorf("HL not configured")
}
if err := h.initExchange(); err != nil {
return "", fmt.Errorf("init: %w", err)
}
isBuy := side == "buy"
size, _ := strconv.ParseFloat(sz, 64)
// Find szDecimals for this coin
decimals := 4
if d, ok := h.szDecimals[coin]; ok {
decimals = d
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
log.Printf("[Order] HL MarketOpen | coin=%s isBuy=%v size=%.*f szDecimals=%d slippage=0.05 px=nil", coin, isBuy, decimals, size, decimals)
result, err := h.exchange.MarketOpen(ctx, coin, isBuy, size, nil, 0.05, nil, nil)
if err != nil {
return "", fmt.Errorf("market open: %w", err)
}
respJSON, _ := json.Marshal(result)
return string(respJSON), nil
}
// EstimateFeeFromResponse calculates the fee using the response's filled size × price
// × configured taker rate. This is NOT an actual fee from HL — HL does not return
// fee amounts in the order response. The result is equivalent to estimating from
// TradeAmountUSD, but more accurate for partial fills since it uses actual filled sz/px.
func (h *HyperLiquidTrade) EstimateFeeFromResponse(orderResponseJSON string, takerFeePct float64) (feeUSD float64, err error) {
// HL MarketOpen returns a single OrderStatus object (NOT wrapped in statuses array):
// {"resting":..., "filled":{"totalSz":"82.5","avgPx":"0.12153","oid":52463955193}, "error":...}
var resp struct {
Resting *json.RawMessage `json:"resting,omitempty"`
Filled *struct {
TotalSz string `json:"totalSz"`
AvgPx string `json:"avgPx"`
} `json:"filled,omitempty"`
Error *string `json:"error,omitempty"`
}
if err := json.Unmarshal([]byte(orderResponseJSON), &resp); err != nil {
return 0, fmt.Errorf("parse: %w", err)
}
if resp.Filled != nil {
sz, _ := strconv.ParseFloat(resp.Filled.TotalSz, 64)
px, _ := strconv.ParseFloat(resp.Filled.AvgPx, 64)
if sz > 0 && px > 0 {
return sz * px * takerFeePct / 100, nil
}
}
return 0, fmt.Errorf("no filled status in response")
}
func (h *HyperLiquidTrade) GetBalance() (float64, error) {
if !h.configured {
return 0, fmt.Errorf("HL not configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// HL testnet USDC is on spot, not perp. Use SpotUserState.
state, err := h.info.SpotUserState(ctx, h.mainAddress)
if err != nil {
return 0, fmt.Errorf("spot user state: %w", err)
}
for _, b := range state.Balances {
if b.Coin == "USDC" {
total, _ := strconv.ParseFloat(b.Total, 64)
hold, _ := strconv.ParseFloat(b.Hold, 64)
return total - hold, nil
}
}
return 0, fmt.Errorf("USDC balance not found in spot state")
}
+125
View File
@@ -0,0 +1,125 @@
package exchange
import (
"encoding/json"
"log"
"strings"
"time"
)
// OKXWS connects to OKX WebSocket for tickers channel (perpetual swaps).
type OKXWS struct {
Tracked []string // OKX symbols like BTC-USDT-SWAP
}
type okxSubscribeMsg struct {
Op string `json:"op"`
Args []okxChannel `json:"args"`
}
type okxChannel struct {
Channel string `json:"channel"`
InstID string `json:"instId"`
}
type okxTickerMsg struct {
Arg okxChannel `json:"arg"`
Data []okxTickerData `json:"data"`
}
type okxTickerData struct {
Last string `json:"last"`
BidPx string `json:"bidPx"`
AskPx string `json:"askPx"`
}
func NewOKXWS(tracked []string) *OKXWS {
return &OKXWS{
Tracked: tracked,
}
}
// Run connects to OKX WS and streams ticker data.
func (o *OKXWS) Run(updateFn func(coin string, price, bid, ask float64)) error {
url := "wss://ws.okx.com:8443/ws/v5/public"
conn := NewPriceConnector(url, "OKX", 120*time.Second, 30*time.Second)
conn.PingInterval = 20 * time.Second // OKX requires ping within 30s
conn.TextPing = true // OKX expects text "ping" message
conn.OnConnect = func() {
log.Printf("[OKX WS] Connected, subscribing (%d symbols)", len(o.Tracked))
// Batch subscriptions — OKX has rate limits (3 req/s, 480/hr)
batchSize := 20
for i := 0; i < len(o.Tracked); i += batchSize {
end := i + batchSize
if end > len(o.Tracked) {
end = len(o.Tracked)
}
batch := o.Tracked[i:end]
args := make([]okxChannel, len(batch))
for j, sym := range batch {
args[j] = okxChannel{
Channel: "tickers",
InstID: sym,
}
}
sub := okxSubscribeMsg{
Op: "subscribe",
Args: args,
}
if err := conn.SendJSON(sub); err != nil {
log.Printf("[OKX WS] Subscribe error (batch %d): %v", i/batchSize, err)
}
}
}
conn.OnMessage = func(msg []byte) {
// Handle OKX text "pong" response
if string(msg) == "pong" {
return
}
// Check for subscription confirmation or error response
var generic map[string]interface{}
if err := json.Unmarshal(msg, &generic); err == nil {
if evt, _ := generic["event"].(string); evt == "error" || evt == "subscribe" {
return
}
}
var ticker okxTickerMsg
if err := json.Unmarshal(msg, &ticker); err != nil {
return
}
if len(ticker.Data) == 0 || ticker.Data[0].Last == "" {
return
}
// Convert BTC-USDT-SWAP -> BTC
coin := okxSymbolToCoin(ticker.Arg.InstID)
if coin == "" {
return
}
price := parseFloat(ticker.Data[0].Last)
if price > 0 {
bid := parseFloat(ticker.Data[0].BidPx)
ask := parseFloat(ticker.Data[0].AskPx)
updateFn(coin, price, bid, ask)
}
}
return conn.Run()
}
// okxSymbolToCoin converts "BTC-USDT-SWAP" to "BTC".
func okxSymbolToCoin(symbol string) string {
// Strip "-USDT-SWAP" suffix
const suffix = "-USDT-SWAP"
if !strings.HasSuffix(symbol, suffix) {
return ""
}
return symbol[:len(symbol)-len(suffix)]
}
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Binance Momentum Monitor</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/binance-main.jsx"></script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
import{r as c,j as t,c as B,R as E}from"./client-DYDkQYN6.js";const w="https://www.binance.com/en/futures/";function M(){const[r,S]=c.useState([]),[h,g]=c.useState([]),[C,x]=c.useState("● 连接中..."),[v,u]=c.useState(!1),[i,y]=c.useState("change_abs"),[l,j]=c.useState("desc"),d=c.useRef(null),p=c.useCallback(()=>{const e=new EventSource("/events");d.current=e,e.addEventListener("connected",()=>{x("● 已连接"),u(!0)}),e.onmessage=a=>{try{const n=JSON.parse(a.data);switch(n.event){case"binance_momentum":S(n.data);break;case"binance_alert":g(s=>[n.data,...s].slice(0,200));break}}catch{}},e.onerror=()=>{x("● 已断开 (重连中...)"),u(!1),e.close(),setTimeout(p,3e3)}},[]);c.useEffect(()=>(p(),()=>{d.current&&d.current.close()}),[p]),c.useEffect(()=>{const a=setInterval(async()=>{try{const s=await(await fetch("/api/binance-alerts?limit=50")).json();s.alerts&&s.alerts.length>0&&g(_=>{const $=new Set(_.map(o=>o.timestamp)),N=[..._];for(const o of s.alerts)$.has(o.timestamp)||N.push(o);return N.slice(0,200)})}catch{}},1e4);return()=>clearInterval(a)},[]);const m=e=>{i===e?j(a=>a==="asc"?"desc":"asc"):(y(e),j("desc"))},f=[...r].sort((e,a)=>{let n,s;switch(i){case"coin":return n=e.coin,s=a.coin,l==="asc"?n.localeCompare(s):s.localeCompare(n);case"price":n=e.price||0,s=a.price||0;break;case"change_abs":n=Math.abs(e.change_pct)||0,s=Math.abs(a.change_pct)||0;break;case"change":n=e.change_pct||0,s=a.change_pct||0;break;default:return 0}return l==="asc"?n-s:s-n}),k=r.filter(e=>e.buffer_full).length,b=r.filter(e=>Math.abs(e.change_pct)>=10).length;return t.jsxs("div",{id:"bm-app",children:[t.jsxs("header",{children:[t.jsxs("div",{children:[t.jsx("h1",{children:"BN 1-Min Momentum Monitor"}),t.jsx("span",{className:"header-subtitle",children:"Binance 1分钟涨跌监控 | 阈值 ≥10%"})]}),t.jsxs("div",{className:"header-right",children:[t.jsxs("span",{className:"stat-badge",children:[r.length," coins"]}),t.jsxs("span",{className:"stat-badge",children:[k," ready"]}),b>0&&t.jsxs("span",{className:"stat-badge stat-alert",children:[b," alerting"]}),t.jsx("span",{className:v?"status-online":"status-offline",children:C})]})]}),h.length>0&&t.jsxs("section",{className:"card card-wide alert-section",children:[t.jsxs("h2",{children:["Alert History (",h.length,")"]}),t.jsx("div",{className:"alert-scroll",children:h.slice(0,50).map((e,a)=>{var n,s;return t.jsxs("div",{className:`alert-item alert-${e.direction}`,children:[t.jsx("span",{className:"alert-icon",children:e.direction==="up"?"🟢":"🔴"}),t.jsx("a",{href:`${w}${e.coin}USDT`,target:"_blank",rel:"noreferrer",className:"alert-coin",children:e.coin}),t.jsxs("span",{className:e.direction==="up"?"text-green":"text-red",children:[e.change_pct>=0?"+":"",(n=e.change_pct)==null?void 0:n.toFixed(2),"%"]}),t.jsxs("span",{className:"alert-price",children:["$",(s=e.price)==null?void 0:s.toFixed(4)]}),t.jsx("span",{className:"alert-time text-dim",children:new Date(e.timestamp).toLocaleTimeString()})]},a)})})]}),t.jsxs("section",{className:"card card-wide",children:[t.jsxs("h2",{children:["All Coins (",r.length,")"]}),t.jsx("div",{className:"table-wrap",style:{maxHeight:"calc(100vh - 200px)"},children:t.jsxs("table",{children:[t.jsx("thead",{children:t.jsxs("tr",{children:[t.jsxs("th",{onClick:()=>m("coin"),style:{cursor:"pointer"},children:["Coin ",i==="coin"?l==="asc"?"▲":"▼":""]}),t.jsxs("th",{onClick:()=>m("price"),style:{cursor:"pointer"},children:["Price ",i==="price"?l==="asc"?"▲":"▼":""]}),t.jsxs("th",{onClick:()=>m("change_abs"),style:{cursor:"pointer"},children:["1m Change ",i==="change_abs"?l==="asc"?"▲":"▼":""]}),t.jsx("th",{children:"Bar"})]})}),t.jsxs("tbody",{children:[f.map(e=>{const a=e.buffer_full?e.change_pct>=10?"row-surge-up":e.change_pct<=-10?"row-surge-down":e.change_pct>0?"row-up":e.change_pct<0?"row-down":"":"row-warmup",n=Math.min(Math.abs(e.change_pct)/15*100,100),s=e.change_pct>=10?"bar-green":e.change_pct<=-10?"bar-red":e.change_pct>0?"bar-green-dim":"bar-red-dim";return t.jsxs("tr",{className:a,children:[t.jsxs("td",{children:[t.jsx("a",{href:`${w}${e.coin}USDT`,target:"_blank",rel:"noreferrer",className:"coin-link",children:e.coin}),!e.buffer_full&&t.jsx("span",{className:"warmup-badge",children:"···"})]}),t.jsx("td",{className:"text-right mono",children:e.price?`$${e.price.toFixed(4)}`:"-"}),t.jsx("td",{className:`text-right mono ${e.change_pct>=10?"text-green":e.change_pct<=-10?"text-red":e.change_pct>0?"text-green":e.change_pct<0?"text-red":"text-dim"}`,children:e.buffer_full?`${e.change_pct>=0?"+":""}${e.change_pct.toFixed(2)}%`:"warming..."}),t.jsx("td",{children:t.jsx("div",{className:"bar-track",children:t.jsx("div",{className:`bar-fill ${s}`,style:{width:`${n}%`}})})})]},e.coin)}),f.length===0&&t.jsx("tr",{children:t.jsx("td",{colSpan:4,className:"loading",children:"Waiting for data..."})})]})]})})]})]})}B.createRoot(document.getElementById("root")).render(t.jsx(E.StrictMode,{children:t.jsx(M,{})}));
+1
View File
@@ -0,0 +1 @@
:root{--bg: #0d1117;--card: #161b22;--border: #30363d;--text: #c9d1d9;--text-dim: #8b949e;--accent: #58a6ff;--green: #3fb950;--red: #f85149;--yellow: #d29922}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.5;min-height:100vh}#bm-app{max-width:1200px;margin:0 auto;padding:16px}header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--card);border:1px solid var(--border);border-radius:8px;margin-bottom:12px;flex-wrap:wrap;gap:8px}header h1{font-size:18px;font-weight:600}.header-subtitle{font-size:12px;color:var(--text-dim);margin-left:12px}.header-right{display:flex;align-items:center;gap:10px;font-size:13px}.stat-badge{background:#58a6ff1a;color:var(--accent);padding:2px 8px;border-radius:10px;font-size:12px;font-weight:500}.stat-alert{background:#f8514926;color:var(--red);animation:pulse 2s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.6}}.status-online{color:var(--green)}.status-offline{color:var(--red)}.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:12px}.card h2{font-size:14px;font-weight:600;color:var(--text-dim);margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.alert-scroll{display:flex;flex-wrap:wrap;gap:6px;max-height:120px;overflow-y:auto}.alert-item{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:4px;font-size:13px;font-variant-numeric:tabular-nums}.alert-up{background:#3fb95014}.alert-down{background:#f8514914}.alert-icon{font-size:14px}.alert-coin{color:var(--accent);text-decoration:none;font-weight:600;min-width:50px}.alert-coin:hover{text-decoration:underline}.alert-price{color:var(--text-dim);min-width:90px}.alert-time{font-size:11px}.table-wrap{overflow-x:auto;overflow-y:auto}table{width:100%;border-collapse:collapse;font-size:13px}th{text-align:left;padding:6px 8px;color:var(--text-dim);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.5px;position:sticky;top:0;background:var(--card);border-bottom:1px solid var(--border);-webkit-user-select:none;user-select:none}th:hover{color:var(--accent)}td{padding:4px 8px;border-bottom:1px solid rgba(48,54,61,.5);white-space:nowrap}tr:hover td{background:#58a6ff0d}.coin-link{color:var(--accent);text-decoration:none;font-weight:600}.coin-link:hover{text-decoration:underline}.warmup-badge{font-size:10px;color:var(--text-dim);margin-left:6px}.row-warmup td{opacity:.45}.row-surge-up td{background:#3fb9501a!important}.row-surge-up:hover td{background:#3fb9502e!important}.row-surge-down td{background:#f851491a!important}.row-surge-down:hover td{background:#f851492e!important}.row-up td{background:#3fb95008}.row-down td{background:#f8514908}.bar-track{width:100px;height:6px;background:#30363d80;border-radius:3px;overflow:hidden}.bar-fill{height:100%;border-radius:3px;transition:width .3s ease}.bar-green{background:var(--green)}.bar-red{background:var(--red)}.bar-green-dim{background:#3fb95080}.bar-red-dim{background:#f8514980}.text-green{color:var(--green)}.text-red{color:var(--red)}.text-dim{color:var(--text-dim)}.text-right{text-align:right}.mono{font-variant-numeric:tabular-nums;font-family:SF Mono,Cascadia Code,monospace}.loading{text-align:center;color:var(--text-dim);padding:20px!important}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#484f58}@media (max-width: 768px){header{flex-direction:column}#bm-app{padding:8px}}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
:root{--bg: #0d1117;--card: #161b22;--border: #30363d;--text: #c9d1d9;--text-dim: #8b949e;--accent: #58a6ff;--green: #3fb950;--red: #f85149;--yellow: #d29922;--blue: #58a6ff}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.5;min-height:100vh}#app{max-width:1440px;margin:0 auto;padding:16px}header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--card);border:1px solid var(--border);border-radius:8px;margin-bottom:16px}header h1{font-size:18px;font-weight:600}.header-meta{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-dim)}.sep{color:var(--border)}.status-offline{color:var(--red)}.status-online{color:var(--green)}.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.card-wide{grid-column:1 / -1}.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px}.card h2{font-size:14px;font-weight:600;color:var(--text-dim);margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.stats-row{display:flex;gap:16px;flex-wrap:wrap}.stat{display:flex;flex-direction:column;align-items:center;min-width:60px}.stat label{font-size:11px;color:var(--text-dim);margin-bottom:2px}.stat span{font-size:20px;font-weight:700}.pct-green{color:var(--green)}.pct-red{color:var(--red)}.pct-gray{color:var(--text-dim)}.pct-yellow{color:var(--yellow)}.pct-blue{color:var(--blue)}#conn-detail{font-size:11px;white-space:nowrap}.table-wrap{overflow-x:auto;max-height:320px;overflow-y:auto}table{width:100%;border-collapse:collapse;font-size:13px}th{text-align:left;padding:6px 8px;color:var(--text-dim);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.5px;position:sticky;top:0;background:var(--card);border-bottom:1px solid var(--border)}td{padding:5px 8px;border-bottom:1px solid rgba(48,54,61,.5);white-space:nowrap}tr:hover td{background:#58a6ff0d}.trade-row{cursor:pointer}.loading{text-align:center;color:var(--text-dim);padding:20px!important}.text-green{color:var(--green)}.text-red{color:var(--red)}.text-yellow{color:var(--yellow)}.text-dim{color:var(--text-dim)}.text-right{text-align:right}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#484f58}@media (max-width: 768px){.grid{grid-template-columns:1fr}header{flex-direction:column;gap:8px}.stats-row{justify-content:center}}#bl-body{display:flex;gap:8px;flex-wrap:wrap}.bl-item{background:#f851491a;border:1px solid rgba(248,81,73,.3);border-radius:4px;padding:4px 10px;font-size:12px;color:var(--red);cursor:default}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;z-index:1000;display:flex;align-items:flex-start;justify-content:center;padding:40px 16px;overflow-y:auto}.modal-content{background:var(--card);border:1px solid var(--border);border-radius:12px;max-width:700px;width:100%;box-shadow:0 8px 32px #00000080}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border)}.modal-header h2{font-size:16px;margin:0;padding:0;border:none;color:var(--text)}.modal-close{background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;padding:4px 8px;border-radius:4px;line-height:1}.modal-close:hover{background:#ffffff1a;color:var(--text)}#trade-detail-body{padding:0}.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:0}.detail-section{padding:14px 20px;border-bottom:1px solid rgba(48,54,61,.4)}.detail-section:last-child{border-bottom:none}.detail-section-full{grid-column:1 / -1}.detail-section h3{font-size:12px;color:var(--text-dim);font-weight:600;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px}.detail-row{display:flex;justify-content:space-between;padding:3px 0;font-size:13px}.detail-row .label{color:var(--text-dim)}.detail-row .value{font-weight:500}.detail-orders{width:100%;font-size:12px}.detail-orders th{background:var(--bg);font-size:10px}.detail-orders td{padding:4px 6px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
:root{--bg: #0d1117;--card: #161b22;--border: #30363d;--text: #c9d1d9;--text-dim: #8b949e;--accent: #58a6ff;--green: #3fb950;--red: #f85149;--yellow: #d29922;--blue: #58a6ff}*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.5;min-height:100vh}#app{max-width:1440px;margin:0 auto;padding:16px}header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--card);border:1px solid var(--border);border-radius:8px;margin-bottom:16px}header h1{font-size:18px;font-weight:600}.header-meta{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-dim)}.sep{color:var(--border)}.status-offline{color:var(--red)}.status-online{color:var(--green)}.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.card-wide{grid-column:1 / -1}.card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px}.card h2{font-size:14px;font-weight:600;color:var(--text-dim);margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.table-wrap{overflow-x:auto;max-height:320px;overflow-y:auto}table{width:100%;border-collapse:collapse;font-size:13px}th{text-align:left;padding:6px 8px;color:var(--text-dim);font-weight:500;font-size:11px;text-transform:uppercase;letter-spacing:.5px;position:sticky;top:0;background:var(--card);border-bottom:1px solid var(--border)}td{padding:5px 8px;border-bottom:1px solid rgba(48,54,61,.5);white-space:nowrap}tr:hover td{background:#58a6ff0d}.loading{text-align:center;color:var(--text-dim);padding:20px!important}.text-green{color:var(--green)}.text-red{color:var(--red)}.text-yellow{color:var(--yellow)}.text-dim{color:var(--text-dim)}.text-right{text-align:right}.text-orange{color:var(--yellow)}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:#484f58}@media (max-width: 768px){.grid{grid-template-columns:1fr}header{flex-direction:column;gap:8px}}.tab-btn{background:none;border:1px solid var(--border);color:var(--text-dim);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:12px;margin-left:6px}.tab-btn.active{background:var(--accent);color:#fff;border-color:var(--accent)}.tab-btn:hover:not(.active){color:var(--text);border-color:var(--text-dim)}#surge-card{grid-column:1 / -1}#surge-snapshot-table td,#surge-events-table td{font-variant-numeric:tabular-nums}.surge-active td{background:#3fb9500f}.surge-active:hover td{background:#3fb9501f!important}.surge-up td{background:#3fb9500a}.surge-up:hover td{background:#3fb9501a!important}.surge-down td{background:#f851490a}.surge-down:hover td{background:#f851491a!important}#spread-card #spread-table td{font-variant-numeric:tabular-nums}#momentum-card{grid-column:1 / -1}#momentum-table th{cursor:pointer;-webkit-user-select:none;user-select:none}#momentum-table th:hover{color:var(--accent)}#momentum-table td{font-variant-numeric:tabular-nums}#trend-card{grid-column:1 / -1}#trend-table th{-webkit-user-select:none;user-select:none}#trend-table td{font-variant-numeric:tabular-nums}.trend-state{font-weight:600;font-size:12px}.trend-alert{background:#d299220d}.trend-alert:hover td{background:#d299221a!important}.trend-confirmed{background:#3fb95014}.trend-confirmed:hover td{background:#3fb95026!important}.trend-exhausting{background:#8b949e0d}.trend-exhausting:hover td{background:#8b949e1a!important}#trend-filter-card{grid-column:1 / -1}#trend-filter-table td{font-variant-numeric:tabular-nums}.filter-pass td{background:#3fb9500f}.filter-pass:hover td{background:#3fb9501f!important}#trend-signal-card{grid-column:1 / -1}#trend-signal-table td{font-variant-numeric:tabular-nums}#high-score-card{grid-column:1 / -1}#high-score-table td{font-variant-numeric:tabular-nums}.signal-enter td{background:#3fb95014}.signal-enter:hover td{background:#3fb95026!important}.signal-exit td{background:#8b949e0d}.signal-exit:hover td{background:#8b949e1a!important}#cm-card,#cm-history-card{grid-column:1 / -1}#cm-table td,#cm-history-table td{font-variant-numeric:tabular-nums}
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Binance Momentum Monitor</title>
<script type="module" crossorigin src="/static/assets/binance-C6HzvyUG.js"></script>
<link rel="modulepreload" crossorigin href="/static/assets/client-DYDkQYN6.js">
<link rel="stylesheet" crossorigin href="/static/assets/binance-DE6-y9x2.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+12 -2
View File
@@ -1,11 +1,21 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exchange Monitor Dashboard</title> <title>Exchange Monitor Dashboard</title>
<script type="module" crossorigin src="/static/assets/index-D_JzXaOQ.js"></script> <script type="module" crossorigin src="/static/assets/main-Cg5_dPUr.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-Bh5bnFYE.css"> <link rel="modulepreload" crossorigin href="/static/assets/client-DYDkQYN6.js">
<link rel="stylesheet" crossorigin href="/static/assets/main-vvNDQq2K.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+9
View File
@@ -1,6 +1,15 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exchange Monitor Dashboard</title> <title>Exchange Monitor Dashboard</title>
-1
View File
@@ -1 +0,0 @@
../baseline-browser-mapping/dist/cli.cjs
-1
View File
@@ -1 +0,0 @@
../browserslist/cli.js
-1
View File
@@ -1 +0,0 @@
../esbuild/bin/esbuild
-1
View File
@@ -1 +0,0 @@
../jsesc/bin/jsesc
-1
View File
@@ -1 +0,0 @@
../json5/lib/cli.js
-1
View File
@@ -1 +0,0 @@
../loose-envify/cli.js
-1
View File
@@ -1 +0,0 @@
../nanoid/bin/nanoid.cjs
-1
View File
@@ -1 +0,0 @@
../@babel/parser/bin/babel-parser.js
-1
View File
@@ -1 +0,0 @@
../rollup/dist/bin/rollup
-1
View File
@@ -1 +0,0 @@
../semver/bin/semver.js
-1
View File
@@ -1 +0,0 @@
../update-browserslist-db/cli.js
-1
View File
@@ -1 +0,0 @@
../vite/bin/vite.js
-891
View File
@@ -1,891 +0,0 @@
{
"name": "exchange-monitor-frontend",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
"integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.28.6",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"dependencies": {
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
"dev": true,
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
"dev": true
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
"integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
"integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"dependencies": {
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true
},
"node_modules/@vitejs/plugin-react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
"dev": true,
"dependencies": {
"@babel/core": "^7.28.0",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.27",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.27",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz",
"integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==",
"dev": true,
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
"electron-to-chromium": "^1.5.328",
"node-releases": "^2.0.36",
"update-browserslist-db": "^1.2.3"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001791",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
"integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.349",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz",
"integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/node-releases": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
"integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/postcss": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rollup": {
"version": "4.60.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
"integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.60.2",
"@rollup/rollup-android-arm64": "4.60.2",
"@rollup/rollup-darwin-arm64": "4.60.2",
"@rollup/rollup-darwin-x64": "4.60.2",
"@rollup/rollup-freebsd-arm64": "4.60.2",
"@rollup/rollup-freebsd-x64": "4.60.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
"@rollup/rollup-linux-arm-musleabihf": "4.60.2",
"@rollup/rollup-linux-arm64-gnu": "4.60.2",
"@rollup/rollup-linux-arm64-musl": "4.60.2",
"@rollup/rollup-linux-loong64-gnu": "4.60.2",
"@rollup/rollup-linux-loong64-musl": "4.60.2",
"@rollup/rollup-linux-ppc64-gnu": "4.60.2",
"@rollup/rollup-linux-ppc64-musl": "4.60.2",
"@rollup/rollup-linux-riscv64-gnu": "4.60.2",
"@rollup/rollup-linux-riscv64-musl": "4.60.2",
"@rollup/rollup-linux-s390x-gnu": "4.60.2",
"@rollup/rollup-linux-x64-gnu": "4.60.2",
"@rollup/rollup-linux-x64-musl": "4.60.2",
"@rollup/rollup-openbsd-x64": "4.60.2",
"@rollup/rollup-openharmony-arm64": "4.60.2",
"@rollup/rollup-win32-arm64-msvc": "4.60.2",
"@rollup/rollup-win32-ia32-msvc": "4.60.2",
"@rollup/rollup-win32-x64-gnu": "4.60.2",
"@rollup/rollup-win32-x64-msvc": "4.60.2",
"fsevents": "~2.3.2"
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"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,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
}
}
}
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```
-217
View File
@@ -1,217 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
-32
View File
@@ -1,32 +0,0 @@
{
"name": "@babel/code-frame",
"version": "7.29.0",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/compat-data
> The compat-data to determine required Babel plugins
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
## Install
Using npm:
```sh
npm install --save @babel/compat-data
```
or using yarn:
```sh
yarn add @babel/compat-data
```
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
module.exports = require("./data/corejs2-built-ins.json");
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
module.exports = require("./data/corejs3-shipped-proposals.json");
File diff suppressed because it is too large Load Diff
@@ -1,5 +0,0 @@
[
"esnext.promise.all-settled",
"esnext.string.match-all",
"esnext.global-this"
]
-18
View File
@@ -1,18 +0,0 @@
{
"es6.module": {
"chrome": "61",
"and_chr": "61",
"edge": "16",
"firefox": "60",
"and_ff": "60",
"node": "13.2.0",
"opera": "48",
"op_mob": "45",
"safari": "10.1",
"ios": "10.3",
"samsung": "8.2",
"android": "61",
"electron": "2.0",
"ios_saf": "10.3"
}
}
-38
View File
@@ -1,38 +0,0 @@
{
"transform-async-to-generator": [
"bugfix/transform-async-arrows-in-class"
],
"transform-parameters": [
"bugfix/transform-edge-default-parameters",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
],
"transform-function-name": [
"bugfix/transform-edge-function-name"
],
"transform-block-scoping": [
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-destructuring": [
"bugfix/transform-safari-rest-destructuring-rhs-array"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
"transform-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"proposal-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"transform-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
],
"proposal-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
]
}
-231
View File
@@ -1,231 +0,0 @@
{
"bugfix/transform-async-arrows-in-class": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"bugfix/transform-edge-default-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-edge-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.9",
"opera_mobile": "41",
"electron": "1.2"
},
"bugfix/transform-safari-block-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "44",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-for-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "4",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "2",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-rest-destructuring-rhs-array": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "34",
"safari": "14.1",
"node": "6",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-tagged-template-caching": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.7.14",
"opera_mobile": "28",
"electron": "0.21"
},
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "15",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "10.1",
"node": "7.6",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
}
}
-843
View File
@@ -1,843 +0,0 @@
{
"transform-explicit-resource-management": {
"chrome": "141",
"edge": "141",
"firefox": "141",
"node": "25",
"electron": "39.0"
},
"transform-duplicate-named-capturing-groups-regex": {
"chrome": "126",
"opera": "112",
"edge": "126",
"firefox": "129",
"safari": "17.4",
"node": "23",
"ios": "17.4",
"rhino": "1.9",
"electron": "31.0"
},
"transform-regexp-modifiers": {
"chrome": "125",
"opera": "111",
"edge": "125",
"firefox": "132",
"node": "23",
"samsung": "27",
"electron": "31.0"
},
"transform-unicode-sets-regex": {
"chrome": "112",
"opera": "98",
"edge": "112",
"firefox": "116",
"safari": "17",
"node": "20",
"deno": "1.32",
"ios": "17",
"samsung": "23",
"opera_mobile": "75",
"electron": "24.0"
},
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
"chrome": "98",
"opera": "84",
"edge": "98",
"firefox": "75",
"safari": "15",
"node": "12",
"deno": "1.18",
"ios": "15",
"samsung": "11",
"opera_mobile": "52",
"electron": "17.0"
},
"bugfix/transform-firefox-class-in-computed-class-key": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "126",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"bugfix/transform-safari-class-field-initializer-scope": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "69",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"proposal-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"transform-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"proposal-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"proposal-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"transform-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"proposal-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"proposal-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"transform-dotall-regex": {
"chrome": "62",
"opera": "49",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "8.10",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"rhino": "1.7.15",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-named-capturing-groups-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-exponentiation-operator": {
"chrome": "52",
"opera": "39",
"edge": "14",
"firefox": "52",
"safari": "10.1",
"node": "7",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"rhino": "1.7.14",
"opera_mobile": "41",
"electron": "1.3"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-literals": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-arrow-functions": {
"chrome": "47",
"opera": "34",
"edge": "13",
"firefox": "43",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "34",
"electron": "0.36"
},
"transform-block-scoped-functions": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "46",
"safari": "10",
"node": "4",
"deno": "1",
"ie": "11",
"ios": "10",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-object-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-shorthand-properties": {
"chrome": "43",
"opera": "30",
"edge": "12",
"firefox": "33",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.14",
"opera_mobile": "30",
"electron": "0.27"
},
"transform-duplicate-keys": {
"chrome": "42",
"opera": "29",
"edge": "12",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "29",
"electron": "0.25"
},
"transform-computed-properties": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "34",
"safari": "7.1",
"node": "4",
"deno": "1",
"ios": "8",
"samsung": "4",
"rhino": "1.8",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-for-of": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-sticky-regex": {
"chrome": "49",
"opera": "36",
"edge": "13",
"firefox": "3",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.15",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-unicode-escapes": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-unicode-regex": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "46",
"safari": "12",
"node": "6",
"deno": "1",
"ios": "12",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-spread": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "14.1",
"node": "6.5",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "11",
"node": "6",
"deno": "1",
"ios": "11",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-typeof-symbol": {
"chrome": "48",
"opera": "35",
"edge": "12",
"firefox": "36",
"safari": "9",
"node": "6",
"deno": "1",
"ios": "9",
"samsung": "5",
"rhino": "1.8",
"opera_mobile": "35",
"electron": "0.37"
},
"transform-new-target": {
"chrome": "46",
"opera": "33",
"edge": "14",
"firefox": "41",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-regenerator": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-member-expression-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-property-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-reserved-words": {
"chrome": "13",
"opera": "10.50",
"edge": "12",
"firefox": "2",
"safari": "3.1",
"node": "0.6",
"deno": "1",
"ie": "9",
"android": "4.4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "10.1",
"electron": "0.20"
},
"transform-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
},
"proposal-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
}
}
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/native-modules.json");
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/overlapping-plugins.json");
-40
View File
@@ -1,40 +0,0 @@
{
"name": "@babel/compat-data",
"version": "7.29.3",
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"description": "The compat-data to determine required Babel plugins",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-compat-data"
},
"publishConfig": {
"access": "public"
},
"exports": {
"./plugins": "./plugins.js",
"./native-modules": "./native-modules.js",
"./corejs2-built-ins": "./corejs2-built-ins.js",
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
"./overlapping-plugins": "./overlapping-plugins.js",
"./plugin-bugfixes": "./plugin-bugfixes.js"
},
"scripts": {
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
},
"keywords": [
"babel",
"compat-table",
"compat-data"
],
"devDependencies": {
"@mdn/browser-compat-data": "^6.0.8",
"core-js-compat": "^3.48.0",
"electron-to-chromium": "^1.5.278"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugin-bugfixes.json");
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugins.json");
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/core
> Babel compiler core.
See our website [@babel/core](https://babeljs.io/docs/babel-core) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20core%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/core
```
or using yarn:
```sh
yarn add @babel/core --dev
```
-5
View File
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=cache-contexts.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../src/config/cache-contexts.ts"],"sourcesContent":["import type { ConfigContext } from \"./config-chain.ts\";\nimport type {\n CallerMetadata,\n TargetsListOrObject,\n} from \"./validation/options.ts\";\n\nexport type { ConfigContext as FullConfig };\n\nexport type FullPreset = {\n targets: TargetsListOrObject;\n} & ConfigContext;\nexport type FullPlugin = {\n assumptions: Record<string, boolean>;\n} & FullPreset;\n\n// Context not including filename since it is used in places that cannot\n// process 'ignore'/'only' and other filename-based logic.\nexport type SimpleConfig = {\n envName: string;\n caller: CallerMetadata | undefined;\n};\nexport type SimplePreset = {\n targets: TargetsListOrObject;\n} & SimpleConfig;\nexport type SimplePlugin = {\n assumptions: Record<string, boolean>;\n} & SimplePreset;\n"],"mappings":"","ignoreList":[]}
-261
View File
@@ -1,261 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assertSimpleType = assertSimpleType;
exports.makeStrongCache = makeStrongCache;
exports.makeStrongCacheSync = makeStrongCacheSync;
exports.makeWeakCache = makeWeakCache;
exports.makeWeakCacheSync = makeWeakCacheSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
const synchronize = gen => {
return _gensync()(gen).sync;
};
function* genTrue() {
return true;
}
function makeWeakCache(handler) {
return makeCachedFunction(WeakMap, handler);
}
function makeWeakCacheSync(handler) {
return synchronize(makeWeakCache(handler));
}
function makeStrongCache(handler) {
return makeCachedFunction(Map, handler);
}
function makeStrongCacheSync(handler) {
return synchronize(makeStrongCache(handler));
}
function makeCachedFunction(CallCache, handler) {
const callCacheSync = new CallCache();
const callCacheAsync = new CallCache();
const futureCache = new CallCache();
return function* cachedFunction(arg, data) {
const asyncContext = yield* (0, _async.isAsync)();
const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data);
if (cached.valid) return cached.value;
const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache);
let finishLock;
let value;
if ((0, _util.isIterableIterator)(handlerResult)) {
value = yield* (0, _async.onFirstPause)(handlerResult, () => {
finishLock = setupAsyncLocks(cache, futureCache, arg);
});
} else {
value = handlerResult;
}
updateFunctionCache(callCache, cache, arg, value);
if (finishLock) {
futureCache.delete(arg);
finishLock.release(value);
}
return value;
};
}
function* getCachedValue(cache, arg, data) {
const cachedValue = cache.get(arg);
if (cachedValue) {
for (const {
value,
valid
} of cachedValue) {
if (yield* valid(data)) return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) {
const cached = yield* getCachedValue(callCache, arg, data);
if (cached.valid) {
return cached;
}
if (asyncContext) {
const cached = yield* getCachedValue(futureCache, arg, data);
if (cached.valid) {
const value = yield* (0, _async.waitFor)(cached.value.promise);
return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function setupAsyncLocks(config, futureCache, arg) {
const finishLock = new Lock();
updateFunctionCache(futureCache, config, arg, finishLock);
return finishLock;
}
function updateFunctionCache(cache, config, arg, value) {
if (!config.configured()) config.forever();
let cachedValue = cache.get(arg);
config.deactivate();
switch (config.mode()) {
case "forever":
cachedValue = [{
value,
valid: genTrue
}];
cache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push({
value,
valid: config.validator()
});
} else {
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
}
}
}
class CacheConfigurator {
constructor(data) {
this._active = true;
this._never = false;
this._forever = false;
this._invalidate = false;
this._configured = false;
this._pairs = [];
this._data = void 0;
this._data = data;
}
simple() {
return makeSimpleConfigurator(this);
}
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using(handler) {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error("Caching has already been configured with .never or .forever()");
}
this._configured = true;
const key = handler(this._data);
const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`);
if ((0, _async.isThenable)(key)) {
return key.then(key => {
this._pairs.push([key, fn]);
return key;
});
}
this._pairs.push([key, fn]);
return key;
}
invalidate(handler) {
this._invalidate = true;
return this.using(handler);
}
validator() {
const pairs = this._pairs;
return function* (data) {
for (const [key, fn] of pairs) {
if (key !== (yield* fn(data))) return false;
}
return true;
};
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(cache) {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();else cache.never();
return;
}
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return cacheFn;
}
function assertSimpleType(value) {
if ((0, _async.isThenable)(value)) {
throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`);
}
if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") {
throw new Error("Cache keys must be either string, boolean, number, null, or undefined.");
}
return value;
}
class Lock {
constructor() {
this.released = false;
this.promise = void 0;
this._resolve = void 0;
this.promise = new Promise(resolve => {
this._resolve = resolve;
});
}
release(value) {
this.released = true;
this._resolve(value);
}
}
0 && 0;
//# sourceMappingURL=caching.js.map
File diff suppressed because one or more lines are too long
-469
View File
@@ -1,469 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildPresetChain = buildPresetChain;
exports.buildPresetChainWalker = void 0;
exports.buildRootChain = buildRootChain;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _options = require("./validation/options.js");
var _patternToRegex = require("./pattern-to-regex.js");
var _printer = require("./printer.js");
var _rewriteStackTrace = require("../errors/rewrite-stack-trace.js");
var _configError = require("../errors/config-error.js");
var _index = require("./files/index.js");
var _caching = require("./caching.js");
var _configDescriptors = require("./config-descriptors.js");
const debug = _debug()("babel:config:config-chain");
function* buildPresetChain(arg, context) {
const chain = yield* buildPresetChainWalker(arg, context);
if (!chain) return null;
return {
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options.map(o => createConfigChainOptions(o)),
files: new Set()
};
}
const buildPresetChainWalker = exports.buildPresetChainWalker = makeChainWalker({
root: preset => loadPresetDescriptors(preset),
env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
overridesEnv: (preset, index, envName) => loadPresetOverridesEnvDescriptors(preset)(index)(envName),
createLogger: () => () => {}
});
const loadPresetDescriptors = (0, _caching.makeWeakCacheSync)(preset => buildRootDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors));
const loadPresetEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, envName)));
const loadPresetOverridesDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index)));
const loadPresetOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index, envName))));
function* buildRootChain(opts, context) {
let configReport, babelRcReport;
const programmaticLogger = new _printer.ConfigPrinter();
const programmaticChain = yield* loadProgrammaticChain({
options: opts,
dirname: context.cwd
}, context, undefined, programmaticLogger);
if (!programmaticChain) return null;
const programmaticReport = yield* programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* (0, _index.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller);
} else if (opts.configFile !== false) {
configFile = yield* (0, _index.findRootConfig)(context.root, context.envName, context.caller);
}
let {
babelrc,
babelrcRoots
} = opts;
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new _printer.ConfigPrinter();
if (configFile) {
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(validatedFile, context, undefined, configFileLogger);
if (!result) return null;
configReport = yield* configFileLogger.output();
if (babelrc === undefined) {
babelrc = validatedFile.options.babelrc;
}
if (babelrcRoots === undefined) {
babelrcRootsDirectory = validatedFile.dirname;
babelrcRoots = validatedFile.options.babelrcRoots;
}
mergeChain(configFileChain, result);
}
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
if ((babelrc === true || babelrc === undefined) && typeof context.filename === "string") {
const pkgData = yield* (0, _index.findPackageData)(context.filename);
if (pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) {
({
ignore: ignoreFile,
config: babelrcFile
} = yield* (0, _index.findRelativeConfig)(pkgData, context.envName, context.caller));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (ignoreFile && shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new _printer.ConfigPrinter();
const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
if (context.showConfig) {
console.log(`Babel configs on "${context.filename}" (ascending priority):\n` + [configReport, babelRcReport, programmaticReport].filter(x => !!x).join("\n\n") + "\n-----End Babel configs-----");
}
const chain = mergeChain(mergeChain(mergeChain(emptyChain(), configFileChain), fileChain), programmaticChain);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => createConfigChainOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files
};
}
function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) {
if (typeof babelrcRoots === "boolean") return babelrcRoots;
const absoluteRoot = context.root;
if (babelrcRoots === undefined) {
return pkgData.directories.includes(absoluteRoot);
}
let babelrcPatterns = babelrcRoots;
if (!Array.isArray(babelrcPatterns)) {
babelrcPatterns = [babelrcPatterns];
}
babelrcPatterns = babelrcPatterns.map(pat => {
return typeof pat === "string" ? _path().resolve(babelrcRootsDirectory, pat) : pat;
});
if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
return pkgData.directories.includes(absoluteRoot);
}
return babelrcPatterns.some(pat => {
if (typeof pat === "string") {
pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory);
}
return pkgData.directories.some(directory => {
return matchPattern(pat, babelrcRootsDirectory, directory, context);
});
});
}
const validateConfigFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("configfile", file.options, file.filepath)
}));
const validateBabelrcFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("babelrcfile", file.options, file.filepath)
}));
const validateExtendFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("extendsfile", file.options, file.filepath)
}));
const loadProgrammaticChain = makeChainWalker({
root: input => buildRootDescriptors(input, "base", _configDescriptors.createCachedDescriptors),
env: (input, envName) => buildEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, envName),
overrides: (input, index) => buildOverrideDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index),
overridesEnv: (input, index, envName) => buildOverrideEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index, envName),
createLogger: (input, context, baseLogger) => buildProgrammaticLogger(input, context, baseLogger)
});
const loadFileChainWalker = makeChainWalker({
root: file => loadFileDescriptors(file),
env: (file, envName) => loadFileEnvDescriptors(file)(envName),
overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
overridesEnv: (file, index, envName) => loadFileOverridesEnvDescriptors(file)(index)(envName),
createLogger: (file, context, baseLogger) => buildFileLogger(file.filepath, context, baseLogger)
});
function* loadFileChain(input, context, files, baseLogger) {
const chain = yield* loadFileChainWalker(input, context, files, baseLogger);
chain == null || chain.files.add(input.filepath);
return chain;
}
const loadFileDescriptors = (0, _caching.makeWeakCacheSync)(file => buildRootDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors));
const loadFileEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, envName)));
const loadFileOverridesDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index)));
const loadFileOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index, envName))));
function buildFileLogger(filepath, context, baseLogger) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Config, {
filepath
});
}
function buildRootDescriptors({
dirname,
options
}, alias, descriptors) {
return descriptors(dirname, options, alias);
}
function buildProgrammaticLogger(_, context, baseLogger) {
var _context$caller;
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Programmatic, {
callerName: (_context$caller = context.caller) == null ? void 0 : _context$caller.name
});
}
function buildEnvDescriptors({
dirname,
options
}, alias, descriptors, envName) {
var _options$env;
const opts = (_options$env = options.env) == null ? void 0 : _options$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null;
}
function buildOverrideDescriptors({
dirname,
options
}, alias, descriptors, index) {
var _options$overrides;
const opts = (_options$overrides = options.overrides) == null ? void 0 : _options$overrides[index];
if (!opts) throw new Error("Assertion failure - missing override");
return descriptors(dirname, opts, `${alias}.overrides[${index}]`);
}
function buildOverrideEnvDescriptors({
dirname,
options
}, alias, descriptors, index, envName) {
var _options$overrides2, _override$env;
const override = (_options$overrides2 = options.overrides) == null ? void 0 : _options$overrides2[index];
if (!override) throw new Error("Assertion failure - missing override");
const opts = (_override$env = override.env) == null ? void 0 : _override$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.overrides[${index}].env["${envName}"]`) : null;
}
function makeChainWalker({
root,
env,
overrides,
overridesEnv,
createLogger
}) {
return function* chainWalker(input, context, files = new Set(), baseLogger) {
const {
dirname
} = input;
const flattenedConfigs = [];
const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined
});
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
index: undefined
});
}
(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideOps,
index,
envName: undefined
});
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (overrideEnvOpts && configIsApplicable(overrideEnvOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideEnvOpts,
index,
envName: context.envName
});
}
}
});
}
if (flattenedConfigs.some(({
config: {
options: {
ignore,
only
}
}
}) => shouldIgnore(context, ignore, only, dirname))) {
return null;
}
const chain = emptyChain();
const logger = createLogger(input, context, baseLogger);
for (const {
config,
index,
envName
} of flattenedConfigs) {
if (!(yield* mergeExtendsChain(chain, config.options, dirname, context, files, baseLogger))) {
return null;
}
logger(config, index, envName);
yield* mergeChainOpts(chain, config);
}
return chain;
};
}
function* mergeExtendsChain(chain, opts, dirname, context, files, baseLogger) {
if (opts.extends === undefined) return true;
const file = yield* (0, _index.loadConfig)(opts.extends, dirname, context.envName, context.caller);
if (files.has(file)) {
throw new Error(`Configuration cycle detected loading ${file.filepath}.\n` + `File already loaded following the config chain:\n` + Array.from(files, file => ` - ${file.filepath}`).join("\n"));
}
files.add(file);
const fileChain = yield* loadFileChain(validateExtendFile(file), context, files, baseLogger);
files.delete(file);
if (!fileChain) return false;
mergeChain(chain, fileChain);
return true;
}
function mergeChain(target, source) {
target.options.push(...source.options);
target.plugins.push(...source.plugins);
target.presets.push(...source.presets);
for (const file of source.files) {
target.files.add(file);
}
return target;
}
function* mergeChainOpts(target, {
options,
plugins,
presets
}) {
target.options.push(options);
target.plugins.push(...(yield* plugins()));
target.presets.push(...(yield* presets()));
return target;
}
function emptyChain() {
return {
options: [],
presets: [],
plugins: [],
files: new Set()
};
}
function createConfigChainOptions(opts) {
const options = Object.assign({}, opts);
delete options.extends;
delete options.env;
delete options.overrides;
delete options.plugins;
delete options.presets;
delete options.passPerPreset;
delete options.ignore;
delete options.only;
delete options.test;
delete options.include;
delete options.exclude;
if (hasOwnProperty.call(options, "sourceMap")) {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
return options;
}
function dedupDescriptors(items) {
const map = new Map();
const descriptors = [];
for (const item of items) {
if (typeof item.value === "function") {
const fnKey = item.value;
let nameMap = map.get(fnKey);
if (!nameMap) {
nameMap = new Map();
map.set(fnKey, nameMap);
}
let desc = nameMap.get(item.name);
if (!desc) {
desc = {
value: item
};
descriptors.push(desc);
if (!item.ownPass) nameMap.set(item.name, desc);
} else {
desc.value = item;
}
} else {
descriptors.push({
value: item
});
}
}
return descriptors.reduce((acc, desc) => {
acc.push(desc.value);
return acc;
}, []);
}
function configIsApplicable({
options
}, dirname, context, configName) {
return (options.test === undefined || configFieldIsApplicable(context, options.test, dirname, configName)) && (options.include === undefined || configFieldIsApplicable(context, options.include, dirname, configName)) && (options.exclude === undefined || !configFieldIsApplicable(context, options.exclude, dirname, configName));
}
function configFieldIsApplicable(context, test, dirname, configName) {
const patterns = Array.isArray(test) ? test : [test];
return matchesPatterns(context, patterns, dirname, configName);
}
function ignoreListReplacer(_key, value) {
if (value instanceof RegExp) {
return String(value);
}
return value;
}
function shouldIgnore(context, ignore, only, dirname) {
if (ignore && matchesPatterns(context, ignore, dirname)) {
var _context$filename;
const message = `No config is applied to "${(_context$filename = context.filename) != null ? _context$filename : "(unknown)"}" because it matches one of \`ignore: ${JSON.stringify(ignore, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
var _context$filename2;
const message = `No config is applied to "${(_context$filename2 = context.filename) != null ? _context$filename2 : "(unknown)"}" because it fails to match one of \`only: ${JSON.stringify(only, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
return false;
}
function matchesPatterns(context, patterns, dirname, configName) {
return patterns.some(pattern => matchPattern(pattern, dirname, context.filename, context, configName));
}
function matchPattern(pattern, dirname, pathToTest, context, configName) {
if (typeof pattern === "function") {
return !!(0, _rewriteStackTrace.endHiddenCallStack)(pattern)(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller
});
}
if (typeof pathToTest !== "string") {
throw new _configError.default(`Configuration contains string/RegExp pattern, but no filename was passed to Babel`, configName);
}
if (typeof pattern === "string") {
pattern = (0, _patternToRegex.default)(pattern, dirname);
}
return pattern.test(pathToTest);
}
0 && 0;
//# sourceMappingURL=config-chain.js.map
File diff suppressed because one or more lines are too long
-190
View File
@@ -1,190 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createCachedDescriptors = createCachedDescriptors;
exports.createDescriptor = createDescriptor;
exports.createUncachedDescriptors = createUncachedDescriptors;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _functional = require("../gensync-utils/functional.js");
var _index = require("./files/index.js");
var _item = require("./item.js");
var _caching = require("./caching.js");
var _resolveTargets = require("./resolve-targets.js");
function isEqualDescriptor(a, b) {
var _a$file, _b$file, _a$file2, _b$file2;
return a.name === b.name && a.value === b.value && a.options === b.options && a.dirname === b.dirname && a.alias === b.alias && a.ownPass === b.ownPass && ((_a$file = a.file) == null ? void 0 : _a$file.request) === ((_b$file = b.file) == null ? void 0 : _b$file.request) && ((_a$file2 = a.file) == null ? void 0 : _a$file2.resolved) === ((_b$file2 = b.file) == null ? void 0 : _b$file2.resolved);
}
function* handlerOf(value) {
return value;
}
function optionsWithResolvedBrowserslistConfigFile(options, dirname) {
if (typeof options.browserslistConfigFile === "string") {
options.browserslistConfigFile = (0, _resolveTargets.resolveBrowserslistConfigFile)(options.browserslistConfigFile, dirname);
}
return options;
}
function createCachedDescriptors(dirname, options, alias) {
const {
plugins,
presets,
passPerPreset
} = options;
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: plugins ? () => createCachedPluginDescriptors(plugins, dirname)(alias) : () => handlerOf([]),
presets: presets ? () => createCachedPresetDescriptors(presets, dirname)(alias)(!!passPerPreset) : () => handlerOf([])
};
}
function createUncachedDescriptors(dirname, options, alias) {
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: (0, _functional.once)(() => createPluginDescriptors(options.plugins || [], dirname, alias)),
presets: (0, _functional.once)(() => createPresetDescriptors(options.presets || [], dirname, alias, !!options.passPerPreset))
};
}
const PRESET_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPresetDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCacheSync)(alias => (0, _caching.makeStrongCache)(function* (passPerPreset) {
const descriptors = yield* createPresetDescriptors(items, dirname, alias, passPerPreset);
return descriptors.map(desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc));
}));
});
const PLUGIN_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPluginDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCache)(function* (alias) {
const descriptors = yield* createPluginDescriptors(items, dirname, alias);
return descriptors.map(desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc));
});
});
const DEFAULT_OPTIONS = {};
function loadCachedDescriptor(cache, desc) {
const {
value,
options = DEFAULT_OPTIONS
} = desc;
if (options === false) return desc;
let cacheByOptions = cache.get(value);
if (!cacheByOptions) {
cacheByOptions = new WeakMap();
cache.set(value, cacheByOptions);
}
let possibilities = cacheByOptions.get(options);
if (!possibilities) {
possibilities = [];
cacheByOptions.set(options, possibilities);
}
if (!possibilities.includes(desc)) {
const matches = possibilities.filter(possibility => isEqualDescriptor(possibility, desc));
if (matches.length > 0) {
return matches[0];
}
possibilities.push(desc);
}
return desc;
}
function* createPresetDescriptors(items, dirname, alias, passPerPreset) {
return yield* createDescriptors("preset", items, dirname, alias, passPerPreset);
}
function* createPluginDescriptors(items, dirname, alias) {
return yield* createDescriptors("plugin", items, dirname, alias);
}
function* createDescriptors(type, items, dirname, alias, ownPass) {
const descriptors = yield* _gensync().all(items.map((item, index) => createDescriptor(item, dirname, {
type,
alias: `${alias}$${index}`,
ownPass: !!ownPass
})));
assertNoDuplicates(descriptors);
return descriptors;
}
function* createDescriptor(pair, dirname, {
type,
alias,
ownPass
}) {
const desc = (0, _item.getItemDescriptor)(pair);
if (desc) {
return desc;
}
let name;
let options;
let value = pair;
if (Array.isArray(value)) {
if (value.length === 3) {
[value, options, name] = value;
} else {
[value, options] = value;
}
}
let file = undefined;
let filepath = null;
if (typeof value === "string") {
if (typeof type !== "string") {
throw new Error("To resolve a string-based item, the type of item must be given");
}
const resolver = type === "plugin" ? _index.loadPlugin : _index.loadPreset;
const request = value;
({
filepath,
value
} = yield* resolver(value, dirname));
file = {
request,
resolved: filepath
};
}
if (!value) {
throw new Error(`Unexpected falsy value: ${String(value)}`);
}
if (typeof value === "object" && value.__esModule) {
if (value.default) {
value = value.default;
} else {
throw new Error("Must export a default export when using ES6 modules.");
}
}
if (typeof value !== "object" && typeof value !== "function") {
throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`);
}
if (filepath !== null && typeof value === "object" && value) {
throw new Error(`Plugin/Preset files are not allowed to export objects, only functions. In ${filepath}`);
}
return {
name,
alias: filepath || alias,
value,
options,
dirname,
ownPass,
file
};
}
function assertNoDuplicates(items) {
const map = new Map();
for (const item of items) {
if (typeof item.value !== "function") continue;
let nameMap = map.get(item.value);
if (!nameMap) {
nameMap = new Set();
map.set(item.value, nameMap);
}
if (nameMap.has(item.name)) {
const conflicts = items.filter(i => i.value === item.value);
throw new Error([`Duplicate plugin/preset detected.`, `If you'd like to use two separate instances of a plugin,`, `they need separate names, e.g.`, ``, ` plugins: [`, ` ['some-plugin', {}],`, ` ['some-plugin', {}, 'some unique name'],`, ` ]`, ``, `Duplicates detected are:`, `${JSON.stringify(conflicts, null, 2)}`].join("\n"));
}
nameMap.add(item.name);
}
}
0 && 0;
//# sourceMappingURL=config-descriptors.js.map
File diff suppressed because one or more lines are too long
-290
View File
@@ -1,290 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.resolveShowConfigPath = resolveShowConfigPath;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _json() {
const data = require("json5");
_json = function () {
return data;
};
return data;
}
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _caching = require("../caching.js");
var _configApi = require("../helpers/config-api.js");
var _utils = require("./utils.js");
var _moduleTypes = require("./module-types.js");
var _patternToRegex = require("../pattern-to-regex.js");
var _configError = require("../../errors/config-error.js");
var fs = require("../../gensync-utils/fs.js");
require("module");
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _async = require("../../gensync-utils/async.js");
const debug = _debug()("babel:config:loading:files:configuration");
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json", "babel.config.cts", "babel.config.ts", "babel.config.mts"];
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts"];
const BABELIGNORE_FILENAME = ".babelignore";
const runConfig = (0, _caching.makeWeakCache)(function* runConfig(options, cache) {
yield* [];
return {
options: (0, _rewriteStackTrace.endHiddenCallStack)(options)((0, _configApi.makeConfigAPI)(cache)),
cacheNeedsConfiguration: !cache.configured()
};
});
function* readConfigCode(filepath, data) {
if (!_fs().existsSync(filepath)) return null;
let options = yield* (0, _moduleTypes.default)(filepath, (yield* (0, _async.isAsync)()) ? "auto" : "require", "You appear to be using a native ECMAScript module configuration " + "file, which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", "You appear to be using a configuration file that contains top-level " + "await, which is only supported when running Babel asynchronously.");
let cacheNeedsConfiguration = false;
if (typeof options === "function") {
({
options,
cacheNeedsConfiguration
} = yield* runConfig(options, data));
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new _configError.default(`Configuration should be an exported JavaScript object.`, filepath);
}
if (typeof options.then === "function") {
options.catch == null || options.catch(() => {});
throw new _configError.default(`You appear to be using an async configuration, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously return your config.`, filepath);
}
if (cacheNeedsConfiguration) throwConfigError(filepath);
return buildConfigFileObject(options, filepath);
}
const cfboaf = new WeakMap();
function buildConfigFileObject(options, filepath) {
let configFilesByFilepath = cfboaf.get(options);
if (!configFilesByFilepath) {
cfboaf.set(options, configFilesByFilepath = new Map());
}
let configFile = configFilesByFilepath.get(filepath);
if (!configFile) {
configFile = {
filepath,
dirname: _path().dirname(filepath),
options
};
configFilesByFilepath.set(filepath, configFile);
}
return configFile;
}
const packageToBabelConfig = (0, _caching.makeWeakCacheSync)(file => {
const babel = file.options.babel;
if (babel === undefined) return null;
if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
throw new _configError.default(`.babel property must be an object`, file.filepath);
}
return {
filepath: file.filepath,
dirname: file.dirname,
options: babel
};
});
const readConfigJSON5 = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = _json().parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing config - ${err.message}`, filepath);
}
if (!options) throw new _configError.default(`No config detected`, filepath);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
delete options.$schema;
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
const readIgnoreConfig = (0, _utils.makeStaticFileCache)((filepath, content) => {
const ignoreDir = _path().dirname(filepath);
const ignorePatterns = content.split("\n").map(line => line.replace(/#.*$/, "").trim()).filter(Boolean);
for (const pattern of ignorePatterns) {
if (pattern.startsWith("!")) {
throw new _configError.default(`Negation of file paths is not supported.`, filepath);
}
}
return {
filepath,
dirname: _path().dirname(filepath),
ignore: ignorePatterns.map(pattern => (0, _patternToRegex.default)(pattern, ignoreDir))
};
});
function findConfigUpwards(rootDir) {
let dirname = rootDir;
for (;;) {
for (const filename of ROOT_CONFIG_FILENAMES) {
if (_fs().existsSync(_path().join(dirname, filename))) {
return dirname;
}
}
const nextDir = _path().dirname(dirname);
if (dirname === nextDir) break;
dirname = nextDir;
}
return null;
}
function* findRelativeConfig(packageData, envName, caller) {
let config = null;
let ignore = null;
const dirname = _path().dirname(packageData.filepath);
for (const loc of packageData.directories) {
if (!config) {
var _packageData$pkg;
config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null);
}
if (!ignore) {
const ignoreLoc = _path().join(loc, BABELIGNORE_FILENAME);
ignore = yield* readIgnoreConfig(ignoreLoc);
if (ignore) {
debug("Found ignore %o from %o.", ignore.filepath, dirname);
}
}
}
return {
config,
ignore
};
}
function findRootConfig(dirname, envName, caller) {
return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller);
}
function* loadOneConfig(names, dirname, envName, caller, previousConfig = null) {
const configs = yield* _gensync().all(names.map(filename => readConfig(_path().join(dirname, filename), envName, caller)));
const config = configs.reduce((previousConfig, config) => {
if (config && previousConfig) {
throw new _configError.default(`Multiple configuration files found. Please remove one:\n` + ` - ${_path().basename(previousConfig.filepath)}\n` + ` - ${config.filepath}\n` + `from ${dirname}`);
}
return config || previousConfig;
}, previousConfig);
if (config) {
debug("Found configuration %o from %o.", config.filepath, dirname);
}
return config;
}
function* loadConfig(name, dirname, envName, caller) {
const filepath = (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(name, {
paths: [dirname]
});
const conf = yield* readConfig(filepath, envName, caller);
if (!conf) {
throw new _configError.default(`Config file contains no configuration data`, filepath);
}
debug("Loaded config %o from %o.", name, dirname);
return conf;
}
function readConfig(filepath, envName, caller) {
const ext = _path().extname(filepath);
switch (ext) {
case ".js":
case ".cjs":
case ".mjs":
case ".ts":
case ".cts":
case ".mts":
return readConfigCode(filepath, {
envName,
caller
});
default:
return readConfigJSON5(filepath);
}
}
function* resolveShowConfigPath(dirname) {
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = _path().resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`);
}
return absolutePath;
}
return null;
}
function throwConfigError(filepath) {
throw new _configError.default(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
module.exports = function(api) {
// The API exposes the following:
// Cache the returned value forever and don't call this function again.
api.cache(true);
// Don't cache at all. Not recommended because it will be very slow.
api.cache(false);
// Cached based on the value of some function. If this function returns a value different from
// a previously-encountered value, the plugins will re-evaluate.
var env = api.cache(() => process.env.NODE_ENV);
// If testing for a specific env, we recommend specifics to avoid instantiating a plugin for
// any possible NODE_ENV value that might come up during plugin execution.
var isProd = api.cache(() => process.env.NODE_ENV === "production");
// .cache(fn) will perform a linear search though instances to find the matching plugin based
// based on previous instantiated plugins. If you want to recreate the plugin and discard the
// previous instance whenever something changes, you may use:
var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");
// Note, we also expose the following more-verbose versions of the above examples:
api.cache.forever(); // api.cache(true)
api.cache.never(); // api.cache(false)
api.cache.using(fn); // api.cache(fn)
// Return the value that will be cached.
return { };
};`, filepath);
}
0 && 0;
//# sourceMappingURL=configuration.js.map
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
module.exports = function import_(filepath) {
return import(filepath);
};
0 && 0;
//# sourceMappingURL=import.cjs.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["module","exports","import_","filepath"],"sources":["../../../src/config/files/import.cjs"],"sourcesContent":["// We keep this in a separate file so that in older node versions, where\n// import() isn't supported, we can try/catch around the require() call\n// when loading this file.\n\nmodule.exports = function import_(filepath) {\n return import(filepath);\n};\n"],"mappings":"AAIAA,MAAM,CAACC,OAAO,GAAG,SAASC,OAAOA,CAACC,QAAQ,EAAE;EAC1C,OAAO,OAAOA,QAAQ,CAAC;AACzB,CAAC;AAAC","ignoreList":[]}
-58
View File
@@ -1,58 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findPackageData = findPackageData;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePlugin = resolvePlugin;
exports.resolvePreset = resolvePreset;
exports.resolveShowConfigPath = resolveShowConfigPath;
function findConfigUpwards(rootDir) {
return null;
}
function* findPackageData(filepath) {
return {
filepath,
directories: [],
pkg: null,
isPackage: false
};
}
function* findRelativeConfig(pkgData, envName, caller) {
return {
config: null,
ignore: null
};
}
function* findRootConfig(dirname, envName, caller) {
return null;
}
function* loadConfig(name, dirname, envName, caller) {
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}
function* resolveShowConfigPath(dirname) {
return null;
}
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = [];
function resolvePlugin(name, dirname) {
return null;
}
function resolvePreset(name, dirname) {
return null;
}
function loadPlugin(name, dirname) {
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
}
function loadPreset(name, dirname) {
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
}
0 && 0;
//# sourceMappingURL=index-browser.js.map
@@ -1 +0,0 @@
{"version":3,"names":["findConfigUpwards","rootDir","findPackageData","filepath","directories","pkg","isPackage","findRelativeConfig","pkgData","envName","caller","config","ignore","findRootConfig","dirname","loadConfig","name","Error","resolveShowConfigPath","ROOT_CONFIG_FILENAMES","exports","resolvePlugin","resolvePreset","loadPlugin","loadPreset"],"sources":["../../../src/config/files/index-browser.ts"],"sourcesContent":["/* c8 ignore start */\n\nimport type { Handler } from \"gensync\";\n\nimport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\n\nimport type { CallerMetadata } from \"../validation/options.ts\";\n\nexport type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };\n\nexport function findConfigUpwards(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n rootDir: string,\n): string | null {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n return {\n filepath,\n directories: [],\n pkg: null,\n isPackage: false,\n };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRelativeConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n pkgData: FilePackageData,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<RelativeConfig> {\n return { config: null, ignore: null };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRootConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile | null> {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* loadConfig(\n name: string,\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile> {\n throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);\n}\n\n// eslint-disable-next-line require-yield\nexport function* resolveShowConfigPath(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n): Handler<string | null> {\n return null;\n}\n\nexport const ROOT_CONFIG_FILENAMES: string[] = [];\n\ntype Resolved =\n | { loader: \"require\"; filepath: string }\n | { loader: \"import\"; filepath: string };\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePlugin(name: string, dirname: string): Resolved | null {\n return null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePreset(name: string, dirname: string): Resolved | null {\n return null;\n}\n\nexport function loadPlugin(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load plugin ${name} relative to ${dirname} in a browser`,\n );\n}\n\nexport function loadPreset(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load preset ${name} relative to ${dirname} in a browser`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeO,SAASA,iBAAiBA,CAE/BC,OAAe,EACA;EACf,OAAO,IAAI;AACb;AAGO,UAAUC,eAAeA,CAACC,QAAgB,EAA4B;EAC3E,OAAO;IACLA,QAAQ;IACRC,WAAW,EAAE,EAAE;IACfC,GAAG,EAAE,IAAI;IACTC,SAAS,EAAE;EACb,CAAC;AACH;AAGO,UAAUC,kBAAkBA,CAEjCC,OAAwB,EAExBC,OAAe,EAEfC,MAAkC,EACT;EACzB,OAAO;IAAEC,MAAM,EAAE,IAAI;IAAEC,MAAM,EAAE;EAAK,CAAC;AACvC;AAGO,UAAUC,cAAcA,CAE7BC,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACN;EAC5B,OAAO,IAAI;AACb;AAGO,UAAUK,UAAUA,CACzBC,IAAY,EACZF,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACb;EACrB,MAAM,IAAIO,KAAK,CAAC,eAAeD,IAAI,gBAAgBF,OAAO,eAAe,CAAC;AAC5E;AAGO,UAAUI,qBAAqBA,CAEpCJ,OAAe,EACS;EACxB,OAAO,IAAI;AACb;AAEO,MAAMK,qBAA+B,GAAAC,OAAA,CAAAD,qBAAA,GAAG,EAAE;AAO1C,SAASE,aAAaA,CAACL,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAGO,SAASQ,aAAaA,CAACN,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAEO,SAASS,UAAUA,CACxBP,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAEO,SAASU,UAAUA,CACxBR,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAAC","ignoreList":[]}
-78
View File
@@ -1,78 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ROOT_CONFIG_FILENAMES", {
enumerable: true,
get: function () {
return _configuration.ROOT_CONFIG_FILENAMES;
}
});
Object.defineProperty(exports, "findConfigUpwards", {
enumerable: true,
get: function () {
return _configuration.findConfigUpwards;
}
});
Object.defineProperty(exports, "findPackageData", {
enumerable: true,
get: function () {
return _package.findPackageData;
}
});
Object.defineProperty(exports, "findRelativeConfig", {
enumerable: true,
get: function () {
return _configuration.findRelativeConfig;
}
});
Object.defineProperty(exports, "findRootConfig", {
enumerable: true,
get: function () {
return _configuration.findRootConfig;
}
});
Object.defineProperty(exports, "loadConfig", {
enumerable: true,
get: function () {
return _configuration.loadConfig;
}
});
Object.defineProperty(exports, "loadPlugin", {
enumerable: true,
get: function () {
return _plugins.loadPlugin;
}
});
Object.defineProperty(exports, "loadPreset", {
enumerable: true,
get: function () {
return _plugins.loadPreset;
}
});
Object.defineProperty(exports, "resolvePlugin", {
enumerable: true,
get: function () {
return _plugins.resolvePlugin;
}
});
Object.defineProperty(exports, "resolvePreset", {
enumerable: true,
get: function () {
return _plugins.resolvePreset;
}
});
Object.defineProperty(exports, "resolveShowConfigPath", {
enumerable: true,
get: function () {
return _configuration.resolveShowConfigPath;
}
});
var _package = require("./package.js");
var _configuration = require("./configuration.js");
var _plugins = require("./plugins.js");
({});
0 && 0;
//# sourceMappingURL=index.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_package","require","_configuration","_plugins"],"sources":["../../../src/config/files/index.ts"],"sourcesContent":["type indexBrowserType = typeof import(\"./index-browser\");\ntype indexType = typeof import(\"./index\");\n\n// Kind of gross, but essentially asserting that the exports of this module are the same as the\n// exports of index-browser, since this file may be replaced at bundle time with index-browser.\n// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n({}) as any as indexBrowserType as indexType;\n\nexport { findPackageData } from \"./package.ts\";\n\nexport {\n findConfigUpwards,\n findRelativeConfig,\n findRootConfig,\n loadConfig,\n resolveShowConfigPath,\n ROOT_CONFIG_FILENAMES,\n} from \"./configuration.ts\";\nexport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\nexport {\n loadPlugin,\n loadPreset,\n resolvePlugin,\n resolvePreset,\n} from \"./plugins.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,IAAAA,QAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAD,OAAA;AAcA,IAAAE,QAAA,GAAAF,OAAA;AAlBA,CAAC,CAAC,CAAC;AAA0C","ignoreList":[]}
-203
View File
@@ -1,203 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = loadCodeDefault;
exports.supportsESM = void 0;
var _async = require("../../gensync-utils/async.js");
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
require("module");
function _semver() {
const data = require("semver");
_semver = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _configError = require("../../errors/config-error.js");
var _transformFile = require("../../transform-file.js");
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
const debug = _debug()("babel:config:loading:files:module-types");
try {
var import_ = require("./import.cjs");
} catch (_unused) {}
const supportsESM = exports.supportsESM = _semver().satisfies(process.versions.node, "^12.17 || >=13.2");
const LOADING_CJS_FILES = new Set();
function loadCjsDefault(filepath) {
if (LOADING_CJS_FILES.has(filepath)) {
debug("Auto-ignoring usage of config %o.", filepath);
return {};
}
let module;
try {
LOADING_CJS_FILES.add(filepath);
module = (0, _rewriteStackTrace.endHiddenCallStack)(require)(filepath);
} finally {
LOADING_CJS_FILES.delete(filepath);
}
return module != null && (module.__esModule || module[Symbol.toStringTag] === "Module") ? module.default || (arguments[1] ? module : undefined) : module;
}
const loadMjsFromPath = (0, _rewriteStackTrace.endHiddenCallStack)(function () {
var _loadMjsFromPath = _asyncToGenerator(function* (filepath) {
const url = (0, _url().pathToFileURL)(filepath).toString() + "?import";
if (!import_) {
throw new _configError.default("Internal error: Native ECMAScript modules aren't supported by this platform.\n", filepath);
}
return yield import_(url);
});
function loadMjsFromPath(_x) {
return _loadMjsFromPath.apply(this, arguments);
}
return loadMjsFromPath;
}());
const tsNotSupportedError = ext => `\
You are using a ${ext} config file, but Babel only supports transpiling .cts configs. Either:
- Use a .cts config file
- Update to Node.js 23.6.0, which has native TypeScript support
- Install tsx to transpile ${ext} files on the fly\
`;
const SUPPORTED_EXTENSIONS = {
".js": "unknown",
".mjs": "esm",
".cjs": "cjs",
".ts": "unknown",
".mts": "esm",
".cts": "cjs"
};
const asyncModules = new Set();
function* loadCodeDefault(filepath, loader, esmError, tlaError) {
let async;
const ext = _path().extname(filepath);
const isTS = ext === ".ts" || ext === ".cts" || ext === ".mts";
const type = SUPPORTED_EXTENSIONS[hasOwnProperty.call(SUPPORTED_EXTENSIONS, ext) ? ext : ".js"];
const pattern = `${loader} ${type}`;
switch (pattern) {
case "require cjs":
case "auto cjs":
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
case "auto unknown":
case "require unknown":
case "require esm":
try {
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
} catch (e) {
if (e.code === "ERR_REQUIRE_ASYNC_MODULE" || e.code === "ERR_REQUIRE_CYCLE_MODULE" && asyncModules.has(filepath)) {
asyncModules.add(filepath);
if (!(async != null ? async : async = yield* (0, _async.isAsync)())) {
throw new _configError.default(tlaError, filepath);
}
} else if (e.code === "ERR_REQUIRE_ESM" || type === "esm") {} else {
throw e;
}
}
case "auto esm":
if (async != null ? async : async = yield* (0, _async.isAsync)()) {
const promise = isTS ? ensureTsSupport(filepath, ext, () => loadMjsFromPath(filepath)) : loadMjsFromPath(filepath);
return (yield* (0, _async.waitFor)(promise)).default;
}
if (isTS) {
throw new _configError.default(tsNotSupportedError(ext), filepath);
} else {
throw new _configError.default(esmError, filepath);
}
default:
throw new Error("Internal Babel error: unreachable code.");
}
}
function ensureTsSupport(filepath, ext, callback) {
if (process.features.typescript || require.extensions[".ts"] || require.extensions[".cts"] || require.extensions[".mts"]) {
return callback();
}
if (ext !== ".cts") {
throw new _configError.default(tsNotSupportedError(ext), filepath);
}
const opts = {
babelrc: false,
configFile: false,
sourceType: "unambiguous",
sourceMaps: "inline",
sourceFileName: _path().basename(filepath),
presets: [[getTSPreset(filepath), Object.assign({
onlyRemoveTypeImports: true,
optimizeConstEnums: true
}, {
allowDeclareFields: true
})]]
};
let handler = function (m, filename) {
if (handler && filename.endsWith(".cts")) {
try {
return m._compile((0, _transformFile.transformFileSync)(filename, Object.assign({}, opts, {
filename
})).code, filename);
} catch (error) {
const packageJson = require("@babel/preset-typescript/package.json");
if (_semver().lt(packageJson.version, "7.21.4")) {
console.error("`.cts` configuration file failed to load, please try to update `@babel/preset-typescript`.");
}
throw error;
}
}
return require.extensions[".js"](m, filename);
};
require.extensions[ext] = handler;
try {
return callback();
} finally {
if (require.extensions[ext] === handler) delete require.extensions[ext];
handler = undefined;
}
}
function getTSPreset(filepath) {
try {
return require("@babel/preset-typescript");
} catch (error) {
if (error.code !== "MODULE_NOT_FOUND") throw error;
let message = "You appear to be using a .cts file as Babel configuration, but the `@babel/preset-typescript` package was not found: please install it!";
if (process.versions.pnp) {
message += `
If you are using Yarn Plug'n'Play, you may also need to add the following configuration to your .yarnrc.yml file:
packageExtensions:
\t"@babel/core@*":
\t\tpeerDependencies:
\t\t\t"@babel/preset-typescript": "*"
`;
}
throw new _configError.default(message, filepath);
}
}
0 && 0;
//# sourceMappingURL=module-types.js.map
File diff suppressed because one or more lines are too long
-61
View File
@@ -1,61 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findPackageData = findPackageData;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _utils = require("./utils.js");
var _configError = require("../../errors/config-error.js");
const PACKAGE_FILENAME = "package.json";
const readConfigPackage = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = JSON.parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing JSON - ${err.message}`, filepath);
}
if (!options) throw new Error(`${filepath}: No config detected`);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
function* findPackageData(filepath) {
let pkg = null;
const directories = [];
let isPackage = true;
let dirname = _path().dirname(filepath);
while (!pkg && _path().basename(dirname) !== "node_modules") {
directories.push(dirname);
pkg = yield* readConfigPackage(_path().join(dirname, PACKAGE_FILENAME));
const nextLoc = _path().dirname(dirname);
if (dirname === nextLoc) {
isPackage = false;
break;
}
dirname = nextLoc;
}
return {
filepath,
directories,
pkg,
isPackage
};
}
0 && 0;
//# sourceMappingURL=package.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_path","data","require","_utils","_configError","PACKAGE_FILENAME","readConfigPackage","makeStaticFileCache","filepath","content","options","JSON","parse","err","ConfigError","message","Error","Array","isArray","dirname","path","findPackageData","pkg","directories","isPackage","basename","push","join","nextLoc"],"sources":["../../../src/config/files/package.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Handler } from \"gensync\";\nimport { makeStaticFileCache } from \"./utils.ts\";\n\nimport type { ConfigFile, FilePackageData } from \"./types.ts\";\n\nimport ConfigError from \"../../errors/config-error.ts\";\n\nconst PACKAGE_FILENAME = \"package.json\";\n\nconst readConfigPackage = makeStaticFileCache(\n (filepath, content): ConfigFile => {\n let options;\n try {\n options = JSON.parse(content) as unknown;\n } catch (err) {\n throw new ConfigError(\n `Error while parsing JSON - ${err.message}`,\n filepath,\n );\n }\n\n if (!options) throw new Error(`${filepath}: No config detected`);\n\n if (typeof options !== \"object\") {\n throw new ConfigError(\n `Config returned typeof ${typeof options}`,\n filepath,\n );\n }\n if (Array.isArray(options)) {\n throw new ConfigError(`Expected config object but found array`, filepath);\n }\n\n return {\n filepath,\n dirname: path.dirname(filepath),\n options,\n };\n },\n);\n\n/**\n * Find metadata about the package that this file is inside of. Resolution\n * of Babel's config requires general package information to decide when to\n * search for .babelrc files\n */\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n let pkg = null;\n const directories = [];\n let isPackage = true;\n\n let dirname = path.dirname(filepath);\n while (!pkg && path.basename(dirname) !== \"node_modules\") {\n directories.push(dirname);\n\n pkg = yield* readConfigPackage(path.join(dirname, PACKAGE_FILENAME));\n\n const nextLoc = path.dirname(dirname);\n if (dirname === nextLoc) {\n isPackage = false;\n break;\n }\n dirname = nextLoc;\n }\n\n return { filepath, directories, pkg, isPackage };\n}\n"],"mappings":";;;;;;AAAA,SAAAA,MAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,KAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA,IAAAE,MAAA,GAAAD,OAAA;AAIA,IAAAE,YAAA,GAAAF,OAAA;AAEA,MAAMG,gBAAgB,GAAG,cAAc;AAEvC,MAAMC,iBAAiB,GAAG,IAAAC,0BAAmB,EAC3C,CAACC,QAAQ,EAAEC,OAAO,KAAiB;EACjC,IAAIC,OAAO;EACX,IAAI;IACFA,OAAO,GAAGC,IAAI,CAACC,KAAK,CAACH,OAAO,CAAY;EAC1C,CAAC,CAAC,OAAOI,GAAG,EAAE;IACZ,MAAM,IAAIC,oBAAW,CACnB,8BAA8BD,GAAG,CAACE,OAAO,EAAE,EAC3CP,QACF,CAAC;EACH;EAEA,IAAI,CAACE,OAAO,EAAE,MAAM,IAAIM,KAAK,CAAC,GAAGR,QAAQ,sBAAsB,CAAC;EAEhE,IAAI,OAAOE,OAAO,KAAK,QAAQ,EAAE;IAC/B,MAAM,IAAII,oBAAW,CACnB,0BAA0B,OAAOJ,OAAO,EAAE,EAC1CF,QACF,CAAC;EACH;EACA,IAAIS,KAAK,CAACC,OAAO,CAACR,OAAO,CAAC,EAAE;IAC1B,MAAM,IAAII,oBAAW,CAAC,wCAAwC,EAAEN,QAAQ,CAAC;EAC3E;EAEA,OAAO;IACLA,QAAQ;IACRW,OAAO,EAAEC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;IAC/BE;EACF,CAAC;AACH,CACF,CAAC;AAOM,UAAUW,eAAeA,CAACb,QAAgB,EAA4B;EAC3E,IAAIc,GAAG,GAAG,IAAI;EACd,MAAMC,WAAW,GAAG,EAAE;EACtB,IAAIC,SAAS,GAAG,IAAI;EAEpB,IAAIL,OAAO,GAAGC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;EACpC,OAAO,CAACc,GAAG,IAAIF,MAAGA,CAAC,CAACK,QAAQ,CAACN,OAAO,CAAC,KAAK,cAAc,EAAE;IACxDI,WAAW,CAACG,IAAI,CAACP,OAAO,CAAC;IAEzBG,GAAG,GAAG,OAAOhB,iBAAiB,CAACc,MAAGA,CAAC,CAACO,IAAI,CAACR,OAAO,EAAEd,gBAAgB,CAAC,CAAC;IAEpE,MAAMuB,OAAO,GAAGR,MAAGA,CAAC,CAACD,OAAO,CAACA,OAAO,CAAC;IACrC,IAAIA,OAAO,KAAKS,OAAO,EAAE;MACvBJ,SAAS,GAAG,KAAK;MACjB;IACF;IACAL,OAAO,GAAGS,OAAO;EACnB;EAEA,OAAO;IAAEpB,QAAQ;IAAEe,WAAW;IAAED,GAAG;IAAEE;EAAU,CAAC;AAClD;AAAC","ignoreList":[]}
-220
View File
@@ -1,220 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePreset = exports.resolvePlugin = void 0;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _async = require("../../gensync-utils/async.js");
var _moduleTypes = require("./module-types.js");
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
var _importMetaResolve = require("../../vendor/import-meta-resolve.js");
require("module");
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
const debug = _debug()("babel:config:loading:files:plugins");
const EXACT_RE = /^module:/;
const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-plugin-)/;
const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-preset-)/;
const BABEL_PLUGIN_ORG_RE = /^(@babel\/)(?!plugin-|[^/]+\/)/;
const BABEL_PRESET_ORG_RE = /^(@babel\/)(?!preset-|[^/]+\/)/;
const OTHER_PLUGIN_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-plugin(?:-|\/|$)|[^/]+\/)/;
const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/;
const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/;
const resolvePlugin = exports.resolvePlugin = resolveStandardizedName.bind(null, "plugin");
const resolvePreset = exports.resolvePreset = resolveStandardizedName.bind(null, "preset");
function* loadPlugin(name, dirname) {
const {
filepath,
loader
} = resolvePlugin(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("plugin", loader, filepath);
debug("Loaded plugin %o from %o.", name, dirname);
return {
filepath,
value
};
}
function* loadPreset(name, dirname) {
const {
filepath,
loader
} = resolvePreset(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("preset", loader, filepath);
debug("Loaded preset %o from %o.", name, dirname);
return {
filepath,
value
};
}
function standardizeName(type, name) {
if (_path().isAbsolute(name)) return name;
const isPreset = type === "preset";
return name.replace(isPreset ? BABEL_PRESET_PREFIX_RE : BABEL_PLUGIN_PREFIX_RE, `babel-${type}-`).replace(isPreset ? BABEL_PRESET_ORG_RE : BABEL_PLUGIN_ORG_RE, `$1${type}-`).replace(isPreset ? OTHER_PRESET_ORG_RE : OTHER_PLUGIN_ORG_RE, `$1babel-${type}-`).replace(OTHER_ORG_DEFAULT_RE, `$1/babel-${type}`).replace(EXACT_RE, "");
}
function* resolveAlternativesHelper(type, name) {
const standardizedName = standardizeName(type, name);
const {
error,
value
} = yield standardizedName;
if (!error) return value;
if (error.code !== "MODULE_NOT_FOUND") throw error;
if (standardizedName !== name && !(yield name).error) {
error.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
}
if (!(yield standardizeName(type, "@babel/" + name)).error) {
error.message += `\n- Did you mean "@babel/${name}"?`;
}
const oppositeType = type === "preset" ? "plugin" : "preset";
if (!(yield standardizeName(oppositeType, name)).error) {
error.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
}
if (type === "plugin") {
const transformName = standardizedName.replace("-proposal-", "-transform-");
if (transformName !== standardizedName && !(yield transformName).error) {
error.message += `\n- Did you mean "${transformName}"?`;
}
}
error.message += `\n
Make sure that all the Babel plugins and presets you are using
are defined as dependencies or devDependencies in your package.json
file. It's possible that the missing plugin is loaded by a preset
you are using that forgot to add the plugin to its dependencies: you
can workaround this problem by explicitly adding the missing package
to your top-level package.json.
`;
throw error;
}
function tryRequireResolve(id, dirname) {
try {
if (dirname) {
return {
error: null,
value: (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(id, {
paths: [dirname]
})
};
} else {
return {
error: null,
value: require.resolve(id)
};
}
} catch (error) {
return {
error,
value: null
};
}
}
function tryImportMetaResolve(id, options) {
try {
return {
error: null,
value: (0, _importMetaResolve.resolve)(id, options)
};
} catch (error) {
return {
error,
value: null
};
}
}
function resolveStandardizedNameForRequire(type, name, dirname) {
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryRequireResolve(res.value, dirname));
}
return {
loader: "require",
filepath: res.value
};
}
function resolveStandardizedNameForImport(type, name, dirname) {
const parentUrl = (0, _url().pathToFileURL)(_path().join(dirname, "./babel-virtual-resolve-base.js")).href;
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryImportMetaResolve(res.value, parentUrl));
}
return {
loader: "auto",
filepath: (0, _url().fileURLToPath)(res.value)
};
}
function resolveStandardizedName(type, name, dirname, allowAsync) {
if (!_moduleTypes.supportsESM || !allowAsync) {
return resolveStandardizedNameForRequire(type, name, dirname);
}
try {
const resolved = resolveStandardizedNameForImport(type, name, dirname);
if (!(0, _fs().existsSync)(resolved.filepath)) {
throw Object.assign(new Error(`Could not resolve "${name}" in file ${dirname}.`), {
type: "MODULE_NOT_FOUND"
});
}
return resolved;
} catch (e) {
try {
return resolveStandardizedNameForRequire(type, name, dirname);
} catch (e2) {
if (e.type === "MODULE_NOT_FOUND") throw e;
if (e2.type === "MODULE_NOT_FOUND") throw e2;
throw e;
}
}
}
var LOADING_MODULES = new Set();
function* requireModule(type, loader, name) {
if (!(yield* (0, _async.isAsync)()) && LOADING_MODULES.has(name)) {
throw new Error(`Reentrant ${type} detected trying to load "${name}". This module is not ignored ` + "and is trying to load itself while compiling itself, leading to a dependency cycle. " + 'We recommend adding it to your "ignore" list in your babelrc, or to a .babelignore.');
}
try {
LOADING_MODULES.add(name);
return yield* (0, _moduleTypes.default)(name, loader, `You appear to be using a native ECMAScript module ${type}, ` + "which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", `You appear to be using a ${type} that contains top-level await, ` + "which is only supported when running Babel asynchronously.", true);
} catch (err) {
err.message = `[BABEL]: ${err.message} (While processing: ${name})`;
throw err;
} finally {
LOADING_MODULES.delete(name);
}
}
0 && 0;
//# sourceMappingURL=plugins.js.map
File diff suppressed because one or more lines are too long
-5
View File
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=types.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../../src/config/files/types.ts"],"sourcesContent":["import type { InputOptions } from \"../index.ts\";\n\nexport type ConfigFile = {\n filepath: string;\n dirname: string;\n options: InputOptions & { babel?: unknown };\n};\n\nexport type IgnoreFile = {\n filepath: string;\n dirname: string;\n ignore: RegExp[];\n};\n\nexport type RelativeConfig = {\n // The actual config, either from package.json#babel, .babelrc, or\n // .babelrc.js, if there was one.\n config: ConfigFile | null;\n // The .babelignore, if there was one.\n ignore: IgnoreFile | null;\n};\n\nexport type FilePackageData = {\n // The file in the package.\n filepath: string;\n // Any ancestor directories of the file that are within the package.\n directories: string[];\n // The contents of the package.json. May not be found if the package just\n // terminated at a node_modules folder without finding one.\n pkg: ConfigFile | null;\n // True if a package.json or node_modules folder was found while traversing\n // the directory structure.\n isPackage: boolean;\n};\n"],"mappings":"","ignoreList":[]}
-36
View File
@@ -1,36 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeStaticFileCache = makeStaticFileCache;
var _caching = require("../caching.js");
var fs = require("../../gensync-utils/fs.js");
function _fs2() {
const data = require("fs");
_fs2 = function () {
return data;
};
return data;
}
function makeStaticFileCache(fn) {
return (0, _caching.makeStrongCache)(function* (filepath, cache) {
const cached = cache.invalidate(() => fileMtime(filepath));
if (cached === null) {
return null;
}
return fn(filepath, yield* fs.readFile(filepath, "utf8"));
});
}
function fileMtime(filepath) {
if (!_fs2().existsSync(filepath)) return null;
try {
return +_fs2().statSync(filepath).mtime;
} catch (e) {
if (e.code !== "ENOENT" && e.code !== "ENOTDIR") throw e;
}
return null;
}
0 && 0;
//# sourceMappingURL=utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_caching","require","fs","_fs2","data","makeStaticFileCache","fn","makeStrongCache","filepath","cache","cached","invalidate","fileMtime","readFile","nodeFs","existsSync","statSync","mtime","e","code"],"sources":["../../../src/config/files/utils.ts"],"sourcesContent":["import type { Handler } from \"gensync\";\n\nimport { makeStrongCache } from \"../caching.ts\";\nimport type { CacheConfigurator } from \"../caching.ts\";\nimport * as fs from \"../../gensync-utils/fs.ts\";\nimport nodeFs from \"node:fs\";\n\nexport function makeStaticFileCache<T>(\n fn: (filepath: string, contents: string) => T,\n) {\n return makeStrongCache(function* (\n filepath: string,\n cache: CacheConfigurator<void>,\n ): Handler<null | T> {\n const cached = cache.invalidate(() => fileMtime(filepath));\n\n if (cached === null) {\n return null;\n }\n\n return fn(filepath, yield* fs.readFile(filepath, \"utf8\"));\n });\n}\n\nfunction fileMtime(filepath: string): number | null {\n if (!nodeFs.existsSync(filepath)) return null;\n\n try {\n return +nodeFs.statSync(filepath).mtime;\n } catch (e) {\n if (e.code !== \"ENOENT\" && e.code !== \"ENOTDIR\") throw e;\n }\n\n return null;\n}\n"],"mappings":";;;;;;AAEA,IAAAA,QAAA,GAAAC,OAAA;AAEA,IAAAC,EAAA,GAAAD,OAAA;AACA,SAAAE,KAAA;EAAA,MAAAC,IAAA,GAAAH,OAAA;EAAAE,IAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEO,SAASC,mBAAmBA,CACjCC,EAA6C,EAC7C;EACA,OAAO,IAAAC,wBAAe,EAAC,WACrBC,QAAgB,EAChBC,KAA8B,EACX;IACnB,MAAMC,MAAM,GAAGD,KAAK,CAACE,UAAU,CAAC,MAAMC,SAAS,CAACJ,QAAQ,CAAC,CAAC;IAE1D,IAAIE,MAAM,KAAK,IAAI,EAAE;MACnB,OAAO,IAAI;IACb;IAEA,OAAOJ,EAAE,CAACE,QAAQ,EAAE,OAAON,EAAE,CAACW,QAAQ,CAACL,QAAQ,EAAE,MAAM,CAAC,CAAC;EAC3D,CAAC,CAAC;AACJ;AAEA,SAASI,SAASA,CAACJ,QAAgB,EAAiB;EAClD,IAAI,CAACM,KAAKA,CAAC,CAACC,UAAU,CAACP,QAAQ,CAAC,EAAE,OAAO,IAAI;EAE7C,IAAI;IACF,OAAO,CAACM,KAAKA,CAAC,CAACE,QAAQ,CAACR,QAAQ,CAAC,CAACS,KAAK;EACzC,CAAC,CAAC,OAAOC,CAAC,EAAE;IACV,IAAIA,CAAC,CAACC,IAAI,KAAK,QAAQ,IAAID,CAAC,CAACC,IAAI,KAAK,SAAS,EAAE,MAAMD,CAAC;EAC1D;EAEA,OAAO,IAAI;AACb;AAAC","ignoreList":[]}
-312
View File
@@ -1,312 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
var context = require("../index.js");
var _plugin = require("./plugin.js");
var _item = require("./item.js");
var _configChain = require("./config-chain.js");
var _deepArray = require("./helpers/deep-array.js");
function _traverse() {
const data = require("@babel/traverse");
_traverse = function () {
return data;
};
return data;
}
var _caching = require("./caching.js");
var _options = require("./validation/options.js");
var _plugins = require("./validation/plugins.js");
var _configApi = require("./helpers/config-api.js");
var _partial = require("./partial.js");
var _configError = require("../errors/config-error.js");
var _default = exports.default = _gensync()(function* loadFullConfig(inputOpts) {
var _opts$assumptions;
const result = yield* (0, _partial.default)(inputOpts);
if (!result) {
return null;
}
const {
options,
context,
fileHandling
} = result;
if (fileHandling === "ignored") {
return null;
}
const optionDefaults = {};
const {
plugins,
presets
} = options;
if (!plugins || !presets) {
throw new Error("Assertion failure - plugins and presets exist");
}
const presetContext = Object.assign({}, context, {
targets: options.targets
});
const toDescriptor = item => {
const desc = (0, _item.getItemDescriptor)(item);
if (!desc) {
throw new Error("Assertion failure - must be config item");
}
return desc;
};
const presetsDescriptors = presets.map(toDescriptor);
const initialPluginsDescriptors = plugins.map(toDescriptor);
const pluginDescriptorsByPass = [[]];
const passes = [];
const externalDependencies = [];
const ignored = yield* enhanceError(context, function* recursePresetDescriptors(rawPresets, pluginDescriptorsPass) {
const presets = [];
for (let i = 0; i < rawPresets.length; i++) {
const descriptor = rawPresets[i];
if (descriptor.options !== false) {
try {
var preset = yield* loadPresetDescriptor(descriptor, presetContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_OPTION") {
(0, _options.checkNoUnwrappedItemOptionPairs)(rawPresets, i, "preset", e);
}
throw e;
}
externalDependencies.push(preset.externalDependencies);
if (descriptor.ownPass) {
presets.push({
preset: preset.chain,
pass: []
});
} else {
presets.unshift({
preset: preset.chain,
pass: pluginDescriptorsPass
});
}
}
}
if (presets.length > 0) {
pluginDescriptorsByPass.splice(1, 0, ...presets.map(o => o.pass).filter(p => p !== pluginDescriptorsPass));
for (const {
preset,
pass
} of presets) {
if (!preset) return true;
pass.push(...preset.plugins);
const ignored = yield* recursePresetDescriptors(preset.presets, pass);
if (ignored) return true;
preset.options.forEach(opts => {
(0, _util.mergeOptions)(optionDefaults, opts);
});
}
}
})(presetsDescriptors, pluginDescriptorsByPass[0]);
if (ignored) return null;
const opts = optionDefaults;
(0, _util.mergeOptions)(opts, options);
const pluginContext = Object.assign({}, presetContext, {
assumptions: (_opts$assumptions = opts.assumptions) != null ? _opts$assumptions : {}
});
yield* enhanceError(context, function* loadPluginDescriptors() {
pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors);
for (const descs of pluginDescriptorsByPass) {
const pass = [];
passes.push(pass);
for (let i = 0; i < descs.length; i++) {
const descriptor = descs[i];
if (descriptor.options !== false) {
try {
var plugin = yield* loadPluginDescriptor(descriptor, pluginContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") {
(0, _options.checkNoUnwrappedItemOptionPairs)(descs, i, "plugin", e);
}
throw e;
}
pass.push(plugin);
externalDependencies.push(plugin.externalDependencies);
}
}
}
})();
opts.plugins = passes[0];
opts.presets = passes.slice(1).filter(plugins => plugins.length > 0).map(plugins => ({
plugins
}));
opts.passPerPreset = opts.presets.length > 0;
return {
options: opts,
passes: passes,
externalDependencies: (0, _deepArray.finalize)(externalDependencies)
};
});
function enhanceError(context, fn) {
return function* (arg1, arg2) {
try {
return yield* fn(arg1, arg2);
} catch (e) {
if (!e.message.startsWith("[BABEL]")) {
var _context$filename;
e.message = `[BABEL] ${(_context$filename = context.filename) != null ? _context$filename : "unknown file"}: ${e.message}`;
}
throw e;
}
};
}
const makeDescriptorLoader = apiFactory => (0, _caching.makeWeakCache)(function* ({
value,
options,
dirname,
alias
}, cache) {
if (options === false) throw new Error("Assertion failure");
options = options || {};
const externalDependencies = [];
let item = value;
if (typeof value === "function") {
const factory = (0, _async.maybeAsync)(value, `You appear to be using an async plugin/preset, but Babel has been called synchronously`);
const api = Object.assign({}, context, apiFactory(cache, externalDependencies));
try {
item = yield* factory(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
}
throw e;
}
}
if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}
if ((0, _async.isThenable)(item)) {
yield* [];
throw new Error(`You appear to be using a promise as a plugin, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, ` + `you may need to upgrade your @babel/core version. ` + `As an alternative, you can prefix the promise with "await". ` + `(While processing: ${JSON.stringify(alias)})`);
}
if (externalDependencies.length > 0 && (!cache.configured() || cache.mode() === "forever")) {
let error = `A plugin/preset has external untracked dependencies ` + `(${externalDependencies[0]}), but the cache `;
if (!cache.configured()) {
error += `has not been configured to be invalidated when the external dependencies change. `;
} else {
error += ` has been configured to never be invalidated. `;
}
error += `Plugins/presets should configure their cache to be invalidated when the external ` + `dependencies change, for example using \`api.cache.invalidate(() => ` + `statSync(filepath).mtimeMs)\` or \`api.cache.never()\`\n` + `(While processing: ${JSON.stringify(alias)})`;
throw new Error(error);
}
return {
value: item,
options,
dirname,
alias,
externalDependencies: (0, _deepArray.finalize)(externalDependencies)
};
});
const pluginDescriptorLoader = makeDescriptorLoader(_configApi.makePluginAPI);
const presetDescriptorLoader = makeDescriptorLoader(_configApi.makePresetAPI);
const instantiatePlugin = (0, _caching.makeWeakCache)(function* ({
value,
options,
dirname,
alias,
externalDependencies
}, cache) {
const pluginObj = (0, _plugins.validatePluginObject)(value);
const plugin = Object.assign({}, pluginObj);
if (plugin.visitor) {
plugin.visitor = _traverse().default.explode(Object.assign({}, plugin.visitor));
}
if (plugin.inherits) {
const inheritsDescriptor = {
name: undefined,
alias: `${alias}$inherits`,
value: plugin.inherits,
options,
dirname
};
const inherits = yield* (0, _async.forwardAsync)(loadPluginDescriptor, run => {
return cache.invalidate(data => run(inheritsDescriptor, data));
});
plugin.pre = chainMaybeAsync(inherits.pre, plugin.pre);
plugin.post = chainMaybeAsync(inherits.post, plugin.post);
plugin.manipulateOptions = chainMaybeAsync(inherits.manipulateOptions, plugin.manipulateOptions);
plugin.visitor = _traverse().default.visitors.merge([inherits.visitor || {}, plugin.visitor || {}]);
if (inherits.externalDependencies.length > 0) {
if (externalDependencies.length === 0) {
externalDependencies = inherits.externalDependencies;
} else {
externalDependencies = (0, _deepArray.finalize)([externalDependencies, inherits.externalDependencies]);
}
}
}
return new _plugin.default(plugin, options, alias, externalDependencies);
});
function* loadPluginDescriptor(descriptor, context) {
if (descriptor.value instanceof _plugin.default) {
if (descriptor.options) {
throw new Error("Passed options to an existing Plugin instance will not work.");
}
return descriptor.value;
}
return yield* instantiatePlugin(yield* pluginDescriptorLoader(descriptor, context), context);
}
const needsFilename = val => val && typeof val !== "function";
const validateIfOptionNeedsFilename = (options, descriptor) => {
if (needsFilename(options.test) || needsFilename(options.include) || needsFilename(options.exclude)) {
const formattedPresetName = descriptor.name ? `"${descriptor.name}"` : "/* your preset */";
throw new _configError.default([`Preset ${formattedPresetName} requires a filename to be set when babel is called directly,`, `\`\`\``, `babel.transformSync(code, { filename: 'file.ts', presets: [${formattedPresetName}] });`, `\`\`\``, `See https://babeljs.io/docs/en/options#filename for more information.`].join("\n"));
}
};
const validatePreset = (preset, context, descriptor) => {
if (!context.filename) {
var _options$overrides;
const {
options
} = preset;
validateIfOptionNeedsFilename(options, descriptor);
(_options$overrides = options.overrides) == null || _options$overrides.forEach(overrideOptions => validateIfOptionNeedsFilename(overrideOptions, descriptor));
}
};
const instantiatePreset = (0, _caching.makeWeakCacheSync)(({
value,
dirname,
alias,
externalDependencies
}) => {
return {
options: (0, _options.validate)("preset", value),
alias,
dirname,
externalDependencies
};
});
function* loadPresetDescriptor(descriptor, context) {
const preset = instantiatePreset(yield* presetDescriptorLoader(descriptor, context));
validatePreset(preset, context, descriptor);
return {
chain: yield* (0, _configChain.buildPresetChain)(preset, context),
externalDependencies: preset.externalDependencies
};
}
function chainMaybeAsync(a, b) {
if (!a) return b;
if (!b) return a;
return function (...args) {
const res = a.apply(this, args);
if (res && typeof res.then === "function") {
return res.then(() => b.apply(this, args));
}
return b.apply(this, args);
};
}
0 && 0;
//# sourceMappingURL=full.js.map
File diff suppressed because one or more lines are too long
-85
View File
@@ -1,85 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeConfigAPI = makeConfigAPI;
exports.makePluginAPI = makePluginAPI;
exports.makePresetAPI = makePresetAPI;
function _semver() {
const data = require("semver");
_semver = function () {
return data;
};
return data;
}
var _index = require("../../index.js");
var _caching = require("../caching.js");
function makeConfigAPI(cache) {
const env = value => cache.using(data => {
if (value === undefined) return data.envName;
if (typeof value === "function") {
return (0, _caching.assertSimpleType)(value(data.envName));
}
return (Array.isArray(value) ? value : [value]).some(entry => {
if (typeof entry !== "string") {
throw new Error("Unexpected non-string value");
}
return entry === data.envName;
});
});
const caller = cb => cache.using(data => (0, _caching.assertSimpleType)(cb(data.caller)));
return {
version: _index.version,
cache: cache.simple(),
env,
async: () => false,
caller,
assertVersion
};
}
function makePresetAPI(cache, externalDependencies) {
const targets = () => JSON.parse(cache.using(data => JSON.stringify(data.targets)));
const addExternalDependency = ref => {
externalDependencies.push(ref);
};
return Object.assign({}, makeConfigAPI(cache), {
targets,
addExternalDependency
});
}
function makePluginAPI(cache, externalDependencies) {
const assumption = name => cache.using(data => data.assumptions[name]);
return Object.assign({}, makePresetAPI(cache, externalDependencies), {
assumption
});
}
function assertVersion(range) {
if (typeof range === "number") {
if (!Number.isInteger(range)) {
throw new Error("Expected string or integer value.");
}
range = `^${range}.0.0-0`;
}
if (typeof range !== "string") {
throw new Error("Expected string or integer value.");
}
if (range === "*" || _semver().satisfies(_index.version, range)) return;
const message = `Requires Babel "${range}", but was loaded with "${_index.version}". ` + `If you are sure you have a compatible version of @babel/core, ` + `it is likely that something in your build process is loading the ` + `wrong version. Inspect the stack trace of this error to look for ` + `the first entry that doesn't mention "@babel/core" or "babel-core" ` + `to see what is calling Babel.`;
const limit = Error.stackTraceLimit;
if (typeof limit === "number" && limit < 25) {
Error.stackTraceLimit = 25;
}
const err = new Error(message);
if (typeof limit === "number") {
Error.stackTraceLimit = limit;
}
throw Object.assign(err, {
code: "BABEL_VERSION_UNSUPPORTED",
version: _index.version,
range
});
}
0 && 0;
//# sourceMappingURL=config-api.js.map
File diff suppressed because one or more lines are too long
-23
View File
@@ -1,23 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.finalize = finalize;
exports.flattenToSet = flattenToSet;
function finalize(deepArr) {
return Object.freeze(deepArr);
}
function flattenToSet(arr) {
const result = new Set();
const stack = [arr];
while (stack.length > 0) {
for (const el of stack.pop()) {
if (Array.isArray(el)) stack.push(el);else result.add(el);
}
}
return result;
}
0 && 0;
//# sourceMappingURL=deep-array.js.map
@@ -1 +0,0 @@
{"version":3,"names":["finalize","deepArr","Object","freeze","flattenToSet","arr","result","Set","stack","length","el","pop","Array","isArray","push","add"],"sources":["../../../src/config/helpers/deep-array.ts"],"sourcesContent":["export type DeepArray<T> = (T | ReadonlyDeepArray<T>)[];\n\n// Just to make sure that DeepArray<T> is not assignable to ReadonlyDeepArray<T>\ndeclare const __marker: unique symbol;\nexport type ReadonlyDeepArray<T> = readonly (T | ReadonlyDeepArray<T>)[] & {\n [__marker]: true;\n};\n\nexport function finalize<T>(deepArr: DeepArray<T>): ReadonlyDeepArray<T> {\n return Object.freeze(deepArr) as ReadonlyDeepArray<T>;\n}\n\nexport function flattenToSet<T extends string>(\n arr: ReadonlyDeepArray<T>,\n): Set<T> {\n const result = new Set<T>();\n const stack = [arr];\n while (stack.length > 0) {\n for (const el of stack.pop()) {\n if (Array.isArray(el)) stack.push(el as ReadonlyDeepArray<T>);\n else result.add(el as T);\n }\n }\n return result;\n}\n"],"mappings":";;;;;;;AAQO,SAASA,QAAQA,CAAIC,OAAqB,EAAwB;EACvE,OAAOC,MAAM,CAACC,MAAM,CAACF,OAAO,CAAC;AAC/B;AAEO,SAASG,YAAYA,CAC1BC,GAAyB,EACjB;EACR,MAAMC,MAAM,GAAG,IAAIC,GAAG,CAAI,CAAC;EAC3B,MAAMC,KAAK,GAAG,CAACH,GAAG,CAAC;EACnB,OAAOG,KAAK,CAACC,MAAM,GAAG,CAAC,EAAE;IACvB,KAAK,MAAMC,EAAE,IAAIF,KAAK,CAACG,GAAG,CAAC,CAAC,EAAE;MAC5B,IAAIC,KAAK,CAACC,OAAO,CAACH,EAAE,CAAC,EAAEF,KAAK,CAACM,IAAI,CAACJ,EAA0B,CAAC,CAAC,KACzDJ,MAAM,CAACS,GAAG,CAACL,EAAO,CAAC;IAC1B;EACF;EACA,OAAOJ,MAAM;AACf;AAAC","ignoreList":[]}
-12
View File
@@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getEnv = getEnv;
function getEnv(defaultValue = "development") {
return process.env.BABEL_ENV || process.env.NODE_ENV || defaultValue;
}
0 && 0;
//# sourceMappingURL=environment.js.map

Some files were not shown because too many files have changed in this diff Show More