gtfs-to-html 2.12.0 → 2.12.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtfs-to-html",
3
- "version": "2.12.0",
3
+ "version": "2.12.2",
4
4
  "private": false,
5
5
  "description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
6
6
  "keywords": [
@@ -35,6 +35,7 @@
35
35
  "dist",
36
36
  "docker",
37
37
  "examples",
38
+ "scripts",
38
39
  "views/default",
39
40
  "config-sample.json"
40
41
  ],
@@ -43,30 +44,32 @@
43
44
  },
44
45
  "scripts": {
45
46
  "build": "tsup",
47
+ "postbuild": "node scripts/postinstall.js",
46
48
  "start": "node ./dist/app",
47
- "prepare": "husky"
49
+ "prepare": "husky && npm run build",
50
+ "postinstall": "node scripts/postinstall.js"
48
51
  },
49
52
  "dependencies": {
50
53
  "@maplibre/maplibre-gl-geocoder": "^1.9.1",
51
- "@turf/helpers": "^7.2.0",
52
- "@turf/simplify": "^7.2.0",
54
+ "@turf/helpers": "^7.3.1",
55
+ "@turf/simplify": "^7.3.1",
53
56
  "anchorme": "^3.0.8",
54
57
  "archiver": "^7.0.1",
55
58
  "cli-table": "^0.3.11",
56
59
  "css.escape": "^1.5.1",
57
60
  "csv-stringify": "^6.6.0",
58
- "express": "^5.1.0",
59
- "gtfs": "^4.18.1",
61
+ "express": "^5.2.1",
62
+ "gtfs": "^4.18.2",
60
63
  "gtfs-realtime-pbf-js-module": "^1.0.0",
61
64
  "js-beautify": "^1.15.4",
62
65
  "lodash-es": "^4.17.21",
63
- "maplibre-gl": "^5.12.0",
64
- "marked": "^17.0.0",
66
+ "maplibre-gl": "^5.14.0",
67
+ "marked": "^17.0.1",
65
68
  "moment": "^2.30.1",
66
69
  "pbf": "^4.0.1",
67
70
  "pretty-error": "^4.0.0",
68
71
  "pug": "^3.0.3",
69
- "puppeteer": "^24.29.1",
72
+ "puppeteer": "^24.32.0",
70
73
  "sanitize-filename": "^1.6.3",
71
74
  "sanitize-html": "^2.17.0",
72
75
  "sqlstring": "^2.3.3",
@@ -77,7 +80,7 @@
77
80
  "devDependencies": {
78
81
  "@types/archiver": "^7.0.0",
79
82
  "@types/cli-table": "^0.3.4",
80
- "@types/express": "^5.0.5",
83
+ "@types/express": "^5.0.6",
81
84
  "@types/insane": "^1.0.0",
82
85
  "@types/js-beautify": "^1.14.3",
83
86
  "@types/lodash-es": "^4.17.12",
@@ -87,11 +90,11 @@
87
90
  "@types/sanitize-html": "^2.16.0",
88
91
  "@types/sqlstring": "^2.3.2",
89
92
  "@types/toposort": "^2.0.7",
90
- "@types/yargs": "^17.0.34",
93
+ "@types/yargs": "^17.0.35",
91
94
  "husky": "^9.1.7",
92
- "lint-staged": "^16.2.6",
93
- "prettier": "^3.6.2",
94
- "tsup": "^8.5.0",
95
+ "lint-staged": "^16.2.7",
96
+ "prettier": "^3.7.4",
97
+ "tsup": "^8.5.1",
95
98
  "typescript": "^5.9.3"
96
99
  },
97
100
  "engines": {
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall Script - Copy Frontend Libraries
5
+ *
6
+ * This script copies browser-compatible JavaScript and CSS files from node_modules
7
+ * into the dist/frontend_libraries directory. These files are needed for the HTML
8
+ * timetables to work in browsers (for maps, geocoding, protocol buffers, etc.).
9
+ *
10
+ * Why this is needed:
11
+ * - The generated HTML files reference these libraries directly
12
+ * - They must be bundled with the package so they're available when installed as a dependency
13
+ * - npm/pnpm may hoist dependencies, so we can't rely on a fixed node_modules path
14
+ *
15
+ * This script handles multiple installation scenarios:
16
+ * - Direct installation (npm install in this package)
17
+ * - Installed as a dependency (node_modules may be hoisted to parent)
18
+ * - Works with both npm and pnpm
19
+ */
20
+
21
+ import { mkdir, copyFile } from 'node:fs/promises';
22
+ import { accessSync } from 'node:fs';
23
+ import { dirname, join } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+
28
+ const targetDir = join(__dirname, '../dist/frontend_libraries');
29
+
30
+ const filesToCopy = [
31
+ {
32
+ package: 'pbf',
33
+ source: 'dist/pbf.js',
34
+ target: 'pbf.js',
35
+ },
36
+ {
37
+ package: 'gtfs-realtime-pbf-js-module',
38
+ source: 'gtfs-realtime.browser.proto.js',
39
+ target: 'gtfs-realtime.browser.proto.js',
40
+ },
41
+ {
42
+ package: 'anchorme',
43
+ source: 'dist/browser/anchorme.min.js',
44
+ target: 'anchorme.min.js',
45
+ },
46
+ {
47
+ package: 'maplibre-gl',
48
+ source: 'dist/maplibre-gl.js',
49
+ target: 'maplibre-gl.js',
50
+ },
51
+ {
52
+ package: 'maplibre-gl',
53
+ source: 'dist/maplibre-gl.css',
54
+ target: 'maplibre-gl.css',
55
+ },
56
+ {
57
+ package: '@maplibre/maplibre-gl-geocoder',
58
+ source: 'dist/maplibre-gl-geocoder.js',
59
+ target: 'maplibre-gl-geocoder.js',
60
+ },
61
+ {
62
+ package: '@maplibre/maplibre-gl-geocoder',
63
+ source: 'dist/maplibre-gl-geocoder.css',
64
+ target: 'maplibre-gl-geocoder.css',
65
+ },
66
+ ];
67
+
68
+ function resolvePackagePath(packageName) {
69
+ const possiblePaths = [
70
+ // Direct dependency (when running in development or as root package)
71
+ join(__dirname, '../node_modules', packageName),
72
+ // Hoisted dependency (when installed as a dependency in another project)
73
+ join(__dirname, '../../', packageName),
74
+ // Deeply nested
75
+ join(__dirname, '../../../', packageName),
76
+ ];
77
+
78
+ for (const path of possiblePaths) {
79
+ try {
80
+ accessSync(path);
81
+ return path;
82
+ } catch {
83
+ continue;
84
+ }
85
+ }
86
+
87
+ throw new Error(`Could not resolve package: ${packageName}`);
88
+ }
89
+
90
+ async function copyFrontendLibraries() {
91
+ try {
92
+ await mkdir(targetDir, { recursive: true });
93
+
94
+ for (const file of filesToCopy) {
95
+ try {
96
+ const packagePath = resolvePackagePath(file.package);
97
+ const sourcePath = join(packagePath, file.source);
98
+ const targetPath = join(targetDir, file.target);
99
+
100
+ await copyFile(sourcePath, targetPath);
101
+ } catch (error) {
102
+ console.error(
103
+ `Failed to copy ${file.package}/${file.source}:`,
104
+ error.message,
105
+ );
106
+ // Continue with other files even if one fails
107
+ }
108
+ }
109
+ } catch (error) {
110
+ console.error('Error copying frontend libraries:', error);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ copyFrontendLibraries();
@@ -103,7 +103,6 @@ a:hover {
103
103
  flex-shrink: 0;
104
104
  font-weight: bold;
105
105
  color: white;
106
- text-shadow: 0 0 2px rgba(0,0,0,0.5);
107
106
  }
108
107
 
109
108
  .timetable-overview .route-color-swatch-large {
@@ -120,11 +119,10 @@ a:hover {
120
119
  flex-shrink: 0;
121
120
  font-weight: bold;
122
121
  color: white;
123
- text-shadow: 0 0 4px rgba(0,0,0,0.5);
124
122
  }
125
123
 
126
- .timetable-overview .btn-blue {
127
- color: rgb(255 255 255);
124
+ .timetable-overview .btn-active {
125
+ color: rgb(255, 255, 255);
128
126
  padding: 0.75rem 1.5rem;
129
127
  background-color: rgb(37 99 235);
130
128
  border-radius: 0.375rem;
@@ -135,7 +133,7 @@ a:hover {
135
133
  text-decoration: none;
136
134
  }
137
135
 
138
- .timetable-overview .btn-blue:hover {
136
+ .timetable-overview .btn-active:hover {
139
137
  background-color: rgb(29 78 216);
140
138
  text-decoration: none;
141
139
  }
@@ -201,8 +201,8 @@ a:hover {
201
201
  display: none;
202
202
  }
203
203
 
204
- .timetable-page .btn-blue {
205
- color: rgb(255 255 255);
204
+ .timetable-page .btn-active {
205
+ color: rgb(255, 255, 255);
206
206
  padding: 0.75rem 1.5rem;
207
207
  background-color: rgb(37 99 235);
208
208
  border-radius: 0.375rem;
@@ -213,12 +213,12 @@ a:hover {
213
213
  text-decoration: none;
214
214
  }
215
215
 
216
- .timetable-page .btn-blue:hover {
216
+ .timetable-page .btn-active:hover {
217
217
  background-color: rgb(29 78 216);
218
218
  text-decoration: none;
219
219
  }
220
220
 
221
- .timetable-page .btn-gray {
221
+ .timetable-page .btn-inactive {
222
222
  color: rgb(75 85 99);
223
223
  padding: 0.75rem 1.5rem;
224
224
  background-color: rgb(209 213 219);
@@ -230,7 +230,7 @@ a:hover {
230
230
  text-decoration: none;
231
231
  }
232
232
 
233
- .timetable-page .btn-gray:hover {
233
+ .timetable-page .btn-inactive:hover {
234
234
  background-color: rgb(201, 206, 213);
235
235
  text-decoration: none;
236
236
  }
@@ -427,7 +427,6 @@ a:hover {
427
427
  flex-shrink: 0;
428
428
  font-weight: bold;
429
429
  color: white;
430
- text-shadow: 0 0 2px rgba(0,0,0,0.5);
431
430
  }
432
431
 
433
432
  .timetable-page .route-color-swatch-large {
@@ -444,7 +443,6 @@ a:hover {
444
443
  flex-shrink: 0;
445
444
  font-weight: bold;
446
445
  color: white;
447
- text-shadow: 0 0 4px rgba(0,0,0,0.5);
448
446
  }
449
447
 
450
448
  /* Map Styles */
@@ -678,7 +676,6 @@ a:hover {
678
676
  flex-shrink: 0;
679
677
  font-weight: bold;
680
678
  color: white;
681
- text-shadow: 0 0 2px rgba(0,0,0,0.5);
682
679
  }
683
680
 
684
681
  .timetable-page .timetable-alerts .alert-header .alert-title {
@@ -99,7 +99,7 @@ function formatStopPopup(feature) {
99
99
  container.appendChild(routeList);
100
100
 
101
101
  const streetviewLink = document.createElement('a');
102
- streetviewLink.className = 'btn-blue btn-sm';
102
+ streetviewLink.className = 'btn-active btn-sm';
103
103
  streetviewLink.href = `https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${feature.geometry.coordinates[1]},${feature.geometry.coordinates[0]}&heading=0&pitch=0&fov=90`;
104
104
  streetviewLink.target = '_blank';
105
105
  streetviewLink.rel = 'noopener noreferrer';
@@ -68,7 +68,7 @@ function formatAlertAsHtml(
68
68
  if (alert.alert.url?.translation?.[0].text) {
69
69
  const moreInfoLink = document.createElement('a');
70
70
  moreInfoLink.href = alert.alert.url.translation[0].text;
71
- moreInfoLink.classList.add('btn-blue', 'btn-sm', 'alert-more-info');
71
+ moreInfoLink.classList.add('btn-active', 'btn-sm', 'alert-more-info');
72
72
  moreInfoLink.textContent = 'More Info';
73
73
  alertBody.appendChild(moreInfoLink);
74
74
  }
@@ -194,7 +194,7 @@ async function updateAlerts() {
194
194
  // Replace the empty message if present
195
195
  const emptyMessage = document.querySelector('.timetable-alert-empty');
196
196
  if (emptyMessage) {
197
- emptyMessage.style.display = '';
197
+ emptyMessage.style.display = 'block';
198
198
  }
199
199
  }
200
200
  } catch (error) {
@@ -194,7 +194,7 @@ function getStopPopupHtml(feature, stop) {
194
194
  html.appendChild(routeList);
195
195
 
196
196
  const streetviewLink = document.createElement('a');
197
- streetviewLink.className = 'btn-blue btn-sm';
197
+ streetviewLink.className = 'btn-active btn-sm';
198
198
  streetviewLink.href = `https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${feature.geometry.coordinates[1]},${feature.geometry.coordinates[0]}&heading=0&pitch=0&fov=90`;
199
199
  streetviewLink.target = '_blank';
200
200
  streetviewLink.rel = 'noopener noreferrer';
@@ -13,8 +13,8 @@ function showSelectedTimetable() {
13
13
  .forEach((element) => {
14
14
  const label = element.closest('label');
15
15
  if (label) {
16
- label.classList.toggle('btn-blue', element.checked);
17
- label.classList.toggle('btn-gray', !element.checked);
16
+ label.classList.toggle('btn-active', element.checked);
17
+ label.classList.toggle('btn-inactive', !element.checked);
18
18
  }
19
19
  });
20
20
 
@@ -23,8 +23,8 @@ function showSelectedTimetable() {
23
23
  .forEach((element) => {
24
24
  const label = element.closest('label');
25
25
  if (label) {
26
- label.classList.toggle('btn-blue', element.checked);
27
- label.classList.toggle('btn-gray', !element.checked);
26
+ label.classList.toggle('btn-active', element.checked);
27
+ label.classList.toggle('btn-inactive', !element.checked);
28
28
  }
29
29
  });
30
30
 
@@ -27,7 +27,7 @@ if timetablePage.consolidatedTimetables.length > 1
27
27
  div
28
28
  h3= dayList
29
29
  each timetable in group
30
- a.btn-blue(href=`#${cssEscape(`timetable_id_${timetable.timetable_id}`)}`)= timetable.timetable_label
30
+ a.btn-active(href=`#${cssEscape(`timetable_id_${timetable.timetable_id}`)}`)= timetable.timetable_label
31
31
 
32
32
  if config.menuType === 'radio'
33
33
  .timetable-radio-menu
@@ -36,13 +36,13 @@ if timetablePage.consolidatedTimetables.length > 1
36
36
  #direction_name_selector
37
37
  h3 Service Direction
38
38
  each timetable, idx in uniqueDirectionTimetables
39
- label(class=idx === 0 ? 'btn-blue' : 'btn-gray')
39
+ label(class=idx === 0 ? 'btn-active' : 'btn-inactive')
40
40
  input.hidden(type="radio" name="directionId" autocomplete="off" value=timetable.direction_id checked=(idx === 0))
41
41
  span= timetable.direction_name || timetable.timetable_label
42
42
  div(hidden=timetablePage.dayLists.length <= 1)
43
43
  #day_list_selector
44
44
  h3 Day of Week
45
45
  each dayList, idx in timetablePage.dayLists
46
- label(class=idx === 0 ? 'btn-blue' : 'btn-gray')
46
+ label(class=idx === 0 ? 'btn-active' : 'btn-inactive')
47
47
  input.hidden(type="radio" name="dayList" autocomplete="off" value=dayList checked=(idx === 0))
48
48
  span= dayList