gtfs-to-html 2.12.5 → 2.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/index.js +142 -33
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +285 -60
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/frontend_libraries/maplibre-gl.js +4 -4
- package/dist/index.d.ts +53 -1
- package/dist/index.js +307 -53
- package/dist/index.js.map +1 -1
- package/package.json +8 -10
package/dist/app/index.js
CHANGED
|
@@ -306,6 +306,30 @@ function formatTripNameForCSV(trip, timetable) {
|
|
|
306
306
|
return tripName;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// src/lib/errors.ts
|
|
310
|
+
import { GtfsErrorCategory, isGtfsError } from "gtfs";
|
|
311
|
+
var GtfsToHtmlError = class extends Error {
|
|
312
|
+
code;
|
|
313
|
+
category;
|
|
314
|
+
isOperational;
|
|
315
|
+
details;
|
|
316
|
+
constructor(message, options) {
|
|
317
|
+
super(message, { cause: options.cause });
|
|
318
|
+
this.name = "GtfsToHtmlError";
|
|
319
|
+
this.code = options.code;
|
|
320
|
+
this.category = options.category;
|
|
321
|
+
this.isOperational = options.isOperational ?? true;
|
|
322
|
+
this.details = options.details;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function isGtfsToHtmlError(error) {
|
|
326
|
+
if (!error || typeof error !== "object") {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
const candidate = error;
|
|
330
|
+
return candidate.name === "GtfsToHtmlError" && typeof candidate.message === "string" && typeof candidate.code === "string" && typeof candidate.category === "string" && typeof candidate.isOperational === "boolean";
|
|
331
|
+
}
|
|
332
|
+
|
|
309
333
|
// src/lib/file-utils.ts
|
|
310
334
|
var homeDirectory = homedir();
|
|
311
335
|
function getPathToThisModuleFolder() {
|
|
@@ -385,7 +409,7 @@ import { featureCollection, round } from "@turf/helpers";
|
|
|
385
409
|
import { clearLine, cursorTo } from "readline";
|
|
386
410
|
import { noop } from "lodash-es";
|
|
387
411
|
import * as colors from "yoctocolors";
|
|
388
|
-
import { getAgencies, getFeedInfo } from "gtfs";
|
|
412
|
+
import { getAgencies, getFeedInfo, isGtfsError as isGtfsError2, formatGtfsError } from "gtfs";
|
|
389
413
|
import Table from "cli-table";
|
|
390
414
|
function logWarning(config2) {
|
|
391
415
|
if (config2.logFunction) {
|
|
@@ -480,7 +504,7 @@ function getAgencyGeoJSON(config2) {
|
|
|
480
504
|
// package.json
|
|
481
505
|
var package_default = {
|
|
482
506
|
name: "gtfs-to-html",
|
|
483
|
-
version: "2.12.
|
|
507
|
+
version: "2.12.6",
|
|
484
508
|
private: false,
|
|
485
509
|
description: "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
486
510
|
keywords: [
|
|
@@ -537,21 +561,21 @@ var package_default = {
|
|
|
537
561
|
archiver: "^7.0.1",
|
|
538
562
|
"cli-table": "^0.3.11",
|
|
539
563
|
"css.escape": "^1.5.1",
|
|
540
|
-
"csv-stringify": "^6.
|
|
564
|
+
"csv-stringify": "^6.7.0",
|
|
541
565
|
express: "^5.2.1",
|
|
542
|
-
gtfs: "^4.18.
|
|
566
|
+
gtfs: "^4.18.4",
|
|
543
567
|
"gtfs-realtime-pbf-js-module": "^1.0.0",
|
|
544
568
|
"js-beautify": "^1.15.4",
|
|
545
569
|
"lodash-es": "^4.17.23",
|
|
546
|
-
"maplibre-gl": "^5.
|
|
547
|
-
marked: "^17.0.
|
|
570
|
+
"maplibre-gl": "^5.21.0",
|
|
571
|
+
marked: "^17.0.5",
|
|
548
572
|
moment: "^2.30.1",
|
|
549
573
|
pbf: "^4.0.1",
|
|
550
574
|
"pretty-error": "^4.0.0",
|
|
551
575
|
pug: "^3.0.4",
|
|
552
|
-
puppeteer: "^24.
|
|
553
|
-
"sanitize-filename": "^1.6.
|
|
554
|
-
"sanitize-html": "^2.17.
|
|
576
|
+
puppeteer: "^24.40.0",
|
|
577
|
+
"sanitize-filename": "^1.6.4",
|
|
578
|
+
"sanitize-html": "^2.17.2",
|
|
555
579
|
sqlstring: "^2.3.3",
|
|
556
580
|
toposort: "^2.0.2",
|
|
557
581
|
yargs: "^18.0.0",
|
|
@@ -561,10 +585,8 @@ var package_default = {
|
|
|
561
585
|
"@types/archiver": "^7.0.0",
|
|
562
586
|
"@types/cli-table": "^0.3.4",
|
|
563
587
|
"@types/express": "^5.0.6",
|
|
564
|
-
"@types/insane": "^1.0.0",
|
|
565
588
|
"@types/js-beautify": "^1.14.3",
|
|
566
589
|
"@types/lodash-es": "^4.17.12",
|
|
567
|
-
"@types/morgan": "^1.9.10",
|
|
568
590
|
"@types/node": "^25",
|
|
569
591
|
"@types/pug": "^2.0.10",
|
|
570
592
|
"@types/sanitize-html": "^2.16.1",
|
|
@@ -1146,8 +1168,17 @@ var getStopsForTimetable = (timetable, config2) => {
|
|
|
1146
1168
|
stop_id: stopId
|
|
1147
1169
|
});
|
|
1148
1170
|
if (stops.length === 0) {
|
|
1149
|
-
throw new
|
|
1150
|
-
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}
|
|
1171
|
+
throw new GtfsToHtmlError(
|
|
1172
|
+
`No stop found found for stop_id=${stopId} in timetable_id=${timetable.timetable_id}`,
|
|
1173
|
+
{
|
|
1174
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1175
|
+
category: "query" /* QUERY */,
|
|
1176
|
+
details: {
|
|
1177
|
+
entity: "stop",
|
|
1178
|
+
stopId,
|
|
1179
|
+
timetableId: timetable.timetable_id
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1151
1182
|
);
|
|
1152
1183
|
}
|
|
1153
1184
|
const stop = {
|
|
@@ -1182,7 +1213,14 @@ var getCalendarsFromConfig = (config2) => {
|
|
|
1182
1213
|
const whereClauses = [];
|
|
1183
1214
|
if (config2.endDate) {
|
|
1184
1215
|
if (!moment2(config2.endDate).isValid()) {
|
|
1185
|
-
throw new
|
|
1216
|
+
throw new GtfsToHtmlError(
|
|
1217
|
+
`Invalid endDate=${config2.endDate} in config.json`,
|
|
1218
|
+
{
|
|
1219
|
+
code: "GTFS_TO_HTML_CONFIG_DATE_INVALID" /* CONFIG_DATE_INVALID */,
|
|
1220
|
+
category: "config" /* CONFIG */,
|
|
1221
|
+
details: { field: "endDate", value: config2.endDate }
|
|
1222
|
+
}
|
|
1223
|
+
);
|
|
1186
1224
|
}
|
|
1187
1225
|
whereClauses.push(
|
|
1188
1226
|
`start_date <= ${sqlString.escape(moment2(config2.endDate).format("YYYYMMDD"))}`
|
|
@@ -1190,7 +1228,14 @@ var getCalendarsFromConfig = (config2) => {
|
|
|
1190
1228
|
}
|
|
1191
1229
|
if (config2.startDate) {
|
|
1192
1230
|
if (!moment2(config2.startDate).isValid()) {
|
|
1193
|
-
throw new
|
|
1231
|
+
throw new GtfsToHtmlError(
|
|
1232
|
+
`Invalid startDate=${config2.startDate} in config.json`,
|
|
1233
|
+
{
|
|
1234
|
+
code: "GTFS_TO_HTML_CONFIG_DATE_INVALID" /* CONFIG_DATE_INVALID */,
|
|
1235
|
+
category: "config" /* CONFIG */,
|
|
1236
|
+
details: { field: "startDate", value: config2.startDate }
|
|
1237
|
+
}
|
|
1238
|
+
);
|
|
1194
1239
|
}
|
|
1195
1240
|
whereClauses.push(
|
|
1196
1241
|
`end_date >= ${sqlString.escape(moment2(config2.startDate).format("YYYYMMDD"))}`
|
|
@@ -1215,16 +1260,34 @@ var getCalendarsFromTimetable = (timetable) => {
|
|
|
1215
1260
|
const whereClauses = [];
|
|
1216
1261
|
if (timetable.end_date) {
|
|
1217
1262
|
if (!moment2(timetable.end_date, "YYYYMMDD", true).isValid()) {
|
|
1218
|
-
throw new
|
|
1219
|
-
`Invalid end_date=${timetable.end_date} for timetable_id=${timetable.timetable_id}
|
|
1263
|
+
throw new GtfsToHtmlError(
|
|
1264
|
+
`Invalid end_date=${timetable.end_date} for timetable_id=${timetable.timetable_id}`,
|
|
1265
|
+
{
|
|
1266
|
+
code: "GTFS_TO_HTML_QUERY_INVALID" /* QUERY_INVALID */,
|
|
1267
|
+
category: "validation" /* VALIDATION */,
|
|
1268
|
+
details: {
|
|
1269
|
+
field: "end_date",
|
|
1270
|
+
value: timetable.end_date,
|
|
1271
|
+
timetableId: timetable.timetable_id
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1220
1274
|
);
|
|
1221
1275
|
}
|
|
1222
1276
|
whereClauses.push(`start_date <= ${sqlString.escape(timetable.end_date)}`);
|
|
1223
1277
|
}
|
|
1224
1278
|
if (timetable.start_date) {
|
|
1225
1279
|
if (!moment2(timetable.start_date, "YYYYMMDD", true).isValid()) {
|
|
1226
|
-
throw new
|
|
1227
|
-
`Invalid start_date=${timetable.start_date} for timetable_id=${timetable.timetable_id}
|
|
1280
|
+
throw new GtfsToHtmlError(
|
|
1281
|
+
`Invalid start_date=${timetable.start_date} for timetable_id=${timetable.timetable_id}`,
|
|
1282
|
+
{
|
|
1283
|
+
code: "GTFS_TO_HTML_QUERY_INVALID" /* QUERY_INVALID */,
|
|
1284
|
+
category: "validation" /* VALIDATION */,
|
|
1285
|
+
details: {
|
|
1286
|
+
field: "start_date",
|
|
1287
|
+
value: timetable.start_date,
|
|
1288
|
+
timetableId: timetable.timetable_id
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1228
1291
|
);
|
|
1229
1292
|
}
|
|
1230
1293
|
whereClauses.push(`end_date >= ${sqlString.escape(timetable.start_date)}`);
|
|
@@ -1269,7 +1332,11 @@ var getAllStationStopIds = (stopId) => {
|
|
|
1269
1332
|
stop_id: stopId
|
|
1270
1333
|
});
|
|
1271
1334
|
if (stops.length === 0) {
|
|
1272
|
-
throw new
|
|
1335
|
+
throw new GtfsToHtmlError(`No stop found for stop_id=${stopId}`, {
|
|
1336
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1337
|
+
category: "query" /* QUERY */,
|
|
1338
|
+
details: { entity: "stop", stopId }
|
|
1339
|
+
});
|
|
1273
1340
|
}
|
|
1274
1341
|
const stop = stops[0];
|
|
1275
1342
|
if (isNullOrEmpty(stop.parent_station)) {
|
|
@@ -1303,8 +1370,13 @@ var getTripsWithSameBlock = (trip, timetable) => {
|
|
|
1303
1370
|
[["stop_sequence", "ASC"]]
|
|
1304
1371
|
);
|
|
1305
1372
|
if (stopTimes.length === 0) {
|
|
1306
|
-
throw new
|
|
1307
|
-
`No stoptimes found found for trip_id=${blockTrip.trip_id}
|
|
1373
|
+
throw new GtfsToHtmlError(
|
|
1374
|
+
`No stoptimes found found for trip_id=${blockTrip.trip_id}`,
|
|
1375
|
+
{
|
|
1376
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1377
|
+
category: "query" /* QUERY */,
|
|
1378
|
+
details: { entity: "stoptime", tripId: blockTrip.trip_id }
|
|
1379
|
+
}
|
|
1308
1380
|
);
|
|
1309
1381
|
}
|
|
1310
1382
|
blockTrip.firstStoptime = first(stopTimes);
|
|
@@ -1645,8 +1717,18 @@ var getDataForTimetablePageById = (timetablePageId) => {
|
|
|
1645
1717
|
);
|
|
1646
1718
|
const uniqueTripDirections = uniqBy2(trips, (trip) => trip.direction_id);
|
|
1647
1719
|
if (uniqueTripDirections.length === 0) {
|
|
1648
|
-
throw new
|
|
1649
|
-
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}
|
|
1720
|
+
throw new GtfsToHtmlError(
|
|
1721
|
+
`No trips found for timetable_page_id=${timetablePageId} route_id=${routeId} direction_id=${directionId}`,
|
|
1722
|
+
{
|
|
1723
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1724
|
+
category: "query" /* QUERY */,
|
|
1725
|
+
details: {
|
|
1726
|
+
entity: "trip",
|
|
1727
|
+
timetablePageId,
|
|
1728
|
+
routeId,
|
|
1729
|
+
directionId
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1650
1732
|
);
|
|
1651
1733
|
}
|
|
1652
1734
|
if (/^[01]*$/.test(calendarCode ?? "")) {
|
|
@@ -1676,8 +1758,13 @@ var getTimetablePageById = (timetablePageId, config2) => {
|
|
|
1676
1758
|
getTimetables()
|
|
1677
1759
|
);
|
|
1678
1760
|
if (timetablePages.length > 1) {
|
|
1679
|
-
throw new
|
|
1680
|
-
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}
|
|
1761
|
+
throw new GtfsToHtmlError(
|
|
1762
|
+
`Multiple timetable_pages found for timetable_page_id=${timetablePageId}`,
|
|
1763
|
+
{
|
|
1764
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_AMBIGUOUS" /* QUERY_RESULT_AMBIGUOUS */,
|
|
1765
|
+
category: "query" /* QUERY */,
|
|
1766
|
+
details: { entity: "timetable_page", timetablePageId }
|
|
1767
|
+
}
|
|
1681
1768
|
);
|
|
1682
1769
|
}
|
|
1683
1770
|
if (timetablePages.length === 1) {
|
|
@@ -1700,8 +1787,13 @@ var getTimetablePageById = (timetablePageId, config2) => {
|
|
|
1700
1787
|
(timetable2) => timetable2.timetable_id === timetablePageId
|
|
1701
1788
|
);
|
|
1702
1789
|
if (timetablePageTimetables.length === 0) {
|
|
1703
|
-
throw new
|
|
1704
|
-
`No timetable found for timetable_page_id=${timetablePageId}
|
|
1790
|
+
throw new GtfsToHtmlError(
|
|
1791
|
+
`No timetable found for timetable_page_id=${timetablePageId}`,
|
|
1792
|
+
{
|
|
1793
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1794
|
+
category: "query" /* QUERY */,
|
|
1795
|
+
details: { entity: "timetable", timetablePageId }
|
|
1796
|
+
}
|
|
1705
1797
|
);
|
|
1706
1798
|
}
|
|
1707
1799
|
return createTimetablePage({
|
|
@@ -1715,8 +1807,13 @@ var getTimetablePageById = (timetablePageId, config2) => {
|
|
|
1715
1807
|
route_short_name: timetablePageId.split("_")[1]
|
|
1716
1808
|
});
|
|
1717
1809
|
if (routes.length === 0) {
|
|
1718
|
-
throw new
|
|
1719
|
-
`No route found for timetable_page_id=${timetablePageId}
|
|
1810
|
+
throw new GtfsToHtmlError(
|
|
1811
|
+
`No route found for timetable_page_id=${timetablePageId}`,
|
|
1812
|
+
{
|
|
1813
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
1814
|
+
category: "query" /* QUERY */,
|
|
1815
|
+
details: { entity: "route", timetablePageId }
|
|
1816
|
+
}
|
|
1720
1817
|
);
|
|
1721
1818
|
}
|
|
1722
1819
|
const { calendars: calendars2, calendarDates: calendarDates2 } = getCalendarsFromConfig(config2);
|
|
@@ -1907,7 +2004,11 @@ function generateTimetableHTML(timetablePage, config2) {
|
|
|
1907
2004
|
function generateOverviewHTML(timetablePages, config2) {
|
|
1908
2005
|
const agencies = getAgencies2();
|
|
1909
2006
|
if (agencies.length === 0) {
|
|
1910
|
-
throw new
|
|
2007
|
+
throw new GtfsToHtmlError("No agencies found", {
|
|
2008
|
+
code: "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */,
|
|
2009
|
+
category: "query" /* QUERY */,
|
|
2010
|
+
details: { entity: "agency" }
|
|
2011
|
+
});
|
|
1911
2012
|
}
|
|
1912
2013
|
const geojson = config2.showMap ? getAgencyGeoJSON(config2) : void 0;
|
|
1913
2014
|
const templateVars = {
|
|
@@ -2287,7 +2388,15 @@ try {
|
|
|
2287
2388
|
console.error(
|
|
2288
2389
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists and run gtfs-to-html to import GTFS before running this app.`
|
|
2289
2390
|
);
|
|
2290
|
-
throw
|
|
2391
|
+
throw new GtfsToHtmlError(
|
|
2392
|
+
`Unable to open sqlite database "${config.sqlitePath}"`,
|
|
2393
|
+
{
|
|
2394
|
+
code: "GTFS_TO_HTML_DATABASE_OPEN_FAILED" /* DATABASE_OPEN_FAILED */,
|
|
2395
|
+
category: "database" /* DATABASE */,
|
|
2396
|
+
details: { sqlitePath: config.sqlitePath, dbCode: error?.code },
|
|
2397
|
+
cause: error
|
|
2398
|
+
}
|
|
2399
|
+
);
|
|
2291
2400
|
}
|
|
2292
2401
|
app.set("views", getPathToViewsFolder(config));
|
|
2293
2402
|
app.set("view engine", "pug");
|
|
@@ -2371,7 +2480,7 @@ app.get("/timetables/:timetablePageId", async (req, res, next) => {
|
|
|
2371
2480
|
const html = await generateTimetableHTML(timetablePage, config);
|
|
2372
2481
|
res.send(html);
|
|
2373
2482
|
} catch (error) {
|
|
2374
|
-
if (error
|
|
2483
|
+
if (isGtfsToHtmlError(error) && error.code === "GTFS_TO_HTML_QUERY_RESULT_NOT_FOUND" /* QUERY_RESULT_NOT_FOUND */) {
|
|
2375
2484
|
res.status(404).send("Timetable page not found");
|
|
2376
2485
|
return;
|
|
2377
2486
|
}
|