gtfs-to-html 2.12.1 → 2.12.3

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/README.md CHANGED
@@ -62,6 +62,7 @@ Many transit agencies use `gtfs-to-html` to generate the schedule pages used on
62
62
  | Agency | Location |
63
63
  | ----------------------------------------------------------------------------- | ----------------------------------- |
64
64
  | [Basin Transit](https://basin-transit.com) | Morongo Basin, California |
65
+ | [Bayway Transit](https://www.baywaytransit.org) | Panama City, Florida |
65
66
  | [Brockton Area Transit Authority](https://ridebat.com) | Brockton, Massachusetts |
66
67
  | [Cape Ann Transportation Authority](https://canntran.com) | Gloucester, Massachusetts |
67
68
  | [Capital Transit](https://juneaucapitaltransit.org) | Juneau, Alaska |
package/dist/app/index.js CHANGED
@@ -39,11 +39,11 @@ function fromGTFSTime(timeString) {
39
39
  function toGTFSTime(time) {
40
40
  return time.format("HH:mm:ss");
41
41
  }
42
- function calendarToCalendarCode(c) {
43
- if (Object.values(c).every((value) => value === null)) {
42
+ function calendarToCalendarCode(calendar) {
43
+ if (Object.values(calendar).every((value) => value === null)) {
44
44
  return "";
45
45
  }
46
- return `${c.monday}${c.tuesday}${c.wednesday}${c.thursday}${c.friday}${c.saturday}${c.sunday}`;
46
+ return `${calendar.monday ?? "0"}${calendar.tuesday ?? "0"}${calendar.wednesday ?? "0"}${calendar.thursday ?? "0"}${calendar.friday ?? "0"}${calendar.saturday ?? "0"}${calendar.sunday ?? "0"}`;
47
47
  }
48
48
  function calendarCodeToCalendar(code) {
49
49
  const days2 = [
@@ -61,6 +61,35 @@ function calendarCodeToCalendar(code) {
61
61
  }
62
62
  return calendar;
63
63
  }
64
+ function calendarToDateList(calendar, startDate, endDate) {
65
+ if (!startDate || !endDate) {
66
+ return [];
67
+ }
68
+ const activeWeekdays = [
69
+ calendar.monday === 1 ? 1 : null,
70
+ calendar.tuesday === 1 ? 2 : null,
71
+ calendar.wednesday === 1 ? 3 : null,
72
+ calendar.thursday === 1 ? 4 : null,
73
+ calendar.friday === 1 ? 5 : null,
74
+ calendar.saturday === 1 ? 6 : null,
75
+ calendar.sunday === 1 ? 7 : null
76
+ ].filter((weekday) => weekday !== null);
77
+ if (activeWeekdays.length === 0) {
78
+ return [];
79
+ }
80
+ const activeWeekdaySet = new Set(activeWeekdays);
81
+ const dates = /* @__PURE__ */ new Set();
82
+ const date = moment(startDate.toString(), "YYYYMMDD");
83
+ const endDateMoment = moment(endDate.toString(), "YYYYMMDD");
84
+ while (date.isSameOrBefore(endDateMoment)) {
85
+ const isoWeekday = date.isoWeekday();
86
+ if (activeWeekdaySet.has(isoWeekday)) {
87
+ dates.add(parseInt(date.format("YYYYMMDD"), 10));
88
+ }
89
+ date.add(1, "day");
90
+ }
91
+ return Array.from(dates);
92
+ }
64
93
  function secondsAfterMidnight(timeString) {
65
94
  return moment.duration(timeString).asSeconds();
66
95
  }
@@ -136,7 +165,6 @@ import {
136
165
  rm
137
166
  } from "fs/promises";
138
167
  import { homedir } from "os";
139
- import { findPackageJSON } from "module";
140
168
  import * as _ from "lodash-es";
141
169
  import { uniqBy } from "lodash-es";
142
170
  import archiver from "archiver";
@@ -259,20 +287,23 @@ function formatTripNameForCSV(trip, timetable) {
259
287
 
260
288
  // src/lib/file-utils.ts
261
289
  var homeDirectory = homedir();
262
- function getPathToViewsFolder(config2) {
263
- if (config2.templatePath) {
264
- return untildify(config2.templatePath);
265
- }
290
+ function getPathToThisModuleFolder() {
266
291
  const __dirname = dirname(fileURLToPath(import.meta.url));
267
- let viewsFolderPath;
292
+ let distFolderPath;
268
293
  if (__dirname.endsWith("/dist/bin") || __dirname.endsWith("/dist/app")) {
269
- viewsFolderPath = resolve(__dirname, "../../views/default");
294
+ distFolderPath = resolve(__dirname, "../../");
270
295
  } else if (__dirname.endsWith("/dist")) {
271
- viewsFolderPath = resolve(__dirname, "../views/default");
296
+ distFolderPath = resolve(__dirname, "../");
272
297
  } else {
273
- viewsFolderPath = resolve(__dirname, "../../views/default");
298
+ distFolderPath = resolve(__dirname, "../../");
299
+ }
300
+ return distFolderPath;
301
+ }
302
+ function getPathToViewsFolder(config2) {
303
+ if (config2.templatePath) {
304
+ return untildify(config2.templatePath);
274
305
  }
275
- return viewsFolderPath;
306
+ return join(getPathToThisModuleFolder(), "views/default");
276
307
  }
277
308
  function getPathToTemplateFile(templateFileName, config2) {
278
309
  const fullTemplateFileName = config2.noHead !== true ? `${templateFileName}_full.pug` : `${templateFileName}.pug`;
@@ -428,7 +459,7 @@ function getAgencyGeoJSON(config2) {
428
459
  // package.json
429
460
  var package_default = {
430
461
  name: "gtfs-to-html",
431
- version: "2.12.1",
462
+ version: "2.12.3",
432
463
  private: false,
433
464
  description: "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
434
465
  keywords: [
@@ -463,6 +494,7 @@ var package_default = {
463
494
  "dist",
464
495
  "docker",
465
496
  "examples",
497
+ "scripts",
466
498
  "views/default",
467
499
  "config-sample.json"
468
500
  ],
@@ -471,30 +503,32 @@ var package_default = {
471
503
  },
472
504
  scripts: {
473
505
  build: "tsup",
506
+ postbuild: "node scripts/postinstall.js",
474
507
  start: "node ./dist/app",
475
- prepare: "husky"
508
+ prepare: "husky && npm run build",
509
+ postinstall: "node scripts/postinstall.js"
476
510
  },
477
511
  dependencies: {
478
512
  "@maplibre/maplibre-gl-geocoder": "^1.9.1",
479
- "@turf/helpers": "^7.3.0",
480
- "@turf/simplify": "^7.3.0",
513
+ "@turf/helpers": "^7.3.1",
514
+ "@turf/simplify": "^7.3.1",
481
515
  anchorme: "^3.0.8",
482
516
  archiver: "^7.0.1",
483
517
  "cli-table": "^0.3.11",
484
518
  "css.escape": "^1.5.1",
485
519
  "csv-stringify": "^6.6.0",
486
- express: "^5.1.0",
487
- gtfs: "^4.18.1",
520
+ express: "^5.2.1",
521
+ gtfs: "^4.18.2",
488
522
  "gtfs-realtime-pbf-js-module": "^1.0.0",
489
523
  "js-beautify": "^1.15.4",
490
524
  "lodash-es": "^4.17.21",
491
- "maplibre-gl": "^5.13.0",
492
- marked: "^17.0.0",
525
+ "maplibre-gl": "^5.14.0",
526
+ marked: "^17.0.1",
493
527
  moment: "^2.30.1",
494
528
  pbf: "^4.0.1",
495
529
  "pretty-error": "^4.0.0",
496
530
  pug: "^3.0.3",
497
- puppeteer: "^24.30.0",
531
+ puppeteer: "^24.32.0",
498
532
  "sanitize-filename": "^1.6.3",
499
533
  "sanitize-html": "^2.17.0",
500
534
  sqlstring: "^2.3.3",
@@ -505,7 +539,7 @@ var package_default = {
505
539
  devDependencies: {
506
540
  "@types/archiver": "^7.0.0",
507
541
  "@types/cli-table": "^0.3.4",
508
- "@types/express": "^5.0.5",
542
+ "@types/express": "^5.0.6",
509
543
  "@types/insane": "^1.0.0",
510
544
  "@types/js-beautify": "^1.14.3",
511
545
  "@types/lodash-es": "^4.17.12",
@@ -518,7 +552,7 @@ var package_default = {
518
552
  "@types/yargs": "^17.0.35",
519
553
  husky: "^9.1.7",
520
554
  "lint-staged": "^16.2.7",
521
- prettier: "^3.6.2",
555
+ prettier: "^3.7.4",
522
556
  tsup: "^8.5.1",
523
557
  typescript: "^5.9.3"
524
558
  },
@@ -1183,9 +1217,9 @@ var getCalendarsFromTimetable = (timetable) => {
1183
1217
  }
1184
1218
  return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
1185
1219
  };
1186
- var getCalendarDatesServiceIds = (startDate, endDate) => {
1220
+ var getCalendarDatesForDateRange = (startDate, endDate) => {
1187
1221
  const db = openDb();
1188
- const whereClauses = ["exception_type = 1"];
1222
+ const whereClauses = [];
1189
1223
  if (endDate) {
1190
1224
  whereClauses.push(`date <= ${sqlString.escape(endDate)}`);
1191
1225
  }
@@ -1193,11 +1227,11 @@ var getCalendarDatesServiceIds = (startDate, endDate) => {
1193
1227
  whereClauses.push(`date >= ${sqlString.escape(startDate)}`);
1194
1228
  }
1195
1229
  const calendarDates = db.prepare(
1196
- `SELECT DISTINCT service_id FROM calendar_dates WHERE ${whereClauses.join(
1230
+ `SELECT service_id, date, exception_type FROM calendar_dates WHERE ${whereClauses.join(
1197
1231
  " AND "
1198
1232
  )}`
1199
1233
  ).all();
1200
- return calendarDates.map((calendarDate) => calendarDate.service_id);
1234
+ return calendarDates;
1201
1235
  };
1202
1236
  var getAllStationStopIds = (stopId) => {
1203
1237
  const stops = getStops({
@@ -1408,13 +1442,49 @@ var formatTimetables = (timetables, config2) => {
1408
1442
  timetable.warnings = [];
1409
1443
  const dayList = formatDays(timetable, config2);
1410
1444
  const calendars = getCalendarsFromTimetable(timetable);
1411
- let serviceIds = calendars.map((calendar) => calendar.service_id);
1445
+ const serviceIds = /* @__PURE__ */ new Set();
1446
+ for (const calendar of calendars) {
1447
+ serviceIds.add(calendar.service_id);
1448
+ }
1412
1449
  if (timetable.include_exceptions === 1) {
1413
- const calendarDatesServiceIds = getCalendarDatesServiceIds(
1450
+ const calendarDates = getCalendarDatesForDateRange(
1414
1451
  timetable.start_date,
1415
1452
  timetable.end_date
1416
1453
  );
1417
- serviceIds = uniq([...serviceIds, ...calendarDatesServiceIds]);
1454
+ const calendarDateGroups = groupBy(calendarDates, "service_id");
1455
+ for (const [serviceId, calendarDateGroup] of Object.entries(
1456
+ calendarDateGroups
1457
+ )) {
1458
+ const calendar = calendars.find(
1459
+ (c) => c.service_id === serviceId
1460
+ );
1461
+ if (calendarDateGroup.some(
1462
+ (calendarDate) => calendarDate.exception_type === 1
1463
+ )) {
1464
+ serviceIds.add(serviceId);
1465
+ }
1466
+ const calendarDateGroupExceptionType2 = calendarDateGroup.filter(
1467
+ (calendarDate) => calendarDate.exception_type === 2
1468
+ );
1469
+ if (timetable.start_date && timetable.end_date && calendar && calendarDateGroupExceptionType2.length > 0) {
1470
+ const datesDuringDateRange = calendarToDateList(
1471
+ calendar,
1472
+ timetable.start_date,
1473
+ timetable.end_date
1474
+ );
1475
+ if (datesDuringDateRange.length === 0) {
1476
+ serviceIds.delete(serviceId);
1477
+ }
1478
+ const everyDateIsExcluded = datesDuringDateRange.every(
1479
+ (dateDuringDateRange) => calendarDateGroupExceptionType2.some(
1480
+ (calendarDate) => calendarDate.date === dateDuringDateRange
1481
+ )
1482
+ );
1483
+ if (everyDateIsExcluded) {
1484
+ serviceIds.delete(serviceId);
1485
+ }
1486
+ }
1487
+ }
1418
1488
  }
1419
1489
  Object.assign(timetable, {
1420
1490
  noServiceSymbolUsed: false,
@@ -1432,7 +1502,7 @@ var formatTimetables = (timetables, config2) => {
1432
1502
  noPickupSymbol: config2.noPickupSymbol,
1433
1503
  interpolatedStopSymbol: config2.interpolatedStopSymbol,
1434
1504
  orientation: timetable.orientation || config2.defaultOrientation,
1435
- service_ids: serviceIds,
1505
+ service_ids: Array.from(serviceIds),
1436
1506
  dayList,
1437
1507
  dayListLong: formatDaysLong(dayList, config2)
1438
1508
  });