Rewrite to Node.js

Phases 1 and 2.
This commit is contained in:
2026-05-09 11:00:21 +02:00
parent 4133d0e9c8
commit b131e3c69a
7395 changed files with 7936 additions and 732925 deletions
+41
View File
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+5
View File
@@ -0,0 +1,5 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+12 -116
View File
@@ -1,125 +1,21 @@
# ÖBB Train Planner
# ÖBB Planner - Next.js Rewrite
Checks the ÖBB real-time schedule, picks the best train for your upcoming calendar events, and shows a live "leave by" countdown from your current location.
This is a rewrite of the ÖBB Planner application using Next.js App Router with TypeScript.
## What's included
```
oebb-planner/
├── server/
│ ├── index.js ← Node.js proxy server (ÖBB API + ICS parser)
│ └── package.json
├── oebb-planner.jsx ← React front-end (use in Claude.ai artifacts or any React app)
└── README.md
```
---
## Quick start
### 1 — Start the proxy server
The server bypasses ÖBB's CORS restrictions and parses ICS calendar files.
## Development
```bash
cd server
npm install
npm start
# → ÖBB Planner API running on http://localhost:3001
npm run dev
```
> **Requires Node.js ≥ 18** (uses built-in `fetch` and `AbortSignal.timeout`).
> For development with auto-reload: `npm run dev`
## Building
### 2 — Open the React app
```bash
npm run build
```
Paste `oebb-planner.jsx` into a Claude.ai artifact (or your own React app).
The app detects whether the server is online and shows a status indicator in the header.
## Testing
---
## Calendar integration
The app reads any standard ICS calendar. Only events that have a **location field** and fall within the **next 14 days** are imported.
### Option A — ICS URL (Google Calendar, Outlook, Fastmail…)
1. In the app, open the **Calendar** panel → **ICS URL** tab
2. Paste your calendar's secret ICS address
**Google Calendar:**
Settings → click your calendar → scroll to *"Secret address in iCal format"* → copy the URL
**Apple Calendar (iCloud):**
Calendar app → right-click calendar → "Share Calendar" → enable public calendar → copy the URL
(change `webcal://` to `https://` or paste as-is — the server handles both)
**Outlook:**
Settings → View all Outlook settings → Calendar → Shared calendars → Publish → ICS link
### Option B — Upload .ics file
Export your calendar as a `.ics` file from any app (File → Export in Apple Calendar, etc.) and upload it via the **Upload File** tab.
---
## How the train matching works
1. Your **current GPS location** is used to find the nearest ÖBB station (via the HAFAS `LocGeoPos` API).
2. Each event's **location field** is used to search for the nearest destination station (`LocMatch`).
3. The HAFAS `TripSearch` endpoint finds journeys departing up to 2 hours before your event.
4. The **"leave by" time** = next catchable train departure 12 minutes (default walk time to station).
5. Data refreshes automatically every **60 seconds**.
---
## Server endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/hafas` | Proxy to `fahrplan.oebb.at/bin/mgate.exe` |
| `GET` | `/api/calendar?url=<ics_url>` | Fetch & parse a remote ICS calendar |
| `POST` | `/api/calendar/parse` | Parse raw ICS content sent in request body |
| `GET` | `/api/health` | Liveness check |
---
## Customisation
| Variable | Location | Default | Description |
|----------|----------|---------|-------------|
| `WALK_MINS` | `oebb-planner.jsx` | `12` | Walk time from your location to origin station |
| `PORT` | `server/index.js` | `3001` | Server port |
| `horizonDays` | `server/index.js` | `14` | How far ahead to look for calendar events |
| `PROXY` | `oebb-planner.jsx` | `http://localhost:3001` | Server URL |
---
## Deploying beyond localhost
To run this on a server or share it with others:
1. Set `PORT` via environment variable: `PORT=8080 npm start`
2. Update `PROXY` in `oebb-planner.jsx` to your server's URL
3. Restrict CORS in `server/index.js` if needed:
```js
app.use(cors({ origin: "https://your-app.example.com" }));
```
---
## Troubleshooting
**"Server offline" shown in the app**
→ Make sure `npm start` is running in the `server/` directory.
**No trains found for an event**
→ Check that the destination in your calendar event is a recognisable ÖBB station name
(e.g. "Graz Hbf", "Salzburg Hauptbahnhof"). Verbose postal addresses are trimmed automatically.
**Calendar import returns 0 events**
→ Only events with a `LOCATION` field set are imported. Make sure your events have a location
and fall within the next 14 days.
**ÖBB returns errors**
→ The HAFAS API occasionally changes. Check `fahrplan.oebb.at` is reachable from your server.
```bash
npm test
```
+517
View File
@@ -0,0 +1,517 @@
# ÖBB Planner — Next.js Rewrite Plan
> **Status:** Planned
> **Created:** 2024
> **Scope:** Full rewrite from CRA + Express to Next.js App Router with new features
---
## 1. Current State
| Layer | Technology | Files |
|-------|-----------|-------|
| Backend | Express.js + CORS + node-ical | `server/index.js` (~140 lines) |
| Frontend | Create React App (React 19) | `oebb-planner.jsx` (~700 lines monolithic component) |
| Tests | Jest + Supertest | `server/__tests__/*.test.js` |
| Build | Separate `npm start` for server + `react-scripts start` for frontend | Two independent processes |
### Problems with current architecture
- Two separate processes to manage
- Monolithic component (~700 lines) — no component breakdown
- CRA is deprecated; `react-scripts` is unmaintained
- No TypeScript
- Inline styles make theming/maintenance painful
- No proper state management pattern
- Duplication between `oebb-planner.jsx` and `oebb-planner-app/src/App.js`
---
## 2. New Features (Beyond Rewrite)
### 2.1 Calendar View (`/calendar`)
A browsable month view that lets you navigate between months, see events as badges on dates, and click into a day to see event + train details.
**How it works:**
- User imports calendar via the existing ICS flow
- Events are stored in React state / localStorage
- A month grid shows event dots on dates that have events
- Clicking a day shows that day's events with their train info
- Clicking an event scrolls to / expands the full event card
**UI wireframe:**
```
┌──────────────────────┬──────────────────────┐
│ ← November 2024 → │ [Day] [Week] [Month] │
├──────────────────────┼──────────────────────┤
│ Su Mo Tu We Th Fr Sa │ Thursday, 14 Nov │
│ 1 2 3 4 5 6 7 │ │
│ 8 9 10 11 12 13 14 │ ● 14:00 Graz Hbf │
│ ● ● │ 🚂 Leave 13:10 │
│ 15 16 17 18 19 20 21 │ 🚲 Leave 11:07 │
│ ● ● │ │
│ ... │ ● 17:00 Linz Hbf │
│ │ 🚂 Leave 15:30 │
│ │ 🚲 Leave 15:45 │
└──────────────────────┴──────────────────────┘
```
### 2.2 Bicycle Routing
**API choice: OSRM Public Demo Server** (`router.project-osrm.org`)
- Free, no API key required, open source
- Supports `bicycle` profile
- Returns distance, duration, turn-by-turn steps
- For production: self-host or switch to OpenRouteService
**Geocoding:** Nominatim (OpenStreetMap) to convert station/event location names to coordinates for routing.
**Data flow:**
```
Origin station (name) → already have coords from geolocation
Destination station (name) → geocode via Nominatim → get coordinates
→ OSRM bicycle route → distance + duration
→ cache in TripData alongside train journeys
```
### 2.3 Train vs Bicycle Comparison
Each event card shows both travel modes side by side:
```
┌───────────────────────────────────────────────────┐
│ 🏔 Meeting in Graz │
│ 📍 Graz Hbf · 14:00 · Thu, 14 Nov │
├───────────────────────────────────────────────────┤
│ 🚂 NEXT BEST TRAIN 🚲 BYCYCLE │
│ RJX 5234 on time 42 km │
│ DEP 13:22 ARR 14:35 2h 15min │
│ PLAT 3 0 emissions │
│ Leave by 13:10 Leave by 11:07 │
└───────────────────────────────────────────────────┘
```
---
## 3. Target Architecture
```
oebb_planner/
├── package.json
├── next.config.js
├── tsconfig.json
├── tailwind.config.ts
├── postcss.config.mjs
├── vitest.config.ts
├── src/
│ ├── app/
│ │ ├── layout.tsx ← root layout (fonts, providers, navbar)
│ │ ├── page.tsx ← main planner page (event cards)
│ │ ├── globals.css
│ │ ├── calendar/
│ │ │ └── page.tsx ← browsable calendar view (NEW)
│ │ ├── api/
│ │ │ ├── hafas/
│ │ │ │ └── route.ts ← POST → proxy to ÖBB HAFAS
│ │ │ ├── calendar/
│ │ │ │ ├── route.ts ← GET → fetch remote ICS calendar
│ │ │ │ └── parse/
│ │ │ │ └── route.ts ← POST → parse raw ICS body
│ │ │ ├── geocode/
│ │ │ │ └── route.ts ← GET → Nominatim geocoding (NEW)
│ │ │ ├── bike-route/
│ │ │ │ └── route.ts ← GET → OSRM bicycle routing (NEW)
│ │ │ └── health/
│ │ │ └── route.ts ← GET → liveness check
│ │ └── not-found.tsx
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Header.tsx ← logo, server status, clock
│ │ │ └── Navbar.tsx ← navigation between pages (NEW)
│ │ ├── calendar/
│ │ │ ├── CalendarPanel.tsx ← collapsible import panel
│ │ │ ├── CalendarView.tsx ← month grid component (NEW)
│ │ │ ├── DayEvents.tsx ← events for a selected day (NEW)
│ │ │ ├── UrlTab.tsx
│ │ │ └── FileTab.tsx
│ │ ├── event/
│ │ │ ├── EventCard.tsx ← single event card (with train+bike)
│ │ │ ├── TrainSection.tsx ← train data in event card
│ │ │ ├── BikeSection.tsx ← bicycle data in event card (NEW)
│ │ │ ├── JourneyList.tsx ← all departures table
│ │ │ └── LeaveByBadge.tsx
│ │ ├── add-event/
│ │ │ └── AddEventModal.tsx
│ │ └── ui/
│ │ ├── Chip.tsx
│ │ ├── Button.tsx
│ │ └── LoadingSpinner.tsx
│ ├── hooks/
│ │ ├── useServerHealth.ts ← polls /api/health every 30s
│ │ ├── useClock.ts ← ticking clock (every 10s)
│ │ ├── useGeolocation.ts ← GPS positioning
│ │ ├── useOriginStation.ts ← finds nearest ÖBB station
│ │ ├── useJourneys.ts ← fetches + caches train data
│ │ ├── useBikeRoute.ts ← fetches bicycle route (NEW)
│ │ ├── useCalendar.ts ← calendar import logic
│ │ └── useEventsStore.ts ← shared events state (NEW)
│ ├── lib/
│ │ ├── hafas.ts ← HAFAS API client + types
│ │ ├── calendar.ts ← ICS helpers (extractEvents, cleanLocation)
│ │ ├── countdown.ts ← leaveBy, countdownInfo
│ │ ├── formatting.ts ← fmtTime, delayColor
│ │ ├── demo.ts ← demo journey generator
│ │ ├── constants.ts ← WALK_MINS, RED, SAMPLE_EVENTS, HAFAS_BASE
│ │ ├── geocode.ts ← Nominatim client (NEW)
│ │ └── bike-route.ts ← OSRM client (NEW)
│ └── types/
│ └── index.ts ← all TypeScript interfaces
├── __tests__/
│ ├── lib/
│ │ ├── calendar.test.ts
│ │ ├── countdown.test.ts
│ │ └── bike-route.test.ts ← NEW
│ └── api/
│ ├── hafas.test.ts
│ ├── calendar.test.ts
│ ├── geocode.test.ts ← NEW
│ ├── bike-route.test.ts ← NEW
│ └── health.test.ts
├── public/
│ ├── favicon.ico
│ └── robots.txt
├── .env.example
└── README.md
```
---
## 4. TypeScript Types
### `src/types/index.ts`
```typescript
// ── HAFAS / Train ───────────────────────────────────────────
interface Journey {
id: string;
sD: Date; // scheduled departure
rD: Date; // real departure
sA: Date; // scheduled arrival
rA: Date; // real arrival
delay: number; // minutes
platform: string;
changes: number;
trains: string[];
cancelled: boolean;
}
interface Station {
name: string;
extId: string;
}
// ── Events ───────────────────────────────────────────────────
interface Event {
id: string;
title: string;
destination: string;
eventTime: Date;
source: "manual" | "calendar";
}
// ── Trip Data (per event) ────────────────────────────────────
interface TripDataEntry {
journeys: Journey[];
destName: string;
demo: boolean;
loading: boolean;
bikeRoute?: BikeRoute | null; // NEW
bikeLoading?: boolean; // NEW
bikeError?: string | null; // NEW
destCoords?: { lat: number; lng: number }; // NEW (cached geocode)
}
// ── Bicycle Routing (NEW) ────────────────────────────────────
interface BikeRoute {
distance: number; // meters
duration: number; // seconds
steps?: BikeStep[];
}
interface BikeStep {
name: string;
distance: number;
duration: number;
instruction: string;
}
// ── Geocoding (NEW) ──────────────────────────────────────────
interface GeocodeResult {
lat: number;
lng: number;
display_name: string;
}
// ── Calendar Import ──────────────────────────────────────────
interface CalendarEvent {
id: string;
title: string;
destination: string;
eventTime: string; // ISO string from API
source: "calendar";
}
// ── UI Helpers ───────────────────────────────────────────────
interface CountdownInfo {
label: string;
color: string;
urgent: boolean;
}
type ServerStatus = null | true | false;
type LiveStatus = null | true | false;
type LocState = "pending" | "granted" | "denied";
type CalStatus = null | "loading" | "ok" | "error";
```
---
## 5. New API Routes
### `POST /api/hafas`
- **Purpose:** Proxy to `fahrplan.oebb.at/bin/mgate.exe`
- **Body:** `{ svcReqL: [...] }` — HAFAS service requests
- **Returns:** HAFAS JSON response
- **Timeout:** 12s
### `GET /api/calendar?url=<ics_url>&days=14`
- **Purpose:** Fetch and parse a remote ICS calendar
- **Returns:** `CalendarEvent[]`
- **Supports:** `https://`, `webcal://`
### `POST /api/calendar/parse`
- **Purpose:** Parse raw ICS content sent in request body
- **Body:** Raw ICS text
- **Returns:** `CalendarEvent[]`
### `GET /api/geocode?name=Graz+Hbf&countrycodes=at` (NEW)
- **Purpose:** Proxy to Nominatim for address→coordinates
- **Upstream:** `nominatim.openstreetmap.org/search?q={name}&format=json&limit=1&countrycodes=at`
- **Returns:** `{ lat, lng, display_name }`
- **Caching:** In-memory cache with TTL to avoid rate limits
### `GET /api/bike-route?fromLat=...&fromLng=...&toLat=...&toLng=...` (NEW)
- **Purpose:** Proxy to OSRM for bicycle routing
- **Upstream:** `router.project-osrm.org/route/v1/bicycle/{lon1},{lat1};{lon2},{lat2}?overview=false`
- **Returns:** `{ distance, duration, steps: [...] }`
### `GET /api/health`
- **Purpose:** Liveness check
- **Returns:** `{ ok: true, ts: ISOString, version: "2.0.0" }`
---
## 6. Key Technical Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Framework | Next.js 15 App Router | Modern standard; API routes are serverless-compatible |
| Language | TypeScript (strict) | Type safety across frontend and backend |
| Styling | Tailwind CSS | Replaces 700 lines of inline styles; consistent theming |
| State | React hooks + Context | No Redux needed for this scale |
| API proxy | Next.js Route Handlers | Same logic as Express, no Express dependency |
| ICS parsing | Keep `node-ical` | Already works, well-tested |
| Bike routing | OSRM public demo | Free, no API key, good enough for dev |
| Geocoding | Nominatim | Free, open source, good Austria coverage |
| Testing | Vitest + Testing Library | Faster than Jest for TS, better DX |
| Deployment | Vercel (recommended) or any Node host | Single artifact |
---
## 7. Migration Phases
### Phase 1 — Scaffold Next.js Project (~30 min)
1. Initialize Next.js 15 with App Router, TypeScript, Tailwind CSS
2. Set up `tsconfig.json` with strict mode
3. Create `.env.example` with `PORT`, `HAFAS_URL`, `NOMINATIM_URL`, `OSRM_URL`
4. Configure `next.config.js` (rewrites if needed)
5. Configure `vitest.config.ts`
6. Set up `postcss.config.mjs`
### Phase 2 — Types + Library Layer (~2 hours)
7. Define TypeScript types in `src/types/index.ts`
8. Port `lib/hafas.ts` — HAFAS API client functions with proper types
9. Port `lib/calendar.ts``extractEvents` + `cleanLocation` (reusable in API routes AND tests)
10. Port `lib/countdown.ts`, `lib/formatting.ts`, `lib/constants.ts`, `lib/demo.ts`
11. Create `lib/geocode.ts` — Nominatim client (NEW)
12. Create `lib/bike-route.ts` — OSRM client (NEW)
### Phase 3 — API Routes (~45 min)
13. `src/app/api/hafas/route.ts` — POST handler, same logic as Express
14. `src/app/api/calendar/route.ts` — GET handler for remote ICS
15. `src/app/api/calendar/parse/route.ts` — POST handler for ICS body
16. `src/app/api/geocode/route.ts` — GET handler for Nominatim (NEW)
17. `src/app/api/bike-route/route.ts` — GET handler for OSRM (NEW)
18. `src/app/api/health/route.ts` — health check
### Phase 4 — Custom Hooks (~2.5 hours)
19. `useServerHealth.ts` — polls `/api/health` every 30s
20. `useClock.ts` — interval that updates `now` every 10s
21. `useGeolocation.ts` — wraps `navigator.geolocation`
22. `useOriginStation.ts` — finds nearest station from geolocation
23. `useJourneys.ts` — the complex `fetchAll` logic, per-event journey fetching
24. `useBikeRoute.ts` — fetches bicycle route for an event (NEW)
25. `useCalendar.ts` — URL/file import with merge logic
26. `useEventsStore.ts` — shared events state via Context (NEW)
### Phase 5 — UI Components (~3.5 hours)
27. `ui/Chip.tsx` — small badge component
28. `ui/Button.tsx` — styled button
29. `ui/LoadingSpinner.tsx` — loading indicator
30. `event/LeaveByBadge.tsx` — countdown badge
31. `event/JourneyList.tsx` — departure rows
32. `event/TrainSection.tsx` — train data in event card
33. `event/BikeSection.tsx` — bicycle data in event card (NEW)
34. `event/EventCard.tsx` — composes train + bike sections
35. `calendar/UrlTab.tsx`
36. `calendar/FileTab.tsx`
37. `calendar/CalendarPanel.tsx`
38. `add-event/AddEventModal.tsx`
39. `layout/Header.tsx`
40. `layout/Navbar.tsx` (NEW)
### Phase 6 — Calendar Page (~1 hour)
41. `calendar/CalendarView.tsx` — month grid component (NEW)
42. `calendar/DayEvents.tsx` — events for a selected day (NEW)
43. `app/calendar/page.tsx` — calendar route (NEW)
### Phase 7 — Tests (~2 hours)
44. Migrate `server/__tests__/*.test.js``__tests__/api/*.test.ts`
45. Add unit tests for `lib/calendar.ts`, `lib/countdown.ts`
46. Add API tests for `geocode` and `bike-route` (NEW)
47. Add component smoke tests with `@testing-library/react`
### Phase 8 — Cleanup (~30 min)
48. Delete old `server/` directory
49. Delete old `oebb-planner-app/` directory
50. Delete `oebb-planner.jsx`
51. Update `README.md` with new architecture and instructions
52. Final integration test
---
## 8. Dependencies
### Runtime
```json
{
"dependencies": {
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"node-ical": "^0.18.0"
}
}
```
### Development
```json
{
"devDependencies": {
"typescript": "^5.6.0",
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/node-ical": "^0.18.0",
"tailwindcss": "^3.4.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0",
"vitest": "^2.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/jest-dom": "^6.0.0",
"jsdom": "^25.0.0"
}
}
```
---
## 9. Environment Variables
```env
# .env.example
# Server port
PORT=3001
# ÖBB HAFAS API
HAFAS_URL=https://fahrplan.oebb.at/bin/mgate.exe
# Nominatim geocoding (OpenStreetMap)
NOMINATIM_URL=https://nominatim.openstreetmap.org
# OSRM bicycle routing
OSRM_URL=https://router.project-osrm.org
# Nominatim user-agent / referer (required by their ToS)
NOMINATIM_USER_AGENT=OebbPlanner/1.0
```
---
## 10. Benefits of the Rewrite
1. **Single process**`npm run dev` starts everything
2. **Type safety** — TypeScript catches bugs at compile time
3. **Composability** — 700-line component becomes ~15 focused components
4. **Reusability**`lib/` functions testable independently
5. **Deployable** — one artifact, works on Vercel / any Node host
6. **Maintainable** — hooks encapsulate side effects, components are pure UI
7. **Modern tooling** — no more CRA, no more `react-scripts eject` anxiety
8. **Bicycle routing** — complete picture of train vs bike for each event
9. **Calendar view** — browse events by date, not just a flat list
10. **Train+bike comparison** — side-by-side travel times in each event card
---
## 11. Estimated Effort
| Phase | Description | Est. Time |
|-------|-------------|-----------|
| 1 | Scaffold Next.js + Tailwind + TS | 30 min |
| 2 | Types + lib layer (incl. geocode, bike-route) | 2 hours |
| 3 | API routes (incl. geocode, bike-route) | 45 min |
| 4 | Custom hooks (incl. bike-route, events-store) | 2.5 hours |
| 5 | UI Components (incl. bike section, navbar) | 3.5 hours |
| 6 | Calendar page (incl. month view, day events) | 1 hour |
| 7 | Tests (API + lib + component) | 2 hours |
| 8 | Cleanup (delete old dirs, update README) | 30 min |
| **Total** | | **~13-14 hours** |
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
+7
View File
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
Submodule oebb-planner-app deleted from f619aae9f4
-692
View File
@@ -1,692 +0,0 @@
import { useState, useEffect, useCallback, useRef } from "react";
// ─────────────────────────────────────────────────────────────
// Config — point at the Node proxy server
// ─────────────────────────────────────────────────────────────
const PROXY = "http://localhost:3001";
// ─────────────────────────────────────────────────────────────
// ÖBB HAFAS API (now via the proxy — no more CORS errors)
// ─────────────────────────────────────────────────────────────
const HAFAS_BASE = {
auth: { aid: "OWDL4fE4ixNiPBBm", type: "AID" },
client: { id: "OeBB", type: "AND", name: "Scotty", v: 100000000 },
ver: "1.57", lang: "de", formatted: false,
};
async function hafas(svcReqL) {
const r = await fetch(`${PROXY}/api/hafas`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...HAFAS_BASE, svcReqL }),
signal: AbortSignal.timeout(10_000),
});
if (!r.ok) throw new Error(`Proxy HTTP ${r.status}`);
return r.json();
}
async function stationByName(name) {
const d = await hafas([{
meth: "LocMatch",
req: { input: { loc: { type: "S", name: `${name}?` }, maxLoc: 3, field: "S" } }
}]);
return d.svcResL?.[0]?.res?.match?.locL ?? [];
}
async function stationByCoord(lat, lng) {
const d = await hafas([{
meth: "LocGeoPos",
req: {
ring: { cCrd: { x: Math.round(lng * 1e6), y: Math.round(lat * 1e6) }, maxDist: 2000 },
getPOIs: false, getStops: true, maxLoc: 1,
}
}]);
return d.svcResL?.[0]?.res?.locL ?? [];
}
function hafasDateTime(dateStr, timeStr) {
if (!dateStr || !timeStr) return null;
let t = timeStr, off = 0;
if (t.length > 6) { off = parseInt(t.slice(0, t.length - 6)); t = t.slice(-6); }
return new Date(+dateStr.slice(0,4), +dateStr.slice(4,6)-1, +dateStr.slice(6,8)+off, +t.slice(0,2), +t.slice(2,4), +t.slice(4,6));
}
function parseJourney(con, prodL) {
const dd = con.dep?.dDateS ?? "";
const sD = hafasDateTime(dd, con.dep?.dTimeS);
const rD = con.dep?.dTimeR ? hafasDateTime(dd, con.dep?.dTimeR) : sD;
const ad = con.arr?.aDateS ?? dd;
const sA = hafasDateTime(ad, con.arr?.aTimeS);
const rA = con.arr?.aTimeR ? hafasDateTime(ad, con.arr?.aTimeR) : sA;
const trains = (con.secL ?? [])
.filter(s => s.type === "JNY" && s.jny?.prodX != null)
.map(s => prodL?.[s.jny.prodX]?.nameS ?? prodL?.[s.jny.prodX]?.name ?? "")
.filter(Boolean);
return {
id: String(sD?.getTime() ?? Math.random()),
sD, rD, sA, rA,
delay: sD && rD ? Math.max(0, Math.round((rD - sD) / 60000)) : 0,
platform: con.dep?.dPlatfR ?? con.dep?.dPlatfS ?? "",
changes: con.chgCnt ?? 0,
trains: trains.length ? trains : ["Train"],
cancelled: !!con.dep?.dCncl,
};
}
async function findJourneys(fromExtId, toExtId, when) {
const d = when;
const ds = `${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}`;
const ts = `${String(d.getHours()).padStart(2,"0")}${String(d.getMinutes()).padStart(2,"0")}00`;
const data = await hafas([{
meth: "TripSearch",
req: {
depLocL: [{ type: "S", extId: fromExtId }],
arrLocL: [{ type: "S", extId: toExtId }],
outDate: ds, outTime: ts, outFrwd: true,
numF: 5, maxChg: 2,
jnyFltrL: [{ type: "PROD", mode: "INC", value: "1023" }],
getTariff: false, getPasslist: false,
}
}]);
const res = data.svcResL?.[0]?.res;
const prodL = res?.common?.prodL ?? [];
return (res?.outConL ?? []).map(c => parseJourney(c, prodL));
}
// ─────────────────────────────────────────────────────────────
// Calendar API
// ─────────────────────────────────────────────────────────────
async function calendarFromURL(icsUrl) {
const r = await fetch(`${PROXY}/api/calendar?url=${encodeURIComponent(icsUrl)}`, {
signal: AbortSignal.timeout(15_000),
});
if (!r.ok) {
const err = await r.json().catch(() => ({ error: `HTTP ${r.status}` }));
throw new Error(err.error ?? `HTTP ${r.status}`);
}
return r.json(); // [{id, title, destination, eventTime, source}]
}
async function calendarFromFile(icsText) {
const r = await fetch(`${PROXY}/api/calendar/parse`, {
method: "POST",
headers: { "Content-Type": "text/calendar" },
body: icsText,
signal: AbortSignal.timeout(10_000),
});
if (!r.ok) {
const err = await r.json().catch(() => ({ error: `HTTP ${r.status}` }));
throw new Error(err.error ?? `HTTP ${r.status}`);
}
return r.json();
}
// ─────────────────────────────────────────────────────────────
// Demo Data (used when server is offline)
// ─────────────────────────────────────────────────────────────
function makeDemoJourneys(walkMins) {
const TYPES = ["RJX","IC","REX","S","D","EC"];
const PLATS = ["1","2","3","4","5","6","7","8","2A","3B","4C"];
const now = new Date();
let t = new Date(now.getTime() + (walkMins + 8) * 60000);
t.setSeconds(0, 0);
t.setMinutes(Math.ceil(t.getMinutes() / 5) * 5);
return Array.from({ length: 5 }, (_, i) => {
if (i > 0) t = new Date(t.getTime() + (12 + Math.random() * 28) * 60000);
const type = TYPES[i % TYPES.length];
const num = 100 + Math.floor(Math.random() * 899);
const durMs = (30 + Math.random() * 95) * 60000;
const delay = Math.random() > 0.72 ? Math.ceil(Math.random() * 9) : 0;
const rD = new Date(t.getTime() + delay * 60000);
return {
id: `demo-${i}`, sD: new Date(t), rD,
sA: new Date(t.getTime() + durMs), rA: new Date(rD.getTime() + durMs),
delay, platform: PLATS[Math.floor(Math.random() * PLATS.length)],
changes: i === 2 ? 1 : 0, trains: [`${type} ${num}`], cancelled: false,
};
});
}
// ─────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────
const WALK_MINS = 12;
const RED = "#EE2127";
const fmtTime = d =>
d?.toLocaleTimeString("de-AT", { hour: "2-digit", minute: "2-digit" }) ?? "";
const leaveBy = rD => new Date(rD.getTime() - WALK_MINS * 60000);
function countdownInfo(rD, now) {
const ms = leaveBy(rD) - now;
if (ms < 0) return { label: "GONE", color: "#EF4444", urgent: true };
const m = Math.floor(ms / 60000);
if (m < 2) return { label: `${Math.ceil(ms/1000)}s`, color: "#EF4444", urgent: true };
if (m < 15) return { label: `${m} min`, color: "#F59E0B", urgent: true };
const h = Math.floor(m / 60);
return { label: h ? `${h}h ${m%60}m` : `${m} min`, color: "#22C55E", urgent: false };
}
const delayColor = d => d > 5 ? "#EF4444" : d > 2 ? "#F59E0B" : "#22C55E";
// ─────────────────────────────────────────────────────────────
// Component
// ─────────────────────────────────────────────────────────────
const SAMPLE_EVENTS = [
{ id: "s1", title: "Meeting in Graz", destination: "Graz Hbf", eventTime: new Date(Date.now() + 3.5*3600000), source: "manual" },
{ id: "s2", title: "Conference in Salzburg", destination: "Salzburg Hbf", eventTime: new Date(Date.now() + 8*3600000), source: "manual" },
];
export default function OebbPlanner() {
// ── Core state ─────────────────────────────────────────────
const [events, setEvents] = useState(SAMPLE_EVENTS);
const [location, setLocation] = useState(null);
const [locState, setLocState] = useState("pending");
const [originStn, setOriginStn] = useState(null);
const [tripData, setTripData] = useState({});
const [isLive, setIsLive] = useState(null);
const [lastUpdated, setLastUpdated] = useState(null);
const [now, setNow] = useState(new Date());
const [showAdd, setShowAdd] = useState(false);
const [form, setForm] = useState({ title:"", destination:"", eventTime:"" });
// ── Server / calendar state ────────────────────────────────
const [serverOnline, setServerOnline] = useState(null); // null|true|false
const [calPanel, setCalPanel] = useState(true); // collapsed?
const [calTab, setCalTab] = useState("url"); // "url"|"file"
const [calUrl, setCalUrl] = useState("");
const [calStatus, setCalStatus] = useState(null); // null|loading|ok|error
const [calMsg, setCalMsg] = useState("");
const fileRef = useRef(null);
const apiOk = useRef(null);
// ── Clock ──────────────────────────────────────────────────
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 10_000);
return () => clearInterval(id);
}, []);
// ── Server health check ────────────────────────────────────
const checkServer = useCallback(async () => {
try {
const r = await fetch(`${PROXY}/api/health`, { signal: AbortSignal.timeout(3000) });
const ok = r.ok && (await r.json()).ok;
setServerOnline(ok);
return ok;
} catch {
setServerOnline(false);
return false;
}
}, []);
useEffect(() => {
checkServer();
const id = setInterval(checkServer, 30_000);
return () => clearInterval(id);
}, [checkServer]);
// ── Geolocation ────────────────────────────────────────────
useEffect(() => {
if (!navigator.geolocation) { setLocState("denied"); return; }
navigator.geolocation.getCurrentPosition(
p => { setLocation({ lat: p.coords.latitude, lng: p.coords.longitude }); setLocState("granted"); },
() => setLocState("denied"),
{ timeout: 10000, maximumAge: 300_000 }
);
}, []);
// ── Find origin station ────────────────────────────────────
useEffect(() => {
if (locState === "pending") return;
(async () => {
if (location && serverOnline) {
try {
const stns = await stationByCoord(location.lat, location.lng);
if (stns.length > 0) {
setOriginStn({ name: stns[0].name, extId: stns[0].extId });
apiOk.current = true;
setIsLive(true);
return;
}
} catch { /* fall through */ }
}
apiOk.current = serverOnline ? null : false;
setIsLive(serverOnline ? null : false);
setOriginStn({ name: "Wien Hauptbahnhof", extId: "1190100" });
})();
}, [location, locState, serverOnline]);
// ── Fetch journeys ─────────────────────────────────────────
const fetchAll = useCallback(async () => {
if (!originStn) return;
for (const ev of events) {
setTripData(p => ({ ...p, [ev.id]: { ...(p[ev.id]??{}), loading: true } }));
const t0 = new Date();
const search = new Date(ev.eventTime.getTime() - 2*3600000);
const when = search < t0 ? t0 : search;
try {
if (apiOk.current === false) throw new Error("demo");
const destList = await stationByName(ev.destination);
if (!destList.length) throw new Error("no station");
const dest = destList[0];
const journeys = await findJourneys(originStn.extId, dest.extId, when);
apiOk.current = true; setIsLive(true);
setTripData(p => ({ ...p, [ev.id]: { journeys, destName: dest.name, demo: false, loading: false } }));
} catch {
if (apiOk.current !== true) { apiOk.current = false; setIsLive(false); }
setTripData(p => ({ ...p, [ev.id]: { journeys: makeDemoJourneys(WALK_MINS), destName: ev.destination, demo: true, loading: false } }));
}
}
setLastUpdated(new Date());
}, [events, originStn]);
useEffect(() => { if (originStn) fetchAll(); }, [originStn, fetchAll]);
useEffect(() => { const id = setInterval(fetchAll, 60_000); return () => clearInterval(id); }, [fetchAll]);
// ── Calendar import helpers ────────────────────────────────
const mergeCalendarEvents = useCallback(calEvents => {
const parsed = calEvents.map(e => ({
...e,
eventTime: new Date(e.eventTime),
}));
setEvents(prev => [
...prev.filter(e => e.source !== "calendar"),
...parsed,
]);
}, []);
const connectCalendarURL = async () => {
if (!calUrl.trim()) return;
setCalStatus("loading"); setCalMsg("");
try {
const evs = await calendarFromURL(calUrl.trim());
mergeCalendarEvents(evs);
setCalStatus("ok");
setCalMsg(`${evs.length} event${evs.length!==1?"s":""} imported (next 14 days, locations only)`);
setCalPanel(false);
} catch (err) {
setCalStatus("error");
setCalMsg(err.message);
}
};
const handleFileUpload = async e => {
const file = e.target.files?.[0];
if (!file) return;
setCalStatus("loading"); setCalMsg("");
try {
const text = await file.text();
const evs = await calendarFromFile(text);
mergeCalendarEvents(evs);
setCalStatus("ok");
setCalMsg(`${evs.length} event${evs.length!==1?"s":""} imported from ${file.name}`);
setCalPanel(false);
} catch (err) {
setCalStatus("error");
setCalMsg(err.message);
}
e.target.value = "";
};
const addManualEvent = () => {
if (!form.title || !form.destination || !form.eventTime) return;
setEvents(p => [...p, {
id: Date.now().toString(),
title: form.title,
destination: form.destination,
eventTime: new Date(form.eventTime),
source: "manual",
}]);
setForm({ title:"", destination:"", eventTime:"" });
setShowAdd(false);
};
const removeEvent = id => setEvents(p => p.filter(e => e.id !== id));
const bestJourney = journeys => journeys?.find(j => leaveBy(j.rD) > now) ?? journeys?.[0];
const calEventCount = events.filter(e => e.source === "calendar").length;
// ─────────────────────────────────────────────────────────────
// Render
// ─────────────────────────────────────────────────────────────
return (
<div style={{ fontFamily:"'DM Sans',sans-serif", background:"#080810", minHeight:"100vh", color:"#DCDCF0" }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&family=Syne:wght@600;700;800&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
*{box-sizing:border-box;margin:0;padding:0}
body{background:#080810}
.card{background:#10101E;border:1px solid #1C1C30;border-radius:14px;overflow:hidden;transition:border-color .2s}
.card:hover{border-color:#2A2A44}
.jrow{display:flex;align-items:center;gap:10px;padding:9px 18px;border-bottom:1px solid #161626;transition:background .15s;cursor:default}
.jrow:last-child{border-bottom:none}
.jrow:hover{background:#121224}
.chip{display:inline-flex;align-items:center;padding:2px 7px;border-radius:4px;font-size:10px;font-weight:600;font-family:'IBM Plex Mono',monospace}
.fab{display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-weight:500;transition:all .15s}
.tab{padding:7px 14px;font-size:12px;font-weight:500;border:none;border-radius:6px;cursor:pointer;transition:all .15s;font-family:'DM Sans',sans-serif}
input[type=text],input[type=datetime-local],input[type=url]{width:100%;background:#14142A;border:1px solid #222236;border-radius:8px;padding:10px 14px;color:#DCDCF0;font-family:'DM Sans',sans-serif;font-size:14px;outline:none}
input:focus{border-color:${RED}}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.fade-up{animation:fadeUp .35s ease}
.drop-zone{border:1.5px dashed #1E1E34;border-radius:10px;padding:24px;text-align:center;cursor:pointer;transition:border-color .2s,background .2s}
.drop-zone:hover{border-color:#44446A;background:#0E0E1E}
`}</style>
{/* ── Header ──────────────────────────────────────────── */}
<header style={{ padding:"16px 24px", display:"flex", justifyContent:"space-between", alignItems:"center", borderBottom:"1px solid #141424", background:"#0A0A18", position:"sticky", top:0, zIndex:20 }}>
<div style={{ display:"flex", alignItems:"center", gap:12 }}>
<div style={{ background:RED, borderRadius:8, padding:"5px 10px", fontFamily:"'Syne',sans-serif", fontWeight:800, fontSize:15, letterSpacing:.5, color:"#fff", userSelect:"none" }}>ÖBB</div>
<div>
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:700, fontSize:17, letterSpacing:-.3, lineHeight:1.2 }}>Train Planner</div>
<div style={{ fontSize:11, color:"#38385A", fontFamily:"'IBM Plex Mono',monospace", marginTop:2 }}>
{originStn ? `${originStn.name}` : locState==="pending" ? "Locating…" : "Default station"}
</div>
</div>
</div>
<div style={{ display:"flex", alignItems:"center", gap:20 }}>
{/* Server status pill */}
<div style={{
display:"flex", alignItems:"center", gap:6, padding:"5px 10px",
background:"#0E0E1C", border:"1px solid #1C1C30", borderRadius:20,
}}>
<div style={{
width:6, height:6, borderRadius:"50%",
background: serverOnline===true?"#22C55E": serverOnline===false?"#EF4444":"#555",
animation: serverOnline===null?"blink 1.5s infinite":"none",
}}/>
<span style={{ fontSize:11, color: serverOnline===true?"#22C55E": serverOnline===false?"#EF4444":"#555", fontFamily:"'IBM Plex Mono',monospace" }}>
{serverOnline===true?"server online": serverOnline===false?"server offline":"checking…"}
</span>
</div>
<div style={{ textAlign:"right" }}>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:24, fontWeight:500, letterSpacing:-1, lineHeight:1 }}>{fmtTime(now)}</div>
<div style={{ display:"flex", alignItems:"center", gap:5, justifyContent:"flex-end", marginTop:3 }}>
<div style={{ width:6, height:6, borderRadius:"50%", background:isLive===true?"#22C55E":isLive===false?"#F59E0B":"#444", animation:isLive===null?"blink 1.5s infinite":"none" }}/>
<span style={{ fontSize:10, color:"#383858" }}>{isLive===true?"Live data":isLive===false?"Demo mode":"Connecting…"}</span>
</div>
</div>
</div>
</header>
{/* ── Server offline banner ────────────────────────────── */}
{serverOnline===false && (
<div style={{ background:"#120000", borderBottom:"1px solid #300000", padding:"10px 24px", fontSize:12, color:"#EF6060", display:"flex", gap:10, alignItems:"center" }}>
<span>🔌</span>
<span>Proxy server offline <strong>live ÖBB data and calendar import unavailable.</strong> Start it with:</span>
<code style={{ background:"#1E0000", padding:"2px 8px", borderRadius:4, fontFamily:"'IBM Plex Mono',monospace", color:"#FF9090" }}>cd server && npm start</code>
</div>
)}
<main style={{ padding:"20px 24px", maxWidth:900, margin:"0 auto", display:"flex", flexDirection:"column", gap:14 }}>
{/* ── Calendar Panel ──────────────────────────────────── */}
<div className="card fade-up">
<button
className="fab"
onClick={() => setCalPanel(p => !p)}
style={{ width:"100%", padding:"14px 18px", background:"transparent", color:"#DCDCF0", justifyContent:"space-between", borderRadius:0 }}
>
<div style={{ display:"flex", alignItems:"center", gap:10 }}>
<span style={{ fontSize:18 }}>📅</span>
<div style={{ textAlign:"left" }}>
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:700, fontSize:15 }}>Calendar</div>
<div style={{ fontSize:11, color:"#44446A", marginTop:1 }}>
{calEventCount > 0
? `${calEventCount} event${calEventCount!==1?"s":""} imported`
: "Connect your ICS calendar to auto-import events with locations"}
</div>
</div>
</div>
<div style={{ display:"flex", alignItems:"center", gap:10 }}>
{calEventCount > 0 && (
<span className="chip" style={{ background:"#0A1A0A", color:"#22C55E" }}>
{calEventCount} events
</span>
)}
<span style={{ color:"#383858", fontSize:18, transform:calPanel?"none":"rotate(-90deg)", transition:"transform .2s" }}></span>
</div>
</button>
{calPanel && (
<div style={{ borderTop:"1px solid #161626", padding:"16px 18px" }}>
{/* Tabs */}
<div style={{ display:"flex", gap:6, marginBottom:16 }}>
{[["url","🔗 ICS URL"],["file","📁 Upload File"]].map(([id, label]) => (
<button key={id} className="tab" onClick={() => setCalTab(id)} style={{
background: calTab===id?"#1C1C34":"transparent",
color: calTab===id?"#DCDCF0":"#44446A",
border: calTab===id?"1px solid #2A2A48":"1px solid transparent",
}}>{label}</button>
))}
</div>
{calTab === "url" && (
<div>
<div style={{ fontSize:11, color:"#44446A", marginBottom:8, lineHeight:1.6 }}>
Paste your calendar's <strong style={{ color:"#8080AA" }}>secret ICS URL</strong>.
Works with Google Calendar, Apple Calendar, Outlook, and most other apps.<br/>
<span style={{ color:"#333352" }}>In Google Calendar: Settings → your calendar → "Secret address in iCal format"</span>
</div>
<div style={{ display:"flex", gap:8 }}>
<input
type="url"
placeholder="https://calendar.google.com/calendar/ical/…/basic.ics"
value={calUrl}
onChange={e => setCalUrl(e.target.value)}
onKeyDown={e => e.key==="Enter" && connectCalendarURL()}
disabled={!serverOnline || calStatus==="loading"}
/>
<button
className="fab"
onClick={connectCalendarURL}
disabled={!serverOnline || !calUrl.trim() || calStatus==="loading"}
style={{ padding:"10px 18px", background:(!serverOnline||!calUrl.trim())?"#161626":RED, color:"#fff", fontSize:13, whiteSpace:"nowrap", opacity:(!serverOnline||!calUrl.trim())?0.5:1 }}
>
{calStatus==="loading" ? "…" : "Connect"}
</button>
</div>
</div>
)}
{calTab === "file" && (
<div>
<div style={{ fontSize:11, color:"#44446A", marginBottom:10 }}>
Export your calendar as an <strong style={{ color:"#8080AA" }}>.ics file</strong> and upload it here.
Works completely offline — the file is parsed by the local server.
</div>
<div
className="drop-zone"
onClick={() => serverOnline && fileRef.current?.click()}
style={{ opacity: serverOnline?1:0.4 }}
>
<div style={{ fontSize:28, marginBottom:8 }}>📂</div>
<div style={{ fontSize:13, color:"#44446A" }}>Click to choose an .ics file</div>
<div style={{ fontSize:11, color:"#2A2A44", marginTop:4 }}>Exported from Apple Calendar, Google, Outlook…</div>
</div>
<input ref={fileRef} type="file" accept=".ics,text/calendar" onChange={handleFileUpload} style={{ display:"none" }} />
</div>
)}
{/* Status message */}
{calMsg && (
<div style={{ marginTop:12, fontSize:12, padding:"8px 12px", borderRadius:8, background: calStatus==="ok"?"#0A180A": calStatus==="error"?"#1A0A0A":"#111128", color: calStatus==="ok"?"#4CAF50": calStatus==="error"?"#EF6060":"#8888CC" }}>
{calStatus==="ok"?"✓ ":calStatus==="error"?"✗ ":""}{calMsg}
</div>
)}
{!serverOnline && (
<div style={{ marginTop:10, fontSize:11, color:"#443030" }}>
⚠ Calendar import requires the server to be running.
</div>
)}
</div>
)}
</div>
{/* ── Section label ────────────────────────────────────── */}
<div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", padding:"2px 4px" }}>
<div style={{ fontSize:11, color:"#2A2A44", textTransform:"uppercase", letterSpacing:1.5 }}>
{events.length} event{events.length!==1?"s":""} · {WALK_MINS} min walk to station
</div>
{isLive===false && (
<span className="chip" style={{ background:"#1A1400", color:"#6A5000" }}>demo data</span>
)}
</div>
{/* ── Event Cards ────────────────────────────────────────── */}
{events.map(ev => {
const data = tripData[ev.id];
const best = data?.journeys ? bestJourney(data.journeys) : null;
const cd = best ? countdownInfo(best.rD, now) : null;
const lb = best ? leaveBy(best.rD) : null;
return (
<div key={ev.id} className="card fade-up">
{/* Event header */}
<div style={{ padding:"14px 18px", display:"flex", gap:12, justifyContent:"space-between", alignItems:"flex-start" }}>
<div style={{ flex:1 }}>
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:700, fontSize:16, display:"flex", alignItems:"center", gap:8, flexWrap:"wrap" }}>
{ev.title}
{ev.source==="calendar" && <span className="chip" style={{ background:"#0A100A", color:"#3A7A3A" }}>📅 calendar</span>}
{data?.demo && <span className="chip" style={{ background:"#181400", color:"#6A5000" }}>demo</span>}
</div>
<div style={{ fontSize:12, color:"#48486A", marginTop:5, display:"flex", gap:16, flexWrap:"wrap" }}>
<span>📍 {data?.destName ?? ev.destination}</span>
<span>🕐 {fmtTime(ev.eventTime)} · {ev.eventTime.toLocaleDateString("de-AT",{weekday:"short",day:"numeric",month:"short"})}</span>
</div>
</div>
<div style={{ display:"flex", alignItems:"flex-start", gap:8, flexShrink:0 }}>
{/* Leave-by box */}
{cd && lb && (
<div style={{ background:"#0C0C1C", border:`1px solid ${cd.urgent?cd.color+"55":"#1E1E34"}`, borderRadius:10, padding:"8px 14px", textAlign:"center", minWidth:96 }}>
<div style={{ fontSize:9, color:"#383858", textTransform:"uppercase", letterSpacing:1.2 }}>Leave by</div>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontWeight:600, fontSize:19, color:cd.color, lineHeight:1.2, marginTop:2 }}>{fmtTime(lb)}</div>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:11, color:cd.color, marginTop:2 }}>in {cd.label}</div>
</div>
)}
{data?.loading && !best && (
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:11, color:"#303050", marginTop:10, animation:"blink 1.5s infinite" }}>
searching…
</div>
)}
<button className="fab" onClick={() => removeEvent(ev.id)} style={{ background:"transparent", color:"#303050", fontSize:18, padding:"2px 6px", marginTop:4 }} title="Remove">×</button>
</div>
</div>
{/* Best train bar */}
{best && (
<div style={{ background:"#0C0C1A", borderTop:"1px solid #161626", borderBottom:"1px solid #161626", padding:"11px 18px", display:"flex", gap:16, alignItems:"center", flexWrap:"wrap" }}>
<div style={{ flex:1, minWidth:160 }}>
<div style={{ fontSize:9, color:"#2C2C48", textTransform:"uppercase", letterSpacing:1.6, marginBottom:5 }}>Next Best Train</div>
<div style={{ display:"flex", alignItems:"center", gap:7, flexWrap:"wrap" }}>
<span style={{ fontFamily:"'IBM Plex Mono',monospace", fontWeight:600, fontSize:15 }}>{best.trains.join(" → ")}</span>
<span className="chip" style={{ background:best.delay>0?"#250A0A":"#0A180A", color:delayColor(best.delay) }}>
{best.delay>0?`+${best.delay} min`:"on time"}
</span>
{best.cancelled && <span className="chip" style={{ background:"#200000", color:"#FF5050" }}>CANCELLED</span>}
{best.changes>0 && <span className="chip" style={{ background:"#101030", color:"#6868A0" }}>{best.changes}× change</span>}
</div>
</div>
<div style={{ display:"flex", gap:22, flexWrap:"wrap", alignItems:"center" }}>
{[
{ label:"DEP", val:fmtTime(best.rD), sub:best.delay>0?fmtTime(best.sD):null, color:best.delay>0?delayColor(best.delay):undefined },
{ label:"ARR", val:fmtTime(best.rA) },
{ label:"PLAT", val:best.platform, color:RED },
].map(item => (
<div key={item.label} style={{ textAlign:"center" }}>
<div style={{ fontSize:9, color:"#2C2C48", letterSpacing:1.6, textTransform:"uppercase" }}>{item.label}</div>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontWeight:600, fontSize:19, color:item.color, lineHeight:1.2 }}>{item.val}</div>
{item.sub && <div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:10, color:"#383858", textDecoration:"line-through" }}>{item.sub}</div>}
</div>
))}
</div>
</div>
)}
{/* Journey list */}
{data?.journeys && (
<div>
<div style={{ padding:"7px 18px 3px", fontSize:9, color:"#222238", textTransform:"uppercase", letterSpacing:2 }}>
All departures
</div>
{data.journeys.map(j => {
const lb2 = leaveBy(j.rD);
const gone = lb2 < now;
const isB = j.id === best?.id;
return (
<div key={j.id} className="jrow" style={{ opacity:gone?.35:1, background:isB?"#0E0E22":undefined }}>
<div style={{ minWidth:90, flexShrink:0 }}>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:12, fontWeight:isB?600:400, color:isB?"#C8C8E0":"#606080" }}>{j.trains[0]}</div>
{j.changes>0 && <div style={{ fontSize:10, color:"#303050" }}>{j.changes}× change</div>}
</div>
<div style={{ display:"flex", gap:16, flex:1, flexWrap:"wrap" }}>
{[{label:"LEAVE",val:fmtTime(lb2)},{label:"DEP",val:fmtTime(j.rD),delay:j.delay},{label:"ARR",val:fmtTime(j.rA)}].map(col => (
<div key={col.label}>
<div style={{ fontSize:9, color:"#242440", letterSpacing:1 }}>{col.label}</div>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontSize:12, color:col.delay>0?delayColor(col.delay):undefined }}>
{col.val}{col.delay>0&&<span style={{fontSize:10}}> +{col.delay}</span>}
</div>
</div>
))}
</div>
<div style={{ textAlign:"right", flexShrink:0 }}>
<div style={{ fontSize:9, color:"#242440" }}>PLAT</div>
<div style={{ fontFamily:"'IBM Plex Mono',monospace", fontWeight:600, fontSize:14, color:RED }}>{j.platform}</div>
</div>
{isB && !gone && <span style={{ fontSize:12, color:"#22C55E", flexShrink:0 }}>★</span>}
{j.cancelled && <span className="chip" style={{ background:"#1E0000", color:"#EF4444", flexShrink:0 }}>✕</span>}
</div>
);
})}
</div>
)}
</div>
);
})}
{/* ── Add manual event ─────────────────────────────────── */}
<button className="fab" onClick={() => setShowAdd(true)} style={{ background:"#10101E", border:"1px dashed #1E1E36", color:"#383858", padding:"14px 20px", fontSize:14, borderRadius:14, width:"100%" }}>
+ Add Event Manually
</button>
{lastUpdated && (
<div style={{ textAlign:"center", fontSize:11, color:"#242440", fontFamily:"'IBM Plex Mono',monospace", marginTop:4 }}>
Updated {fmtTime(lastUpdated)} · auto-refresh 60s ·{" "}
<span onClick={fetchAll} style={{ cursor:"pointer", color:"#343458", textDecoration:"underline" }}>refresh now</span>
</div>
)}
</main>
{/* ── Add Event Modal ─────────────────────────────────────── */}
{showAdd && (
<div onClick={e => e.target===e.currentTarget && setShowAdd(false)} style={{ position:"fixed", inset:0, background:"rgba(4,4,12,.8)", display:"flex", alignItems:"center", justifyContent:"center", padding:20, zIndex:200 }}>
<div style={{ background:"#10101E", border:"1px solid #1E1E32", borderRadius:16, padding:26, width:"100%", maxWidth:400 }} className="fade-up">
<div style={{ fontFamily:"'Syne',sans-serif", fontWeight:700, fontSize:18, marginBottom:22 }}>Add Event Manually</div>
{[
{ label:"EVENT TITLE", key:"title", type:"text", ph:"e.g. Client Meeting in Linz" },
{ label:"DESTINATION STATION", key:"destination", type:"text", ph:"e.g. Linz Hbf, Bregenz" },
{ label:"EVENT TIME", key:"eventTime", type:"datetime-local", ph:"" },
].map(f => (
<div key={f.key} style={{ marginBottom:14 }}>
<div style={{ fontSize:10, color:"#383858", letterSpacing:1.2, textTransform:"uppercase", marginBottom:6 }}>{f.label}</div>
<input type={f.type} placeholder={f.ph} value={form[f.key]} onChange={e => setForm(p => ({ ...p, [f.key]: e.target.value }))} />
</div>
))}
<div style={{ display:"flex", gap:10, marginTop:20 }}>
<button className="fab" onClick={() => setShowAdd(false)} style={{ flex:1, padding:"11px", background:"#181828", color:"#555570", fontSize:14 }}>Cancel</button>
<button className="fab" onClick={addManualEvent} style={{ flex:1, padding:"11px", background:RED, color:"#fff", fontSize:14 }}>Add Event</button>
</div>
</div>
</div>
)}
</div>
);
}
+6693
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "oebb_planner",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"test": "echo 'No tests yet'"
},
"dependencies": {
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.2.6",
"tailwindcss": "^4",
"typescript": "^5"
}
}
+7
View File
@@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
+1
View File
@@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

-477
View File
@@ -1,477 +0,0 @@
const request = require("supertest");
// ── Setup ──
// Set a test port before requiring the server module
const TEST_PORT = 3501;
process.env.PORT = String(TEST_PORT);
// Mock global fetch before requiring server (for HAFAS proxy tests)
let mockFetch;
let server;
beforeAll(async () => {
mockFetch = jest.fn();
global.fetch = mockFetch;
// Require server after setting PORT and mocking fetch
server = require("../index.js");
// Wait for server to be ready by polling health endpoint
await waitForServer();
});
afterAll((done) => {
if (server) {
server.close(done);
}
jest.restoreAllMocks();
});
const BASE = `http://localhost:${TEST_PORT}`;
async function waitForServer(maxAttempts = 20) {
for (let i = 0; i < maxAttempts; i++) {
try {
await fetch(`${BASE}/api/health`);
return; // Server is ready
} catch {
await new Promise((r) => setTimeout(r, 100));
}
}
throw new Error("Server did not start in time");
}
// ── Health Endpoint ──
describe("GET /api/health", () => {
test("returns ok: true with timestamp and version", async () => {
const res = await request(BASE).get("/api/health");
expect(res.statusCode).toBe(200);
expect(res.body.ok).toBe(true);
expect(res.body).toHaveProperty("ts");
expect(res.body).toHaveProperty("version", "1.0.0");
expect(new Date(res.body.ts)).toBeInstanceOf(Date);
expect(() => new Date(res.body.ts)).not.toThrow();
});
});
// ── Calendar Parse Endpoint ──
describe("POST /api/calendar/parse", () => {
const futureDate = () => {
const d = new Date(Date.now() + 2 * 24 * 3600000);
return d.toISOString().replace(/[-T:]/g, "").slice(0, 8) + "T120000";
};
const pastDate = "20200101T100000";
const icsWithEvents = (overrides = "") => `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:Test Meeting
LOCATION:Graz Hbf
UID:test-1@example.com
END:VEVENT
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:Another Event
LOCATION:Salzburg Hbf, 5020 Salzburg
UID:test-2@example.com
END:VEVENT
END:VCALENDAR`;
test("parses valid ICS with multiple events", async () => {
const res = await request(BASE).post("/api/calendar/parse").send(icsWithEvents());
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body.length).toBe(2);
});
test("returns properly structured event objects", async () => {
const res = await request(BASE).post("/api/calendar/parse").send(icsWithEvents());
res.body.forEach((event) => {
expect(event).toHaveProperty("id");
expect(event).toHaveProperty("title");
expect(event).toHaveProperty("destination");
expect(event).toHaveProperty("eventTime");
expect(event.source).toBe("calendar");
});
});
test("filters out events without location field", async () => {
const icsNoLoc = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:No Location Event
UID:no-loc@example.com
END:VEVENT
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:Has Location Event
LOCATION:Vienna Hbf
UID:has-loc@example.com
END:VEVENT
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(icsNoLoc);
expect(res.statusCode).toBe(200);
expect(res.body.length).toBe(1);
expect(res.body[0].title).toBe("Has Location Event");
});
test("filters out past events", async () => {
const icsPast = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${pastDate}
SUMMARY:Old Event
LOCATION:Graz Hbf
UID:old@example.com
END:VEVENT
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(icsPast);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual([]);
});
test("returns empty array for ICS with no VEVENT entries", async () => {
const emptyICS = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(emptyICS);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual([]);
});
test("returns 400 for invalid ICS content", async () => {
const res = await request(BASE).post("/api/calendar/parse").send("This is not valid ICS content {{{");
expect(res.statusCode).toBe(400);
expect(res.body).toHaveProperty("error");
expect(res.body.error).toContain("Invalid ICS");
});
test("cleans verbose location fields", async () => {
const icsVerbose = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:Meeting
LOCATION:Graz Hauptbahnhof, Europaplatz 5, 8020 Graz
UID:verbose@example.com
END:VEVENT
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(icsVerbose);
expect(res.statusCode).toBe(200);
expect(res.body.length).toBe(1);
expect(res.body[0].destination).toBe("Graz Hauptbahnhof");
});
test("handles ICS with escaped newlines in location", async () => {
const icsNewlines = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${futureDate()}
SUMMARY:Meeting
LOCATION:Graz Hbf\\nEuropaplatz 5\\n8020 Graz
UID:nl@example.com
END:VEVENT
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(icsNewlines);
expect(res.statusCode).toBe(200);
expect(res.body.length).toBe(1);
expect(res.body[0].destination).toBe("Graz Hbf");
});
test("sorts events by eventTime ascending", async () => {
const d1 = new Date(Date.now() + 3 * 24 * 3600000);
const d2 = new Date(Date.now() + 2 * 24 * 3600000);
const d3 = new Date(Date.now() + 4 * 24 * 3600000);
const fmt = (d) => d.toISOString().replace(/[-T:]/g, "").slice(0, 8) + "T120000";
const icsUnsorted = `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Test//Test//EN
BEGIN:VEVENT
DTSTART:${fmt(d1)}
SUMMARY:Later
LOCATION:Graz Hbf
UID:later@example.com
END:VEVENT
BEGIN:VEVENT
DTSTART:${fmt(d2)}
SUMMARY:Sooner
LOCATION:Vienna Hbf
UID:sooner@example.com
END:VEVENT
BEGIN:VEVENT
DTSTART:${fmt(d3)}
SUMMARY:Latest
LOCATION:Salzburg Hbf
UID:latest@example.com
END:VEVENT
END:VCALENDAR`;
const res = await request(BASE).post("/api/calendar/parse").send(icsUnsorted);
expect(res.body.length).toBe(3);
expect(res.body[0].title).toBe("Sooner");
expect(res.body[1].title).toBe("Later");
expect(res.body[2].title).toBe("Latest");
});
});
// ── Calendar URL Endpoint ──
describe("GET /api/calendar", () => {
test("returns 400 when url query parameter is missing", async () => {
const res = await request(BASE).get("/api/calendar");
expect(res.statusCode).toBe(400);
expect(res.body.error).toContain("url query parameter is required");
});
test("handles unreachable host gracefully with 502", async () => {
const res = await request(BASE).get("/api/calendar").query({ url: "http://192.0.2.1/nonexistent.ics" }); // TEST-NET-1, unreachable
expect(res.statusCode).toBe(502);
expect(res.body).toHaveProperty("error");
});
test("accepts webcal:// protocol without crashing", async () => {
const res = await request(BASE).get("/api/calendar").query({ url: "webcal://192.0.2.1/cal.ics" });
// Connection will fail but endpoint should handle it gracefully
expect([400, 502]).toContain(res.statusCode);
});
test("accepts days query parameter", async () => {
const res = await request(BASE).get("/api/calendar").query({
url: "http://192.0.2.1/cal.ics",
days: 30,
});
expect(res.statusCode).toBe(502);
});
});
// ── HAFAS Proxy Endpoint ──
describe("POST /api/hafas", () => {
beforeEach(() => {
mockFetch.mockReset();
});
test("forwards request to ÖBB HAFAS API", async () => {
const mockResponse = {
svcResL: [
{
res: {
match: { loc: { name: "Graz Hbf", extId: "8000263" } },
},
},
],
};
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve(mockResponse),
});
const payload = {
svcReqL: [
{
meth: "LocMatch",
req: {
input: {
loc: { type: "S", name: "Graz Hbf?" },
maxLoc: 3,
field: "S",
},
},
},
],
};
const res = await request(BASE).post("/api/hafas").send(payload);
expect(res.statusCode).toBe(200);
expect(res.body).toEqual(mockResponse);
expect(mockFetch).toHaveBeenCalledTimes(1);
});
test("forwards request with correct headers and body", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({}),
});
const payload = { svcReqL: [{ meth: "TestMethod", req: {} }] };
await request(BASE).post("/api/hafas").send(payload);
const fetchCall = mockFetch.mock.calls[0];
expect(fetchCall[0]).toBe("https://fahrplan.oebb.at/bin/mgate.exe");
expect(fetchCall[1].method).toBe("POST");
expect(fetchCall[1].headers["Content-Type"]).toBe("application/json");
expect(JSON.parse(fetchCall[1].body)).toMatchObject(payload);
});
test("returns 502 when HAFAS upstream returns error", async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
});
const res = await request(BASE).post("/api/hafas").send({ svcReqL: [] });
expect(res.statusCode).toBe(502);
expect(res.body.error).toContain("ÖBB returned HTTP 500");
});
test("returns 502 when HAFAS upstream throws", async () => {
mockFetch.mockRejectedValueOnce(new Error("Network error"));
const res = await request(BASE).post("/api/hafas").send({ svcReqL: [] });
expect(res.statusCode).toBe(502);
expect(res.body.error).toContain("Network error");
});
test("respects timeout for slow responses", async () => {
// Simulate a response that takes longer than 12 seconds
mockFetch.mockImplementationOnce(() => new Promise((resolve) => setTimeout(resolve, 13000)));
const startTime = Date.now();
const res = await request(BASE).post("/api/hafas").send({ svcReqL: [] });
const elapsed = Date.now() - startTime;
// Should timeout before 13 seconds
expect(elapsed).toBeLessThan(13000);
expect(res.statusCode).toBe(502);
});
test("handles empty svcReqL array", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: () => Promise.resolve({ svcResL: [] }),
});
const res = await request(BASE).post("/api/hafas").send({ svcReqL: [] });
expect(res.statusCode).toBe(200);
});
});
// ── Middleware & CORS ──
describe("Middleware and CORS", () => {
test("allows CORS from any origin", async () => {
const res = await request(BASE).get("/api/health").set("Origin", "https://example.com");
expect(res.header["access-control-allow-origin"]).toBe("*");
});
test("handles OPTIONS preflight requests", async () => {
const res = await request(BASE)
.options("/api/health")
.set("Origin", "https://example.com")
.set("Access-Control-Request-Method", "GET");
expect(res.statusCode).toBe(204);
});
test("rejects malformed JSON with 400", async () => {
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({}),
});
const res = await request(BASE)
.post("/api/hafas")
.set("Content-Type", "application/json")
.send("{ not valid json }");
expect(res.statusCode).toBe(400);
});
test("rejects oversized payloads with 413", async () => {
const largePayload = JSON.stringify({
data: "x".repeat(6 * 1024 * 1024), // 6MB
});
const res = await request(BASE).post("/api/hafas").send(largePayload);
expect(res.statusCode).toBe(413);
});
});
// ── Route Handling ──
describe("Route Handling", () => {
test("returns 404 for non-existent routes", async () => {
const res = await request(BASE).get("/api/nonexistent");
expect(res.statusCode).toBe(404);
});
test("returns 405 for wrong HTTP method on hafas", async () => {
const res = await request(BASE).get("/api/hafas");
expect(res.statusCode).toBe(405);
});
test("returns 405 for wrong HTTP method on calendar parse", async () => {
const res = await request(BASE).get("/api/calendar/parse");
expect(res.statusCode).toBe(405);
});
});
// ── Server Configuration ──
describe("Server Configuration", () => {
test("uses configured PORT from environment", () => {
expect(server.address().port).toBe(TEST_PORT);
});
test("is listening and accepting connections", () => {
expect(server.listening).toBe(true);
});
test("handles multiple concurrent requests", async () => {
const requests = Array.from({ length: 10 }, () => request(BASE).get("/api/health"));
const responses = await Promise.all(requests);
expect(responses.length).toBe(10);
responses.forEach((res) => {
expect(res.statusCode).toBe(200);
expect(res.body.ok).toBe(true);
});
});
});
-234
View File
@@ -1,234 +0,0 @@
/**
* Tests for server helper functions
*
* Since the helper functions (extractEvents, cleanLocation) are not exported
* from index.js, we reproduce them here for thorough unit testing.
*/
// ── Reproduced helpers (mirror of server/index.js) ──
function cleanLocation(loc) {
if (!loc) return "";
const firstLine = loc.split(/\\n|\r\n|\n/)[0].trim();
const parts = firstLine.split(",").map(s => s.trim()).filter(Boolean);
return (parts[0] || firstLine).substring(0, 120);
}
function extractEvents(icalData, horizonDays = 14) {
const now = new Date();
const horizon = new Date(now.getTime() + horizonDays * 86_400_000);
return Object.values(icalData)
.filter(e => e.type === "VEVENT")
.filter(e => {
if (!e.start) return false;
const start = new Date(e.start);
return start > now && start < horizon;
})
.filter(e => e.location)
.map(e => ({
id: String(e.uid || `ical-${Date.now()}-${Math.random()}`),
title: (e.summary || "Event").trim(),
destination: cleanLocation(String(e.location)),
eventTime: new Date(e.start).toISOString(),
source: "calendar",
}))
.sort((a, b) => new Date(a.eventTime) - new Date(b.eventTime));
}
// ── Tests ──
describe("cleanLocation", () => {
test("returns empty string for falsy input", () => {
expect(cleanLocation(null)).toBe("");
expect(cleanLocation(undefined)).toBe("");
expect(cleanLocation("")).toBe("");
});
test("returns simple location unchanged", () => {
expect(cleanLocation("Graz Hbf")).toBe("Graz Hbf");
expect(cleanLocation("Salzburg Hauptbahnhof")).toBe("Salzburg Hauptbahnhof");
});
test("extracts first segment before comma", () => {
expect(cleanLocation("Graz Hbf, Europaplatz 5, 8020 Graz")).toBe("Graz Hbf");
expect(cleanLocation("Vienna Airport, Terminal 3, 1300 Wien")).toBe("Vienna Airport");
});
test("handles escaped newlines", () => {
expect(cleanLocation("Graz Hbf\\nEuropaplatz 5\\n8020 Graz")).toBe("Graz Hbf");
});
test("handles actual newlines", () => {
expect(cleanLocation("Graz Hbf\nEuropaplatz 5\n8020 Graz")).toBe("Graz Hbf");
});
test("handles Windows-style newlines", () => {
expect(cleanLocation("Graz Hbf\r\nEuropaplatz 5\r\n8020 Graz")).toBe("Graz Hbf");
});
test("truncates to 120 characters", () => {
const longName = "A".repeat(130);
expect(cleanLocation(longName).length).toBe(120);
});
test("handles multiple commas and whitespace", () => {
expect(cleanLocation(" Graz Hbf , some address , 8020 Graz ")).toBe("Graz Hbf");
});
test("handles empty parts after split", () => {
expect(cleanLocation(", , Graz Hbf, ")).toBe("Graz Hbf");
});
});
describe("extractEvents", () => {
const makeEvent = (overrides = {}) => ({
type: "VEVENT",
uid: "test-uid",
summary: "Test Event",
start: new Date(Date.now() + 2 * 3600000), // 2 hours from now
location: "Graz Hbf",
...overrides,
});
test("filters non-VEVENT items", () => {
const data = {
"event-1": { type: "VEVENT", uid: "1", summary: "A", start: new Date(Date.now() + 3600000), location: "Graz Hbf" },
"val-1": { type: "VCALENDAR", uid: "2", summary: "B", start: new Date(Date.now() + 3600000), location: "Graz Hbf" },
};
const result = extractEvents(data);
expect(result.length).toBe(1);
expect(result[0].title).toBe("A");
});
test("filters events without location", () => {
const data = {
"event-1": makeEvent({ uid: "1", location: "Graz Hbf" }),
"event-2": makeEvent({ uid: "2", location: null }),
"event-3": makeEvent({ uid: "3", location: undefined }),
};
const result = extractEvents(data);
expect(result.length).toBe(1);
expect(result[0].id).toBe("1");
});
test("filters events without start date", () => {
const data = {
"event-1": makeEvent({ uid: "1" }),
"event-2": makeEvent({ uid: "2", start: null }),
"event-3": makeEvent({ uid: "3", start: undefined }),
};
const result = extractEvents(data);
expect(result.length).toBe(1);
});
test("filters past events", () => {
const data = {
"event-1": makeEvent({ uid: "1", start: new Date(Date.now() - 3600000) }),
"event-2": makeEvent({ uid: "2", start: new Date(Date.now() + 3600000) }),
};
const result = extractEvents(data);
expect(result.length).toBe(1);
expect(result[0].id).toBe("2");
});
test("filters events beyond horizon", () => {
const data = {
"event-1": makeEvent({ uid: "1", start: new Date(Date.now() + 3600000) }),
"event-2": makeEvent({ uid: "2", start: new Date(Date.now() + 30 * 24 * 3600000) }),
};
const result = extractEvents(data, 14);
expect(result.length).toBe(1);
expect(result[0].id).toBe("1");
});
test("respects custom horizon days", () => {
const data = {
"event-1": makeEvent({ uid: "1", start: new Date(Date.now() + 3600000) }),
"event-2": makeEvent({ uid: "2", start: new Date(Date.now() + 20 * 24 * 3600000) }),
};
// With 30 days horizon, both should be included
expect(extractEvents(data, 30).length).toBe(2);
// With 10 days horizon, only the first should be included
expect(extractEvents(data, 10).length).toBe(1);
});
test("sorts events by eventTime ascending", () => {
const data = {
"event-3": makeEvent({ uid: "3", start: new Date(Date.now() + 1 * 3600000) }),
"event-1": makeEvent({ uid: "1", start: new Date(Date.now() + 5 * 3600000) }),
"event-2": makeEvent({ uid: "2", start: new Date(Date.now() + 3 * 3600000) }),
};
const result = extractEvents(data);
expect(result[0].id).toBe("3");
expect(result[1].id).toBe("2");
expect(result[2].id).toBe("1");
});
test("includes required fields in output", () => {
const data = {
"event-1": makeEvent({ uid: "test-123", summary: "Meeting", location: "Graz Hbf, Europaplatz 5" }),
};
const result = extractEvents(data);
expect(result.length).toBe(1);
const event = result[0];
expect(event).toHaveProperty("id", "test-123");
expect(event).toHaveProperty("title", "Meeting");
expect(event).toHaveProperty("destination", "Graz Hbf");
expect(event).toHaveProperty("eventTime");
expect(event).toHaveProperty("source", "calendar");
});
test("uses default title when summary is missing", () => {
const data = {
"event-1": makeEvent({ uid: "1", summary: undefined }),
};
const result = extractEvents(data);
expect(result[0].title).toBe("Event");
});
test("uses generated uid when uid is missing", () => {
const data = {
"event-1": makeEvent({ uid: undefined }),
};
const result = extractEvents(data);
expect(result[0].id).toMatch(/^ical-/);
});
test("cleans location field in output", () => {
const data = {
"event-1": makeEvent({ uid: "1", location: "Graz Hbf, Europaplatz 5, 8020 Graz" }),
};
const result = extractEvents(data);
expect(result[0].destination).toBe("Graz Hbf");
});
test("returns empty array for empty input", () => {
expect(extractEvents({})).toEqual([]);
});
test("returns empty array for events array-like object", () => {
const data = [];
expect(extractEvents(data)).toEqual([]);
});
test("handles timezone-aware start dates", () => {
const future = new Date(Date.now() + 5 * 3600000);
const data = {
"event-1": makeEvent({ uid: "1", start: future.toISOString() }),
};
const result = extractEvents(data);
expect(result.length).toBe(1);
expect(result[0].eventTime).toBe(future.toISOString());
});
test("handles multiple events with same start time", () => {
const sameTime = new Date(Date.now() + 4 * 3600000);
const data = {
"event-1": makeEvent({ uid: "1", start: sameTime, location: "Graz Hbf" }),
"event-2": makeEvent({ uid: "2", start: sameTime, location: "Vienna Hbf" }),
};
const result = extractEvents(data);
expect(result.length).toBe(2);
});
});
-135
View File
@@ -1,135 +0,0 @@
// ─────────────────────────────────────────────────────────────────────────────
// ÖBB Train Planner — Backend Server
// POST /api/hafas → proxy to ÖBB HAFAS (bypasses CORS)
// GET /api/calendar?url=… → fetch & parse a remote ICS calendar
// POST /api/calendar/parse → parse ICS sent as plain text in body
// GET /api/health → liveness check
// ─────────────────────────────────────────────────────────────────────────────
const express = require("express");
const cors = require("cors");
const ical = require("node-ical");
const app = express();
const PORT = process.env.PORT || 3001;
// ── Middleware ────────────────────────────────────────────────────────────────
app.use(cors()); // allow all origins in dev
app.use(express.json({ limit: "5mb" }));
// ── ÖBB HAFAS Proxy ──────────────────────────────────────────────────────────
// The HAFAS mgate endpoint blocks browser fetch() via CORS.
// This route forwards the request server-side, where CORS doesn't apply.
const HAFAS_URL = "https://fahrplan.oebb.at/bin/mgate.exe";
app.post("/api/hafas", async (req, res) => {
try {
const upstream = await fetch(HAFAS_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req.body),
signal: AbortSignal.timeout(12_000),
});
if (!upstream.ok) {
return res.status(upstream.status).json({ error: `ÖBB returned HTTP ${upstream.status}` });
}
const data = await upstream.json();
res.json(data);
} catch (err) {
console.error("[hafas]", err.message);
res.status(502).json({ error: err.message });
}
});
// ── Calendar: fetch remote ICS URL ───────────────────────────────────────────
// Supports https:// and webcal:// URLs (Google Cal, Apple, Outlook, Fastmail…)
app.get("/api/calendar", async (req, res) => {
let { url, days } = req.query;
if (!url) return res.status(400).json({ error: "url query parameter is required" });
// webcal:// → https://
url = decodeURIComponent(url).replace(/^webcal:/i, "https:");
try {
const events = await ical.async.fromURL(url);
res.json(extractEvents(events, Number(days) || 14));
} catch (err) {
console.error("[calendar url]", err.message);
res.status(502).json({ error: `Could not fetch calendar: ${err.message}` });
}
});
// ── Calendar: parse uploaded ICS body ────────────────────────────────────────
// Client reads the .ics file as text and POSTs the raw content here.
app.post(
"/api/calendar/parse",
express.text({ type: "*/*", limit: "5mb" }),
(req, res) => {
try {
const events = ical.sync.parseICS(req.body);
res.json(extractEvents(events));
} catch (err) {
console.error("[calendar parse]", err.message);
res.status(400).json({ error: `Invalid ICS: ${err.message}` });
}
}
);
// ── Health ────────────────────────────────────────────────────────────────────
app.get("/api/health", (_req, res) => {
res.json({ ok: true, ts: new Date().toISOString(), version: "1.0.0" });
});
// ── ICS → normalised event list ───────────────────────────────────────────────
function extractEvents(icalData, horizonDays = 14) {
const now = new Date();
const horizon = new Date(now.getTime() + horizonDays * 86_400_000);
return Object.values(icalData)
.filter(e => e.type === "VEVENT")
.filter(e => {
if (!e.start) return false;
const start = new Date(e.start);
return start > now && start < horizon;
})
.filter(e => e.location) // only events with a location
.map(e => ({
id: String(e.uid || `ical-${Date.now()}-${Math.random()}`),
title: (e.summary || "Event").trim(),
destination: cleanLocation(String(e.location)),
eventTime: new Date(e.start).toISOString(),
source: "calendar",
}))
.sort((a, b) => new Date(a.eventTime) - new Date(b.eventTime));
}
// Strip verbose postal addresses down to something the station search can use.
// e.g. "Graz Hauptbahnhof, Europaplatz 5, 8020 Graz" → "Graz Hauptbahnhof"
function cleanLocation(loc) {
if (!loc) return "";
// Handle escaped newlines from some calendar apps
const firstLine = loc.split(/\\n|\r\n|\n/)[0].trim();
// If the first segment before a comma looks like a station/venue name, use it
const parts = firstLine.split(",").map(s => s.trim()).filter(Boolean);
// Return the first part if it exists; fall back to the whole string
return (parts[0] || firstLine).substring(0, 120);
}
// ── Start ─────────────────────────────────────────────────────────────────────
app.listen(PORT, () => {
console.log(`\n🚂 ÖBB Planner API → http://localhost:${PORT}\n`);
console.log(" Endpoints:");
console.log(` POST /api/hafas ÖBB HAFAS proxy`);
console.log(` GET /api/calendar?url=<ics_url> Remote ICS calendar`);
console.log(` POST /api/calendar/parse Upload raw ICS`);
console.log(` GET /api/health\n`);
});
-1
View File
@@ -1 +0,0 @@
../baseline-browser-mapping/dist/cli.cjs
-1
View File
@@ -1 +0,0 @@
../browserslist/cli.js
-1
View File
@@ -1 +0,0 @@
../esprima/bin/esparse.js
-1
View File
@@ -1 +0,0 @@
../esprima/bin/esvalidate.js
-1
View File
@@ -1 +0,0 @@
../glob/dist/esm/bin.mjs
-1
View File
@@ -1 +0,0 @@
../import-local/fixtures/cli.js
-1
View File
@@ -1 +0,0 @@
../jest/bin/jest.js
-1
View File
@@ -1 +0,0 @@
../js-yaml/bin/js-yaml.js
-1
View File
@@ -1 +0,0 @@
../jsesc/bin/jsesc
-1
View File
@@ -1 +0,0 @@
../json5/lib/cli.js
-1
View File
@@ -1 +0,0 @@
../mime/cli.js
-1
View File
@@ -1 +0,0 @@
../napi-postinstall/lib/cli.js
-1
View File
@@ -1 +0,0 @@
../which/bin/node-which
-1
View File
@@ -1 +0,0 @@
../@babel/parser/bin/babel-parser.js
-1
View File
@@ -1 +0,0 @@
../semver/bin/semver.js
-1
View File
@@ -1 +0,0 @@
../update-browserslist-db/cli.js
-1
View File
@@ -1 +0,0 @@
../uuid/dist/bin/uuid
-5254
View File
File diff suppressed because it is too large Load Diff
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/code-frame
> Generate errors that contain a code frame that point to source locations.
See our website [@babel/code-frame](https://babeljs.io/docs/babel-code-frame) for more information.
## Install
Using npm:
```sh
npm install --save-dev @babel/code-frame
```
or using yarn:
```sh
yarn add @babel/code-frame --dev
```
-217
View File
@@ -1,217 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var picocolors = require('picocolors');
var jsTokens = require('js-tokens');
var helperValidatorIdentifier = require('@babel/helper-validator-identifier');
function isColorSupported() {
return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported
);
}
const compose = (f, g) => v => f(g(v));
function buildDefs(colors) {
return {
keyword: colors.cyan,
capitalized: colors.yellow,
jsxIdentifier: colors.yellow,
punctuator: colors.yellow,
number: colors.magenta,
string: colors.green,
regex: colors.magenta,
comment: colors.gray,
invalid: compose(compose(colors.white, colors.bgRed), colors.bold),
gutter: colors.gray,
marker: compose(colors.red, colors.bold),
message: compose(colors.red, colors.bold),
reset: colors.reset
};
}
const defsOn = buildDefs(picocolors.createColors(true));
const defsOff = buildDefs(picocolors.createColors(false));
function getDefs(enabled) {
return enabled ? defsOn : defsOff;
}
const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]);
const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/;
const BRACKET = /^[()[\]{}]$/;
let tokenize;
const JSX_TAG = /^[a-z][\w-]*$/i;
const getTokenType = function (token, offset, text) {
if (token.type === "name") {
const tokenValue = token.value;
if (helperValidatorIdentifier.isKeyword(tokenValue) || helperValidatorIdentifier.isStrictReservedWord(tokenValue, true) || sometimesKeywords.has(tokenValue)) {
return "keyword";
}
if (JSX_TAG.test(tokenValue) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === "</")) {
return "jsxIdentifier";
}
const firstChar = String.fromCodePoint(tokenValue.codePointAt(0));
if (firstChar !== firstChar.toLowerCase()) {
return "capitalized";
}
}
if (token.type === "punctuator" && BRACKET.test(token.value)) {
return "bracket";
}
if (token.type === "invalid" && (token.value === "@" || token.value === "#")) {
return "punctuator";
}
return token.type;
};
tokenize = function* (text) {
let match;
while (match = jsTokens.default.exec(text)) {
const token = jsTokens.matchToToken(match);
yield {
type: getTokenType(token, match.index, text),
value: token.value
};
}
};
function highlight(text) {
if (text === "") return "";
const defs = getDefs(true);
let highlighted = "";
for (const {
type,
value
} of tokenize(text)) {
if (type in defs) {
highlighted += value.split(NEWLINE$1).map(str => defs[type](str)).join("\n");
} else {
highlighted += value;
}
}
return highlighted;
}
let deprecationWarningShown = false;
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function getMarkerLines(loc, source, opts, startLineBaseZero) {
const startLoc = Object.assign({
column: 0,
line: -1
}, loc.start);
const endLoc = Object.assign({}, startLoc, loc.end);
const {
linesAbove = 2,
linesBelow = 3
} = opts || {};
const startLine = startLoc.line - startLineBaseZero;
const startColumn = startLoc.column;
const endLine = endLoc.line - startLineBaseZero;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) {
start = 0;
}
if (endLine === -1) {
end = source.length;
}
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) {
for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) {
markerLines[lineNumber] = true;
} else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) {
markerLines[lineNumber] = [0, endColumn];
} else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
} else {
if (startColumn === endColumn) {
if (startColumn) {
markerLines[startLine] = [startColumn, 0];
} else {
markerLines[startLine] = true;
}
} else {
markerLines[startLine] = [startColumn, endColumn - startColumn];
}
}
return {
start,
end,
markerLines
};
}
function codeFrameColumns(rawLines, loc, opts = {}) {
const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode;
const startLineBaseZero = (opts.startLine || 1) - 1;
const defs = getDefs(shouldHighlight);
const lines = rawLines.split(NEWLINE);
const {
start,
end,
markerLines
} = getMarkerLines(loc, lines, opts, startLineBaseZero);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end + startLineBaseZero).length;
const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines;
let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number + startLineBaseZero}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join("");
if (lastMarkerLine && opts.message) {
markerLine += " " + defs.message(opts.message);
}
}
return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join("");
} else {
return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`;
}
}).join("\n");
if (opts.message && !hasColumns) {
frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
}
if (shouldHighlight) {
return defs.reset(frame);
} else {
return frame;
}
}
function index (rawLines, lineNumber, colNumber, opts = {}) {
if (!deprecationWarningShown) {
deprecationWarningShown = true;
const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`.";
if (process.emitWarning) {
process.emitWarning(message, "DeprecationWarning");
} else {
const deprecationError = new Error(message);
deprecationError.name = "DeprecationWarning";
console.warn(new Error(message));
}
}
colNumber = Math.max(colNumber, 0);
const location = {
start: {
column: colNumber,
line: lineNumber
}
};
return codeFrameColumns(rawLines, location, opts);
}
exports.codeFrameColumns = codeFrameColumns;
exports.default = index;
exports.highlight = highlight;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
-32
View File
@@ -1,32 +0,0 @@
{
"name": "@babel/code-frame",
"version": "7.29.0",
"description": "Generate errors that contain a code frame that point to source locations.",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-code-frame",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-code-frame"
},
"main": "./lib/index.js",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"devDependencies": {
"charcodes": "^0.2.0",
"import-meta-resolve": "^4.1.0",
"strip-ansi": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/compat-data
> The compat-data to determine required Babel plugins
See our website [@babel/compat-data](https://babeljs.io/docs/babel-compat-data) for more information.
## Install
Using npm:
```sh
npm install --save @babel/compat-data
```
or using yarn:
```sh
yarn add @babel/compat-data
```
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file as Babel 8 drop support of core-js 2
module.exports = require("./data/corejs2-built-ins.json");
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file now that it is included in babel-plugin-polyfill-corejs3
module.exports = require("./data/corejs3-shipped-proposals.json");
File diff suppressed because it is too large Load Diff
@@ -1,5 +0,0 @@
[
"esnext.promise.all-settled",
"esnext.string.match-all",
"esnext.global-this"
]
-18
View File
@@ -1,18 +0,0 @@
{
"es6.module": {
"chrome": "61",
"and_chr": "61",
"edge": "16",
"firefox": "60",
"and_ff": "60",
"node": "13.2.0",
"opera": "48",
"op_mob": "45",
"safari": "10.1",
"ios": "10.3",
"samsung": "8.2",
"android": "61",
"electron": "2.0",
"ios_saf": "10.3"
}
}
-38
View File
@@ -1,38 +0,0 @@
{
"transform-async-to-generator": [
"bugfix/transform-async-arrows-in-class"
],
"transform-parameters": [
"bugfix/transform-edge-default-parameters",
"bugfix/transform-safari-id-destructuring-collision-in-function-expression"
],
"transform-function-name": [
"bugfix/transform-edge-function-name"
],
"transform-block-scoping": [
"bugfix/transform-safari-block-shadowing",
"bugfix/transform-safari-for-shadowing"
],
"transform-destructuring": [
"bugfix/transform-safari-rest-destructuring-rhs-array"
],
"transform-template-literals": [
"bugfix/transform-tagged-template-caching"
],
"transform-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"proposal-optional-chaining": [
"bugfix/transform-v8-spread-parameters-in-optional-chaining"
],
"transform-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
],
"proposal-class-properties": [
"bugfix/transform-v8-static-class-fields-redefine-readonly",
"bugfix/transform-firefox-class-in-computed-class-key",
"bugfix/transform-safari-class-field-initializer-scope"
]
}
-231
View File
@@ -1,231 +0,0 @@
{
"bugfix/transform-async-arrows-in-class": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"bugfix/transform-edge-default-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-edge-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.9",
"opera_mobile": "41",
"electron": "1.2"
},
"bugfix/transform-safari-block-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "44",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-for-shadowing": {
"chrome": "49",
"opera": "36",
"edge": "12",
"firefox": "4",
"safari": "11",
"node": "6",
"deno": "1",
"ie": "11",
"ios": "11",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-id-destructuring-collision-in-function-expression": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "2",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-safari-rest-destructuring-rhs-array": {
"chrome": "49",
"opera": "36",
"edge": "14",
"firefox": "34",
"safari": "14.1",
"node": "6",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"bugfix/transform-tagged-template-caching": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.7.14",
"opera_mobile": "28",
"electron": "0.21"
},
"bugfix/transform-v8-spread-parameters-in-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-optional-chaining": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "74",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "15",
"firefox": "52",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "10.1",
"node": "7.6",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
}
}
-843
View File
@@ -1,843 +0,0 @@
{
"transform-explicit-resource-management": {
"chrome": "141",
"edge": "141",
"firefox": "141",
"node": "25",
"electron": "39.0"
},
"transform-duplicate-named-capturing-groups-regex": {
"chrome": "126",
"opera": "112",
"edge": "126",
"firefox": "129",
"safari": "17.4",
"node": "23",
"ios": "17.4",
"rhino": "1.9",
"electron": "31.0"
},
"transform-regexp-modifiers": {
"chrome": "125",
"opera": "111",
"edge": "125",
"firefox": "132",
"node": "23",
"samsung": "27",
"electron": "31.0"
},
"transform-unicode-sets-regex": {
"chrome": "112",
"opera": "98",
"edge": "112",
"firefox": "116",
"safari": "17",
"node": "20",
"deno": "1.32",
"ios": "17",
"samsung": "23",
"opera_mobile": "75",
"electron": "24.0"
},
"bugfix/transform-v8-static-class-fields-redefine-readonly": {
"chrome": "98",
"opera": "84",
"edge": "98",
"firefox": "75",
"safari": "15",
"node": "12",
"deno": "1.18",
"ios": "15",
"samsung": "11",
"opera_mobile": "52",
"electron": "17.0"
},
"bugfix/transform-firefox-class-in-computed-class-key": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "126",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"bugfix/transform-safari-class-field-initializer-scope": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "69",
"safari": "16",
"node": "12",
"deno": "1",
"ios": "16",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"proposal-class-static-block": {
"chrome": "94",
"opera": "80",
"edge": "94",
"firefox": "93",
"safari": "16.4",
"node": "16.11",
"deno": "1.14",
"ios": "16.4",
"samsung": "17",
"opera_mobile": "66",
"electron": "15.0"
},
"transform-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-private-property-in-object": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "90",
"safari": "15",
"node": "16.9",
"deno": "1.9",
"ios": "15",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"proposal-class-properties": {
"chrome": "74",
"opera": "62",
"edge": "79",
"firefox": "90",
"safari": "14.1",
"node": "12",
"deno": "1",
"ios": "14.5",
"samsung": "11",
"opera_mobile": "53",
"electron": "6.0"
},
"transform-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-private-methods": {
"chrome": "84",
"opera": "70",
"edge": "84",
"firefox": "90",
"safari": "15",
"node": "14.6",
"deno": "1",
"ios": "15",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"proposal-numeric-separator": {
"chrome": "75",
"opera": "62",
"edge": "79",
"firefox": "70",
"safari": "13",
"node": "12.5",
"deno": "1",
"ios": "13",
"samsung": "11",
"rhino": "1.7.14",
"opera_mobile": "54",
"electron": "6.0"
},
"transform-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"proposal-logical-assignment-operators": {
"chrome": "85",
"opera": "71",
"edge": "85",
"firefox": "79",
"safari": "14",
"node": "15",
"deno": "1.2",
"ios": "14",
"samsung": "14",
"opera_mobile": "60",
"electron": "10.0"
},
"transform-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"proposal-nullish-coalescing-operator": {
"chrome": "80",
"opera": "67",
"edge": "80",
"firefox": "72",
"safari": "13.1",
"node": "14",
"deno": "1",
"ios": "13.4",
"samsung": "13",
"rhino": "1.8",
"opera_mobile": "57",
"electron": "8.0"
},
"transform-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"proposal-optional-chaining": {
"chrome": "91",
"opera": "77",
"edge": "91",
"firefox": "74",
"safari": "13.1",
"node": "16.9",
"deno": "1.9",
"ios": "13.4",
"samsung": "16",
"opera_mobile": "64",
"electron": "13.0"
},
"transform-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-json-strings": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "62",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "9",
"rhino": "1.7.14",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-optional-catch-binding": {
"chrome": "66",
"opera": "53",
"edge": "79",
"firefox": "58",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-parameters": {
"chrome": "49",
"opera": "36",
"edge": "18",
"firefox": "52",
"safari": "16.3",
"node": "6",
"deno": "1",
"ios": "16.3",
"samsung": "5",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"proposal-async-generator-functions": {
"chrome": "63",
"opera": "50",
"edge": "79",
"firefox": "57",
"safari": "12",
"node": "10",
"deno": "1",
"ios": "12",
"samsung": "8",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"proposal-object-rest-spread": {
"chrome": "60",
"opera": "47",
"edge": "79",
"firefox": "55",
"safari": "11.1",
"node": "8.3",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"opera_mobile": "44",
"electron": "2.0"
},
"transform-dotall-regex": {
"chrome": "62",
"opera": "49",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "8.10",
"deno": "1",
"ios": "11.3",
"samsung": "8",
"rhino": "1.7.15",
"opera_mobile": "46",
"electron": "3.0"
},
"transform-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"proposal-unicode-property-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-named-capturing-groups-regex": {
"chrome": "64",
"opera": "51",
"edge": "79",
"firefox": "78",
"safari": "11.1",
"node": "10",
"deno": "1",
"ios": "11.3",
"samsung": "9",
"rhino": "1.9",
"opera_mobile": "47",
"electron": "3.0"
},
"transform-async-to-generator": {
"chrome": "55",
"opera": "42",
"edge": "15",
"firefox": "52",
"safari": "11",
"node": "7.6",
"deno": "1",
"ios": "11",
"samsung": "6",
"opera_mobile": "42",
"electron": "1.6"
},
"transform-exponentiation-operator": {
"chrome": "52",
"opera": "39",
"edge": "14",
"firefox": "52",
"safari": "10.1",
"node": "7",
"deno": "1",
"ios": "10.3",
"samsung": "6",
"rhino": "1.7.14",
"opera_mobile": "41",
"electron": "1.3"
},
"transform-template-literals": {
"chrome": "41",
"opera": "28",
"edge": "13",
"firefox": "34",
"safari": "13",
"node": "4",
"deno": "1",
"ios": "13",
"samsung": "3.4",
"rhino": "1.9",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-literals": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-function-name": {
"chrome": "51",
"opera": "38",
"edge": "79",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-arrow-functions": {
"chrome": "47",
"opera": "34",
"edge": "13",
"firefox": "43",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.13",
"opera_mobile": "34",
"electron": "0.36"
},
"transform-block-scoped-functions": {
"chrome": "41",
"opera": "28",
"edge": "12",
"firefox": "46",
"safari": "10",
"node": "4",
"deno": "1",
"ie": "11",
"ios": "10",
"samsung": "3.4",
"opera_mobile": "28",
"electron": "0.21"
},
"transform-classes": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-object-super": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-shorthand-properties": {
"chrome": "43",
"opera": "30",
"edge": "12",
"firefox": "33",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.14",
"opera_mobile": "30",
"electron": "0.27"
},
"transform-duplicate-keys": {
"chrome": "42",
"opera": "29",
"edge": "12",
"firefox": "34",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "3.4",
"opera_mobile": "29",
"electron": "0.25"
},
"transform-computed-properties": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "34",
"safari": "7.1",
"node": "4",
"deno": "1",
"ios": "8",
"samsung": "4",
"rhino": "1.8",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-for-of": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "10",
"node": "6.5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-sticky-regex": {
"chrome": "49",
"opera": "36",
"edge": "13",
"firefox": "3",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"rhino": "1.7.15",
"opera_mobile": "36",
"electron": "0.37"
},
"transform-unicode-escapes": {
"chrome": "44",
"opera": "31",
"edge": "12",
"firefox": "53",
"safari": "9",
"node": "4",
"deno": "1",
"ios": "9",
"samsung": "4",
"rhino": "1.7.15",
"opera_mobile": "32",
"electron": "0.30"
},
"transform-unicode-regex": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "46",
"safari": "12",
"node": "6",
"deno": "1",
"ios": "12",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-spread": {
"chrome": "46",
"opera": "33",
"edge": "13",
"firefox": "45",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-destructuring": {
"chrome": "51",
"opera": "38",
"edge": "15",
"firefox": "53",
"safari": "14.1",
"node": "6.5",
"deno": "1",
"ios": "14.5",
"samsung": "5",
"opera_mobile": "41",
"electron": "1.2"
},
"transform-block-scoping": {
"chrome": "50",
"opera": "37",
"edge": "14",
"firefox": "53",
"safari": "11",
"node": "6",
"deno": "1",
"ios": "11",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-typeof-symbol": {
"chrome": "48",
"opera": "35",
"edge": "12",
"firefox": "36",
"safari": "9",
"node": "6",
"deno": "1",
"ios": "9",
"samsung": "5",
"rhino": "1.8",
"opera_mobile": "35",
"electron": "0.37"
},
"transform-new-target": {
"chrome": "46",
"opera": "33",
"edge": "14",
"firefox": "41",
"safari": "10",
"node": "5",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "33",
"electron": "0.36"
},
"transform-regenerator": {
"chrome": "50",
"opera": "37",
"edge": "13",
"firefox": "53",
"safari": "10",
"node": "6",
"deno": "1",
"ios": "10",
"samsung": "5",
"opera_mobile": "37",
"electron": "1.1"
},
"transform-member-expression-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-property-literals": {
"chrome": "7",
"opera": "12",
"edge": "12",
"firefox": "2",
"safari": "5.1",
"node": "0.4",
"deno": "1",
"ie": "9",
"android": "4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "12",
"electron": "0.20"
},
"transform-reserved-words": {
"chrome": "13",
"opera": "10.50",
"edge": "12",
"firefox": "2",
"safari": "3.1",
"node": "0.6",
"deno": "1",
"ie": "9",
"android": "4.4",
"ios": "6",
"phantom": "1.9",
"samsung": "1",
"rhino": "1.7.13",
"opera_mobile": "10.1",
"electron": "0.20"
},
"transform-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
},
"proposal-export-namespace-from": {
"chrome": "72",
"deno": "1.0",
"edge": "79",
"firefox": "80",
"node": "13.2.0",
"opera": "60",
"opera_mobile": "51",
"safari": "14.1",
"ios": "14.5",
"samsung": "11.0",
"android": "72",
"electron": "5.0"
}
}
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/native-modules.json");
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/overlapping-plugins.json");
-40
View File
@@ -1,40 +0,0 @@
{
"name": "@babel/compat-data",
"version": "7.29.3",
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"description": "The compat-data to determine required Babel plugins",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-compat-data"
},
"publishConfig": {
"access": "public"
},
"exports": {
"./plugins": "./plugins.js",
"./native-modules": "./native-modules.js",
"./corejs2-built-ins": "./corejs2-built-ins.js",
"./corejs3-shipped-proposals": "./corejs3-shipped-proposals.js",
"./overlapping-plugins": "./overlapping-plugins.js",
"./plugin-bugfixes": "./plugin-bugfixes.js"
},
"scripts": {
"build-data": "./scripts/download-compat-table.sh && node ./scripts/build-data.mjs && node ./scripts/build-modules-support.mjs && node ./scripts/build-bugfixes-targets.mjs"
},
"keywords": [
"babel",
"compat-table",
"compat-data"
],
"devDependencies": {
"@mdn/browser-compat-data": "^6.0.8",
"core-js-compat": "^3.48.0",
"electron-to-chromium": "^1.5.278"
},
"engines": {
"node": ">=6.9.0"
},
"type": "commonjs"
}
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugin-bugfixes.json");
-2
View File
@@ -1,2 +0,0 @@
// Todo (Babel 8): remove this file, in Babel 8 users import the .json directly
module.exports = require("./data/plugins.json");
-22
View File
@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-19
View File
@@ -1,19 +0,0 @@
# @babel/core
> Babel compiler core.
See our website [@babel/core](https://babeljs.io/docs/babel-core) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20core%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/core
```
or using yarn:
```sh
yarn add @babel/core --dev
```
-5
View File
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=cache-contexts.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../src/config/cache-contexts.ts"],"sourcesContent":["import type { ConfigContext } from \"./config-chain.ts\";\nimport type {\n CallerMetadata,\n TargetsListOrObject,\n} from \"./validation/options.ts\";\n\nexport type { ConfigContext as FullConfig };\n\nexport type FullPreset = {\n targets: TargetsListOrObject;\n} & ConfigContext;\nexport type FullPlugin = {\n assumptions: Record<string, boolean>;\n} & FullPreset;\n\n// Context not including filename since it is used in places that cannot\n// process 'ignore'/'only' and other filename-based logic.\nexport type SimpleConfig = {\n envName: string;\n caller: CallerMetadata | undefined;\n};\nexport type SimplePreset = {\n targets: TargetsListOrObject;\n} & SimpleConfig;\nexport type SimplePlugin = {\n assumptions: Record<string, boolean>;\n} & SimplePreset;\n"],"mappings":"","ignoreList":[]}
-261
View File
@@ -1,261 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.assertSimpleType = assertSimpleType;
exports.makeStrongCache = makeStrongCache;
exports.makeStrongCacheSync = makeStrongCacheSync;
exports.makeWeakCache = makeWeakCache;
exports.makeWeakCacheSync = makeWeakCacheSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
const synchronize = gen => {
return _gensync()(gen).sync;
};
function* genTrue() {
return true;
}
function makeWeakCache(handler) {
return makeCachedFunction(WeakMap, handler);
}
function makeWeakCacheSync(handler) {
return synchronize(makeWeakCache(handler));
}
function makeStrongCache(handler) {
return makeCachedFunction(Map, handler);
}
function makeStrongCacheSync(handler) {
return synchronize(makeStrongCache(handler));
}
function makeCachedFunction(CallCache, handler) {
const callCacheSync = new CallCache();
const callCacheAsync = new CallCache();
const futureCache = new CallCache();
return function* cachedFunction(arg, data) {
const asyncContext = yield* (0, _async.isAsync)();
const callCache = asyncContext ? callCacheAsync : callCacheSync;
const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data);
if (cached.valid) return cached.value;
const cache = new CacheConfigurator(data);
const handlerResult = handler(arg, cache);
let finishLock;
let value;
if ((0, _util.isIterableIterator)(handlerResult)) {
value = yield* (0, _async.onFirstPause)(handlerResult, () => {
finishLock = setupAsyncLocks(cache, futureCache, arg);
});
} else {
value = handlerResult;
}
updateFunctionCache(callCache, cache, arg, value);
if (finishLock) {
futureCache.delete(arg);
finishLock.release(value);
}
return value;
};
}
function* getCachedValue(cache, arg, data) {
const cachedValue = cache.get(arg);
if (cachedValue) {
for (const {
value,
valid
} of cachedValue) {
if (yield* valid(data)) return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) {
const cached = yield* getCachedValue(callCache, arg, data);
if (cached.valid) {
return cached;
}
if (asyncContext) {
const cached = yield* getCachedValue(futureCache, arg, data);
if (cached.valid) {
const value = yield* (0, _async.waitFor)(cached.value.promise);
return {
valid: true,
value
};
}
}
return {
valid: false,
value: null
};
}
function setupAsyncLocks(config, futureCache, arg) {
const finishLock = new Lock();
updateFunctionCache(futureCache, config, arg, finishLock);
return finishLock;
}
function updateFunctionCache(cache, config, arg, value) {
if (!config.configured()) config.forever();
let cachedValue = cache.get(arg);
config.deactivate();
switch (config.mode()) {
case "forever":
cachedValue = [{
value,
valid: genTrue
}];
cache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push({
value,
valid: config.validator()
});
} else {
cachedValue = [{
value,
valid: config.validator()
}];
cache.set(arg, cachedValue);
}
}
}
class CacheConfigurator {
constructor(data) {
this._active = true;
this._never = false;
this._forever = false;
this._invalidate = false;
this._configured = false;
this._pairs = [];
this._data = void 0;
this._data = data;
}
simple() {
return makeSimpleConfigurator(this);
}
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using(handler) {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error("Caching has already been configured with .never or .forever()");
}
this._configured = true;
const key = handler(this._data);
const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`);
if ((0, _async.isThenable)(key)) {
return key.then(key => {
this._pairs.push([key, fn]);
return key;
});
}
this._pairs.push([key, fn]);
return key;
}
invalidate(handler) {
this._invalidate = true;
return this.using(handler);
}
validator() {
const pairs = this._pairs;
return function* (data) {
for (const [key, fn] of pairs) {
if (key !== (yield* fn(data))) return false;
}
return true;
};
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(cache) {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();else cache.never();
return;
}
return cache.using(() => assertSimpleType(val()));
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => assertSimpleType(cb()));
cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb()));
return cacheFn;
}
function assertSimpleType(value) {
if ((0, _async.isThenable)(value)) {
throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`);
}
if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") {
throw new Error("Cache keys must be either string, boolean, number, null, or undefined.");
}
return value;
}
class Lock {
constructor() {
this.released = false;
this.promise = void 0;
this._resolve = void 0;
this.promise = new Promise(resolve => {
this._resolve = resolve;
});
}
release(value) {
this.released = true;
this._resolve(value);
}
}
0 && 0;
//# sourceMappingURL=caching.js.map
File diff suppressed because one or more lines are too long
-469
View File
@@ -1,469 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildPresetChain = buildPresetChain;
exports.buildPresetChainWalker = void 0;
exports.buildRootChain = buildRootChain;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _options = require("./validation/options.js");
var _patternToRegex = require("./pattern-to-regex.js");
var _printer = require("./printer.js");
var _rewriteStackTrace = require("../errors/rewrite-stack-trace.js");
var _configError = require("../errors/config-error.js");
var _index = require("./files/index.js");
var _caching = require("./caching.js");
var _configDescriptors = require("./config-descriptors.js");
const debug = _debug()("babel:config:config-chain");
function* buildPresetChain(arg, context) {
const chain = yield* buildPresetChainWalker(arg, context);
if (!chain) return null;
return {
plugins: dedupDescriptors(chain.plugins),
presets: dedupDescriptors(chain.presets),
options: chain.options.map(o => createConfigChainOptions(o)),
files: new Set()
};
}
const buildPresetChainWalker = exports.buildPresetChainWalker = makeChainWalker({
root: preset => loadPresetDescriptors(preset),
env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
overridesEnv: (preset, index, envName) => loadPresetOverridesEnvDescriptors(preset)(index)(envName),
createLogger: () => () => {}
});
const loadPresetDescriptors = (0, _caching.makeWeakCacheSync)(preset => buildRootDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors));
const loadPresetEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, envName)));
const loadPresetOverridesDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index)));
const loadPresetOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index, envName))));
function* buildRootChain(opts, context) {
let configReport, babelRcReport;
const programmaticLogger = new _printer.ConfigPrinter();
const programmaticChain = yield* loadProgrammaticChain({
options: opts,
dirname: context.cwd
}, context, undefined, programmaticLogger);
if (!programmaticChain) return null;
const programmaticReport = yield* programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* (0, _index.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller);
} else if (opts.configFile !== false) {
configFile = yield* (0, _index.findRootConfig)(context.root, context.envName, context.caller);
}
let {
babelrc,
babelrcRoots
} = opts;
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new _printer.ConfigPrinter();
if (configFile) {
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(validatedFile, context, undefined, configFileLogger);
if (!result) return null;
configReport = yield* configFileLogger.output();
if (babelrc === undefined) {
babelrc = validatedFile.options.babelrc;
}
if (babelrcRoots === undefined) {
babelrcRootsDirectory = validatedFile.dirname;
babelrcRoots = validatedFile.options.babelrcRoots;
}
mergeChain(configFileChain, result);
}
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
if ((babelrc === true || babelrc === undefined) && typeof context.filename === "string") {
const pkgData = yield* (0, _index.findPackageData)(context.filename);
if (pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) {
({
ignore: ignoreFile,
config: babelrcFile
} = yield* (0, _index.findRelativeConfig)(pkgData, context.envName, context.caller));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (ignoreFile && shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new _printer.ConfigPrinter();
const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
if (context.showConfig) {
console.log(`Babel configs on "${context.filename}" (ascending priority):\n` + [configReport, babelRcReport, programmaticReport].filter(x => !!x).join("\n\n") + "\n-----End Babel configs-----");
}
const chain = mergeChain(mergeChain(mergeChain(emptyChain(), configFileChain), fileChain), programmaticChain);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => createConfigChainOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files
};
}
function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) {
if (typeof babelrcRoots === "boolean") return babelrcRoots;
const absoluteRoot = context.root;
if (babelrcRoots === undefined) {
return pkgData.directories.includes(absoluteRoot);
}
let babelrcPatterns = babelrcRoots;
if (!Array.isArray(babelrcPatterns)) {
babelrcPatterns = [babelrcPatterns];
}
babelrcPatterns = babelrcPatterns.map(pat => {
return typeof pat === "string" ? _path().resolve(babelrcRootsDirectory, pat) : pat;
});
if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
return pkgData.directories.includes(absoluteRoot);
}
return babelrcPatterns.some(pat => {
if (typeof pat === "string") {
pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory);
}
return pkgData.directories.some(directory => {
return matchPattern(pat, babelrcRootsDirectory, directory, context);
});
});
}
const validateConfigFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("configfile", file.options, file.filepath)
}));
const validateBabelrcFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("babelrcfile", file.options, file.filepath)
}));
const validateExtendFile = (0, _caching.makeWeakCacheSync)(file => ({
filepath: file.filepath,
dirname: file.dirname,
options: (0, _options.validate)("extendsfile", file.options, file.filepath)
}));
const loadProgrammaticChain = makeChainWalker({
root: input => buildRootDescriptors(input, "base", _configDescriptors.createCachedDescriptors),
env: (input, envName) => buildEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, envName),
overrides: (input, index) => buildOverrideDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index),
overridesEnv: (input, index, envName) => buildOverrideEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index, envName),
createLogger: (input, context, baseLogger) => buildProgrammaticLogger(input, context, baseLogger)
});
const loadFileChainWalker = makeChainWalker({
root: file => loadFileDescriptors(file),
env: (file, envName) => loadFileEnvDescriptors(file)(envName),
overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
overridesEnv: (file, index, envName) => loadFileOverridesEnvDescriptors(file)(index)(envName),
createLogger: (file, context, baseLogger) => buildFileLogger(file.filepath, context, baseLogger)
});
function* loadFileChain(input, context, files, baseLogger) {
const chain = yield* loadFileChainWalker(input, context, files, baseLogger);
chain == null || chain.files.add(input.filepath);
return chain;
}
const loadFileDescriptors = (0, _caching.makeWeakCacheSync)(file => buildRootDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors));
const loadFileEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, envName)));
const loadFileOverridesDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index)));
const loadFileOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index, envName))));
function buildFileLogger(filepath, context, baseLogger) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Config, {
filepath
});
}
function buildRootDescriptors({
dirname,
options
}, alias, descriptors) {
return descriptors(dirname, options, alias);
}
function buildProgrammaticLogger(_, context, baseLogger) {
var _context$caller;
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Programmatic, {
callerName: (_context$caller = context.caller) == null ? void 0 : _context$caller.name
});
}
function buildEnvDescriptors({
dirname,
options
}, alias, descriptors, envName) {
var _options$env;
const opts = (_options$env = options.env) == null ? void 0 : _options$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null;
}
function buildOverrideDescriptors({
dirname,
options
}, alias, descriptors, index) {
var _options$overrides;
const opts = (_options$overrides = options.overrides) == null ? void 0 : _options$overrides[index];
if (!opts) throw new Error("Assertion failure - missing override");
return descriptors(dirname, opts, `${alias}.overrides[${index}]`);
}
function buildOverrideEnvDescriptors({
dirname,
options
}, alias, descriptors, index, envName) {
var _options$overrides2, _override$env;
const override = (_options$overrides2 = options.overrides) == null ? void 0 : _options$overrides2[index];
if (!override) throw new Error("Assertion failure - missing override");
const opts = (_override$env = override.env) == null ? void 0 : _override$env[envName];
return opts ? descriptors(dirname, opts, `${alias}.overrides[${index}].env["${envName}"]`) : null;
}
function makeChainWalker({
root,
env,
overrides,
overridesEnv,
createLogger
}) {
return function* chainWalker(input, context, files = new Set(), baseLogger) {
const {
dirname
} = input;
const flattenedConfigs = [];
const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined
});
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
index: undefined
});
}
(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideOps,
index,
envName: undefined
});
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (overrideEnvOpts && configIsApplicable(overrideEnvOpts, dirname, context, input.filepath)) {
flattenedConfigs.push({
config: overrideEnvOpts,
index,
envName: context.envName
});
}
}
});
}
if (flattenedConfigs.some(({
config: {
options: {
ignore,
only
}
}
}) => shouldIgnore(context, ignore, only, dirname))) {
return null;
}
const chain = emptyChain();
const logger = createLogger(input, context, baseLogger);
for (const {
config,
index,
envName
} of flattenedConfigs) {
if (!(yield* mergeExtendsChain(chain, config.options, dirname, context, files, baseLogger))) {
return null;
}
logger(config, index, envName);
yield* mergeChainOpts(chain, config);
}
return chain;
};
}
function* mergeExtendsChain(chain, opts, dirname, context, files, baseLogger) {
if (opts.extends === undefined) return true;
const file = yield* (0, _index.loadConfig)(opts.extends, dirname, context.envName, context.caller);
if (files.has(file)) {
throw new Error(`Configuration cycle detected loading ${file.filepath}.\n` + `File already loaded following the config chain:\n` + Array.from(files, file => ` - ${file.filepath}`).join("\n"));
}
files.add(file);
const fileChain = yield* loadFileChain(validateExtendFile(file), context, files, baseLogger);
files.delete(file);
if (!fileChain) return false;
mergeChain(chain, fileChain);
return true;
}
function mergeChain(target, source) {
target.options.push(...source.options);
target.plugins.push(...source.plugins);
target.presets.push(...source.presets);
for (const file of source.files) {
target.files.add(file);
}
return target;
}
function* mergeChainOpts(target, {
options,
plugins,
presets
}) {
target.options.push(options);
target.plugins.push(...(yield* plugins()));
target.presets.push(...(yield* presets()));
return target;
}
function emptyChain() {
return {
options: [],
presets: [],
plugins: [],
files: new Set()
};
}
function createConfigChainOptions(opts) {
const options = Object.assign({}, opts);
delete options.extends;
delete options.env;
delete options.overrides;
delete options.plugins;
delete options.presets;
delete options.passPerPreset;
delete options.ignore;
delete options.only;
delete options.test;
delete options.include;
delete options.exclude;
if (hasOwnProperty.call(options, "sourceMap")) {
options.sourceMaps = options.sourceMap;
delete options.sourceMap;
}
return options;
}
function dedupDescriptors(items) {
const map = new Map();
const descriptors = [];
for (const item of items) {
if (typeof item.value === "function") {
const fnKey = item.value;
let nameMap = map.get(fnKey);
if (!nameMap) {
nameMap = new Map();
map.set(fnKey, nameMap);
}
let desc = nameMap.get(item.name);
if (!desc) {
desc = {
value: item
};
descriptors.push(desc);
if (!item.ownPass) nameMap.set(item.name, desc);
} else {
desc.value = item;
}
} else {
descriptors.push({
value: item
});
}
}
return descriptors.reduce((acc, desc) => {
acc.push(desc.value);
return acc;
}, []);
}
function configIsApplicable({
options
}, dirname, context, configName) {
return (options.test === undefined || configFieldIsApplicable(context, options.test, dirname, configName)) && (options.include === undefined || configFieldIsApplicable(context, options.include, dirname, configName)) && (options.exclude === undefined || !configFieldIsApplicable(context, options.exclude, dirname, configName));
}
function configFieldIsApplicable(context, test, dirname, configName) {
const patterns = Array.isArray(test) ? test : [test];
return matchesPatterns(context, patterns, dirname, configName);
}
function ignoreListReplacer(_key, value) {
if (value instanceof RegExp) {
return String(value);
}
return value;
}
function shouldIgnore(context, ignore, only, dirname) {
if (ignore && matchesPatterns(context, ignore, dirname)) {
var _context$filename;
const message = `No config is applied to "${(_context$filename = context.filename) != null ? _context$filename : "(unknown)"}" because it matches one of \`ignore: ${JSON.stringify(ignore, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
var _context$filename2;
const message = `No config is applied to "${(_context$filename2 = context.filename) != null ? _context$filename2 : "(unknown)"}" because it fails to match one of \`only: ${JSON.stringify(only, ignoreListReplacer)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
return false;
}
function matchesPatterns(context, patterns, dirname, configName) {
return patterns.some(pattern => matchPattern(pattern, dirname, context.filename, context, configName));
}
function matchPattern(pattern, dirname, pathToTest, context, configName) {
if (typeof pattern === "function") {
return !!(0, _rewriteStackTrace.endHiddenCallStack)(pattern)(pathToTest, {
dirname,
envName: context.envName,
caller: context.caller
});
}
if (typeof pathToTest !== "string") {
throw new _configError.default(`Configuration contains string/RegExp pattern, but no filename was passed to Babel`, configName);
}
if (typeof pattern === "string") {
pattern = (0, _patternToRegex.default)(pattern, dirname);
}
return pattern.test(pathToTest);
}
0 && 0;
//# sourceMappingURL=config-chain.js.map
File diff suppressed because one or more lines are too long
-190
View File
@@ -1,190 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createCachedDescriptors = createCachedDescriptors;
exports.createDescriptor = createDescriptor;
exports.createUncachedDescriptors = createUncachedDescriptors;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _functional = require("../gensync-utils/functional.js");
var _index = require("./files/index.js");
var _item = require("./item.js");
var _caching = require("./caching.js");
var _resolveTargets = require("./resolve-targets.js");
function isEqualDescriptor(a, b) {
var _a$file, _b$file, _a$file2, _b$file2;
return a.name === b.name && a.value === b.value && a.options === b.options && a.dirname === b.dirname && a.alias === b.alias && a.ownPass === b.ownPass && ((_a$file = a.file) == null ? void 0 : _a$file.request) === ((_b$file = b.file) == null ? void 0 : _b$file.request) && ((_a$file2 = a.file) == null ? void 0 : _a$file2.resolved) === ((_b$file2 = b.file) == null ? void 0 : _b$file2.resolved);
}
function* handlerOf(value) {
return value;
}
function optionsWithResolvedBrowserslistConfigFile(options, dirname) {
if (typeof options.browserslistConfigFile === "string") {
options.browserslistConfigFile = (0, _resolveTargets.resolveBrowserslistConfigFile)(options.browserslistConfigFile, dirname);
}
return options;
}
function createCachedDescriptors(dirname, options, alias) {
const {
plugins,
presets,
passPerPreset
} = options;
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: plugins ? () => createCachedPluginDescriptors(plugins, dirname)(alias) : () => handlerOf([]),
presets: presets ? () => createCachedPresetDescriptors(presets, dirname)(alias)(!!passPerPreset) : () => handlerOf([])
};
}
function createUncachedDescriptors(dirname, options, alias) {
return {
options: optionsWithResolvedBrowserslistConfigFile(options, dirname),
plugins: (0, _functional.once)(() => createPluginDescriptors(options.plugins || [], dirname, alias)),
presets: (0, _functional.once)(() => createPresetDescriptors(options.presets || [], dirname, alias, !!options.passPerPreset))
};
}
const PRESET_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPresetDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCacheSync)(alias => (0, _caching.makeStrongCache)(function* (passPerPreset) {
const descriptors = yield* createPresetDescriptors(items, dirname, alias, passPerPreset);
return descriptors.map(desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc));
}));
});
const PLUGIN_DESCRIPTOR_CACHE = new WeakMap();
const createCachedPluginDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => {
const dirname = cache.using(dir => dir);
return (0, _caching.makeStrongCache)(function* (alias) {
const descriptors = yield* createPluginDescriptors(items, dirname, alias);
return descriptors.map(desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc));
});
});
const DEFAULT_OPTIONS = {};
function loadCachedDescriptor(cache, desc) {
const {
value,
options = DEFAULT_OPTIONS
} = desc;
if (options === false) return desc;
let cacheByOptions = cache.get(value);
if (!cacheByOptions) {
cacheByOptions = new WeakMap();
cache.set(value, cacheByOptions);
}
let possibilities = cacheByOptions.get(options);
if (!possibilities) {
possibilities = [];
cacheByOptions.set(options, possibilities);
}
if (!possibilities.includes(desc)) {
const matches = possibilities.filter(possibility => isEqualDescriptor(possibility, desc));
if (matches.length > 0) {
return matches[0];
}
possibilities.push(desc);
}
return desc;
}
function* createPresetDescriptors(items, dirname, alias, passPerPreset) {
return yield* createDescriptors("preset", items, dirname, alias, passPerPreset);
}
function* createPluginDescriptors(items, dirname, alias) {
return yield* createDescriptors("plugin", items, dirname, alias);
}
function* createDescriptors(type, items, dirname, alias, ownPass) {
const descriptors = yield* _gensync().all(items.map((item, index) => createDescriptor(item, dirname, {
type,
alias: `${alias}$${index}`,
ownPass: !!ownPass
})));
assertNoDuplicates(descriptors);
return descriptors;
}
function* createDescriptor(pair, dirname, {
type,
alias,
ownPass
}) {
const desc = (0, _item.getItemDescriptor)(pair);
if (desc) {
return desc;
}
let name;
let options;
let value = pair;
if (Array.isArray(value)) {
if (value.length === 3) {
[value, options, name] = value;
} else {
[value, options] = value;
}
}
let file = undefined;
let filepath = null;
if (typeof value === "string") {
if (typeof type !== "string") {
throw new Error("To resolve a string-based item, the type of item must be given");
}
const resolver = type === "plugin" ? _index.loadPlugin : _index.loadPreset;
const request = value;
({
filepath,
value
} = yield* resolver(value, dirname));
file = {
request,
resolved: filepath
};
}
if (!value) {
throw new Error(`Unexpected falsy value: ${String(value)}`);
}
if (typeof value === "object" && value.__esModule) {
if (value.default) {
value = value.default;
} else {
throw new Error("Must export a default export when using ES6 modules.");
}
}
if (typeof value !== "object" && typeof value !== "function") {
throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`);
}
if (filepath !== null && typeof value === "object" && value) {
throw new Error(`Plugin/Preset files are not allowed to export objects, only functions. In ${filepath}`);
}
return {
name,
alias: filepath || alias,
value,
options,
dirname,
ownPass,
file
};
}
function assertNoDuplicates(items) {
const map = new Map();
for (const item of items) {
if (typeof item.value !== "function") continue;
let nameMap = map.get(item.value);
if (!nameMap) {
nameMap = new Set();
map.set(item.value, nameMap);
}
if (nameMap.has(item.name)) {
const conflicts = items.filter(i => i.value === item.value);
throw new Error([`Duplicate plugin/preset detected.`, `If you'd like to use two separate instances of a plugin,`, `they need separate names, e.g.`, ``, ` plugins: [`, ` ['some-plugin', {}],`, ` ['some-plugin', {}, 'some unique name'],`, ` ]`, ``, `Duplicates detected are:`, `${JSON.stringify(conflicts, null, 2)}`].join("\n"));
}
nameMap.add(item.name);
}
}
0 && 0;
//# sourceMappingURL=config-descriptors.js.map
File diff suppressed because one or more lines are too long
-290
View File
@@ -1,290 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.resolveShowConfigPath = resolveShowConfigPath;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _json() {
const data = require("json5");
_json = function () {
return data;
};
return data;
}
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _caching = require("../caching.js");
var _configApi = require("../helpers/config-api.js");
var _utils = require("./utils.js");
var _moduleTypes = require("./module-types.js");
var _patternToRegex = require("../pattern-to-regex.js");
var _configError = require("../../errors/config-error.js");
var fs = require("../../gensync-utils/fs.js");
require("module");
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _async = require("../../gensync-utils/async.js");
const debug = _debug()("babel:config:loading:files:configuration");
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json", "babel.config.cts", "babel.config.ts", "babel.config.mts"];
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts"];
const BABELIGNORE_FILENAME = ".babelignore";
const runConfig = (0, _caching.makeWeakCache)(function* runConfig(options, cache) {
yield* [];
return {
options: (0, _rewriteStackTrace.endHiddenCallStack)(options)((0, _configApi.makeConfigAPI)(cache)),
cacheNeedsConfiguration: !cache.configured()
};
});
function* readConfigCode(filepath, data) {
if (!_fs().existsSync(filepath)) return null;
let options = yield* (0, _moduleTypes.default)(filepath, (yield* (0, _async.isAsync)()) ? "auto" : "require", "You appear to be using a native ECMAScript module configuration " + "file, which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", "You appear to be using a configuration file that contains top-level " + "await, which is only supported when running Babel asynchronously.");
let cacheNeedsConfiguration = false;
if (typeof options === "function") {
({
options,
cacheNeedsConfiguration
} = yield* runConfig(options, data));
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new _configError.default(`Configuration should be an exported JavaScript object.`, filepath);
}
if (typeof options.then === "function") {
options.catch == null || options.catch(() => {});
throw new _configError.default(`You appear to be using an async configuration, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously return your config.`, filepath);
}
if (cacheNeedsConfiguration) throwConfigError(filepath);
return buildConfigFileObject(options, filepath);
}
const cfboaf = new WeakMap();
function buildConfigFileObject(options, filepath) {
let configFilesByFilepath = cfboaf.get(options);
if (!configFilesByFilepath) {
cfboaf.set(options, configFilesByFilepath = new Map());
}
let configFile = configFilesByFilepath.get(filepath);
if (!configFile) {
configFile = {
filepath,
dirname: _path().dirname(filepath),
options
};
configFilesByFilepath.set(filepath, configFile);
}
return configFile;
}
const packageToBabelConfig = (0, _caching.makeWeakCacheSync)(file => {
const babel = file.options.babel;
if (babel === undefined) return null;
if (typeof babel !== "object" || Array.isArray(babel) || babel === null) {
throw new _configError.default(`.babel property must be an object`, file.filepath);
}
return {
filepath: file.filepath,
dirname: file.dirname,
options: babel
};
});
const readConfigJSON5 = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = _json().parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing config - ${err.message}`, filepath);
}
if (!options) throw new _configError.default(`No config detected`, filepath);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
delete options.$schema;
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
const readIgnoreConfig = (0, _utils.makeStaticFileCache)((filepath, content) => {
const ignoreDir = _path().dirname(filepath);
const ignorePatterns = content.split("\n").map(line => line.replace(/#.*$/, "").trim()).filter(Boolean);
for (const pattern of ignorePatterns) {
if (pattern.startsWith("!")) {
throw new _configError.default(`Negation of file paths is not supported.`, filepath);
}
}
return {
filepath,
dirname: _path().dirname(filepath),
ignore: ignorePatterns.map(pattern => (0, _patternToRegex.default)(pattern, ignoreDir))
};
});
function findConfigUpwards(rootDir) {
let dirname = rootDir;
for (;;) {
for (const filename of ROOT_CONFIG_FILENAMES) {
if (_fs().existsSync(_path().join(dirname, filename))) {
return dirname;
}
}
const nextDir = _path().dirname(dirname);
if (dirname === nextDir) break;
dirname = nextDir;
}
return null;
}
function* findRelativeConfig(packageData, envName, caller) {
let config = null;
let ignore = null;
const dirname = _path().dirname(packageData.filepath);
for (const loc of packageData.directories) {
if (!config) {
var _packageData$pkg;
config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null);
}
if (!ignore) {
const ignoreLoc = _path().join(loc, BABELIGNORE_FILENAME);
ignore = yield* readIgnoreConfig(ignoreLoc);
if (ignore) {
debug("Found ignore %o from %o.", ignore.filepath, dirname);
}
}
}
return {
config,
ignore
};
}
function findRootConfig(dirname, envName, caller) {
return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller);
}
function* loadOneConfig(names, dirname, envName, caller, previousConfig = null) {
const configs = yield* _gensync().all(names.map(filename => readConfig(_path().join(dirname, filename), envName, caller)));
const config = configs.reduce((previousConfig, config) => {
if (config && previousConfig) {
throw new _configError.default(`Multiple configuration files found. Please remove one:\n` + ` - ${_path().basename(previousConfig.filepath)}\n` + ` - ${config.filepath}\n` + `from ${dirname}`);
}
return config || previousConfig;
}, previousConfig);
if (config) {
debug("Found configuration %o from %o.", config.filepath, dirname);
}
return config;
}
function* loadConfig(name, dirname, envName, caller) {
const filepath = (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(name, {
paths: [dirname]
});
const conf = yield* readConfig(filepath, envName, caller);
if (!conf) {
throw new _configError.default(`Config file contains no configuration data`, filepath);
}
debug("Loaded config %o from %o.", name, dirname);
return conf;
}
function readConfig(filepath, envName, caller) {
const ext = _path().extname(filepath);
switch (ext) {
case ".js":
case ".cjs":
case ".mjs":
case ".ts":
case ".cts":
case ".mts":
return readConfigCode(filepath, {
envName,
caller
});
default:
return readConfigJSON5(filepath);
}
}
function* resolveShowConfigPath(dirname) {
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = _path().resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`);
}
return absolutePath;
}
return null;
}
function throwConfigError(filepath) {
throw new _configError.default(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
module.exports = function(api) {
// The API exposes the following:
// Cache the returned value forever and don't call this function again.
api.cache(true);
// Don't cache at all. Not recommended because it will be very slow.
api.cache(false);
// Cached based on the value of some function. If this function returns a value different from
// a previously-encountered value, the plugins will re-evaluate.
var env = api.cache(() => process.env.NODE_ENV);
// If testing for a specific env, we recommend specifics to avoid instantiating a plugin for
// any possible NODE_ENV value that might come up during plugin execution.
var isProd = api.cache(() => process.env.NODE_ENV === "production");
// .cache(fn) will perform a linear search though instances to find the matching plugin based
// based on previous instantiated plugins. If you want to recreate the plugin and discard the
// previous instance whenever something changes, you may use:
var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");
// Note, we also expose the following more-verbose versions of the above examples:
api.cache.forever(); // api.cache(true)
api.cache.never(); // api.cache(false)
api.cache.using(fn); // api.cache(fn)
// Return the value that will be cached.
return { };
};`, filepath);
}
0 && 0;
//# sourceMappingURL=configuration.js.map
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
module.exports = function import_(filepath) {
return import(filepath);
};
0 && 0;
//# sourceMappingURL=import.cjs.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["module","exports","import_","filepath"],"sources":["../../../src/config/files/import.cjs"],"sourcesContent":["// We keep this in a separate file so that in older node versions, where\n// import() isn't supported, we can try/catch around the require() call\n// when loading this file.\n\nmodule.exports = function import_(filepath) {\n return import(filepath);\n};\n"],"mappings":"AAIAA,MAAM,CAACC,OAAO,GAAG,SAASC,OAAOA,CAACC,QAAQ,EAAE;EAC1C,OAAO,OAAOA,QAAQ,CAAC;AACzB,CAAC;AAAC","ignoreList":[]}
-58
View File
@@ -1,58 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ROOT_CONFIG_FILENAMES = void 0;
exports.findConfigUpwards = findConfigUpwards;
exports.findPackageData = findPackageData;
exports.findRelativeConfig = findRelativeConfig;
exports.findRootConfig = findRootConfig;
exports.loadConfig = loadConfig;
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePlugin = resolvePlugin;
exports.resolvePreset = resolvePreset;
exports.resolveShowConfigPath = resolveShowConfigPath;
function findConfigUpwards(rootDir) {
return null;
}
function* findPackageData(filepath) {
return {
filepath,
directories: [],
pkg: null,
isPackage: false
};
}
function* findRelativeConfig(pkgData, envName, caller) {
return {
config: null,
ignore: null
};
}
function* findRootConfig(dirname, envName, caller) {
return null;
}
function* loadConfig(name, dirname, envName, caller) {
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}
function* resolveShowConfigPath(dirname) {
return null;
}
const ROOT_CONFIG_FILENAMES = exports.ROOT_CONFIG_FILENAMES = [];
function resolvePlugin(name, dirname) {
return null;
}
function resolvePreset(name, dirname) {
return null;
}
function loadPlugin(name, dirname) {
throw new Error(`Cannot load plugin ${name} relative to ${dirname} in a browser`);
}
function loadPreset(name, dirname) {
throw new Error(`Cannot load preset ${name} relative to ${dirname} in a browser`);
}
0 && 0;
//# sourceMappingURL=index-browser.js.map
@@ -1 +0,0 @@
{"version":3,"names":["findConfigUpwards","rootDir","findPackageData","filepath","directories","pkg","isPackage","findRelativeConfig","pkgData","envName","caller","config","ignore","findRootConfig","dirname","loadConfig","name","Error","resolveShowConfigPath","ROOT_CONFIG_FILENAMES","exports","resolvePlugin","resolvePreset","loadPlugin","loadPreset"],"sources":["../../../src/config/files/index-browser.ts"],"sourcesContent":["/* c8 ignore start */\n\nimport type { Handler } from \"gensync\";\n\nimport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\n\nimport type { CallerMetadata } from \"../validation/options.ts\";\n\nexport type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData };\n\nexport function findConfigUpwards(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n rootDir: string,\n): string | null {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n return {\n filepath,\n directories: [],\n pkg: null,\n isPackage: false,\n };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRelativeConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n pkgData: FilePackageData,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<RelativeConfig> {\n return { config: null, ignore: null };\n}\n\n// eslint-disable-next-line require-yield\nexport function* findRootConfig(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile | null> {\n return null;\n}\n\n// eslint-disable-next-line require-yield\nexport function* loadConfig(\n name: string,\n dirname: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n envName: string,\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n caller: CallerMetadata | undefined,\n): Handler<ConfigFile> {\n throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);\n}\n\n// eslint-disable-next-line require-yield\nexport function* resolveShowConfigPath(\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n dirname: string,\n): Handler<string | null> {\n return null;\n}\n\nexport const ROOT_CONFIG_FILENAMES: string[] = [];\n\ntype Resolved =\n | { loader: \"require\"; filepath: string }\n | { loader: \"import\"; filepath: string };\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePlugin(name: string, dirname: string): Resolved | null {\n return null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function resolvePreset(name: string, dirname: string): Resolved | null {\n return null;\n}\n\nexport function loadPlugin(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load plugin ${name} relative to ${dirname} in a browser`,\n );\n}\n\nexport function loadPreset(\n name: string,\n dirname: string,\n): Handler<{\n filepath: string;\n value: unknown;\n}> {\n throw new Error(\n `Cannot load preset ${name} relative to ${dirname} in a browser`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAeO,SAASA,iBAAiBA,CAE/BC,OAAe,EACA;EACf,OAAO,IAAI;AACb;AAGO,UAAUC,eAAeA,CAACC,QAAgB,EAA4B;EAC3E,OAAO;IACLA,QAAQ;IACRC,WAAW,EAAE,EAAE;IACfC,GAAG,EAAE,IAAI;IACTC,SAAS,EAAE;EACb,CAAC;AACH;AAGO,UAAUC,kBAAkBA,CAEjCC,OAAwB,EAExBC,OAAe,EAEfC,MAAkC,EACT;EACzB,OAAO;IAAEC,MAAM,EAAE,IAAI;IAAEC,MAAM,EAAE;EAAK,CAAC;AACvC;AAGO,UAAUC,cAAcA,CAE7BC,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACN;EAC5B,OAAO,IAAI;AACb;AAGO,UAAUK,UAAUA,CACzBC,IAAY,EACZF,OAAe,EAEfL,OAAe,EAEfC,MAAkC,EACb;EACrB,MAAM,IAAIO,KAAK,CAAC,eAAeD,IAAI,gBAAgBF,OAAO,eAAe,CAAC;AAC5E;AAGO,UAAUI,qBAAqBA,CAEpCJ,OAAe,EACS;EACxB,OAAO,IAAI;AACb;AAEO,MAAMK,qBAA+B,GAAAC,OAAA,CAAAD,qBAAA,GAAG,EAAE;AAO1C,SAASE,aAAaA,CAACL,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAGO,SAASQ,aAAaA,CAACN,IAAY,EAAEF,OAAe,EAAmB;EAC5E,OAAO,IAAI;AACb;AAEO,SAASS,UAAUA,CACxBP,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAEO,SAASU,UAAUA,CACxBR,IAAY,EACZF,OAAe,EAId;EACD,MAAM,IAAIG,KAAK,CACb,sBAAsBD,IAAI,gBAAgBF,OAAO,eACnD,CAAC;AACH;AAAC","ignoreList":[]}
-78
View File
@@ -1,78 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "ROOT_CONFIG_FILENAMES", {
enumerable: true,
get: function () {
return _configuration.ROOT_CONFIG_FILENAMES;
}
});
Object.defineProperty(exports, "findConfigUpwards", {
enumerable: true,
get: function () {
return _configuration.findConfigUpwards;
}
});
Object.defineProperty(exports, "findPackageData", {
enumerable: true,
get: function () {
return _package.findPackageData;
}
});
Object.defineProperty(exports, "findRelativeConfig", {
enumerable: true,
get: function () {
return _configuration.findRelativeConfig;
}
});
Object.defineProperty(exports, "findRootConfig", {
enumerable: true,
get: function () {
return _configuration.findRootConfig;
}
});
Object.defineProperty(exports, "loadConfig", {
enumerable: true,
get: function () {
return _configuration.loadConfig;
}
});
Object.defineProperty(exports, "loadPlugin", {
enumerable: true,
get: function () {
return _plugins.loadPlugin;
}
});
Object.defineProperty(exports, "loadPreset", {
enumerable: true,
get: function () {
return _plugins.loadPreset;
}
});
Object.defineProperty(exports, "resolvePlugin", {
enumerable: true,
get: function () {
return _plugins.resolvePlugin;
}
});
Object.defineProperty(exports, "resolvePreset", {
enumerable: true,
get: function () {
return _plugins.resolvePreset;
}
});
Object.defineProperty(exports, "resolveShowConfigPath", {
enumerable: true,
get: function () {
return _configuration.resolveShowConfigPath;
}
});
var _package = require("./package.js");
var _configuration = require("./configuration.js");
var _plugins = require("./plugins.js");
({});
0 && 0;
//# sourceMappingURL=index.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_package","require","_configuration","_plugins"],"sources":["../../../src/config/files/index.ts"],"sourcesContent":["type indexBrowserType = typeof import(\"./index-browser\");\ntype indexType = typeof import(\"./index\");\n\n// Kind of gross, but essentially asserting that the exports of this module are the same as the\n// exports of index-browser, since this file may be replaced at bundle time with index-browser.\n// eslint-disable-next-line @typescript-eslint/no-unused-expressions\n({}) as any as indexBrowserType as indexType;\n\nexport { findPackageData } from \"./package.ts\";\n\nexport {\n findConfigUpwards,\n findRelativeConfig,\n findRootConfig,\n loadConfig,\n resolveShowConfigPath,\n ROOT_CONFIG_FILENAMES,\n} from \"./configuration.ts\";\nexport type {\n ConfigFile,\n IgnoreFile,\n RelativeConfig,\n FilePackageData,\n} from \"./types.ts\";\nexport {\n loadPlugin,\n loadPreset,\n resolvePlugin,\n resolvePreset,\n} from \"./plugins.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,IAAAA,QAAA,GAAAC,OAAA;AAEA,IAAAC,cAAA,GAAAD,OAAA;AAcA,IAAAE,QAAA,GAAAF,OAAA;AAlBA,CAAC,CAAC,CAAC;AAA0C","ignoreList":[]}
-203
View File
@@ -1,203 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = loadCodeDefault;
exports.supportsESM = void 0;
var _async = require("../../gensync-utils/async.js");
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
require("module");
function _semver() {
const data = require("semver");
_semver = function () {
return data;
};
return data;
}
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
var _rewriteStackTrace = require("../../errors/rewrite-stack-trace.js");
var _configError = require("../../errors/config-error.js");
var _transformFile = require("../../transform-file.js");
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
const debug = _debug()("babel:config:loading:files:module-types");
try {
var import_ = require("./import.cjs");
} catch (_unused) {}
const supportsESM = exports.supportsESM = _semver().satisfies(process.versions.node, "^12.17 || >=13.2");
const LOADING_CJS_FILES = new Set();
function loadCjsDefault(filepath) {
if (LOADING_CJS_FILES.has(filepath)) {
debug("Auto-ignoring usage of config %o.", filepath);
return {};
}
let module;
try {
LOADING_CJS_FILES.add(filepath);
module = (0, _rewriteStackTrace.endHiddenCallStack)(require)(filepath);
} finally {
LOADING_CJS_FILES.delete(filepath);
}
return module != null && (module.__esModule || module[Symbol.toStringTag] === "Module") ? module.default || (arguments[1] ? module : undefined) : module;
}
const loadMjsFromPath = (0, _rewriteStackTrace.endHiddenCallStack)(function () {
var _loadMjsFromPath = _asyncToGenerator(function* (filepath) {
const url = (0, _url().pathToFileURL)(filepath).toString() + "?import";
if (!import_) {
throw new _configError.default("Internal error: Native ECMAScript modules aren't supported by this platform.\n", filepath);
}
return yield import_(url);
});
function loadMjsFromPath(_x) {
return _loadMjsFromPath.apply(this, arguments);
}
return loadMjsFromPath;
}());
const tsNotSupportedError = ext => `\
You are using a ${ext} config file, but Babel only supports transpiling .cts configs. Either:
- Use a .cts config file
- Update to Node.js 23.6.0, which has native TypeScript support
- Install tsx to transpile ${ext} files on the fly\
`;
const SUPPORTED_EXTENSIONS = {
".js": "unknown",
".mjs": "esm",
".cjs": "cjs",
".ts": "unknown",
".mts": "esm",
".cts": "cjs"
};
const asyncModules = new Set();
function* loadCodeDefault(filepath, loader, esmError, tlaError) {
let async;
const ext = _path().extname(filepath);
const isTS = ext === ".ts" || ext === ".cts" || ext === ".mts";
const type = SUPPORTED_EXTENSIONS[hasOwnProperty.call(SUPPORTED_EXTENSIONS, ext) ? ext : ".js"];
const pattern = `${loader} ${type}`;
switch (pattern) {
case "require cjs":
case "auto cjs":
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
case "auto unknown":
case "require unknown":
case "require esm":
try {
if (isTS) {
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
} else {
return loadCjsDefault(filepath, arguments[2]);
}
} catch (e) {
if (e.code === "ERR_REQUIRE_ASYNC_MODULE" || e.code === "ERR_REQUIRE_CYCLE_MODULE" && asyncModules.has(filepath)) {
asyncModules.add(filepath);
if (!(async != null ? async : async = yield* (0, _async.isAsync)())) {
throw new _configError.default(tlaError, filepath);
}
} else if (e.code === "ERR_REQUIRE_ESM" || type === "esm") {} else {
throw e;
}
}
case "auto esm":
if (async != null ? async : async = yield* (0, _async.isAsync)()) {
const promise = isTS ? ensureTsSupport(filepath, ext, () => loadMjsFromPath(filepath)) : loadMjsFromPath(filepath);
return (yield* (0, _async.waitFor)(promise)).default;
}
if (isTS) {
throw new _configError.default(tsNotSupportedError(ext), filepath);
} else {
throw new _configError.default(esmError, filepath);
}
default:
throw new Error("Internal Babel error: unreachable code.");
}
}
function ensureTsSupport(filepath, ext, callback) {
if (process.features.typescript || require.extensions[".ts"] || require.extensions[".cts"] || require.extensions[".mts"]) {
return callback();
}
if (ext !== ".cts") {
throw new _configError.default(tsNotSupportedError(ext), filepath);
}
const opts = {
babelrc: false,
configFile: false,
sourceType: "unambiguous",
sourceMaps: "inline",
sourceFileName: _path().basename(filepath),
presets: [[getTSPreset(filepath), Object.assign({
onlyRemoveTypeImports: true,
optimizeConstEnums: true
}, {
allowDeclareFields: true
})]]
};
let handler = function (m, filename) {
if (handler && filename.endsWith(".cts")) {
try {
return m._compile((0, _transformFile.transformFileSync)(filename, Object.assign({}, opts, {
filename
})).code, filename);
} catch (error) {
const packageJson = require("@babel/preset-typescript/package.json");
if (_semver().lt(packageJson.version, "7.21.4")) {
console.error("`.cts` configuration file failed to load, please try to update `@babel/preset-typescript`.");
}
throw error;
}
}
return require.extensions[".js"](m, filename);
};
require.extensions[ext] = handler;
try {
return callback();
} finally {
if (require.extensions[ext] === handler) delete require.extensions[ext];
handler = undefined;
}
}
function getTSPreset(filepath) {
try {
return require("@babel/preset-typescript");
} catch (error) {
if (error.code !== "MODULE_NOT_FOUND") throw error;
let message = "You appear to be using a .cts file as Babel configuration, but the `@babel/preset-typescript` package was not found: please install it!";
if (process.versions.pnp) {
message += `
If you are using Yarn Plug'n'Play, you may also need to add the following configuration to your .yarnrc.yml file:
packageExtensions:
\t"@babel/core@*":
\t\tpeerDependencies:
\t\t\t"@babel/preset-typescript": "*"
`;
}
throw new _configError.default(message, filepath);
}
}
0 && 0;
//# sourceMappingURL=module-types.js.map
File diff suppressed because one or more lines are too long
-61
View File
@@ -1,61 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findPackageData = findPackageData;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _utils = require("./utils.js");
var _configError = require("../../errors/config-error.js");
const PACKAGE_FILENAME = "package.json";
const readConfigPackage = (0, _utils.makeStaticFileCache)((filepath, content) => {
let options;
try {
options = JSON.parse(content);
} catch (err) {
throw new _configError.default(`Error while parsing JSON - ${err.message}`, filepath);
}
if (!options) throw new Error(`${filepath}: No config detected`);
if (typeof options !== "object") {
throw new _configError.default(`Config returned typeof ${typeof options}`, filepath);
}
if (Array.isArray(options)) {
throw new _configError.default(`Expected config object but found array`, filepath);
}
return {
filepath,
dirname: _path().dirname(filepath),
options
};
});
function* findPackageData(filepath) {
let pkg = null;
const directories = [];
let isPackage = true;
let dirname = _path().dirname(filepath);
while (!pkg && _path().basename(dirname) !== "node_modules") {
directories.push(dirname);
pkg = yield* readConfigPackage(_path().join(dirname, PACKAGE_FILENAME));
const nextLoc = _path().dirname(dirname);
if (dirname === nextLoc) {
isPackage = false;
break;
}
dirname = nextLoc;
}
return {
filepath,
directories,
pkg,
isPackage
};
}
0 && 0;
//# sourceMappingURL=package.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_path","data","require","_utils","_configError","PACKAGE_FILENAME","readConfigPackage","makeStaticFileCache","filepath","content","options","JSON","parse","err","ConfigError","message","Error","Array","isArray","dirname","path","findPackageData","pkg","directories","isPackage","basename","push","join","nextLoc"],"sources":["../../../src/config/files/package.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Handler } from \"gensync\";\nimport { makeStaticFileCache } from \"./utils.ts\";\n\nimport type { ConfigFile, FilePackageData } from \"./types.ts\";\n\nimport ConfigError from \"../../errors/config-error.ts\";\n\nconst PACKAGE_FILENAME = \"package.json\";\n\nconst readConfigPackage = makeStaticFileCache(\n (filepath, content): ConfigFile => {\n let options;\n try {\n options = JSON.parse(content) as unknown;\n } catch (err) {\n throw new ConfigError(\n `Error while parsing JSON - ${err.message}`,\n filepath,\n );\n }\n\n if (!options) throw new Error(`${filepath}: No config detected`);\n\n if (typeof options !== \"object\") {\n throw new ConfigError(\n `Config returned typeof ${typeof options}`,\n filepath,\n );\n }\n if (Array.isArray(options)) {\n throw new ConfigError(`Expected config object but found array`, filepath);\n }\n\n return {\n filepath,\n dirname: path.dirname(filepath),\n options,\n };\n },\n);\n\n/**\n * Find metadata about the package that this file is inside of. Resolution\n * of Babel's config requires general package information to decide when to\n * search for .babelrc files\n */\nexport function* findPackageData(filepath: string): Handler<FilePackageData> {\n let pkg = null;\n const directories = [];\n let isPackage = true;\n\n let dirname = path.dirname(filepath);\n while (!pkg && path.basename(dirname) !== \"node_modules\") {\n directories.push(dirname);\n\n pkg = yield* readConfigPackage(path.join(dirname, PACKAGE_FILENAME));\n\n const nextLoc = path.dirname(dirname);\n if (dirname === nextLoc) {\n isPackage = false;\n break;\n }\n dirname = nextLoc;\n }\n\n return { filepath, directories, pkg, isPackage };\n}\n"],"mappings":";;;;;;AAAA,SAAAA,MAAA;EAAA,MAAAC,IAAA,GAAAC,OAAA;EAAAF,KAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEA,IAAAE,MAAA,GAAAD,OAAA;AAIA,IAAAE,YAAA,GAAAF,OAAA;AAEA,MAAMG,gBAAgB,GAAG,cAAc;AAEvC,MAAMC,iBAAiB,GAAG,IAAAC,0BAAmB,EAC3C,CAACC,QAAQ,EAAEC,OAAO,KAAiB;EACjC,IAAIC,OAAO;EACX,IAAI;IACFA,OAAO,GAAGC,IAAI,CAACC,KAAK,CAACH,OAAO,CAAY;EAC1C,CAAC,CAAC,OAAOI,GAAG,EAAE;IACZ,MAAM,IAAIC,oBAAW,CACnB,8BAA8BD,GAAG,CAACE,OAAO,EAAE,EAC3CP,QACF,CAAC;EACH;EAEA,IAAI,CAACE,OAAO,EAAE,MAAM,IAAIM,KAAK,CAAC,GAAGR,QAAQ,sBAAsB,CAAC;EAEhE,IAAI,OAAOE,OAAO,KAAK,QAAQ,EAAE;IAC/B,MAAM,IAAII,oBAAW,CACnB,0BAA0B,OAAOJ,OAAO,EAAE,EAC1CF,QACF,CAAC;EACH;EACA,IAAIS,KAAK,CAACC,OAAO,CAACR,OAAO,CAAC,EAAE;IAC1B,MAAM,IAAII,oBAAW,CAAC,wCAAwC,EAAEN,QAAQ,CAAC;EAC3E;EAEA,OAAO;IACLA,QAAQ;IACRW,OAAO,EAAEC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;IAC/BE;EACF,CAAC;AACH,CACF,CAAC;AAOM,UAAUW,eAAeA,CAACb,QAAgB,EAA4B;EAC3E,IAAIc,GAAG,GAAG,IAAI;EACd,MAAMC,WAAW,GAAG,EAAE;EACtB,IAAIC,SAAS,GAAG,IAAI;EAEpB,IAAIL,OAAO,GAAGC,MAAGA,CAAC,CAACD,OAAO,CAACX,QAAQ,CAAC;EACpC,OAAO,CAACc,GAAG,IAAIF,MAAGA,CAAC,CAACK,QAAQ,CAACN,OAAO,CAAC,KAAK,cAAc,EAAE;IACxDI,WAAW,CAACG,IAAI,CAACP,OAAO,CAAC;IAEzBG,GAAG,GAAG,OAAOhB,iBAAiB,CAACc,MAAGA,CAAC,CAACO,IAAI,CAACR,OAAO,EAAEd,gBAAgB,CAAC,CAAC;IAEpE,MAAMuB,OAAO,GAAGR,MAAGA,CAAC,CAACD,OAAO,CAACA,OAAO,CAAC;IACrC,IAAIA,OAAO,KAAKS,OAAO,EAAE;MACvBJ,SAAS,GAAG,KAAK;MACjB;IACF;IACAL,OAAO,GAAGS,OAAO;EACnB;EAEA,OAAO;IAAEpB,QAAQ;IAAEe,WAAW;IAAED,GAAG;IAAEE;EAAU,CAAC;AAClD;AAAC","ignoreList":[]}
-220
View File
@@ -1,220 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.loadPlugin = loadPlugin;
exports.loadPreset = loadPreset;
exports.resolvePreset = exports.resolvePlugin = void 0;
function _debug() {
const data = require("debug");
_debug = function () {
return data;
};
return data;
}
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _async = require("../../gensync-utils/async.js");
var _moduleTypes = require("./module-types.js");
function _url() {
const data = require("url");
_url = function () {
return data;
};
return data;
}
var _importMetaResolve = require("../../vendor/import-meta-resolve.js");
require("module");
function _fs() {
const data = require("fs");
_fs = function () {
return data;
};
return data;
}
const debug = _debug()("babel:config:loading:files:plugins");
const EXACT_RE = /^module:/;
const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-plugin-)/;
const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-preset-)/;
const BABEL_PLUGIN_ORG_RE = /^(@babel\/)(?!plugin-|[^/]+\/)/;
const BABEL_PRESET_ORG_RE = /^(@babel\/)(?!preset-|[^/]+\/)/;
const OTHER_PLUGIN_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-plugin(?:-|\/|$)|[^/]+\/)/;
const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/;
const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/;
const resolvePlugin = exports.resolvePlugin = resolveStandardizedName.bind(null, "plugin");
const resolvePreset = exports.resolvePreset = resolveStandardizedName.bind(null, "preset");
function* loadPlugin(name, dirname) {
const {
filepath,
loader
} = resolvePlugin(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("plugin", loader, filepath);
debug("Loaded plugin %o from %o.", name, dirname);
return {
filepath,
value
};
}
function* loadPreset(name, dirname) {
const {
filepath,
loader
} = resolvePreset(name, dirname, yield* (0, _async.isAsync)());
const value = yield* requireModule("preset", loader, filepath);
debug("Loaded preset %o from %o.", name, dirname);
return {
filepath,
value
};
}
function standardizeName(type, name) {
if (_path().isAbsolute(name)) return name;
const isPreset = type === "preset";
return name.replace(isPreset ? BABEL_PRESET_PREFIX_RE : BABEL_PLUGIN_PREFIX_RE, `babel-${type}-`).replace(isPreset ? BABEL_PRESET_ORG_RE : BABEL_PLUGIN_ORG_RE, `$1${type}-`).replace(isPreset ? OTHER_PRESET_ORG_RE : OTHER_PLUGIN_ORG_RE, `$1babel-${type}-`).replace(OTHER_ORG_DEFAULT_RE, `$1/babel-${type}`).replace(EXACT_RE, "");
}
function* resolveAlternativesHelper(type, name) {
const standardizedName = standardizeName(type, name);
const {
error,
value
} = yield standardizedName;
if (!error) return value;
if (error.code !== "MODULE_NOT_FOUND") throw error;
if (standardizedName !== name && !(yield name).error) {
error.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
}
if (!(yield standardizeName(type, "@babel/" + name)).error) {
error.message += `\n- Did you mean "@babel/${name}"?`;
}
const oppositeType = type === "preset" ? "plugin" : "preset";
if (!(yield standardizeName(oppositeType, name)).error) {
error.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
}
if (type === "plugin") {
const transformName = standardizedName.replace("-proposal-", "-transform-");
if (transformName !== standardizedName && !(yield transformName).error) {
error.message += `\n- Did you mean "${transformName}"?`;
}
}
error.message += `\n
Make sure that all the Babel plugins and presets you are using
are defined as dependencies or devDependencies in your package.json
file. It's possible that the missing plugin is loaded by a preset
you are using that forgot to add the plugin to its dependencies: you
can workaround this problem by explicitly adding the missing package
to your top-level package.json.
`;
throw error;
}
function tryRequireResolve(id, dirname) {
try {
if (dirname) {
return {
error: null,
value: (((v, w) => (v = v.split("."), w = w.split("."), +v[0] > +w[0] || v[0] == w[0] && +v[1] >= +w[1]))(process.versions.node, "8.9") ? require.resolve : (r, {
paths: [b]
}, M = require("module")) => {
let f = M._findPath(r, M._nodeModulePaths(b).concat(b));
if (f) return f;
f = new Error(`Cannot resolve module '${r}'`);
f.code = "MODULE_NOT_FOUND";
throw f;
})(id, {
paths: [dirname]
})
};
} else {
return {
error: null,
value: require.resolve(id)
};
}
} catch (error) {
return {
error,
value: null
};
}
}
function tryImportMetaResolve(id, options) {
try {
return {
error: null,
value: (0, _importMetaResolve.resolve)(id, options)
};
} catch (error) {
return {
error,
value: null
};
}
}
function resolveStandardizedNameForRequire(type, name, dirname) {
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryRequireResolve(res.value, dirname));
}
return {
loader: "require",
filepath: res.value
};
}
function resolveStandardizedNameForImport(type, name, dirname) {
const parentUrl = (0, _url().pathToFileURL)(_path().join(dirname, "./babel-virtual-resolve-base.js")).href;
const it = resolveAlternativesHelper(type, name);
let res = it.next();
while (!res.done) {
res = it.next(tryImportMetaResolve(res.value, parentUrl));
}
return {
loader: "auto",
filepath: (0, _url().fileURLToPath)(res.value)
};
}
function resolveStandardizedName(type, name, dirname, allowAsync) {
if (!_moduleTypes.supportsESM || !allowAsync) {
return resolveStandardizedNameForRequire(type, name, dirname);
}
try {
const resolved = resolveStandardizedNameForImport(type, name, dirname);
if (!(0, _fs().existsSync)(resolved.filepath)) {
throw Object.assign(new Error(`Could not resolve "${name}" in file ${dirname}.`), {
type: "MODULE_NOT_FOUND"
});
}
return resolved;
} catch (e) {
try {
return resolveStandardizedNameForRequire(type, name, dirname);
} catch (e2) {
if (e.type === "MODULE_NOT_FOUND") throw e;
if (e2.type === "MODULE_NOT_FOUND") throw e2;
throw e;
}
}
}
var LOADING_MODULES = new Set();
function* requireModule(type, loader, name) {
if (!(yield* (0, _async.isAsync)()) && LOADING_MODULES.has(name)) {
throw new Error(`Reentrant ${type} detected trying to load "${name}". This module is not ignored ` + "and is trying to load itself while compiling itself, leading to a dependency cycle. " + 'We recommend adding it to your "ignore" list in your babelrc, or to a .babelignore.');
}
try {
LOADING_MODULES.add(name);
return yield* (0, _moduleTypes.default)(name, loader, `You appear to be using a native ECMAScript module ${type}, ` + "which is only supported when running Babel asynchronously " + "or when using the Node.js `--experimental-require-module` flag.", `You appear to be using a ${type} that contains top-level await, ` + "which is only supported when running Babel asynchronously.", true);
} catch (err) {
err.message = `[BABEL]: ${err.message} (While processing: ${name})`;
throw err;
} finally {
LOADING_MODULES.delete(name);
}
}
0 && 0;
//# sourceMappingURL=plugins.js.map
File diff suppressed because one or more lines are too long
-5
View File
@@ -1,5 +0,0 @@
"use strict";
0 && 0;
//# sourceMappingURL=types.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":[],"sources":["../../../src/config/files/types.ts"],"sourcesContent":["import type { InputOptions } from \"../index.ts\";\n\nexport type ConfigFile = {\n filepath: string;\n dirname: string;\n options: InputOptions & { babel?: unknown };\n};\n\nexport type IgnoreFile = {\n filepath: string;\n dirname: string;\n ignore: RegExp[];\n};\n\nexport type RelativeConfig = {\n // The actual config, either from package.json#babel, .babelrc, or\n // .babelrc.js, if there was one.\n config: ConfigFile | null;\n // The .babelignore, if there was one.\n ignore: IgnoreFile | null;\n};\n\nexport type FilePackageData = {\n // The file in the package.\n filepath: string;\n // Any ancestor directories of the file that are within the package.\n directories: string[];\n // The contents of the package.json. May not be found if the package just\n // terminated at a node_modules folder without finding one.\n pkg: ConfigFile | null;\n // True if a package.json or node_modules folder was found while traversing\n // the directory structure.\n isPackage: boolean;\n};\n"],"mappings":"","ignoreList":[]}
-36
View File
@@ -1,36 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeStaticFileCache = makeStaticFileCache;
var _caching = require("../caching.js");
var fs = require("../../gensync-utils/fs.js");
function _fs2() {
const data = require("fs");
_fs2 = function () {
return data;
};
return data;
}
function makeStaticFileCache(fn) {
return (0, _caching.makeStrongCache)(function* (filepath, cache) {
const cached = cache.invalidate(() => fileMtime(filepath));
if (cached === null) {
return null;
}
return fn(filepath, yield* fs.readFile(filepath, "utf8"));
});
}
function fileMtime(filepath) {
if (!_fs2().existsSync(filepath)) return null;
try {
return +_fs2().statSync(filepath).mtime;
} catch (e) {
if (e.code !== "ENOENT" && e.code !== "ENOTDIR") throw e;
}
return null;
}
0 && 0;
//# sourceMappingURL=utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["_caching","require","fs","_fs2","data","makeStaticFileCache","fn","makeStrongCache","filepath","cache","cached","invalidate","fileMtime","readFile","nodeFs","existsSync","statSync","mtime","e","code"],"sources":["../../../src/config/files/utils.ts"],"sourcesContent":["import type { Handler } from \"gensync\";\n\nimport { makeStrongCache } from \"../caching.ts\";\nimport type { CacheConfigurator } from \"../caching.ts\";\nimport * as fs from \"../../gensync-utils/fs.ts\";\nimport nodeFs from \"node:fs\";\n\nexport function makeStaticFileCache<T>(\n fn: (filepath: string, contents: string) => T,\n) {\n return makeStrongCache(function* (\n filepath: string,\n cache: CacheConfigurator<void>,\n ): Handler<null | T> {\n const cached = cache.invalidate(() => fileMtime(filepath));\n\n if (cached === null) {\n return null;\n }\n\n return fn(filepath, yield* fs.readFile(filepath, \"utf8\"));\n });\n}\n\nfunction fileMtime(filepath: string): number | null {\n if (!nodeFs.existsSync(filepath)) return null;\n\n try {\n return +nodeFs.statSync(filepath).mtime;\n } catch (e) {\n if (e.code !== \"ENOENT\" && e.code !== \"ENOTDIR\") throw e;\n }\n\n return null;\n}\n"],"mappings":";;;;;;AAEA,IAAAA,QAAA,GAAAC,OAAA;AAEA,IAAAC,EAAA,GAAAD,OAAA;AACA,SAAAE,KAAA;EAAA,MAAAC,IAAA,GAAAH,OAAA;EAAAE,IAAA,YAAAA,CAAA;IAAA,OAAAC,IAAA;EAAA;EAAA,OAAAA,IAAA;AAAA;AAEO,SAASC,mBAAmBA,CACjCC,EAA6C,EAC7C;EACA,OAAO,IAAAC,wBAAe,EAAC,WACrBC,QAAgB,EAChBC,KAA8B,EACX;IACnB,MAAMC,MAAM,GAAGD,KAAK,CAACE,UAAU,CAAC,MAAMC,SAAS,CAACJ,QAAQ,CAAC,CAAC;IAE1D,IAAIE,MAAM,KAAK,IAAI,EAAE;MACnB,OAAO,IAAI;IACb;IAEA,OAAOJ,EAAE,CAACE,QAAQ,EAAE,OAAON,EAAE,CAACW,QAAQ,CAACL,QAAQ,EAAE,MAAM,CAAC,CAAC;EAC3D,CAAC,CAAC;AACJ;AAEA,SAASI,SAASA,CAACJ,QAAgB,EAAiB;EAClD,IAAI,CAACM,KAAKA,CAAC,CAACC,UAAU,CAACP,QAAQ,CAAC,EAAE,OAAO,IAAI;EAE7C,IAAI;IACF,OAAO,CAACM,KAAKA,CAAC,CAACE,QAAQ,CAACR,QAAQ,CAAC,CAACS,KAAK;EACzC,CAAC,CAAC,OAAOC,CAAC,EAAE;IACV,IAAIA,CAAC,CAACC,IAAI,KAAK,QAAQ,IAAID,CAAC,CAACC,IAAI,KAAK,SAAS,EAAE,MAAMD,CAAC;EAC1D;EAEA,OAAO,IAAI;AACb;AAAC","ignoreList":[]}
-312
View File
@@ -1,312 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _async = require("../gensync-utils/async.js");
var _util = require("./util.js");
var context = require("../index.js");
var _plugin = require("./plugin.js");
var _item = require("./item.js");
var _configChain = require("./config-chain.js");
var _deepArray = require("./helpers/deep-array.js");
function _traverse() {
const data = require("@babel/traverse");
_traverse = function () {
return data;
};
return data;
}
var _caching = require("./caching.js");
var _options = require("./validation/options.js");
var _plugins = require("./validation/plugins.js");
var _configApi = require("./helpers/config-api.js");
var _partial = require("./partial.js");
var _configError = require("../errors/config-error.js");
var _default = exports.default = _gensync()(function* loadFullConfig(inputOpts) {
var _opts$assumptions;
const result = yield* (0, _partial.default)(inputOpts);
if (!result) {
return null;
}
const {
options,
context,
fileHandling
} = result;
if (fileHandling === "ignored") {
return null;
}
const optionDefaults = {};
const {
plugins,
presets
} = options;
if (!plugins || !presets) {
throw new Error("Assertion failure - plugins and presets exist");
}
const presetContext = Object.assign({}, context, {
targets: options.targets
});
const toDescriptor = item => {
const desc = (0, _item.getItemDescriptor)(item);
if (!desc) {
throw new Error("Assertion failure - must be config item");
}
return desc;
};
const presetsDescriptors = presets.map(toDescriptor);
const initialPluginsDescriptors = plugins.map(toDescriptor);
const pluginDescriptorsByPass = [[]];
const passes = [];
const externalDependencies = [];
const ignored = yield* enhanceError(context, function* recursePresetDescriptors(rawPresets, pluginDescriptorsPass) {
const presets = [];
for (let i = 0; i < rawPresets.length; i++) {
const descriptor = rawPresets[i];
if (descriptor.options !== false) {
try {
var preset = yield* loadPresetDescriptor(descriptor, presetContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_OPTION") {
(0, _options.checkNoUnwrappedItemOptionPairs)(rawPresets, i, "preset", e);
}
throw e;
}
externalDependencies.push(preset.externalDependencies);
if (descriptor.ownPass) {
presets.push({
preset: preset.chain,
pass: []
});
} else {
presets.unshift({
preset: preset.chain,
pass: pluginDescriptorsPass
});
}
}
}
if (presets.length > 0) {
pluginDescriptorsByPass.splice(1, 0, ...presets.map(o => o.pass).filter(p => p !== pluginDescriptorsPass));
for (const {
preset,
pass
} of presets) {
if (!preset) return true;
pass.push(...preset.plugins);
const ignored = yield* recursePresetDescriptors(preset.presets, pass);
if (ignored) return true;
preset.options.forEach(opts => {
(0, _util.mergeOptions)(optionDefaults, opts);
});
}
}
})(presetsDescriptors, pluginDescriptorsByPass[0]);
if (ignored) return null;
const opts = optionDefaults;
(0, _util.mergeOptions)(opts, options);
const pluginContext = Object.assign({}, presetContext, {
assumptions: (_opts$assumptions = opts.assumptions) != null ? _opts$assumptions : {}
});
yield* enhanceError(context, function* loadPluginDescriptors() {
pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors);
for (const descs of pluginDescriptorsByPass) {
const pass = [];
passes.push(pass);
for (let i = 0; i < descs.length; i++) {
const descriptor = descs[i];
if (descriptor.options !== false) {
try {
var plugin = yield* loadPluginDescriptor(descriptor, pluginContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") {
(0, _options.checkNoUnwrappedItemOptionPairs)(descs, i, "plugin", e);
}
throw e;
}
pass.push(plugin);
externalDependencies.push(plugin.externalDependencies);
}
}
}
})();
opts.plugins = passes[0];
opts.presets = passes.slice(1).filter(plugins => plugins.length > 0).map(plugins => ({
plugins
}));
opts.passPerPreset = opts.presets.length > 0;
return {
options: opts,
passes: passes,
externalDependencies: (0, _deepArray.finalize)(externalDependencies)
};
});
function enhanceError(context, fn) {
return function* (arg1, arg2) {
try {
return yield* fn(arg1, arg2);
} catch (e) {
if (!e.message.startsWith("[BABEL]")) {
var _context$filename;
e.message = `[BABEL] ${(_context$filename = context.filename) != null ? _context$filename : "unknown file"}: ${e.message}`;
}
throw e;
}
};
}
const makeDescriptorLoader = apiFactory => (0, _caching.makeWeakCache)(function* ({
value,
options,
dirname,
alias
}, cache) {
if (options === false) throw new Error("Assertion failure");
options = options || {};
const externalDependencies = [];
let item = value;
if (typeof value === "function") {
const factory = (0, _async.maybeAsync)(value, `You appear to be using an async plugin/preset, but Babel has been called synchronously`);
const api = Object.assign({}, context, apiFactory(cache, externalDependencies));
try {
item = yield* factory(api, options, dirname);
} catch (e) {
if (alias) {
e.message += ` (While processing: ${JSON.stringify(alias)})`;
}
throw e;
}
}
if (!item || typeof item !== "object") {
throw new Error("Plugin/Preset did not return an object.");
}
if ((0, _async.isThenable)(item)) {
yield* [];
throw new Error(`You appear to be using a promise as a plugin, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, ` + `you may need to upgrade your @babel/core version. ` + `As an alternative, you can prefix the promise with "await". ` + `(While processing: ${JSON.stringify(alias)})`);
}
if (externalDependencies.length > 0 && (!cache.configured() || cache.mode() === "forever")) {
let error = `A plugin/preset has external untracked dependencies ` + `(${externalDependencies[0]}), but the cache `;
if (!cache.configured()) {
error += `has not been configured to be invalidated when the external dependencies change. `;
} else {
error += ` has been configured to never be invalidated. `;
}
error += `Plugins/presets should configure their cache to be invalidated when the external ` + `dependencies change, for example using \`api.cache.invalidate(() => ` + `statSync(filepath).mtimeMs)\` or \`api.cache.never()\`\n` + `(While processing: ${JSON.stringify(alias)})`;
throw new Error(error);
}
return {
value: item,
options,
dirname,
alias,
externalDependencies: (0, _deepArray.finalize)(externalDependencies)
};
});
const pluginDescriptorLoader = makeDescriptorLoader(_configApi.makePluginAPI);
const presetDescriptorLoader = makeDescriptorLoader(_configApi.makePresetAPI);
const instantiatePlugin = (0, _caching.makeWeakCache)(function* ({
value,
options,
dirname,
alias,
externalDependencies
}, cache) {
const pluginObj = (0, _plugins.validatePluginObject)(value);
const plugin = Object.assign({}, pluginObj);
if (plugin.visitor) {
plugin.visitor = _traverse().default.explode(Object.assign({}, plugin.visitor));
}
if (plugin.inherits) {
const inheritsDescriptor = {
name: undefined,
alias: `${alias}$inherits`,
value: plugin.inherits,
options,
dirname
};
const inherits = yield* (0, _async.forwardAsync)(loadPluginDescriptor, run => {
return cache.invalidate(data => run(inheritsDescriptor, data));
});
plugin.pre = chainMaybeAsync(inherits.pre, plugin.pre);
plugin.post = chainMaybeAsync(inherits.post, plugin.post);
plugin.manipulateOptions = chainMaybeAsync(inherits.manipulateOptions, plugin.manipulateOptions);
plugin.visitor = _traverse().default.visitors.merge([inherits.visitor || {}, plugin.visitor || {}]);
if (inherits.externalDependencies.length > 0) {
if (externalDependencies.length === 0) {
externalDependencies = inherits.externalDependencies;
} else {
externalDependencies = (0, _deepArray.finalize)([externalDependencies, inherits.externalDependencies]);
}
}
}
return new _plugin.default(plugin, options, alias, externalDependencies);
});
function* loadPluginDescriptor(descriptor, context) {
if (descriptor.value instanceof _plugin.default) {
if (descriptor.options) {
throw new Error("Passed options to an existing Plugin instance will not work.");
}
return descriptor.value;
}
return yield* instantiatePlugin(yield* pluginDescriptorLoader(descriptor, context), context);
}
const needsFilename = val => val && typeof val !== "function";
const validateIfOptionNeedsFilename = (options, descriptor) => {
if (needsFilename(options.test) || needsFilename(options.include) || needsFilename(options.exclude)) {
const formattedPresetName = descriptor.name ? `"${descriptor.name}"` : "/* your preset */";
throw new _configError.default([`Preset ${formattedPresetName} requires a filename to be set when babel is called directly,`, `\`\`\``, `babel.transformSync(code, { filename: 'file.ts', presets: [${formattedPresetName}] });`, `\`\`\``, `See https://babeljs.io/docs/en/options#filename for more information.`].join("\n"));
}
};
const validatePreset = (preset, context, descriptor) => {
if (!context.filename) {
var _options$overrides;
const {
options
} = preset;
validateIfOptionNeedsFilename(options, descriptor);
(_options$overrides = options.overrides) == null || _options$overrides.forEach(overrideOptions => validateIfOptionNeedsFilename(overrideOptions, descriptor));
}
};
const instantiatePreset = (0, _caching.makeWeakCacheSync)(({
value,
dirname,
alias,
externalDependencies
}) => {
return {
options: (0, _options.validate)("preset", value),
alias,
dirname,
externalDependencies
};
});
function* loadPresetDescriptor(descriptor, context) {
const preset = instantiatePreset(yield* presetDescriptorLoader(descriptor, context));
validatePreset(preset, context, descriptor);
return {
chain: yield* (0, _configChain.buildPresetChain)(preset, context),
externalDependencies: preset.externalDependencies
};
}
function chainMaybeAsync(a, b) {
if (!a) return b;
if (!b) return a;
return function (...args) {
const res = a.apply(this, args);
if (res && typeof res.then === "function") {
return res.then(() => b.apply(this, args));
}
return b.apply(this, args);
};
}
0 && 0;
//# sourceMappingURL=full.js.map
File diff suppressed because one or more lines are too long
-85
View File
@@ -1,85 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.makeConfigAPI = makeConfigAPI;
exports.makePluginAPI = makePluginAPI;
exports.makePresetAPI = makePresetAPI;
function _semver() {
const data = require("semver");
_semver = function () {
return data;
};
return data;
}
var _index = require("../../index.js");
var _caching = require("../caching.js");
function makeConfigAPI(cache) {
const env = value => cache.using(data => {
if (value === undefined) return data.envName;
if (typeof value === "function") {
return (0, _caching.assertSimpleType)(value(data.envName));
}
return (Array.isArray(value) ? value : [value]).some(entry => {
if (typeof entry !== "string") {
throw new Error("Unexpected non-string value");
}
return entry === data.envName;
});
});
const caller = cb => cache.using(data => (0, _caching.assertSimpleType)(cb(data.caller)));
return {
version: _index.version,
cache: cache.simple(),
env,
async: () => false,
caller,
assertVersion
};
}
function makePresetAPI(cache, externalDependencies) {
const targets = () => JSON.parse(cache.using(data => JSON.stringify(data.targets)));
const addExternalDependency = ref => {
externalDependencies.push(ref);
};
return Object.assign({}, makeConfigAPI(cache), {
targets,
addExternalDependency
});
}
function makePluginAPI(cache, externalDependencies) {
const assumption = name => cache.using(data => data.assumptions[name]);
return Object.assign({}, makePresetAPI(cache, externalDependencies), {
assumption
});
}
function assertVersion(range) {
if (typeof range === "number") {
if (!Number.isInteger(range)) {
throw new Error("Expected string or integer value.");
}
range = `^${range}.0.0-0`;
}
if (typeof range !== "string") {
throw new Error("Expected string or integer value.");
}
if (range === "*" || _semver().satisfies(_index.version, range)) return;
const message = `Requires Babel "${range}", but was loaded with "${_index.version}". ` + `If you are sure you have a compatible version of @babel/core, ` + `it is likely that something in your build process is loading the ` + `wrong version. Inspect the stack trace of this error to look for ` + `the first entry that doesn't mention "@babel/core" or "babel-core" ` + `to see what is calling Babel.`;
const limit = Error.stackTraceLimit;
if (typeof limit === "number" && limit < 25) {
Error.stackTraceLimit = 25;
}
const err = new Error(message);
if (typeof limit === "number") {
Error.stackTraceLimit = limit;
}
throw Object.assign(err, {
code: "BABEL_VERSION_UNSUPPORTED",
version: _index.version,
range
});
}
0 && 0;
//# sourceMappingURL=config-api.js.map
File diff suppressed because one or more lines are too long
-23
View File
@@ -1,23 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.finalize = finalize;
exports.flattenToSet = flattenToSet;
function finalize(deepArr) {
return Object.freeze(deepArr);
}
function flattenToSet(arr) {
const result = new Set();
const stack = [arr];
while (stack.length > 0) {
for (const el of stack.pop()) {
if (Array.isArray(el)) stack.push(el);else result.add(el);
}
}
return result;
}
0 && 0;
//# sourceMappingURL=deep-array.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"names":["finalize","deepArr","Object","freeze","flattenToSet","arr","result","Set","stack","length","el","pop","Array","isArray","push","add"],"sources":["../../../src/config/helpers/deep-array.ts"],"sourcesContent":["export type DeepArray<T> = (T | ReadonlyDeepArray<T>)[];\n\n// Just to make sure that DeepArray<T> is not assignable to ReadonlyDeepArray<T>\ndeclare const __marker: unique symbol;\nexport type ReadonlyDeepArray<T> = readonly (T | ReadonlyDeepArray<T>)[] & {\n [__marker]: true;\n};\n\nexport function finalize<T>(deepArr: DeepArray<T>): ReadonlyDeepArray<T> {\n return Object.freeze(deepArr) as ReadonlyDeepArray<T>;\n}\n\nexport function flattenToSet<T extends string>(\n arr: ReadonlyDeepArray<T>,\n): Set<T> {\n const result = new Set<T>();\n const stack = [arr];\n while (stack.length > 0) {\n for (const el of stack.pop()) {\n if (Array.isArray(el)) stack.push(el as ReadonlyDeepArray<T>);\n else result.add(el as T);\n }\n }\n return result;\n}\n"],"mappings":";;;;;;;AAQO,SAASA,QAAQA,CAAIC,OAAqB,EAAwB;EACvE,OAAOC,MAAM,CAACC,MAAM,CAACF,OAAO,CAAC;AAC/B;AAEO,SAASG,YAAYA,CAC1BC,GAAyB,EACjB;EACR,MAAMC,MAAM,GAAG,IAAIC,GAAG,CAAI,CAAC;EAC3B,MAAMC,KAAK,GAAG,CAACH,GAAG,CAAC;EACnB,OAAOG,KAAK,CAACC,MAAM,GAAG,CAAC,EAAE;IACvB,KAAK,MAAMC,EAAE,IAAIF,KAAK,CAACG,GAAG,CAAC,CAAC,EAAE;MAC5B,IAAIC,KAAK,CAACC,OAAO,CAACH,EAAE,CAAC,EAAEF,KAAK,CAACM,IAAI,CAACJ,EAA0B,CAAC,CAAC,KACzDJ,MAAM,CAACS,GAAG,CAACL,EAAO,CAAC;IAC1B;EACF;EACA,OAAOJ,MAAM;AACf;AAAC","ignoreList":[]}
-12
View File
@@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getEnv = getEnv;
function getEnv(defaultValue = "development") {
return process.env.BABEL_ENV || process.env.NODE_ENV || defaultValue;
}
0 && 0;
//# sourceMappingURL=environment.js.map
@@ -1 +0,0 @@
{"version":3,"names":["getEnv","defaultValue","process","env","BABEL_ENV","NODE_ENV"],"sources":["../../../src/config/helpers/environment.ts"],"sourcesContent":["export function getEnv(defaultValue: string = \"development\"): string {\n return process.env.BABEL_ENV || process.env.NODE_ENV || defaultValue;\n}\n"],"mappings":";;;;;;AAAO,SAASA,MAAMA,CAACC,YAAoB,GAAG,aAAa,EAAU;EACnE,OAAOC,OAAO,CAACC,GAAG,CAACC,SAAS,IAAIF,OAAO,CAACC,GAAG,CAACE,QAAQ,IAAIJ,YAAY;AACtE;AAAC","ignoreList":[]}
-87
View File
@@ -1,87 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createConfigItem = createConfigItem;
exports.createConfigItemAsync = createConfigItemAsync;
exports.createConfigItemSync = createConfigItemSync;
Object.defineProperty(exports, "default", {
enumerable: true,
get: function () {
return _full.default;
}
});
exports.loadOptions = loadOptions;
exports.loadOptionsAsync = loadOptionsAsync;
exports.loadOptionsSync = loadOptionsSync;
exports.loadPartialConfig = loadPartialConfig;
exports.loadPartialConfigAsync = loadPartialConfigAsync;
exports.loadPartialConfigSync = loadPartialConfigSync;
function _gensync() {
const data = require("gensync");
_gensync = function () {
return data;
};
return data;
}
var _full = require("./full.js");
var _partial = require("./partial.js");
var _item = require("./item.js");
var _rewriteStackTrace = require("../errors/rewrite-stack-trace.js");
const loadPartialConfigRunner = _gensync()(_partial.loadPartialConfig);
function loadPartialConfigAsync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(loadPartialConfigRunner.async)(...args);
}
function loadPartialConfigSync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(loadPartialConfigRunner.sync)(...args);
}
function loadPartialConfig(opts, callback) {
if (callback !== undefined) {
(0, _rewriteStackTrace.beginHiddenCallStack)(loadPartialConfigRunner.errback)(opts, callback);
} else if (typeof opts === "function") {
(0, _rewriteStackTrace.beginHiddenCallStack)(loadPartialConfigRunner.errback)(undefined, opts);
} else {
return loadPartialConfigSync(opts);
}
}
function* loadOptionsImpl(opts) {
var _config$options;
const config = yield* (0, _full.default)(opts);
return (_config$options = config == null ? void 0 : config.options) != null ? _config$options : null;
}
const loadOptionsRunner = _gensync()(loadOptionsImpl);
function loadOptionsAsync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(loadOptionsRunner.async)(...args);
}
function loadOptionsSync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(loadOptionsRunner.sync)(...args);
}
function loadOptions(opts, callback) {
if (callback !== undefined) {
(0, _rewriteStackTrace.beginHiddenCallStack)(loadOptionsRunner.errback)(opts, callback);
} else if (typeof opts === "function") {
(0, _rewriteStackTrace.beginHiddenCallStack)(loadOptionsRunner.errback)(undefined, opts);
} else {
return loadOptionsSync(opts);
}
}
const createConfigItemRunner = _gensync()(_item.createConfigItem);
function createConfigItemAsync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(createConfigItemRunner.async)(...args);
}
function createConfigItemSync(...args) {
return (0, _rewriteStackTrace.beginHiddenCallStack)(createConfigItemRunner.sync)(...args);
}
function createConfigItem(target, options, callback) {
if (callback !== undefined) {
(0, _rewriteStackTrace.beginHiddenCallStack)(createConfigItemRunner.errback)(target, options, callback);
} else if (typeof options === "function") {
(0, _rewriteStackTrace.beginHiddenCallStack)(createConfigItemRunner.errback)(target, undefined, callback);
} else {
return createConfigItemSync(target, options);
}
}
0 && 0;
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
-67
View File
@@ -1,67 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createConfigItem = createConfigItem;
exports.createItemFromDescriptor = createItemFromDescriptor;
exports.getItemDescriptor = getItemDescriptor;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _configDescriptors = require("./config-descriptors.js");
function createItemFromDescriptor(desc) {
return new ConfigItem(desc);
}
function* createConfigItem(value, {
dirname = ".",
type
} = {}) {
const descriptor = yield* (0, _configDescriptors.createDescriptor)(value, _path().resolve(dirname), {
type,
alias: "programmatic item"
});
return createItemFromDescriptor(descriptor);
}
const CONFIG_ITEM_BRAND = Symbol.for("@babel/core@7 - ConfigItem");
function getItemDescriptor(item) {
if (item != null && item[CONFIG_ITEM_BRAND]) {
return item._descriptor;
}
return undefined;
}
class ConfigItem {
constructor(descriptor) {
this._descriptor = void 0;
this[CONFIG_ITEM_BRAND] = true;
this.value = void 0;
this.options = void 0;
this.dirname = void 0;
this.name = void 0;
this.file = void 0;
this._descriptor = descriptor;
Object.defineProperty(this, "_descriptor", {
enumerable: false
});
Object.defineProperty(this, CONFIG_ITEM_BRAND, {
enumerable: false
});
this.value = this._descriptor.value;
this.options = this._descriptor.options;
this.dirname = this._descriptor.dirname;
this.name = this._descriptor.name;
this.file = this._descriptor.file ? {
request: this._descriptor.file.request,
resolved: this._descriptor.file.resolved
} : undefined;
Object.freeze(this);
}
}
Object.freeze(ConfigItem.prototype);
0 && 0;
//# sourceMappingURL=item.js.map
File diff suppressed because one or more lines are too long
-158
View File
@@ -1,158 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = loadPrivatePartialConfig;
exports.loadPartialConfig = loadPartialConfig;
function _path() {
const data = require("path");
_path = function () {
return data;
};
return data;
}
var _plugin = require("./plugin.js");
var _util = require("./util.js");
var _item = require("./item.js");
var _configChain = require("./config-chain.js");
var _environment = require("./helpers/environment.js");
var _options = require("./validation/options.js");
var _index = require("./files/index.js");
var _resolveTargets = require("./resolve-targets.js");
const _excluded = ["showIgnoredFiles"];
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
function resolveRootMode(rootDir, rootMode) {
switch (rootMode) {
case "root":
return rootDir;
case "upward-optional":
{
const upwardRootDir = (0, _index.findConfigUpwards)(rootDir);
return upwardRootDir === null ? rootDir : upwardRootDir;
}
case "upward":
{
const upwardRootDir = (0, _index.findConfigUpwards)(rootDir);
if (upwardRootDir !== null) return upwardRootDir;
throw Object.assign(new Error(`Babel was run with rootMode:"upward" but a root could not ` + `be found when searching upward from "${rootDir}".\n` + `One of the following config files must be in the directory tree: ` + `"${_index.ROOT_CONFIG_FILENAMES.join(", ")}".`), {
code: "BABEL_ROOT_NOT_FOUND",
dirname: rootDir
});
}
default:
throw new Error(`Assertion failure - unknown rootMode value.`);
}
}
function* loadPrivatePartialConfig(inputOpts) {
if (inputOpts != null && (typeof inputOpts !== "object" || Array.isArray(inputOpts))) {
throw new Error("Babel options must be an object, null, or undefined");
}
const args = inputOpts ? (0, _options.validate)("arguments", inputOpts) : {};
const {
envName = (0, _environment.getEnv)(),
cwd = ".",
root: rootDir = ".",
rootMode = "root",
caller,
cloneInputAst = true
} = args;
const absoluteCwd = _path().resolve(cwd);
const absoluteRootDir = resolveRootMode(_path().resolve(absoluteCwd, rootDir), rootMode);
const filename = typeof args.filename === "string" ? _path().resolve(cwd, args.filename) : undefined;
const showConfigPath = yield* (0, _index.resolveShowConfigPath)(absoluteCwd);
const context = {
filename,
cwd: absoluteCwd,
root: absoluteRootDir,
envName,
caller,
showConfig: showConfigPath === filename
};
const configChain = yield* (0, _configChain.buildRootChain)(args, context);
if (!configChain) return null;
const merged = {
assumptions: {}
};
configChain.options.forEach(opts => {
(0, _util.mergeOptions)(merged, opts);
});
const options = Object.assign({}, merged, {
targets: (0, _resolveTargets.resolveTargets)(merged, absoluteRootDir),
cloneInputAst,
babelrc: false,
configFile: false,
browserslistConfigFile: false,
passPerPreset: false,
envName: context.envName,
cwd: context.cwd,
root: context.root,
rootMode: "root",
filename: typeof context.filename === "string" ? context.filename : undefined,
plugins: configChain.plugins.map(descriptor => (0, _item.createItemFromDescriptor)(descriptor)),
presets: configChain.presets.map(descriptor => (0, _item.createItemFromDescriptor)(descriptor))
});
return {
options,
context,
fileHandling: configChain.fileHandling,
ignore: configChain.ignore,
babelrc: configChain.babelrc,
config: configChain.config,
files: configChain.files
};
}
function* loadPartialConfig(opts) {
let showIgnoredFiles = false;
if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
var _opts = opts;
({
showIgnoredFiles
} = _opts);
opts = _objectWithoutPropertiesLoose(_opts, _excluded);
_opts;
}
const result = yield* loadPrivatePartialConfig(opts);
if (!result) return null;
const {
options,
babelrc,
ignore,
config,
fileHandling,
files
} = result;
if (fileHandling === "ignored" && !showIgnoredFiles) {
return null;
}
(options.plugins || []).forEach(item => {
if (item.value instanceof _plugin.default) {
throw new Error("Passing cached plugin instances is not supported in " + "babel.loadPartialConfig()");
}
});
return new PartialConfig(options, babelrc ? babelrc.filepath : undefined, ignore ? ignore.filepath : undefined, config ? config.filepath : undefined, fileHandling, files);
}
class PartialConfig {
constructor(options, babelrc, ignore, config, fileHandling, files) {
this.options = void 0;
this.babelrc = void 0;
this.babelignore = void 0;
this.config = void 0;
this.fileHandling = void 0;
this.files = void 0;
this.options = options;
this.babelignore = ignore;
this.babelrc = babelrc;
this.config = config;
this.fileHandling = fileHandling;
this.files = files;
Object.freeze(this);
}
hasFilesystemConfig() {
return this.babelrc !== undefined || this.config !== undefined;
}
}
Object.freeze(PartialConfig.prototype);
0 && 0;
//# sourceMappingURL=partial.js.map
File diff suppressed because one or more lines are too long

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