mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
chore: update all dependencies (#1209)
* chore: update all dependencies
* chore: remove lint errors
* fix(client): restore typecheck after dependency bump
vitest 4 types vi.fn() as Mock<Procedure | Constructable>, which no
longer assigns to the strictly-typed onUpdate prop; type the mock
explicitly. TS6 + the new transitive @types/node 25 stopped auto-
including node builtin module types, so import('node:buffer') failed;
add @types/node as a direct client devDependency and a scoped node
type reference in the one test that needs it.
* test: fix constructor mocks for vitest 4 Reflect.construct semantics
vitest 4 resolves new-invoked mocks via Reflect.construct, which rejects
arrow-function implementations (including mockReturnValue sugar) as
non-constructable. Convert mapbox-gl and better-sqlite3 mocks that the
code instantiates with new to regular function implementations.
This commit is contained in:
+6
-5
@@ -58,11 +58,12 @@
|
|||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||||
"@types/leaflet": "^1.9.8",
|
"@types/leaflet": "^1.9.8",
|
||||||
|
"@types/node": "^25.9.3",
|
||||||
"@types/react": "^19.2.15",
|
"@types/react": "^19.2.15",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-window": "^1.8.8",
|
"@types/react-window": "^1.8.8",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^4.1.9",
|
||||||
"autoprefixer": "^10.4.18",
|
"autoprefixer": "^10.4.18",
|
||||||
"eslint": "^10.2.1",
|
"eslint": "^10.2.1",
|
||||||
"eslint-config-flat-gitignore": "^2.3.0",
|
"eslint-config-flat-gitignore": "^2.3.0",
|
||||||
@@ -80,8 +81,8 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.2",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.58.2",
|
||||||
"vite": "^5.1.4",
|
"vite": "^8.0.16",
|
||||||
"vite-plugin-pwa": "^0.21.0",
|
"vite-plugin-pwa": "^1.3.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// FE-COMP-MDTOOLBAR-001 to FE-COMP-MDTOOLBAR-006
|
// FE-COMP-MDTOOLBAR-001 to FE-COMP-MDTOOLBAR-006
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||||
import { render, screen, fireEvent } from '../../../tests/helpers/render';
|
import { render, screen, fireEvent } from '../../../tests/helpers/render';
|
||||||
import MarkdownToolbar from './MarkdownToolbar';
|
import MarkdownToolbar from './MarkdownToolbar';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -16,10 +16,10 @@ function createTextareaRef(value = '', selectionStart = 0, selectionEnd = 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('MarkdownToolbar', () => {
|
describe('MarkdownToolbar', () => {
|
||||||
let onUpdate: ReturnType<typeof vi.fn>;
|
let onUpdate: Mock<(value: string) => void>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
onUpdate = vi.fn();
|
onUpdate = vi.fn<(value: string) => void>();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('FE-COMP-MDTOOLBAR-001: renders all 8 toolbar buttons', () => {
|
it('FE-COMP-MDTOOLBAR-001: renders all 8 toolbar buttons', () => {
|
||||||
|
|||||||
@@ -31,21 +31,29 @@ const glMap = vi.hoisted(() => ({
|
|||||||
vi.mock('mapbox-gl', () => ({
|
vi.mock('mapbox-gl', () => ({
|
||||||
default: {
|
default: {
|
||||||
accessToken: '',
|
accessToken: '',
|
||||||
Map: vi.fn(() => glMap),
|
Map: vi.fn(function () {
|
||||||
Marker: vi.fn(() => ({
|
return glMap
|
||||||
|
}),
|
||||||
|
Marker: vi.fn(function () {
|
||||||
|
return {
|
||||||
setLngLat: vi.fn().mockReturnThis(),
|
setLngLat: vi.fn().mockReturnThis(),
|
||||||
addTo: vi.fn().mockReturnThis(),
|
addTo: vi.fn().mockReturnThis(),
|
||||||
remove: vi.fn(),
|
remove: vi.fn(),
|
||||||
getElement: vi.fn(() => document.createElement('div')),
|
getElement: vi.fn(() => document.createElement('div')),
|
||||||
})),
|
}
|
||||||
LngLatBounds: vi.fn(() => ({ extend: vi.fn().mockReturnThis() })),
|
}),
|
||||||
|
LngLatBounds: vi.fn(function () {
|
||||||
|
return { extend: vi.fn().mockReturnThis() }
|
||||||
|
}),
|
||||||
NavigationControl: vi.fn(),
|
NavigationControl: vi.fn(),
|
||||||
Popup: vi.fn(() => ({
|
Popup: vi.fn(function () {
|
||||||
|
return {
|
||||||
setLngLat: vi.fn().mockReturnThis(),
|
setLngLat: vi.fn().mockReturnThis(),
|
||||||
setHTML: vi.fn().mockReturnThis(),
|
setHTML: vi.fn().mockReturnThis(),
|
||||||
addTo: vi.fn().mockReturnThis(),
|
addTo: vi.fn().mockReturnThis(),
|
||||||
remove: vi.fn(),
|
remove: vi.fn(),
|
||||||
})),
|
}
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
vi.mock('mapbox-gl/dist/mapbox-gl.css', () => ({}))
|
vi.mock('mapbox-gl/dist/mapbox-gl.css', () => ({}))
|
||||||
@@ -63,7 +71,9 @@ vi.mock('./locationMarkerMapbox', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('./reservationsMapbox', () => ({
|
vi.mock('./reservationsMapbox', () => ({
|
||||||
ReservationMapboxOverlay: vi.fn().mockImplementation(() => ({ update: vi.fn() })),
|
ReservationMapboxOverlay: vi.fn(function () {
|
||||||
|
return { update: vi.fn() }
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../../hooks/useGeolocation', () => ({
|
vi.mock('../../hooks/useGeolocation', () => ({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { server } from '../../helpers/msw/server';
|
import { server } from '../../helpers/msw/server';
|
||||||
|
|||||||
Generated
+5549
-2226
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -25,7 +25,7 @@
|
|||||||
"format:check": "npm run format:check --workspace=shared && npm run format:check --workspace=server && npm run format:check --workspace=client"
|
"format:check": "npm run format:check --workspace=shared && npm run format:check --workspace=server && npm run format:check --workspace=client"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.2.1"
|
"concurrently": "^10.0.3"
|
||||||
},
|
},
|
||||||
"comment:overrides": "Force a single React 19 across the workspace so the test renderer (@testing-library/react) and the app share one react-dom.",
|
"comment:overrides": "Force a single React 19 across the workspace so the test renderer (@testing-library/react) and the app share one react-dom.",
|
||||||
"overrides": {
|
"overrides": {
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
"react-dom": "19.2.6"
|
"react-dom": "19.2.6"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-linux-x64-musl": "4.60.4",
|
"@rollup/rollup-linux-x64-musl": "4.62.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.60.4",
|
"@rollup/rollup-linux-arm64-musl": "4.62.0",
|
||||||
"@img/sharp-linuxmusl-x64": "0.33.5",
|
"@img/sharp-linuxmusl-x64": "0.35.1",
|
||||||
"@img/sharp-linuxmusl-arm64": "0.33.5"
|
"@img/sharp-linuxmusl-arm64": "0.35.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -94,11 +94,11 @@
|
|||||||
"@types/unzipper": "^0.10.11",
|
"@types/unzipper": "^0.10.11",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^4.1.9",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
"tz-lookup": "^6.1.25",
|
"tz-lookup": "^6.1.25",
|
||||||
"unplugin-swc": "^1.5.9",
|
"unplugin-swc": "^1.5.9",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+811
-255
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
|||||||
|
import { ADDON_IDS } from '../../addons';
|
||||||
import { db } from '../../db/database';
|
import { db } from '../../db/database';
|
||||||
import { logError, logInfo } from '../auditLog';
|
|
||||||
import { broadcast } from '../../websocket';
|
import { broadcast } from '../../websocket';
|
||||||
import { isAddonEnabled } from '../adminService';
|
import { isAddonEnabled } from '../adminService';
|
||||||
import { ADDON_IDS } from '../../addons';
|
import { logError, logInfo } from '../auditLog';
|
||||||
import { getReservation, getReservationWithJoins, updateReservation } from '../reservationService';
|
import { getReservation, getReservationWithJoins, updateReservation } from '../reservationService';
|
||||||
import { getAirtrailCredentials } from './airtrailService';
|
|
||||||
import {
|
import {
|
||||||
AirtrailAuthError,
|
AirtrailAuthError,
|
||||||
AirtrailCreds,
|
AirtrailCreds,
|
||||||
@@ -15,6 +14,7 @@ import {
|
|||||||
saveFlight,
|
saveFlight,
|
||||||
} from './airtrailClient';
|
} from './airtrailClient';
|
||||||
import { canonicalHash, mapFlightToReservation } from './airtrailMapper';
|
import { canonicalHash, mapFlightToReservation } from './airtrailMapper';
|
||||||
|
import { getAirtrailCredentials } from './airtrailService';
|
||||||
|
|
||||||
/** Global on/off: the addon must be enabled and sync not explicitly turned off. */
|
/** Global on/off: the addon must be enabled and sync not explicitly turned off. */
|
||||||
export function syncGloballyEnabled(): boolean {
|
export function syncGloballyEnabled(): boolean {
|
||||||
@@ -59,7 +59,7 @@ async function syncOwner(uid: number): Promise<number> {
|
|||||||
if (err instanceof AirtrailAuthError) logError(`AirTrail sync: invalid API key for user ${uid}`);
|
if (err instanceof AirtrailAuthError) logError(`AirTrail sync: invalid API key for user ${uid}`);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const byId = new Map(flights.map(f => [String(f.id), f]));
|
const byId = new Map(flights.map((f) => [String(f.id), f]));
|
||||||
|
|
||||||
const linked = db
|
const linked = db
|
||||||
.prepare(
|
.prepare(
|
||||||
@@ -145,15 +145,15 @@ function splitLocal(dt: string | null | undefined): { date: string | null; time:
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildSavePayload(reservation: any, existing: AirtrailFlightRaw): AirtrailSavePayload | null {
|
function buildSavePayload(reservation: any, existing: AirtrailFlightRaw): AirtrailSavePayload | null {
|
||||||
let meta: Record<string, any> = {};
|
let meta: Record<string, any>;
|
||||||
try {
|
try {
|
||||||
meta = reservation.metadata ? JSON.parse(reservation.metadata) : {};
|
meta = reservation.metadata ? JSON.parse(reservation.metadata) : {};
|
||||||
} catch {
|
} catch {
|
||||||
meta = {};
|
meta = {};
|
||||||
}
|
}
|
||||||
const endpoints: any[] = reservation.endpoints || [];
|
const endpoints: any[] = reservation.endpoints || [];
|
||||||
const fromEp = endpoints.find(e => e.role === 'from');
|
const fromEp = endpoints.find((e) => e.role === 'from');
|
||||||
const toEp = endpoints.find(e => e.role === 'to');
|
const toEp = endpoints.find((e) => e.role === 'to');
|
||||||
const fromCode = fromEp?.code || existing.from?.iata || existing.from?.icao || null;
|
const fromCode = fromEp?.code || existing.from?.iata || existing.from?.icao || null;
|
||||||
const toCode = toEp?.code || existing.to?.iata || existing.to?.icao || null;
|
const toCode = toEp?.code || existing.to?.iata || existing.to?.icao || null;
|
||||||
if (!fromCode || !toCode) return null;
|
if (!fromCode || !toCode) return null;
|
||||||
@@ -164,7 +164,7 @@ function buildSavePayload(reservation: any, existing: AirtrailFlightRaw): Airtra
|
|||||||
|
|
||||||
// Preserve the existing seat manifest (an update replaces all seats); fall back
|
// Preserve the existing seat manifest (an update replaces all seats); fall back
|
||||||
// to the key-owner placeholder so AirTrail attributes it to the connecting user.
|
// to the key-owner placeholder so AirTrail attributes it to the connecting user.
|
||||||
const seats = (existing.seats ?? []).map(s => ({
|
const seats = (existing.seats ?? []).map((s) => ({
|
||||||
userId: s.userId,
|
userId: s.userId,
|
||||||
guestName: s.guestName,
|
guestName: s.guestName,
|
||||||
seat: s.seat,
|
seat: s.seat,
|
||||||
@@ -179,7 +179,7 @@ function buildSavePayload(reservation: any, existing: AirtrailFlightRaw): Airtra
|
|||||||
// a userId), leaving any co-passenger seats untouched.
|
// a userId), leaving any co-passenger seats untouched.
|
||||||
const seatNumber = typeof meta.seat === 'string' && meta.seat.trim() ? meta.seat.trim() : null;
|
const seatNumber = typeof meta.seat === 'string' && meta.seat.trim() ? meta.seat.trim() : null;
|
||||||
if (seatNumber) {
|
if (seatNumber) {
|
||||||
const ownSeat = seats.find(s => s.userId) ?? seats[0];
|
const ownSeat = seats.find((s) => s.userId) ?? seats[0];
|
||||||
if (ownSeat) ownSeat.seatNumber = seatNumber;
|
if (ownSeat) ownSeat.seatNumber = seatNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
|||||||
import path from 'node:path';
|
import { db } from '../db/database';
|
||||||
|
|
||||||
|
import { Jimp, JimpMime } from 'jimp';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import fsPromises from 'node:fs/promises';
|
import fsPromises from 'node:fs/promises';
|
||||||
import crypto from 'node:crypto';
|
import path from 'node:path';
|
||||||
import { Jimp, JimpMime } from 'jimp';
|
|
||||||
import { db } from '../db/database';
|
|
||||||
|
|
||||||
// Overridable for tests (mirrors the TREK_DB_FILE seam) so the suite never touches
|
// Overridable for tests (mirrors the TREK_DB_FILE seam) so the suite never touches
|
||||||
// the real uploads tree.
|
// the real uploads tree.
|
||||||
@@ -26,7 +27,9 @@ const knownOnDisk = new Set<string>();
|
|||||||
// Ensure upload dir exists once at startup — avoids sync FS calls inside put() on every write.
|
// Ensure upload dir exists once at startup — avoids sync FS calls inside put() on every write.
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(GOOGLE_PHOTO_DIR, { recursive: true });
|
fs.mkdirSync(GOOGLE_PHOTO_DIR, { recursive: true });
|
||||||
} catch { /* already exists */ }
|
} catch {
|
||||||
|
/* already exists */
|
||||||
|
}
|
||||||
|
|
||||||
function filePath(placeId: string): string {
|
function filePath(placeId: string): string {
|
||||||
// Hash to avoid filename collisions — coords:lat:lng pseudo-IDs contain characters that
|
// Hash to avoid filename collisions — coords:lat:lng pseudo-IDs contain characters that
|
||||||
@@ -46,9 +49,9 @@ interface CachedPhoto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function get(placeId: string): CachedPhoto | null {
|
export function get(placeId: string): CachedPhoto | null {
|
||||||
const row = db.prepare(
|
const row = db
|
||||||
'SELECT attribution FROM google_place_photo_meta WHERE place_id = ? AND error_at IS NULL'
|
.prepare('SELECT attribution FROM google_place_photo_meta WHERE place_id = ? AND error_at IS NULL')
|
||||||
).get(placeId) as { attribution: string | null } | undefined;
|
.get(placeId) as { attribution: string | null } | undefined;
|
||||||
|
|
||||||
if (!row) return null;
|
if (!row) return null;
|
||||||
|
|
||||||
@@ -68,9 +71,9 @@ export function get(placeId: string): CachedPhoto | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getErrored(placeId: string): boolean {
|
export function getErrored(placeId: string): boolean {
|
||||||
const row = db.prepare(
|
const row = db
|
||||||
'SELECT error_at FROM google_place_photo_meta WHERE place_id = ? AND error_at IS NOT NULL'
|
.prepare('SELECT error_at FROM google_place_photo_meta WHERE place_id = ? AND error_at IS NOT NULL')
|
||||||
).get(placeId) as { error_at: number } | undefined;
|
.get(placeId) as { error_at: number } | undefined;
|
||||||
|
|
||||||
if (!row) return false;
|
if (!row) return false;
|
||||||
return Date.now() - row.error_at < ERROR_TTL;
|
return Date.now() - row.error_at < ERROR_TTL;
|
||||||
@@ -79,7 +82,7 @@ export function getErrored(placeId: string): boolean {
|
|||||||
export function markError(placeId: string): void {
|
export function markError(placeId: string): void {
|
||||||
knownOnDisk.delete(placeId);
|
knownOnDisk.delete(placeId);
|
||||||
db.prepare(
|
db.prepare(
|
||||||
'INSERT OR REPLACE INTO google_place_photo_meta (place_id, attribution, fetched_at, error_at) VALUES (?, NULL, ?, ?)'
|
'INSERT OR REPLACE INTO google_place_photo_meta (place_id, attribution, fetched_at, error_at) VALUES (?, NULL, ?, ?)',
|
||||||
).run(placeId, Date.now(), Date.now());
|
).run(placeId, Date.now(), Date.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,21 +112,28 @@ export async function put(placeId: string, bytes: Buffer, attribution: string |
|
|||||||
knownOnDisk.add(placeId);
|
knownOnDisk.add(placeId);
|
||||||
|
|
||||||
db.prepare(
|
db.prepare(
|
||||||
'INSERT OR REPLACE INTO google_place_photo_meta (place_id, attribution, fetched_at, error_at) VALUES (?, ?, ?, NULL)'
|
'INSERT OR REPLACE INTO google_place_photo_meta (place_id, attribution, fetched_at, error_at) VALUES (?, ?, ?, NULL)',
|
||||||
).run(placeId, attribution, Date.now());
|
).run(placeId, attribution, Date.now());
|
||||||
|
|
||||||
return { photoUrl: proxyUrl(placeId), filePath: fp, attribution };
|
return { photoUrl: proxyUrl(placeId), filePath: fp, attribution };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInFlight(placeId: string): Promise<{ filePath: string; attribution: string | null } | null> | undefined {
|
export function getInFlight(
|
||||||
|
placeId: string,
|
||||||
|
): Promise<{ filePath: string; attribution: string | null } | null> | undefined {
|
||||||
return inFlight.get(placeId);
|
return inFlight.get(placeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setInFlight(placeId: string, promise: Promise<{ filePath: string; attribution: string | null } | null>): void {
|
export function setInFlight(
|
||||||
|
placeId: string,
|
||||||
|
promise: Promise<{ filePath: string; attribution: string | null } | null>,
|
||||||
|
): void {
|
||||||
inFlight.set(placeId, promise);
|
inFlight.set(placeId, promise);
|
||||||
promise
|
promise
|
||||||
.finally(() => inFlight.delete(placeId))
|
.finally(() => inFlight.delete(placeId))
|
||||||
.catch(() => { /* awaiter logs; this .catch only prevents unhandledRejection */ });
|
.catch(() => {
|
||||||
|
/* awaiter logs; this .catch only prevents unhandledRejection */
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serveFilePath(placeId: string): string | null {
|
export function serveFilePath(placeId: string): string | null {
|
||||||
@@ -138,14 +148,18 @@ export function serveFilePath(placeId: string): string | null {
|
|||||||
// Google place_id (the dedup key) or by the stable proxy URL stored in image_url
|
// Google place_id (the dedup key) or by the stable proxy URL stored in image_url
|
||||||
// (covers coords: pseudo-ids, which never have a google_place_id).
|
// (covers coords: pseudo-ids, which never have a google_place_id).
|
||||||
function isReferenced(placeId: string): boolean {
|
function isReferenced(placeId: string): boolean {
|
||||||
const row = db.prepare(
|
const row = db
|
||||||
'SELECT 1 FROM places WHERE google_place_id = ? OR image_url = ? LIMIT 1'
|
.prepare('SELECT 1 FROM places WHERE google_place_id = ? OR image_url = ? LIMIT 1')
|
||||||
).get(placeId, proxyUrl(placeId));
|
.get(placeId, proxyUrl(placeId));
|
||||||
return !!row;
|
return !!row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteEntry(placeId: string): void {
|
function deleteEntry(placeId: string): void {
|
||||||
try { fs.unlinkSync(filePath(placeId)); } catch { /* already gone */ }
|
try {
|
||||||
|
fs.unlinkSync(filePath(placeId));
|
||||||
|
} catch {
|
||||||
|
/* already gone */
|
||||||
|
}
|
||||||
db.prepare('DELETE FROM google_place_photo_meta WHERE place_id = ?').run(placeId);
|
db.prepare('DELETE FROM google_place_photo_meta WHERE place_id = ?').run(placeId);
|
||||||
knownOnDisk.delete(placeId);
|
knownOnDisk.delete(placeId);
|
||||||
}
|
}
|
||||||
@@ -175,11 +189,20 @@ export function sweepOrphans(): number {
|
|||||||
|
|
||||||
// Pass 2: files on disk that no surviving meta row maps to (e.g. left over from a
|
// Pass 2: files on disk that no surviving meta row maps to (e.g. left over from a
|
||||||
// crash between writeFile and the DB upsert, or a meta row deleted out-of-band).
|
// crash between writeFile and the DB upsert, or a meta row deleted out-of-band).
|
||||||
let entries: string[] = [];
|
let entries: string[];
|
||||||
try { entries = fs.readdirSync(GOOGLE_PHOTO_DIR); } catch { entries = []; }
|
try {
|
||||||
|
entries = fs.readdirSync(GOOGLE_PHOTO_DIR);
|
||||||
|
} catch {
|
||||||
|
entries = [];
|
||||||
|
}
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.endsWith('.jpg') || keepFiles.has(entry)) continue;
|
if (!entry.endsWith('.jpg') || keepFiles.has(entry)) continue;
|
||||||
try { fs.unlinkSync(path.join(GOOGLE_PHOTO_DIR, entry)); removed++; } catch { /* race */ }
|
try {
|
||||||
|
fs.unlinkSync(path.join(GOOGLE_PHOTO_DIR, entry));
|
||||||
|
removed++;
|
||||||
|
} catch {
|
||||||
|
/* race */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
|
|||||||
@@ -769,7 +769,9 @@ describe('BACKUP-042 restoreFromZip — integrity check fails', () => {
|
|||||||
}),
|
}),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
};
|
};
|
||||||
DatabaseMock.mockReturnValue(fakeDbInstance);
|
DatabaseMock.mockImplementation(function () {
|
||||||
|
return fakeDbInstance;
|
||||||
|
});
|
||||||
|
|
||||||
const result = await restoreFromZip('/data/tmp/upload.zip');
|
const result = await restoreFromZip('/data/tmp/upload.zip');
|
||||||
|
|
||||||
@@ -803,7 +805,9 @@ describe('BACKUP-043 restoreFromZip — missing required table', () => {
|
|||||||
}),
|
}),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
};
|
};
|
||||||
DatabaseMock.mockReturnValue(fakeDbInstance);
|
DatabaseMock.mockImplementation(function () {
|
||||||
|
return fakeDbInstance;
|
||||||
|
});
|
||||||
|
|
||||||
const result = await restoreFromZip('/data/tmp/upload.zip');
|
const result = await restoreFromZip('/data/tmp/upload.zip');
|
||||||
|
|
||||||
@@ -827,7 +831,7 @@ describe('BACKUP-044 restoreFromZip — Database constructor throws (invalid SQL
|
|||||||
);
|
);
|
||||||
fsMock.rmSync.mockReturnValue(undefined);
|
fsMock.rmSync.mockReturnValue(undefined);
|
||||||
|
|
||||||
DatabaseMock.mockImplementation(() => {
|
DatabaseMock.mockImplementation(function () {
|
||||||
throw new Error('file is not a database');
|
throw new Error('file is not a database');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -862,7 +866,9 @@ describe('BACKUP-045 restoreFromZip — full success path (no uploads)', () => {
|
|||||||
}),
|
}),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
};
|
};
|
||||||
DatabaseMock.mockReturnValue(fakeDbInstance);
|
DatabaseMock.mockImplementation(function () {
|
||||||
|
return fakeDbInstance;
|
||||||
|
});
|
||||||
return fakeDbInstance;
|
return fakeDbInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,7 +1003,9 @@ describe('BACKUP-046 restoreFromZip — with uploads directory', () => {
|
|||||||
}),
|
}),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
};
|
};
|
||||||
DatabaseMock.mockReturnValue(fakeDbInstance);
|
DatabaseMock.mockImplementation(function () {
|
||||||
|
return fakeDbInstance;
|
||||||
|
});
|
||||||
|
|
||||||
fsMock.existsSync.mockImplementation((p: string) => {
|
fsMock.existsSync.mockImplementation((p: string) => {
|
||||||
// travel.db present, extractedUploads present
|
// travel.db present, extractedUploads present
|
||||||
@@ -1052,7 +1060,9 @@ describe('BACKUP-046 restoreFromZip — with uploads directory', () => {
|
|||||||
}),
|
}),
|
||||||
close: vi.fn(),
|
close: vi.fn(),
|
||||||
};
|
};
|
||||||
DatabaseMock.mockReturnValue(fakeDbInstance);
|
DatabaseMock.mockImplementation(function () {
|
||||||
|
return fakeDbInstance;
|
||||||
|
});
|
||||||
|
|
||||||
fsMock.existsSync.mockImplementation((p: string) => {
|
fsMock.existsSync.mockImplementation((p: string) => {
|
||||||
if (String(p).endsWith('travel.db')) return true;
|
if (String(p).endsWith('travel.db')) return true;
|
||||||
|
|||||||
+32
-17
@@ -5,38 +5,53 @@
|
|||||||
"description": "Shared API contracts (Zod schemas) — single source of truth for TREK server and client.",
|
"description": "Shared API contracts (Zod schemas) — single source of truth for TREK server and client.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.mjs",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.cts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"import": {
|
||||||
"import": "./dist/index.js",
|
"types": "./dist/index.d.mts",
|
||||||
"require": "./dist/index.cjs"
|
"default": "./dist/index.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/index.d.cts",
|
||||||
|
"default": "./dist/index.cjs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"./i18n": {
|
"./i18n": {
|
||||||
"types": "./dist/i18n/index.d.ts",
|
"import": {
|
||||||
"import": "./dist/i18n/index.js",
|
"types": "./dist/i18n/index.d.mts",
|
||||||
"require": "./dist/i18n/index.cjs"
|
"default": "./dist/i18n/index.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/i18n/index.d.cts",
|
||||||
|
"default": "./dist/i18n/index.cjs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"./i18n/*": {
|
"./i18n/*": {
|
||||||
"types": "./dist/i18n/*/index.d.ts",
|
"import": {
|
||||||
"import": "./dist/i18n/*/index.js",
|
"types": "./dist/i18n/*/index.d.mts",
|
||||||
"require": "./dist/i18n/*/index.cjs"
|
"default": "./dist/i18n/*/index.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/i18n/*/index.d.cts",
|
||||||
|
"default": "./dist/i18n/*/index.cjs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typesVersions": {
|
"typesVersions": {
|
||||||
"*": {
|
"*": {
|
||||||
"i18n": [
|
"i18n": [
|
||||||
"./dist/i18n/index.d.ts"
|
"./dist/i18n/index.d.cts"
|
||||||
],
|
],
|
||||||
"i18n/*": [
|
"i18n/*": [
|
||||||
"./dist/i18n/*/index.d.ts"
|
"./dist/i18n/*/index.d.cts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsdown",
|
||||||
"build:watch": "tsup --watch",
|
"build:watch": "tsdown --watch",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
@@ -59,9 +74,9 @@
|
|||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"prettier": "3.8.3",
|
"prettier": "3.8.3",
|
||||||
"prettier-plugin-organize-imports": "^4.3.0",
|
"prettier-plugin-organize-imports": "^4.3.0",
|
||||||
"tsup": "^8.5.1",
|
"tsdown": "^0.22.2",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.2",
|
||||||
"typescript-eslint": "^8.58.2",
|
"typescript-eslint": "^8.58.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'tsup'
|
import { defineConfig } from 'tsdown'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Root barrel + i18n metadata barrel + one entry per locale (lazy-load chunks)
|
// Root barrel + i18n metadata barrel + one entry per locale (lazy-load chunks)
|
||||||
@@ -6,5 +6,8 @@ export default defineConfig({
|
|||||||
format: ['cjs', 'esm'],
|
format: ['cjs', 'esm'],
|
||||||
dts: true,
|
dts: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
external: ['zod'],
|
deps: {
|
||||||
|
neverBundle: ['zod'],
|
||||||
|
},
|
||||||
|
target: false,
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user