Add CI pipeline, pre-commit hooks, and offline caching
CI / lint-typecheck-test (push) Has been cancelled
CI / lint-typecheck-test (push) Has been cancelled
Configure GitHub Actions for linting, typechecking, and testing. Add Husky and lint-staged for pre-commit checks. Implement API response caching in AsyncStorage for offline support. Move core tests to packages/core and add vitest configuration.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# EditorConfig for TimeToLeave
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,35 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
jobs:
|
||||
lint-typecheck-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
- name: Build web
|
||||
run: npm run build
|
||||
@@ -0,0 +1 @@
|
||||
npx lint-staged
|
||||
+68
-60
@@ -1,79 +1,87 @@
|
||||
# TimeToLeave - Post-MVP Improvements Plan
|
||||
|
||||
> **Last updated:** 2026-05-19
|
||||
> This plan reflects the current state of the codebase after the initial MVP and mobile rewrite.
|
||||
|
||||
## Already Implemented (No Longer TODO)
|
||||
|
||||
The following items from earlier versions of this plan have been completed and are in production:
|
||||
|
||||
- **Native Calendar Import** — `expo-calendar` integration with device calendar sync, permission requests, and multiple calendar source selection.
|
||||
- **Push / Local Notifications** — `expo-notifications` with local notification scheduling. Server-side push (FCM/APNs) for live journey updates remains deferred.
|
||||
- **Dark / Light Theming** — System theme following and manual dark/light toggle on both web and mobile.
|
||||
- **Auto-Refresh** — `useFocusEffect`-based refresh on mobile when returning to foreground.
|
||||
|
||||
---
|
||||
|
||||
## High Priority
|
||||
|
||||
### 1. Native Calendar Import
|
||||
- Integrate with expo-calendar or react-native-calendar-events
|
||||
- Request calendar permissions
|
||||
- Auto-sync events from Google/Apple calendars
|
||||
- Support multiple calendar sources
|
||||
### 1. Offline-First Architecture
|
||||
- Cache journey and route data in `AsyncStorage` / `localStorage` for offline viewing.
|
||||
- Background sync when connection is restored.
|
||||
- Add explicit "offline mode" UI indicators.
|
||||
- Conflict resolution for concurrent event edits.
|
||||
|
||||
### 2. Push Notifications
|
||||
- Implement FCM for Android and APNs for iOS
|
||||
- Server-side notification triggers for journey changes
|
||||
- Real-time updates when train status changes
|
||||
- Fallback to local notifications when offline
|
||||
### 2. Real Map Integration
|
||||
- Integrate `expo-maps` / `@vis.gl/react-google-maps` for visual route display.
|
||||
- Show origin, destination, and train stations on a map.
|
||||
- Display bike route with turn-by-turn directions.
|
||||
- Alternative route suggestions (e.g., faster vs. fewer changes).
|
||||
|
||||
### 3. Offline-First Architecture
|
||||
- Use expo-sqlite for local caching
|
||||
- Cache journey data for offline access
|
||||
- Background sync when connection restored
|
||||
- Conflict resolution for concurrent edits
|
||||
### 3. Multiple Origins Support
|
||||
- Allow different origins per event (Home, Work, Custom presets).
|
||||
- Quick origin switching in event detail and settings.
|
||||
- Store origin presets in persistent settings.
|
||||
|
||||
---
|
||||
|
||||
## Medium Priority
|
||||
|
||||
### 4. Real Map Integration
|
||||
- Integrate expo-maps for visual route display
|
||||
- Show train stations on map
|
||||
- Display bike route with turn-by-turn directions
|
||||
- Alternative route suggestions
|
||||
### 4. Server-Side Push Notifications
|
||||
- Implement FCM for Android and APNs for iOS for real-time journey disruption alerts.
|
||||
- Real-time delay/cancellation push notifications.
|
||||
- Fallback to local notifications when the server is unreachable.
|
||||
|
||||
### 5. Multiple Origins Support
|
||||
- Allow different origins per event
|
||||
- Home/Work/Custom origin presets
|
||||
- Quick origin switching in event detail
|
||||
### 5. Accessibility
|
||||
- TalkBack / VoiceOver screen reader support on mobile.
|
||||
- Dynamic type scaling.
|
||||
- High contrast mode.
|
||||
- WCAG 2.1 AA compliance audit on web.
|
||||
|
||||
### 6. Auto-Refresh
|
||||
- Refresh journey data when returning to app
|
||||
- Background refresh for active events
|
||||
- Configurable refresh intervals
|
||||
### 6. Analytics & Crash Reporting
|
||||
- Integrate Sentry for error tracking on web and mobile.
|
||||
- Opt-in usage analytics.
|
||||
- Performance monitoring (Web Vitals, React Native startup time).
|
||||
- In-app user feedback collection.
|
||||
|
||||
---
|
||||
|
||||
## Lower Priority
|
||||
|
||||
### 7. Accessibility
|
||||
- TalkBack/VoiceOver support
|
||||
- Dynamic type scaling
|
||||
- High contrast mode
|
||||
- Screen reader optimizations
|
||||
### 7. Advanced Features
|
||||
- Shared events with friends / family (collaborative departure planning).
|
||||
- Recurring event templates.
|
||||
- Journey history and statistics dashboard.
|
||||
- Export / import event data (ICS, JSON).
|
||||
|
||||
### 8. Theming
|
||||
- Dark mode support
|
||||
- System theme following
|
||||
- Custom color schemes
|
||||
- Accessibility-compliant color contrasts
|
||||
---
|
||||
|
||||
### 9. Analytics & Crash Reporting
|
||||
- Sentry or similar for error tracking
|
||||
- Usage analytics (opt-in)
|
||||
- Performance monitoring
|
||||
- User feedback collection
|
||||
## Technical Debt & Quality
|
||||
|
||||
### 10. Advanced Features
|
||||
- Shared events with friends/family
|
||||
- Recurring event templates
|
||||
- Journey history and statistics
|
||||
- Export/import event data
|
||||
### 8. Code Quality
|
||||
- Extract shared hooks to `packages/hooks` (deduplicate web and mobile hook implementations).
|
||||
- Increase unit test coverage across all packages.
|
||||
- Add Playwright E2E tests for web critical flows.
|
||||
- Add Detox E2E tests for mobile critical flows.
|
||||
- Bundle size analysis and reduction.
|
||||
|
||||
## Technical Debt
|
||||
### 9. Developer Experience
|
||||
- Consolidate ESLint to Flat Config everywhere.
|
||||
- Add pre-commit hooks (`husky` + `lint-staged`).
|
||||
- Add GitHub Actions CI pipeline.
|
||||
- Architecture Decision Records (ADRs) in `docs/adr/`.
|
||||
|
||||
### 11. Code Quality
|
||||
- More comprehensive test coverage
|
||||
- E2E tests for critical flows
|
||||
- Performance optimization
|
||||
- Bundle size reduction
|
||||
|
||||
### 12. Documentation
|
||||
- User documentation
|
||||
- API documentation
|
||||
- Contributing guidelines
|
||||
- Architecture decisions (ADRs)
|
||||
### 10. Documentation
|
||||
- Keep `POST_MVP_PLAN.md` and `CHECKLIST.md` in sync with reality.
|
||||
- Contributing guidelines (`CONTRIBUTING.md`).
|
||||
- API versioning policy once the backend grows.
|
||||
|
||||
-2434
File diff suppressed because it is too large
Load Diff
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['expo'],
|
||||
rules: {
|
||||
'react-native/no-inline-styles': 'off',
|
||||
},
|
||||
ignorePatterns: ['node_modules/', '.expo/', 'dist/'],
|
||||
};
|
||||
@@ -5,4 +5,7 @@ module.exports = {
|
||||
'^@react-native-async-storage/async-storage$':
|
||||
'@react-native-async-storage/async-storage/jest/async-storage-mock',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*|@fortawesome/.*)',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -12,6 +12,14 @@ import { faArrowsRotate, faBicycle, faTrain, faTriangleExclamation } from '@fort
|
||||
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import type { RouteProp } from '@react-navigation/native';
|
||||
import { loadEvents, loadOriginStation, loadNotificationSettings } from '../store/eventStore';
|
||||
import {
|
||||
getCachedJourneys,
|
||||
getCachedBikeRoute,
|
||||
getCachedWalkRoute,
|
||||
setCachedJourneys,
|
||||
setCachedBikeRoute,
|
||||
setCachedWalkRoute,
|
||||
} from '../store/apiCache';
|
||||
import { api } from '../services/api';
|
||||
import type { Journey, BikeRoute, Station, Event as CalendarEvent, WalkRoute } from '@timetoleave/core';
|
||||
import { useDestinationStation } from '../hooks/useDestinationStation';
|
||||
@@ -79,6 +87,15 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
||||
const fetchData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Preload cached data immediately so the UI isn't empty
|
||||
const cachedJourneys = await getCachedJourneys(eventId);
|
||||
const cachedBike = await getCachedBikeRoute(eventId);
|
||||
const cachedWalk = await getCachedWalkRoute(eventId);
|
||||
if (cachedJourneys) setJourneys(cachedJourneys);
|
||||
if (cachedBike) setBikeRoute(cachedBike);
|
||||
if (cachedWalk) setWalkRoute(cachedWalk);
|
||||
|
||||
try {
|
||||
const [events, originStation, settings] = await Promise.all([
|
||||
loadEvents(),
|
||||
@@ -111,8 +128,14 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
||||
|
||||
const walkDurationSeconds = settings.showWalkingOption ? (walkHook.walkRoute?.duration ?? 0) : 0;
|
||||
const target = stationArrivalTarget(found.eventTime, settings.arrivalBufferMinutes, walkDurationSeconds);
|
||||
try {
|
||||
const results = await api.searchJourneys(originStation.extId, destExtId, target, { arriveBy: true });
|
||||
setJourneys(results);
|
||||
await setCachedJourneys(eventId, results);
|
||||
} catch {
|
||||
if (!cachedJourneys) throw new Error('Failed to load journeys');
|
||||
// Keep stale data, mark as offline
|
||||
}
|
||||
} else if (destStation.error) {
|
||||
setError(`Destination station not resolvable: ${destStation.error}`);
|
||||
}
|
||||
@@ -125,15 +148,22 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
||||
destCoords.coords.lat, destCoords.coords.lng,
|
||||
);
|
||||
setBikeRoute(bike);
|
||||
await setCachedBikeRoute(eventId, bike);
|
||||
}
|
||||
} catch {
|
||||
setBikeRoute(null);
|
||||
if (!cachedBike) setBikeRoute(null);
|
||||
} finally {
|
||||
setLoadingBike(false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Error loading');
|
||||
const msg = err instanceof Error ? err.message : 'Error loading';
|
||||
// If we have any cached data, show a soft offline warning instead of a hard error
|
||||
if (cachedJourneys || cachedBike || cachedWalk) {
|
||||
setError(`${msg} (showing cached data)`);
|
||||
} else {
|
||||
setError(msg);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -144,7 +174,10 @@ export function EventDetailScreen({ navigation, route }: ScreenProps) {
|
||||
useEffect(() => {
|
||||
setWalkRoute(walkHook.walkRoute);
|
||||
setLoadingWalk(walkHook.loading);
|
||||
}, [walkHook.walkRoute, walkHook.loading]);
|
||||
if (walkHook.walkRoute) {
|
||||
setCachedWalkRoute(eventId, walkHook.walkRoute).catch(() => {});
|
||||
}
|
||||
}, [walkHook.walkRoute, walkHook.loading, eventId]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
setBikeRoute(null);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Offline cache for API responses.
|
||||
*
|
||||
* Stores the last successful journey, bike-route, and walk-route results
|
||||
* per event (keyed by event id) so the detail screen can show stale data
|
||||
* when the device is offline.
|
||||
*/
|
||||
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import type { Journey, BikeRoute, WalkRoute } from '@timetoleave/core';
|
||||
|
||||
const CACHE_PREFIX = '@timetoleave_cache_';
|
||||
const MAX_AGE_MS = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
interface CacheEntry<T> {
|
||||
data: T;
|
||||
ts: number;
|
||||
}
|
||||
|
||||
async function getCache<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(CACHE_PREFIX + key);
|
||||
if (!raw) return null;
|
||||
const entry: CacheEntry<T> = JSON.parse(raw);
|
||||
if (Date.now() - entry.ts > MAX_AGE_MS) return null;
|
||||
return entry.data;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function setCache<T>(key: string, data: T): Promise<void> {
|
||||
try {
|
||||
const entry: CacheEntry<T> = { data, ts: Date.now() };
|
||||
await AsyncStorage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));
|
||||
} catch {
|
||||
// Silently fail on quota exceeded / privacy mode
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCachedJourneys(eventId: string): Promise<Journey[] | null> {
|
||||
const raw = await getCache<Array<Omit<Journey, 'sD' | 'rD' | 'sA' | 'rA'> & { sD: string; rD: string; sA: string; rA: string }>>(`journeys_${eventId}`);
|
||||
if (!raw) return null;
|
||||
return raw.map((j) => ({
|
||||
...j,
|
||||
sD: new Date(j.sD),
|
||||
rD: new Date(j.rD),
|
||||
sA: new Date(j.sA),
|
||||
rA: new Date(j.rA),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function setCachedJourneys(eventId: string, journeys: Journey[]): Promise<void> {
|
||||
const serializable = journeys.map((j) => ({
|
||||
...j,
|
||||
sD: j.sD.toISOString(),
|
||||
rD: j.rD.toISOString(),
|
||||
sA: j.sA.toISOString(),
|
||||
rA: j.rA.toISOString(),
|
||||
}));
|
||||
await setCache(`journeys_${eventId}`, serializable);
|
||||
}
|
||||
|
||||
export async function getCachedBikeRoute(eventId: string): Promise<BikeRoute | null> {
|
||||
return getCache(`bike_${eventId}`);
|
||||
}
|
||||
|
||||
export async function setCachedBikeRoute(eventId: string, route: BikeRoute): Promise<void> {
|
||||
await setCache(`bike_${eventId}`, route);
|
||||
}
|
||||
|
||||
export async function getCachedWalkRoute(eventId: string): Promise<WalkRoute | null> {
|
||||
return getCache(`walk_${eventId}`);
|
||||
}
|
||||
|
||||
export async function setCachedWalkRoute(eventId: string, route: WalkRoute): Promise<void> {
|
||||
await setCache(`walk_${eventId}`, route);
|
||||
}
|
||||
|
||||
export async function clearApiCache(): Promise<void> {
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const cacheKeys = keys.filter((k) => k.startsWith(CACHE_PREFIX));
|
||||
await AsyncStorage.multiRemove(cacheKeys);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['next/core-web-vitals'],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
ignorePatterns: ['node_modules/', '.next/', 'out/', 'dist/'],
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useEventsStore } from "@/hooks/useEventsStore";
|
||||
import { useOriginStation } from "@/hooks/useOriginStation";
|
||||
import EventCard from "@/app/event/EventCard";
|
||||
@@ -15,9 +16,13 @@ export default function Home() {
|
||||
const { events } = useEventsStore();
|
||||
const { station: originStation } = useOriginStation();
|
||||
|
||||
const upcoming = events
|
||||
const upcoming = useMemo(
|
||||
() =>
|
||||
events
|
||||
.filter((e) => e.eventTime >= new Date())
|
||||
.sort((a, b) => a.eventTime.getTime() - b.eventTime.getTime());
|
||||
.sort((a, b) => a.eventTime.getTime() - b.eventTime.getTime()),
|
||||
[events]
|
||||
);
|
||||
const nextEvent = upcoming[0] ?? null;
|
||||
|
||||
return (
|
||||
|
||||
Generated
+528
-14
@@ -11,19 +11,9 @@
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"expo": "~54.0.34",
|
||||
"expo-calendar": "~15.0.8",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-location": "~19.0.8",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "^15.15.5"
|
||||
"devDependencies": {
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^17.0.5"
|
||||
}
|
||||
},
|
||||
"apps/mobile": {
|
||||
@@ -7464,6 +7454,69 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
|
||||
"integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^8.0.0",
|
||||
"string-width": "^8.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/string-width": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
|
||||
"integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.5.0",
|
||||
"strip-ansi": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
@@ -8312,6 +8365,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
|
||||
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
@@ -9018,6 +9084,13 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||
@@ -9859,6 +9932,19 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
||||
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -10324,6 +10410,22 @@
|
||||
"integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "9.1.7",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"husky": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -13151,6 +13253,125 @@
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "17.0.5",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.5.tgz",
|
||||
"integrity": "sha512-d12yC+/e8RhBjZtaxZn71FyrgU/P5e+uAPifhCLwdosQZP/zamSdKRWDC30ocVIbzDKiFG1McHc/LUgB92GIPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"listr2": "^10.2.1",
|
||||
"picomatch": "^4.0.4",
|
||||
"string-argv": "^0.3.2",
|
||||
"tinyexec": "^1.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.22.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/lint-staged"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"yaml": "^2.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz",
|
||||
"integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cli-truncate": "^5.2.0",
|
||||
"eventemitter3": "^5.0.4",
|
||||
"log-update": "^6.1.0",
|
||||
"rfdc": "^1.4.1",
|
||||
"wrap-ansi": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/string-width": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
|
||||
"integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.5.0",
|
||||
"strip-ansi": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2/node_modules/wrap-ansi": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz",
|
||||
"integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.3",
|
||||
"string-width": "^8.2.0",
|
||||
"strip-ansi": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -13283,6 +13504,222 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/ansi-escapes": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
|
||||
"integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/cli-cursor": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/emoji-regex": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/restore-cursor": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/wrap-ansi": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"string-width": "^7.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@@ -13855,6 +14292,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
@@ -15758,6 +16208,13 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@@ -16392,6 +16849,52 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
|
||||
"integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.3",
|
||||
"is-fullwidth-code-point": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/slugify": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz",
|
||||
@@ -16597,6 +17100,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/string-argv": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
||||
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/string-length": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||
@@ -18410,7 +18923,8 @@
|
||||
"date-fns": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5"
|
||||
"typescript": "^5",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-16
@@ -12,29 +12,24 @@
|
||||
"start": "npm run start -w apps/web",
|
||||
"lint": "npm run lint -w apps/web && npm run lint -w apps/mobile && npm run lint -w packages/core && npm run lint -w packages/api-client",
|
||||
"typecheck": "npm run typecheck -w apps/web && npm run typecheck -w apps/mobile && npm run typecheck -w packages/core && npm run typecheck -w packages/api-client",
|
||||
"test": "npm run test -w apps/web && npm run test -w apps/mobile",
|
||||
"test": "npm run test -w apps/web && npm run test -w apps/mobile && npm run test -w packages/core",
|
||||
"dev:mobile": "npm run start -w apps/mobile",
|
||||
"typecheck:mobile": "npm run typecheck -w apps/mobile",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios"
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"bash -c 'npm run typecheck'"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-test-renderer": "19.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"expo": "~54.0.34",
|
||||
"expo-calendar": "~15.0.8",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-location": "~19.0.8",
|
||||
"expo-notifications": "~0.32.17",
|
||||
"expo-status-bar": "~3.0.9",
|
||||
"react": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "^15.15.5"
|
||||
"devDependencies": {
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^17.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src/"
|
||||
"lint": "eslint src/",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5"
|
||||
"typescript": "^5",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { calculateCountdown } from "@timetoleave/core";
|
||||
import { calculateCountdown } from "../countdown-utils";
|
||||
|
||||
describe("countdown-utils", () => {
|
||||
describe("calculateCountdown", () => {
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parseHafasTime, hafasDateTime } from "@timetoleave/core";
|
||||
import { parseHafasTime, hafasDateTime } from "../hafas-time";
|
||||
|
||||
describe("getTimezoneOffsetMinutes (via parseHafasTime)", () => {
|
||||
/*
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "node",
|
||||
globals: true,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user