Compare commits
10
Commits
c0cafb0400
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c021003861 | ||
|
|
23f3c61c9f | ||
|
|
b2ff322ef3 | ||
|
|
d38782490c | ||
|
|
559d7bb870 | ||
|
|
73dac50a36 | ||
|
|
b7767c95ae | ||
|
|
047571921e | ||
|
|
2f4d7869a9 | ||
|
|
f97ac16b1c |
+15
-28
@@ -1,39 +1,26 @@
|
||||
# 交易所监控 + 自动套利
|
||||
# 三所价差异动监控
|
||||
# 复制为 .env 并填入实际值
|
||||
|
||||
# Telegram 推送
|
||||
# Telegram 推送 (可选)
|
||||
TELEGRAM_BOT_TOKEN=***
|
||||
TELEGRAM_CHAT_ID=你的聊天ID
|
||||
|
||||
# ============================================
|
||||
# 自动交易开关 (设置为 1 启用)
|
||||
TRADE_ENABLED=0
|
||||
|
||||
# 交易参数
|
||||
TRADE_THRESHOLD=0.15 # 最低套利利润率 (%)
|
||||
TRADE_AMOUNT_USD=10 # 每腿金额 (USDT)
|
||||
TRADE_COOLDOWN_MS=30000 # 同一币种套利冷却 (毫秒)
|
||||
|
||||
# Bitget API (需开通合约API)
|
||||
# Bitget API (WS 行情)
|
||||
BITGET_API_KEY=***
|
||||
BITGET_API_SECRET=***
|
||||
BITGET_PASSPHRASE=你的密码短语
|
||||
|
||||
# HyperLiquid API (钱包私钥)
|
||||
HL_PRIVATE_KEY=你的ed25519私钥(hex)
|
||||
HL_ADDRESS=你的钱包地址
|
||||
# Binance API (WS 行情 + K线 REST)
|
||||
BINANCE_API_KEY=***
|
||||
BINANCE_API_SECRET=***
|
||||
|
||||
# OKX 行情为公开 WS,无需 API Key
|
||||
|
||||
# 网络代理 (国内环境需要)
|
||||
HTTPS_PROXY=http://127.0.0.1:7890
|
||||
|
||||
# ============================================
|
||||
# 以下为 ema-monitor 使用的参数 (保持不变)
|
||||
DATA_API_BASE=http://localhost:80
|
||||
SYMBOL=BTC/USDT:USDT
|
||||
FETCH_LIMIT_BASE=8000
|
||||
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 # 每腿模拟滑点 (%)
|
||||
# 扫描参数 (可选,config.json 已有默认值)
|
||||
# SURGE_ENABLED=true
|
||||
# MOMENTUM_ENABLED=true
|
||||
# TREND_ENABLED=true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
.DS_Store
|
||||
hl_helper/node_modules/
|
||||
.env
|
||||
exchange-monitor
|
||||
|
||||
@@ -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.
|
||||
@@ -1,33 +1,38 @@
|
||||
# ⚡ 跨交易所永续合约套利监控
|
||||
# 三所价差异动监控
|
||||
|
||||
Bitget ↔ HyperLiquid 跨交易所永续合约价差套利系统。支持模拟盘/实盘交易、价差监控、自动开仓/加仓/平仓、Web 仪表盘。
|
||||
Binance + OKX + Bitget 三交易所价差异动实时检测系统。通过追踪不同交易所之间的价差异常,捕捉币价「启动」的瞬间。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- **实时价差监控** — 200ms 扫描间隔,追踪 DOGE/LINK/ONDO/OP/WIF/ARB 六个币种
|
||||
- **自动套利交易** — 价差超过阈值自动开仓,收敛自动平仓,支持多级加仓
|
||||
- **模拟/实盘双模式** — `TestMode` 控制,模拟模式无需真实 API Key
|
||||
- **三所价差扫描** — 实时计算 Binance/OKX/Bitget 之间的最大价差,按价差排序展示
|
||||
- **自适应 Surge 检测** — 每个币维护独立的滚动窗口基线,检测价差异常飙升
|
||||
- **方向判断** — 根据领先交易所判断上涨/下跌启动方向
|
||||
- **动量扫描** — 多时间窗口 (1s/5s/15s/60s) 价格变动率追踪
|
||||
- **趋势检测** — 跨交易所一致性确认的趋势状态机 (idle→alert→confirmed→exhausting)
|
||||
- **累积变动** — 1m/5m/1h 多交易所共识变动追踪
|
||||
- **趋势过滤** — K 线数据 + EMA52 趋势过滤,识别安静后的异动
|
||||
- **Web 仪表盘** — Go 内置 HTTP Server + Vite React 前端,SSE 实时推送
|
||||
- **SQLite 持久化** — 交易记录、订单明细、手续费明细全量存储
|
||||
- **手续费精确计算** — 逐笔累加实际 USD 手续费(开仓费+平仓费),非百分比估算
|
||||
- **交易所独立资金管理** — 每交易所 $500 初始资金,开仓前检查两边余额充足
|
||||
- **模拟滑点** — 可配置 `mock_slippage_pct`,模拟真实滑点对净利的影响
|
||||
- **持仓详情弹窗** — 点击持仓卡片查看完整交易详情(价差、PnL、手续费分腿)
|
||||
- **Telegram 通知** — 开仓/平仓/异常实时推送
|
||||
- **SQLite 持久化** — Surge 事件、累积变动事件全量存储
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ scanner.go ← 每 200ms 扫描价差 │
|
||||
│ ↓ 发现机会 (NetProfit > 阈值) │
|
||||
│ trader.go ← 开仓/加仓/平仓逻辑 │
|
||||
│ ↓ 持久化 │
|
||||
│ db/ ← SQLite (trades / orders / system) │
|
||||
│ ↓ SSE 推送 │
|
||||
│ dashboard.go ← HTTP Server :8888 │
|
||||
│ ↓ │
|
||||
│ frontend/ ← Vite + React 仪表盘 │
|
||||
│ Exchange WS (Bitget + Binance + OKX) │
|
||||
│ ↓ 价格推送 │
|
||||
│ PriceStore ← 内存价格存储 │
|
||||
│ ↓ │
|
||||
│ scanner.go ← 三所价差扫描 │
|
||||
│ ↓ │
|
||||
│ surge_detector.go ← 自适应Surge检测 │
|
||||
│ momentum.go ← 动量扫描 │
|
||||
│ trend.go ← 趋势检测 │
|
||||
│ cumulative.go ← 累积变动追踪 │
|
||||
│ trend_filter.go ← K线趋势过滤 │
|
||||
│ ↓ SSE 推送 │
|
||||
│ dashboard.go ← HTTP Server :8888 │
|
||||
│ ↓ │
|
||||
│ frontend/ ← Vite + React 仪表盘 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -39,22 +44,29 @@ Bitget ↔ HyperLiquid 跨交易所永续合约价差套利系统。支持模拟
|
||||
|
||||
```json
|
||||
{
|
||||
"arb_threshold": 0.3,
|
||||
"scan_interval_ms": 200,
|
||||
"trade_enabled": true,
|
||||
"test_mode": true,
|
||||
"trade_threshold": 0.20,
|
||||
"take_profit_pct": 0.20,
|
||||
"mock_slippage_pct": 0.05,
|
||||
"trade_amount_usd": 5,
|
||||
"max_positions": 5,
|
||||
"initial_capital": 500,
|
||||
"taker_fee_bitget": 0.060,
|
||||
"taker_fee_hyperliquid": 0.045,
|
||||
"telegram_bot_token": "xxx",
|
||||
"telegram_chat_id": "xxx"
|
||||
"alert_cooldown_sec": 300,
|
||||
"surge_enabled": true,
|
||||
"surge_window_size": 600,
|
||||
"surge_baseline_multiplier": 3.0,
|
||||
"surge_min_abs_spread_pct": 0.05,
|
||||
"surge_cooldown_sec": 60,
|
||||
"momentum_enabled": true,
|
||||
"momentum_threshold_pct": 0.25,
|
||||
"trend_enabled": true,
|
||||
"trend_baseline_window": 600,
|
||||
"trend_anomaly_mul": 3.0,
|
||||
"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. 启动
|
||||
|
||||
```bash
|
||||
@@ -79,54 +91,28 @@ npm run dev # 开发模式 (Vite HMR :5173)
|
||||
npm run build # 构建生产版本
|
||||
```
|
||||
|
||||
后端优先从 `frontend/dist/` 读取静态文件(热加载),回退到 Go embed。
|
||||
|
||||
## 配置参数
|
||||
|
||||
| 参数 | 说明 | 默认 |
|
||||
|------|------|------|
|
||||
| `scan_interval_ms` | 扫描间隔 (ms) | 200 |
|
||||
| `trade_threshold` | 开仓净利阈值 (%) | 0.20 |
|
||||
| `take_profit_pct` | 止盈净利 (%) | 0.20 |
|
||||
| `mock_slippage_pct` | 模拟滑点 (%) | 0.05 |
|
||||
| `trade_amount_usd` | 每腿交易额 ($) | 5 |
|
||||
| `max_positions` | 最大并行持仓 | 5 |
|
||||
| `taker_fee_bitget` | Bitget 吃单费率 (%) | 0.060 |
|
||||
| `taker_fee_hyperliquid` | HyperLiquid 吃单费率 (%) | 0.045 |
|
||||
| `scale_step_pct` | 加仓步长 (%) | 0.10 |
|
||||
| `position_timeout` | 最长持仓时间 | 10m |
|
||||
| `leg_delay` | 两腿下单间隔 | 300ms |
|
||||
| `initial_capital` | 每交易所初始资金 ($) | 500 |
|
||||
| `surge_enabled` | 启用 Surge 检测 | true |
|
||||
| `surge_window_size` | 滚动窗口样本数 | 600 |
|
||||
| `surge_baseline_multiplier` | 基线倍数(阈值 = 基线 × N) | 3.0 |
|
||||
| `surge_min_abs_spread_pct` | 最小绝对价差 % | 0.05 |
|
||||
| `surge_cooldown_sec` | 同币冷却秒数 | 60 |
|
||||
| `momentum_enabled` | 启用动量扫描 | true |
|
||||
| `trend_enabled` | 启用趋势检测 | true |
|
||||
| `trend_baseline_window` | 趋势基线窗口 | 600 |
|
||||
| `trend_anomaly_mul` | 异常检测倍数 | 3.0 |
|
||||
| `trend_confirm_ticks` | 确认所需次数 | 3 |
|
||||
|
||||
## 数据库
|
||||
|
||||
SQLite (`data/trades.db`),三张核心表:
|
||||
SQLite (`data/trades.db`),核心表:
|
||||
|
||||
| 表 | 说明 |
|
||||
|----|------|
|
||||
| `trades` | 交易主表 — 价差、PnL、手续费 ($) |
|
||||
| `orders` | 订单明细 — 每腿的开仓/加仓/平仓、手续费 ($) |
|
||||
| `system_orders` | 系统订单 — 双向关联 long↔short 订单 |
|
||||
|
||||
## 版本历史
|
||||
|
||||
### 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 图表
|
||||
| `surge_events` | Surge 异动事件 — 币种、价格、价差、方向、领先交易所 |
|
||||
| `cm_events` | 累积变动事件 — 1m/5m/1h 多所共识变动 |
|
||||
| `trend_events` | 趋势状态变迁 — idle→alert→confirmed→exhausting 全生命周期 |
|
||||
| `trend_signals` | 趋势过滤信号 — K 线安静 + EMA 异动信号记录 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config holds all system configuration.
|
||||
@@ -13,83 +12,63 @@ type Config struct {
|
||||
TelegramBotToken string
|
||||
TelegramChatID string
|
||||
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)
|
||||
|
||||
// Automated trading
|
||||
TradeEnabled bool
|
||||
TradeThreshold float64 // minimum profit % to execute trade
|
||||
TradeAmountUSD float64 // amount per leg in USDT
|
||||
TradeCooldownMs int // ms between trades of same coin
|
||||
MaxPositions int // max concurrent open positions (0 = unlimited)
|
||||
// Surge detection
|
||||
SurgeEnabled bool
|
||||
SurgeWindowSize int // rolling window samples (default: 600 = ~30s)
|
||||
SurgeBaselineMultiplier float64 // baseline * N = threshold (default: 3.0)
|
||||
SurgeMinAbsSpreadPct float64 // minimum absolute spread % (default: 0.05)
|
||||
SurgeCooldownSec int // cooldown seconds per coin (default: 60)
|
||||
|
||||
// Capital
|
||||
InitialCapital float64 // starting capital in USD (for PnL % calculation)
|
||||
// Momentum scanning mode
|
||||
MomentumEnabled bool
|
||||
MomentumThresholdPct float64
|
||||
|
||||
// Blacklist — stale spread observation
|
||||
BlacklistDuration time.Duration // how long a coin stays blacklisted (0 = permanent)
|
||||
// Trend detection mode
|
||||
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)
|
||||
ExcludedCoins []string
|
||||
|
||||
// Test mode (no real API keys needed)
|
||||
TestMode bool
|
||||
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)
|
||||
// Binance momentum detection (1-minute Binance-only price change)
|
||||
BinanceMomentumEnabled bool
|
||||
BinanceMomentumThresholdPct float64 // default: 10.0
|
||||
BinanceMomentumCooldownSec int // default: 300 (5 min)
|
||||
BinanceMomentumWindowSec int // default: 60
|
||||
}
|
||||
|
||||
// jsonConfig maps config.json fields (non-secret defaults checked into git).
|
||||
type jsonConfig struct {
|
||||
TestMode bool `json:"test_mode"`
|
||||
TradeEnabled bool `json:"trade_enabled"`
|
||||
ArbThreshold float64 `json:"arb_threshold"`
|
||||
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"`
|
||||
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"`
|
||||
ArbThreshold float64 `json:"arb_threshold"`
|
||||
ScanIntervalMs int `json:"scan_interval_ms"`
|
||||
AlertCooldownSec int `json:"alert_cooldown_sec"`
|
||||
|
||||
// New: exchange fees
|
||||
TakerFeeBitget float64 `json:"taker_fee_bitget"`
|
||||
TakerFeeHyperLiquid float64 `json:"taker_fee_hyperliquid"`
|
||||
// Surge detection
|
||||
SurgeEnabled bool `json:"surge_enabled"`
|
||||
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
|
||||
TakeProfitPct float64 `json:"take_profit_pct"`
|
||||
PositionTimeoutSec int `json:"position_timeout_sec"`
|
||||
LegDelayMs int `json:"leg_delay_ms"`
|
||||
ReversalTolerancePct float64 `json:"reversal_tolerance_pct"`
|
||||
ScaleStepPct float64 `json:"scale_step_pct"`
|
||||
ScaleCooldownSec int `json:"scale_cooldown_sec"`
|
||||
// Momentum scanning
|
||||
MomentumEnabled bool `json:"momentum_enabled"`
|
||||
MomentumThresholdPct float64 `json:"momentum_threshold_pct"`
|
||||
|
||||
// Trend detection
|
||||
TrendEnabled bool `json:"trend_enabled"`
|
||||
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 {
|
||||
@@ -129,42 +108,29 @@ func LoadConfig() *Config {
|
||||
ArbThreshold: getFloat("ARB_THRESHOLD", jsonCfg.ArbThreshold),
|
||||
ScanIntervalMs: int(getFloat("SCAN_INTERVAL_MS", float64(jsonCfg.ScanIntervalMs))),
|
||||
|
||||
TradeEnabled: getBool("TRADE_ENABLED", jsonCfg.TradeEnabled),
|
||||
TradeThreshold: getFloat("TRADE_THRESHOLD", jsonCfg.TradeThreshold),
|
||||
TradeAmountUSD: getFloat("TRADE_AMOUNT_USD", jsonCfg.TradeAmountUSD),
|
||||
TradeCooldownMs: int(getFloat("TRADE_COOLDOWN_MS", float64(jsonCfg.TradeCooldownMs))),
|
||||
MaxPositions: int(getFloat("MAX_POSITIONS", float64(jsonCfg.MaxPositions))),
|
||||
// Surge detection
|
||||
SurgeEnabled: getBool("SURGE_ENABLED", jsonCfg.SurgeEnabled),
|
||||
SurgeWindowSize: int(getFloat("SURGE_WINDOW_SIZE", float64(jsonCfg.SurgeWindowSize))),
|
||||
SurgeBaselineMultiplier: getFloat("SURGE_BASELINE_MULTIPLIER", jsonCfg.SurgeBaselineMultiplier),
|
||||
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),
|
||||
MockSlippagePct: getFloat("MOCK_SLIPPAGE_PCT", jsonCfg.MockSlippagePct),
|
||||
|
||||
// Exchange fee rates
|
||||
TakerFeeBitget: getFloat("TAKER_FEE_BITGET", jsonCfg.TakerFeeBitget),
|
||||
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", ""),
|
||||
// Binance momentum detection
|
||||
BinanceMomentumEnabled: getBool("BINANCE_MOMENTUM_ENABLED", jsonCfg.BinanceMomentumEnabled),
|
||||
BinanceMomentumThresholdPct: getFloat("BINANCE_MOMENTUM_THRESHOLD_PCT", jsonCfg.BinanceMomentumThresholdPct),
|
||||
BinanceMomentumCooldownSec: int(getFloat("BINANCE_MOMENTUM_COOLDOWN_SEC", float64(jsonCfg.BinanceMomentumCooldownSec))),
|
||||
BinanceMomentumWindowSec: int(getFloat("BINANCE_MOMENTUM_WINDOW_SEC", float64(jsonCfg.BinanceMomentumWindowSec))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,28 +138,29 @@ func loadJSONConfig() jsonConfig {
|
||||
def := jsonConfig{
|
||||
ArbThreshold: 0.03,
|
||||
ScanIntervalMs: 500,
|
||||
TradeThreshold: 0.15,
|
||||
TradeAmountUSD: 10,
|
||||
TradeCooldownMs: 30000,
|
||||
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
|
||||
TakerFeeBitget: 0.060, // 0.060%
|
||||
TakerFeeHyperLiquid: 0.045, // 0.045%
|
||||
// Surge detection
|
||||
SurgeEnabled: true,
|
||||
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
|
||||
TakeProfitPct: 0.20, // 0.20% net profit take-profit
|
||||
PositionTimeoutSec: 1800, // 30 minutes
|
||||
LegDelayMs: 300, // 300ms between legs
|
||||
ReversalTolerancePct: 0.1, // 0.1% tolerance for entry sanity check
|
||||
// Momentum scanning
|
||||
MomentumThresholdPct: 0.25, // 0.25% change flags momentum
|
||||
|
||||
// Scale-in parameters
|
||||
ScaleStepPct: 0.10, // 0.10% spread widening per scale level
|
||||
ScaleCooldownSec: 5, // 5 seconds between scales
|
||||
// Trend detection
|
||||
TrendBaselineWindow: 600, // ~30s at 50ms tick
|
||||
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")
|
||||
@@ -213,65 +180,58 @@ func loadJSONConfig() jsonConfig {
|
||||
if cfg.ScanIntervalMs != 0 {
|
||||
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 {
|
||||
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 {
|
||||
def.MaxPositions = cfg.MaxPositions
|
||||
if cfg.SurgeBaselineMultiplier != 0 {
|
||||
def.SurgeBaselineMultiplier = cfg.SurgeBaselineMultiplier
|
||||
}
|
||||
if cfg.BlacklistDuration != 0 {
|
||||
def.BlacklistDuration = cfg.BlacklistDuration
|
||||
if cfg.SurgeMinAbsSpreadPct != 0 {
|
||||
def.SurgeMinAbsSpreadPct = cfg.SurgeMinAbsSpreadPct
|
||||
}
|
||||
if cfg.InitialCapital != 0 {
|
||||
def.InitialCapital = cfg.InitialCapital
|
||||
if cfg.SurgeCooldownSec != 0 {
|
||||
def.SurgeCooldownSec = cfg.SurgeCooldownSec
|
||||
}
|
||||
|
||||
// New config fields
|
||||
if cfg.TakerFeeBitget != 0 {
|
||||
def.TakerFeeBitget = cfg.TakerFeeBitget
|
||||
if cfg.MomentumThresholdPct != 0 {
|
||||
def.MomentumThresholdPct = cfg.MomentumThresholdPct
|
||||
}
|
||||
if cfg.TakerFeeHyperLiquid != 0 {
|
||||
def.TakerFeeHyperLiquid = cfg.TakerFeeHyperLiquid
|
||||
|
||||
// Trend detection JSON overrides
|
||||
if cfg.TrendBaselineWindow != 0 {
|
||||
def.TrendBaselineWindow = cfg.TrendBaselineWindow
|
||||
}
|
||||
if cfg.TakeProfitPct != 0 {
|
||||
def.TakeProfitPct = cfg.TakeProfitPct
|
||||
if cfg.TrendAnomalyMul != 0 {
|
||||
def.TrendAnomalyMul = cfg.TrendAnomalyMul
|
||||
}
|
||||
if cfg.PositionTimeoutSec != 0 {
|
||||
def.PositionTimeoutSec = cfg.PositionTimeoutSec
|
||||
if cfg.TrendConfirmTicks != 0 {
|
||||
def.TrendConfirmTicks = cfg.TrendConfirmTicks
|
||||
}
|
||||
if cfg.LegDelayMs != 0 {
|
||||
def.LegDelayMs = cfg.LegDelayMs
|
||||
if cfg.TrendAlertCooldown != 0 {
|
||||
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 {
|
||||
def.ScaleStepPct = cfg.ScaleStepPct
|
||||
if cfg.BinanceMomentumCooldownSec != 0 {
|
||||
def.BinanceMomentumCooldownSec = cfg.BinanceMomentumCooldownSec
|
||||
}
|
||||
if cfg.ScaleCooldownSec != 0 {
|
||||
def.ScaleCooldownSec = cfg.ScaleCooldownSec
|
||||
}
|
||||
if len(cfg.ExcludedCoins) > 0 {
|
||||
def.ExcludedCoins = cfg.ExcludedCoins
|
||||
if cfg.BinanceMomentumWindowSec != 0 {
|
||||
def.BinanceMomentumWindowSec = cfg.BinanceMomentumWindowSec
|
||||
}
|
||||
|
||||
// Boolean fields: zero default is false, so use OR logic
|
||||
// When JSON has true → true || false = true (override)
|
||||
// When JSON has false → false || false = false (keep default)
|
||||
def.TestMode = cfg.TestMode || def.TestMode
|
||||
def.TradeEnabled = cfg.TradeEnabled || def.TradeEnabled
|
||||
def.MomentumEnabled = cfg.MomentumEnabled || def.MomentumEnabled
|
||||
def.TrendEnabled = cfg.TrendEnabled || def.TrendEnabled
|
||||
def.SurgeEnabled = cfg.SurgeEnabled || def.SurgeEnabled
|
||||
def.BinanceMomentumEnabled = cfg.BinanceMomentumEnabled || def.BinanceMomentumEnabled
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
+16
-18
@@ -1,23 +1,21 @@
|
||||
{
|
||||
"test_mode": false,
|
||||
"trade_enabled": true,
|
||||
"arb_threshold": 0.3,
|
||||
"scan_interval_ms": 200,
|
||||
"trade_threshold": 0.3,
|
||||
"trade_amount_usd": 20,
|
||||
"trade_cooldown_ms": 30000,
|
||||
"alert_cooldown_sec": 300,
|
||||
"mock_slippage_pct": 0.05,
|
||||
"max_positions": 1,
|
||||
"blacklist_duration_sec": 3600,
|
||||
"initial_capital": 1000,
|
||||
"taker_fee_bitget": 0.060,
|
||||
"taker_fee_hyperliquid": 0.045,
|
||||
"take_profit_pct": 0.3,
|
||||
"spread_reverse_exit_pct": 0,
|
||||
"position_timeout_sec": 1800,
|
||||
"leg_delay_ms": 300,
|
||||
"reversal_tolerance_pct": 0.1,
|
||||
"scale_step_pct": 0.3,
|
||||
"scale_cooldown_sec": 5
|
||||
"surge_enabled": true,
|
||||
"surge_window_size": 600,
|
||||
"surge_baseline_multiplier": 3.0,
|
||||
"surge_min_abs_spread_pct": 0.05,
|
||||
"surge_cooldown_sec": 60,
|
||||
"momentum_enabled": true,
|
||||
"momentum_threshold_pct": 0.25,
|
||||
"trend_enabled": true,
|
||||
"trend_baseline_window": 600,
|
||||
"trend_anomaly_mul": 3.0,
|
||||
"trend_confirm_ticks": 3,
|
||||
"trend_alert_cooldown_ms": 60000,
|
||||
"binance_momentum_enabled": true,
|
||||
"binance_momentum_threshold_pct": 10.0,
|
||||
"binance_momentum_cooldown_sec": 300,
|
||||
"binance_momentum_window_sec": 60
|
||||
}
|
||||
|
||||
+506
@@ -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
|
||||
}
|
||||
+245
-306
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"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 {
|
||||
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 {
|
||||
mu sync.RWMutex
|
||||
buffers map[string][]spreadPoint // coin -> spread points
|
||||
buffers map[string][]spreadPoint
|
||||
}
|
||||
|
||||
func newSpreadHistory() *spreadHistory {
|
||||
@@ -186,31 +185,83 @@ type Dashboard struct {
|
||||
history *priceHistory
|
||||
spreads *spreadHistory
|
||||
store *PriceStore
|
||||
trader *Trader
|
||||
db *db.DB
|
||||
addr string
|
||||
cfg *Config
|
||||
|
||||
// cached arb scan results
|
||||
// cached scan results
|
||||
mu sync.RWMutex
|
||||
lastScan []*ArbOpportunity
|
||||
lastScan []ThreeExSpread
|
||||
scanTime time.Time
|
||||
|
||||
// P3-5: connection status — exchange -> last update time
|
||||
// connection status — exchange -> last update time
|
||||
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 {
|
||||
return &Dashboard{
|
||||
hub: NewSSEHub(),
|
||||
history: newPriceHistory(),
|
||||
spreads: newSpreadHistory(),
|
||||
store: store,
|
||||
trader: trader,
|
||||
db: database,
|
||||
addr: addr,
|
||||
connMap: make(map[string]time.Time),
|
||||
func NewDashboard(store *PriceStore, database *db.DB, addr string, cfg *Config, momentumTracker *MomentumTracker, trendDetector *TrendDetector, cumulativeTracker *CumulativeTracker, trendFilter *TrendFilter, surgeDetector *SurgeDetector, binanceMomentumDetector *BinanceMomentumDetector) *Dashboard {
|
||||
d := &Dashboard{
|
||||
hub: NewSSEHub(),
|
||||
history: newPriceHistory(),
|
||||
spreads: newSpreadHistory(),
|
||||
store: store,
|
||||
db: database,
|
||||
addr: addr,
|
||||
cfg: cfg,
|
||||
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() {
|
||||
@@ -223,25 +274,24 @@ func (d *Dashboard) Run() {
|
||||
if diskFS := os.DirFS("frontend/dist"); true {
|
||||
if _, diskErr := fs.Stat(diskFS, "index.html"); diskErr == nil {
|
||||
staticSub = diskFS
|
||||
log.Printf("[Web] Serving from disk: frontend/dist/ (hot reload enabled)")
|
||||
}
|
||||
}
|
||||
if err != nil && staticSub == nil {
|
||||
log.Printf("[Web] Failed to create static sub-fs: %v", err)
|
||||
} else {
|
||||
if err == nil && staticSub != nil {
|
||||
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub))))
|
||||
}
|
||||
|
||||
mux.HandleFunc("GET /", d.handleIndex)
|
||||
mux.HandleFunc("GET /api/status", d.handleStatus)
|
||||
mux.HandleFunc("GET /api/history", d.handleHistory)
|
||||
mux.HandleFunc("GET /api/spread-history", d.handleSpreadHistory) // P3-2
|
||||
mux.HandleFunc("GET /api/trades", d.handleTrades)
|
||||
mux.HandleFunc("GET /api/trade/", d.handleTradeDetail)
|
||||
mux.HandleFunc("GET /api/connections", d.handleConnStatus) // P3-5
|
||||
mux.HandleFunc("GET /api/spread-history", d.handleSpreadHistory)
|
||||
mux.HandleFunc("GET /api/connections", d.handleConnStatus)
|
||||
mux.HandleFunc("GET /api/trend-history", d.handleTrendHistory)
|
||||
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("POST /api/stop", d.handleStop)
|
||||
mux.HandleFunc("POST /api/start", d.handleStart)
|
||||
mux.HandleFunc("GET /binance", d.handleBinanceIndex)
|
||||
mux.HandleFunc("GET /api/binance-alerts", d.handleBinanceAlerts)
|
||||
|
||||
server := &http.Server{
|
||||
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.
|
||||
func (d *Dashboard) broadcastLoop() {
|
||||
tick := time.NewTicker(1 * time.Second)
|
||||
@@ -329,7 +317,7 @@ func (d *Dashboard) broadcastLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
// 1. Prices + spreads + connection status
|
||||
// 1. Prices + 3-exchange spreads
|
||||
var prices []map[string]interface{}
|
||||
for _, coin := range TrackedCoins {
|
||||
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]
|
||||
hlP := exMap[ExHyperLiquid]
|
||||
if bgP > 0 && hlP > 0 {
|
||||
spreadPct := (hlP - bgP) / bgP * 100
|
||||
entry["bg_hl_spread"] = spreadPct
|
||||
if bnP > 0 && okxP > 0 && bgP > 0 {
|
||||
prices_ := []float64{bnP, okxP, bgP}
|
||||
minP, maxP := prices_[0], prices_[0]
|
||||
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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
d.hub.Broadcast("prices", prices)
|
||||
|
||||
// 2. Open positions with live PnL (P3-3) — read from decoupled snapshot, never blocks trader
|
||||
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
|
||||
// 2. 3-exchange scan results
|
||||
d.mu.RLock()
|
||||
scanCopy := d.lastScan
|
||||
d.mu.RUnlock()
|
||||
|
||||
if len(scanCopy) > 0 {
|
||||
scanList := make([]map[string]interface{}, 0, len(scanCopy))
|
||||
for _, opp := range scanCopy {
|
||||
for _, s := range scanCopy {
|
||||
scanList = append(scanList, map[string]interface{}{
|
||||
"coin": opp.Coin,
|
||||
"direction": opp.Direction,
|
||||
"buy_ex": opp.BuyEx,
|
||||
"sell_ex": opp.SellEx,
|
||||
"buy_price": opp.BuyPrice,
|
||||
"sell_price": opp.SellPrice,
|
||||
"net_profit": opp.NetProfit,
|
||||
"gross": opp.GrossBasis,
|
||||
"coin": s.Coin,
|
||||
"spread_pct": s.SpreadPct,
|
||||
"bn_price": s.BnPrice,
|
||||
"okx_price": s.OkxPrice,
|
||||
"bg_price": s.BgPrice,
|
||||
"max_ex": s.MaxEx,
|
||||
"min_ex": s.MinEx,
|
||||
})
|
||||
}
|
||||
d.hub.Broadcast("arb", scanList)
|
||||
d.hub.Broadcast("spread_3ex", scanList)
|
||||
}
|
||||
|
||||
// 4. Stats + connection status (P3-5)
|
||||
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
|
||||
// 3. Connection status
|
||||
d.connMu.RLock()
|
||||
connInfo := make(map[string]string)
|
||||
for ex, lastTime := range d.connMap {
|
||||
@@ -491,59 +392,73 @@ func (d *Dashboard) broadcastLoop() {
|
||||
}
|
||||
}
|
||||
d.connMu.RUnlock()
|
||||
stats["connections"] = connInfo
|
||||
|
||||
// Trading status
|
||||
stats["trading"] = map[string]interface{}{
|
||||
"active": !d.trader.IsShuttingDown(),
|
||||
"mode": d.trader.ModeLabel(),
|
||||
"test": d.trader.cfg.TestMode,
|
||||
"target": d.trader.realTradesTarget,
|
||||
"done": d.trader.realTradesDone,
|
||||
status := map[string]interface{}{
|
||||
"coins": len(prices),
|
||||
"connections": connInfo,
|
||||
}
|
||||
d.hub.Broadcast("status", status)
|
||||
|
||||
// Per-exchange fund tracking
|
||||
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,
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
stats["exchange_funds"] = exFundsMap
|
||||
|
||||
// Blacklist — stale spread coins
|
||||
bl := d.trader.GetBlacklist()
|
||||
blList := make([]map[string]interface{}, 0, len(bl))
|
||||
for coin, t := range bl {
|
||||
if d.trader.cfg.BlacklistDuration > 0 && time.Since(t) >= d.trader.cfg.BlacklistDuration {
|
||||
continue // expired, will be cleaned up on next check
|
||||
// 5. Trend detection (if enabled)
|
||||
if d.trendDetector != nil && d.cfg.TrendEnabled {
|
||||
d.trendDetector.Tick()
|
||||
trendData := d.trendDetector.Snapshot()
|
||||
if len(trendData) > 0 {
|
||||
d.hub.Broadcast("trend", trendData)
|
||||
}
|
||||
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)
|
||||
// 6. Cumulative change tracking
|
||||
if d.cumulativeTracker != nil {
|
||||
d.cumulativeTracker.Tick()
|
||||
cmData := d.cumulativeTracker.GetTopCoins(30)
|
||||
if len(cmData) > 0 {
|
||||
d.hub.Broadcast("cumulative", cmData)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.lastScan = opps
|
||||
d.lastScan = spreads
|
||||
d.scanTime = time.Now()
|
||||
d.mu.Unlock()
|
||||
}
|
||||
@@ -552,14 +467,14 @@ func (d *Dashboard) RecordPrice(coin, exchange string, price float64) {
|
||||
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) {
|
||||
d.connMu.Lock()
|
||||
d.connMap[exchange] = time.Now()
|
||||
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{}) {
|
||||
d.hub.Broadcast(event, data)
|
||||
}
|
||||
@@ -575,7 +490,6 @@ func (d *Dashboard) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||
// Try disk first (hot reload)
|
||||
data, err = os.ReadFile("frontend/dist/index.html")
|
||||
if err != nil {
|
||||
// Fall back to embed
|
||||
data, err = staticFS.ReadFile("frontend/dist/index.html")
|
||||
}
|
||||
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) {
|
||||
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{}{
|
||||
"prices": snap,
|
||||
"positions": len(positions),
|
||||
"stats": map[string]int{"total": total, "converged": converged, "diverged": diverged, "flat": flat},
|
||||
"exchange_funds": exFundsMap,
|
||||
"prices": snap,
|
||||
"coins": len(snap),
|
||||
}
|
||||
writeJSON(w, resp)
|
||||
}
|
||||
@@ -620,7 +518,7 @@ func (d *Dashboard) handleHistory(w http.ResponseWriter, r *http.Request) {
|
||||
for c := range snap {
|
||||
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
|
||||
}
|
||||
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) {
|
||||
coin := r.URL.Query().Get("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) {
|
||||
d.connMu.RLock()
|
||||
conns := make(map[string]string)
|
||||
@@ -664,51 +562,72 @@ func (d *Dashboard) handleConnStatus(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, conns)
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleTrades(w http.ResponseWriter, r *http.Request) {
|
||||
if d.db == nil {
|
||||
writeJSON(w, map[string]interface{}{"trades": []interface{}{}, "total": 0})
|
||||
return
|
||||
}
|
||||
page := 1
|
||||
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
|
||||
func (d *Dashboard) handleTrendHistory(w http.ResponseWriter, r *http.Request) {
|
||||
var events interface{}
|
||||
if d.db != nil {
|
||||
records, err := d.db.GetTrendEvents(200)
|
||||
if err == nil {
|
||||
events = records
|
||||
}
|
||||
}
|
||||
trades, total, err := d.db.GetTrades(page, limit, coin)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
if events == nil {
|
||||
if d.trendDetector != nil {
|
||||
events = d.trendDetector.GetEvents(200)
|
||||
} else {
|
||||
events = []interface{}{}
|
||||
}
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"trades": trades,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
})
|
||||
writeJSON(w, map[string]interface{}{"events": events})
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleTradeDetail(w http.ResponseWriter, r *http.Request) {
|
||||
if d.db == nil {
|
||||
http.Error(w, "DB not available", 503)
|
||||
return
|
||||
func (d *Dashboard) handleCmHistory(w http.ResponseWriter, r *http.Request) {
|
||||
var events interface{}
|
||||
if d.db != nil {
|
||||
records, err := d.db.GetCmEvents(200)
|
||||
if err == nil {
|
||||
events = records
|
||||
}
|
||||
}
|
||||
var id int64
|
||||
if _, err := fmt.Sscanf(r.URL.Path, "/api/trade/%d", &id); err != nil {
|
||||
http.Error(w, "Invalid trade ID", 400)
|
||||
return
|
||||
if events == nil {
|
||||
if d.cumulativeTracker != nil {
|
||||
events = d.cumulativeTracker.GetEvents(200)
|
||||
} else {
|
||||
events = []interface{}{}
|
||||
}
|
||||
}
|
||||
trade, orders, err := d.db.GetTradeByID(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 404)
|
||||
return
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to in-memory
|
||||
if d.surgeDetector != nil {
|
||||
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) {
|
||||
@@ -743,14 +662,34 @@ func (d *Dashboard) handleSSE(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleStop(w http.ResponseWriter, r *http.Request) {
|
||||
d.trader.Stop()
|
||||
writeJSON(w, map[string]string{"status": "stopped", "message": "Trading stopped, positions closing"})
|
||||
// handleBinanceIndex serves the Binance momentum monitor page.
|
||||
func (d *Dashboard) handleBinanceIndex(w http.ResponseWriter, r *http.Request) {
|
||||
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) {
|
||||
d.trader.Start()
|
||||
writeJSON(w, map[string]string{"status": "started", "message": "Trading resumed"})
|
||||
// handleBinanceAlerts returns recent Binance momentum alerts.
|
||||
func (d *Dashboard) handleBinanceAlerts(w http.ResponseWriter, r *http.Request) {
|
||||
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{}) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -108,6 +108,85 @@ func (d *DB) migrate() error {
|
||||
created_at DATETIME NOT NULL
|
||||
);
|
||||
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)
|
||||
if err != nil {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
+56
-18
@@ -4,39 +4,50 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BinanceWS connects to Binance WS for ticker data.
|
||||
// Splits symbols across multiple combined-stream connections.
|
||||
type BinanceWS struct {
|
||||
Tracked []string
|
||||
Tracked []string
|
||||
connections int
|
||||
}
|
||||
|
||||
func NewBinanceWS(tracked []string) *BinanceWS {
|
||||
return &BinanceWS{Tracked: tracked}
|
||||
conns := int(math.Ceil(float64(len(tracked)) / 60))
|
||||
if conns < 1 {
|
||||
conns = 1
|
||||
}
|
||||
if conns > 10 {
|
||||
conns = 10
|
||||
}
|
||||
return &BinanceWS{Tracked: tracked, connections: conns}
|
||||
}
|
||||
// Run connects to Binance WS and streams bookTicker data.
|
||||
func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) error {
|
||||
|
||||
func (b *BinanceWS) runSingle(symbols []string, connIdx int, updateFn func(coin string, price, bid, ask float64)) error {
|
||||
streams := ""
|
||||
for i, sym := range b.Tracked {
|
||||
for i, sym := range symbols {
|
||||
if i > 0 {
|
||||
streams += "/"
|
||||
}
|
||||
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)
|
||||
conn.PingInterval = 45 * time.Second
|
||||
url := fmt.Sprintf("wss://fstream.binance.com/stream?streams=%s", streams)
|
||||
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() {
|
||||
log.Printf("[Binance WS] Connected")
|
||||
log.Printf("[%s] Connected (%d symbols)", name, len(symbols))
|
||||
}
|
||||
|
||||
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
|
||||
if err := json.Unmarshal(msg, &raw); err != nil {
|
||||
return
|
||||
@@ -46,12 +57,12 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
|
||||
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{}
|
||||
if err := json.Unmarshal(dataRaw, &dataMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
symbol, _ := dataMap["s"].(string)
|
||||
bidStr, _ := dataMap["b"].(string)
|
||||
askStr, _ := dataMap["a"].(string)
|
||||
@@ -59,13 +70,12 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
|
||||
return
|
||||
}
|
||||
|
||||
bid, err1 := strconv.ParseFloat(bidStr, 64)
|
||||
ask, err2 := strconv.ParseFloat(askStr, 64)
|
||||
if err1 != nil || err2 != nil || bid <= 0 || ask <= 0 {
|
||||
bid := parseFloat(bidStr)
|
||||
ask := parseFloat(askStr)
|
||||
if bid <= 0 || ask <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Extract coin name (e.g., "BTCUSDT" -> "BTC")
|
||||
coin := symbolToCoin(symbol, "USDT")
|
||||
if coin == "" {
|
||||
return
|
||||
@@ -77,3 +87,31 @@ func (b *BinanceWS) Run(updateFn func(coin string, price, bid, ask float64)) err
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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)]
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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}}
|
||||
+40
File diff suppressed because one or more lines are too long
-1
@@ -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}
|
||||
-40
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
@@ -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}
|
||||
Vendored
+23
@@ -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>
|
||||
Vendored
+12
-2
@@ -1,11 +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>Exchange Monitor Dashboard</title>
|
||||
<script type="module" crossorigin src="/static/assets/index-D_JzXaOQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/index-Bh5bnFYE.css">
|
||||
<script type="module" crossorigin src="/static/assets/main-Cg5_dPUr.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/static/assets/client-DYDkQYN6.js">
|
||||
<link rel="stylesheet" crossorigin href="/static/assets/main-vvNDQq2K.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
<!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>Exchange Monitor Dashboard</title>
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
../baseline-browser-mapping/dist/cli.cjs
|
||||
-1
@@ -1 +0,0 @@
|
||||
../browserslist/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../esbuild/bin/esbuild
|
||||
-1
@@ -1 +0,0 @@
|
||||
../jsesc/bin/jsesc
|
||||
-1
@@ -1 +0,0 @@
|
||||
../json5/lib/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../loose-envify/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../nanoid/bin/nanoid.cjs
|
||||
-1
@@ -1 +0,0 @@
|
||||
../@babel/parser/bin/babel-parser.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../rollup/dist/bin/rollup
|
||||
-1
@@ -1 +0,0 @@
|
||||
../semver/bin/semver.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../update-browserslist-db/cli.js
|
||||
-1
@@ -1 +0,0 @@
|
||||
../vite/bin/vite.js
|
||||
-891
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-32
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
-2
@@ -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");
|
||||
-2120
File diff suppressed because it is too large
Load Diff
-5
@@ -1,5 +0,0 @@
|
||||
[
|
||||
"esnext.promise.all-settled",
|
||||
"esnext.string.match-all",
|
||||
"esnext.global-this"
|
||||
]
|
||||
-18
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
0 && 0;
|
||||
|
||||
//# sourceMappingURL=cache-contexts.js.map
|
||||
-1
@@ -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
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-469
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-190
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-290
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-6
@@ -1,6 +0,0 @@
|
||||
module.exports = function import_(filepath) {
|
||||
return import(filepath);
|
||||
};
|
||||
0 && 0;
|
||||
|
||||
//# sourceMappingURL=import.cjs.map
|
||||
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-61
@@ -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
@@ -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
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-5
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
0 && 0;
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
-1
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-85
@@ -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
|
||||
-1
File diff suppressed because one or more lines are too long
-23
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user