transit-departures-widget 2.7.2 → 2.8.0
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 +1 -0
- package/dist/app/index.d.ts +1 -2
- package/dist/app/index.js +81 -552
- package/dist/app/index.js.map +1 -1
- package/dist/bin/transit-departures-widget.d.ts +1 -1
- package/dist/bin/transit-departures-widget.js +23 -638
- package/dist/bin/transit-departures-widget.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +87 -0
- package/dist/browser/pbf.js +1 -0
- package/dist/index.d.ts +29 -26
- package/dist/index.js +2 -590
- package/dist/src-D_ggoL9P.js +55 -0
- package/dist/src-D_ggoL9P.js.map +1 -0
- package/dist/utils-BxiS6d7j.js +368 -0
- package/dist/utils-BxiS6d7j.js.map +1 -0
- package/package.json +11 -12
- package/dist/frontend_libraries/pbf.js +0 -1
- package/dist/index.js.map +0 -1
- /package/dist/{frontend_libraries → browser}/accessible-autocomplete.min.css +0 -0
- /package/dist/{frontend_libraries → browser}/accessible-autocomplete.min.js +0 -0
- /package/dist/{frontend_libraries → browser}/gtfs-realtime.browser.proto.js +0 -0
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ An demo of the widget is available at https://transit-departures-widget.blinktag
|
|
|
54
54
|
|
|
55
55
|
The following transit agencies use `transit-departures-widget` on their websites:
|
|
56
56
|
|
|
57
|
+
- [Cascades East Transit](https://cascadeseasttransit.com)
|
|
57
58
|
- [County Connection](https://countyconnection.com)
|
|
58
59
|
- [Kings Area Regional Transit](https://kartbus.org)
|
|
59
60
|
- [Marin Transit](https://marintransit.org/)
|
package/dist/app/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { }
|
|
1
|
+
export { };
|
package/dist/app/index.js
CHANGED
|
@@ -1,578 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { c as getPathToViewsFolder, n as generateTransitDeparturesWidgetJson, r as setDefaultConfig, t as generateTransitDeparturesWidgetHtml } from "../utils-BxiS6d7j.js";
|
|
2
|
+
import { clone, omit } from "lodash-es";
|
|
3
|
+
import { importGtfs, openDb } from "gtfs";
|
|
4
|
+
import untildify from "untildify";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
import yargs from "yargs";
|
|
6
8
|
import { hideBin } from "yargs/helpers";
|
|
7
|
-
import {
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
8
10
|
import express from "express";
|
|
9
|
-
import { clone, omit } from "lodash-es";
|
|
10
|
-
import untildify2 from "untildify";
|
|
11
|
-
|
|
12
|
-
// src/lib/file-utils.ts
|
|
13
|
-
import { dirname, join, resolve } from "path";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
15
|
-
import {
|
|
16
|
-
access,
|
|
17
|
-
cp,
|
|
18
|
-
copyFile,
|
|
19
|
-
mkdir,
|
|
20
|
-
readdir,
|
|
21
|
-
readFile,
|
|
22
|
-
rm
|
|
23
|
-
} from "fs/promises";
|
|
24
|
-
import beautify from "js-beautify";
|
|
25
|
-
import pug from "pug";
|
|
26
|
-
import untildify from "untildify";
|
|
27
|
-
function getPathToThisModuleFolder() {
|
|
28
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
let distFolderPath;
|
|
30
|
-
if (__dirname.endsWith("/dist/bin") || __dirname.endsWith("/dist/app")) {
|
|
31
|
-
distFolderPath = resolve(__dirname, "../../");
|
|
32
|
-
} else if (__dirname.endsWith("/dist")) {
|
|
33
|
-
distFolderPath = resolve(__dirname, "../");
|
|
34
|
-
} else {
|
|
35
|
-
distFolderPath = resolve(__dirname, "../../");
|
|
36
|
-
}
|
|
37
|
-
return distFolderPath;
|
|
38
|
-
}
|
|
39
|
-
function getPathToViewsFolder(config2) {
|
|
40
|
-
if (config2.templatePath) {
|
|
41
|
-
return untildify(config2.templatePath);
|
|
42
|
-
}
|
|
43
|
-
return join(getPathToThisModuleFolder(), "views/widget");
|
|
44
|
-
}
|
|
45
|
-
function getPathToTemplateFile(templateFileName, config2) {
|
|
46
|
-
const fullTemplateFileName = config2.noHead !== true ? `${templateFileName}_full.pug` : `${templateFileName}.pug`;
|
|
47
|
-
return join(getPathToViewsFolder(config2), fullTemplateFileName);
|
|
48
|
-
}
|
|
49
|
-
async function renderFile(templateFileName, templateVars, config2) {
|
|
50
|
-
const templatePath = getPathToTemplateFile(templateFileName, config2);
|
|
51
|
-
const html = await pug.renderFile(templatePath, templateVars);
|
|
52
|
-
if (config2.beautify === true) {
|
|
53
|
-
return beautify.html_beautify(html, { indent_size: 2 });
|
|
54
|
-
}
|
|
55
|
-
return html;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// src/lib/utils.ts
|
|
59
|
-
import { openDb, getDirections, getRoutes, getStops, getTrips } from "gtfs";
|
|
60
|
-
import { groupBy, last, maxBy, size, sortBy, uniqBy } from "lodash-es";
|
|
61
|
-
import sqlString from "sqlstring-sqlite";
|
|
62
|
-
import toposort from "toposort";
|
|
63
|
-
|
|
64
|
-
// src/lib/logging/log.ts
|
|
65
|
-
import { clearLine, cursorTo } from "readline";
|
|
66
|
-
import { noop } from "lodash-es";
|
|
67
|
-
import * as colors from "yoctocolors";
|
|
68
|
-
var formatWarning = (text) => {
|
|
69
|
-
const warningMessage = `${colors.underline("Warning")}: ${text}`;
|
|
70
|
-
return colors.yellow(warningMessage);
|
|
71
|
-
};
|
|
72
|
-
var formatError = (error) => {
|
|
73
|
-
const messageText = error instanceof Error ? error.message : error;
|
|
74
|
-
const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
|
|
75
|
-
"Error: ",
|
|
76
|
-
""
|
|
77
|
-
)}`;
|
|
78
|
-
return colors.red(errorMessage);
|
|
79
|
-
};
|
|
80
|
-
var logInfo = (config2) => {
|
|
81
|
-
if (config2.verbose === false) {
|
|
82
|
-
return noop;
|
|
83
|
-
}
|
|
84
|
-
if (config2.logFunction) {
|
|
85
|
-
return config2.logFunction;
|
|
86
|
-
}
|
|
87
|
-
return (text, overwrite) => {
|
|
88
|
-
if (overwrite === true && process.stdout.isTTY) {
|
|
89
|
-
clearLine(process.stdout, 0);
|
|
90
|
-
cursorTo(process.stdout, 0);
|
|
91
|
-
} else {
|
|
92
|
-
process.stdout.write("\n");
|
|
93
|
-
}
|
|
94
|
-
process.stdout.write(text);
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
var logWarn = (config2) => {
|
|
98
|
-
if (config2.logFunction) {
|
|
99
|
-
return config2.logFunction;
|
|
100
|
-
}
|
|
101
|
-
return (text) => {
|
|
102
|
-
process.stdout.write(`
|
|
103
|
-
${formatWarning(text)}
|
|
104
|
-
`);
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
var logError = (config2) => {
|
|
108
|
-
if (config2.logFunction) {
|
|
109
|
-
return config2.logFunction;
|
|
110
|
-
}
|
|
111
|
-
return (text) => {
|
|
112
|
-
process.stdout.write(`
|
|
113
|
-
${formatError(text)}
|
|
114
|
-
`);
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
function createLogger(config2) {
|
|
118
|
-
return {
|
|
119
|
-
info: logInfo(config2),
|
|
120
|
-
warn: logWarn(config2),
|
|
121
|
-
error: logError(config2)
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// src/lib/logging/messages.ts
|
|
126
|
-
var messages = {
|
|
127
|
-
noActiveCalendarsGlobal: "No active calendars found for the configured date range - returning empty routes and stops",
|
|
128
|
-
noActiveCalendarsForRoute: (routeId) => `route_id ${routeId} has no active calendars in range - skipping directions`,
|
|
129
|
-
noActiveCalendarsForDirection: (routeId, directionId) => `route_id ${routeId} direction ${directionId} has no active calendars in range - skipping stops`,
|
|
130
|
-
routeHasNoDirections: (routeId) => `route_id ${routeId} has no directions - skipping`,
|
|
131
|
-
stopNotFound: (routeId, directionId, stopId) => `stop_id ${stopId} for route ${routeId} direction ${directionId} not found - dropping`
|
|
132
|
-
};
|
|
133
11
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
noHead: false,
|
|
141
|
-
refreshIntervalSeconds: 20,
|
|
142
|
-
skipImport: false,
|
|
143
|
-
timeFormat: "12hour",
|
|
144
|
-
includeCoordinates: false,
|
|
145
|
-
overwriteExistingFiles: true,
|
|
146
|
-
verbose: true
|
|
147
|
-
};
|
|
148
|
-
const config2 = Object.assign(defaults, initialConfig);
|
|
149
|
-
const viewsFolderPath = getPathToViewsFolder(config2);
|
|
150
|
-
const i18n = new I18n({
|
|
151
|
-
directory: join2(viewsFolderPath, "locales"),
|
|
152
|
-
defaultLocale: config2.locale,
|
|
153
|
-
updateFiles: false
|
|
154
|
-
});
|
|
155
|
-
const configWithI18n = Object.assign(config2, {
|
|
156
|
-
__: i18n.__
|
|
157
|
-
});
|
|
158
|
-
return configWithI18n;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// src/lib/utils.ts
|
|
162
|
-
var getCalendarsForDateRange = (config2) => {
|
|
163
|
-
const db = openDb(config2);
|
|
164
|
-
let whereClause = "";
|
|
165
|
-
const whereClauses = [];
|
|
166
|
-
if (config2.endDate) {
|
|
167
|
-
whereClauses.push(`start_date <= ${sqlString.escape(config2.endDate)}`);
|
|
168
|
-
}
|
|
169
|
-
if (config2.startDate) {
|
|
170
|
-
whereClauses.push(`end_date >= ${sqlString.escape(config2.startDate)}`);
|
|
171
|
-
}
|
|
172
|
-
if (whereClauses.length > 0) {
|
|
173
|
-
whereClause = `WHERE ${whereClauses.join(" AND ")}`;
|
|
174
|
-
}
|
|
175
|
-
return db.prepare(`SELECT * FROM calendar ${whereClause}`).all();
|
|
176
|
-
};
|
|
177
|
-
function formatRouteName(route) {
|
|
178
|
-
let routeName = "";
|
|
179
|
-
if (route.route_short_name !== null) {
|
|
180
|
-
routeName += route.route_short_name;
|
|
181
|
-
}
|
|
182
|
-
if (route.route_short_name !== null && route.route_long_name !== null) {
|
|
183
|
-
routeName += " - ";
|
|
184
|
-
}
|
|
185
|
-
if (route.route_long_name !== null) {
|
|
186
|
-
routeName += route.route_long_name;
|
|
187
|
-
}
|
|
188
|
-
return routeName;
|
|
189
|
-
}
|
|
190
|
-
function getDirectionsForRoute(route, config2) {
|
|
191
|
-
const logger = createLogger(config2);
|
|
192
|
-
const db = openDb(config2);
|
|
193
|
-
const directions = getDirections({ route_id: route.route_id }, [
|
|
194
|
-
"direction_id",
|
|
195
|
-
"direction"
|
|
196
|
-
]).filter((direction) => direction.direction_id !== void 0).map((direction) => ({
|
|
197
|
-
direction_id: direction.direction_id,
|
|
198
|
-
direction: direction.direction
|
|
199
|
-
}));
|
|
200
|
-
const calendars = getCalendarsForDateRange(config2);
|
|
201
|
-
if (calendars.length === 0) {
|
|
202
|
-
logger.warn(messages.noActiveCalendarsForRoute(route.route_id));
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
if (directions.length === 0) {
|
|
206
|
-
const headsigns = db.prepare(
|
|
207
|
-
`SELECT direction_id, trip_headsign, count(*) AS count FROM trips WHERE route_id = ? AND service_id IN (${calendars.map((calendar) => `'${calendar.service_id}'`).join(", ")}) GROUP BY direction_id, trip_headsign`
|
|
208
|
-
).all(route.route_id);
|
|
209
|
-
for (const group of Object.values(groupBy(headsigns, "direction_id"))) {
|
|
210
|
-
const mostCommonHeadsign = maxBy(group, "count");
|
|
211
|
-
directions.push({
|
|
212
|
-
direction_id: mostCommonHeadsign.direction_id,
|
|
213
|
-
direction: config2.__("To {{{headsign}}}", {
|
|
214
|
-
headsign: mostCommonHeadsign.trip_headsign
|
|
215
|
-
})
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return directions;
|
|
220
|
-
}
|
|
221
|
-
function sortStopIdsBySequence(stoptimes) {
|
|
222
|
-
const stoptimesGroupedByTrip = groupBy(stoptimes, "trip_id");
|
|
223
|
-
try {
|
|
224
|
-
const stopGraph = [];
|
|
225
|
-
for (const tripStoptimes of Object.values(stoptimesGroupedByTrip)) {
|
|
226
|
-
const sortedStopIds = sortBy(tripStoptimes, "stop_sequence").map(
|
|
227
|
-
(stoptime) => stoptime.stop_id
|
|
228
|
-
);
|
|
229
|
-
for (const [index, stopId] of sortedStopIds.entries()) {
|
|
230
|
-
if (index === sortedStopIds.length - 1) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
stopGraph.push([stopId, sortedStopIds[index + 1]]);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return toposort(
|
|
237
|
-
stopGraph
|
|
238
|
-
);
|
|
239
|
-
} catch {
|
|
240
|
-
}
|
|
241
|
-
const longestTripStoptimes = maxBy(
|
|
242
|
-
Object.values(stoptimesGroupedByTrip),
|
|
243
|
-
(stoptimes2) => size(stoptimes2)
|
|
244
|
-
);
|
|
245
|
-
if (!longestTripStoptimes) {
|
|
246
|
-
return [];
|
|
247
|
-
}
|
|
248
|
-
return longestTripStoptimes.map((stoptime) => stoptime.stop_id);
|
|
249
|
-
}
|
|
250
|
-
function getStopsForDirection(route, direction, config2, stopCache) {
|
|
251
|
-
const logger = createLogger(config2);
|
|
252
|
-
const db = openDb(config2);
|
|
253
|
-
const calendars = getCalendarsForDateRange(config2);
|
|
254
|
-
if (calendars.length === 0) {
|
|
255
|
-
logger.warn(
|
|
256
|
-
messages.noActiveCalendarsForDirection(
|
|
257
|
-
route.route_id,
|
|
258
|
-
direction.direction_id
|
|
259
|
-
)
|
|
260
|
-
);
|
|
261
|
-
return [];
|
|
262
|
-
}
|
|
263
|
-
const whereClause = formatWhereClauses({
|
|
264
|
-
direction_id: direction.direction_id,
|
|
265
|
-
route_id: route.route_id,
|
|
266
|
-
service_id: calendars.map((calendar) => calendar.service_id)
|
|
267
|
-
});
|
|
268
|
-
const stoptimes = db.prepare(
|
|
269
|
-
`SELECT stop_id, stop_sequence, trip_id FROM stop_times WHERE trip_id IN (SELECT trip_id FROM trips ${whereClause}) ORDER BY stop_sequence ASC`
|
|
270
|
-
).all();
|
|
271
|
-
const sortedStopIds = sortStopIdsBySequence(stoptimes);
|
|
272
|
-
const deduplicatedStopIds = sortedStopIds.reduce(
|
|
273
|
-
(memo, stopId) => {
|
|
274
|
-
if (last(memo) !== stopId) {
|
|
275
|
-
memo.push(stopId);
|
|
276
|
-
}
|
|
277
|
-
return memo;
|
|
278
|
-
},
|
|
279
|
-
[]
|
|
280
|
-
);
|
|
281
|
-
deduplicatedStopIds.pop();
|
|
282
|
-
const stopFields = [
|
|
283
|
-
"stop_id",
|
|
284
|
-
"stop_name",
|
|
285
|
-
"stop_code",
|
|
286
|
-
"parent_station"
|
|
287
|
-
];
|
|
288
|
-
if (config2.includeCoordinates) {
|
|
289
|
-
stopFields.push("stop_lat", "stop_lon");
|
|
290
|
-
}
|
|
291
|
-
const missingStopIds = stopCache ? deduplicatedStopIds.filter((stopId) => !stopCache.has(stopId)) : deduplicatedStopIds;
|
|
292
|
-
const fetchedStops = missingStopIds.length ? getStops(
|
|
293
|
-
{ stop_id: missingStopIds },
|
|
294
|
-
stopFields
|
|
295
|
-
) : [];
|
|
296
|
-
if (stopCache) {
|
|
297
|
-
for (const stop of fetchedStops) {
|
|
298
|
-
stopCache.set(stop.stop_id, stop);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return deduplicatedStopIds.map((stopId) => {
|
|
302
|
-
const stop = stopCache?.get(stopId) ?? fetchedStops.find((candidate) => candidate.stop_id === stopId);
|
|
303
|
-
if (!stop) {
|
|
304
|
-
logger.warn(
|
|
305
|
-
messages.stopNotFound(route.route_id, direction.direction_id, stopId)
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
return stop;
|
|
309
|
-
}).filter(Boolean);
|
|
310
|
-
}
|
|
311
|
-
function generateTransitDeparturesWidgetHtml(config2) {
|
|
312
|
-
const templateVars = {
|
|
313
|
-
config: config2,
|
|
314
|
-
__: config2.__
|
|
315
|
-
};
|
|
316
|
-
return renderFile("widget", templateVars, config2);
|
|
317
|
-
}
|
|
318
|
-
function generateTransitDeparturesWidgetJson(config2) {
|
|
319
|
-
const logger = createLogger(config2);
|
|
320
|
-
const calendars = getCalendarsForDateRange(config2);
|
|
321
|
-
if (calendars.length === 0) {
|
|
322
|
-
logger.warn(messages.noActiveCalendarsGlobal);
|
|
323
|
-
return { routes: [], stops: [] };
|
|
324
|
-
}
|
|
325
|
-
const routes = getRoutes();
|
|
326
|
-
const stops = [];
|
|
327
|
-
const filteredRoutes = [];
|
|
328
|
-
const stopCache = /* @__PURE__ */ new Map();
|
|
329
|
-
for (const route of routes) {
|
|
330
|
-
const routeWithFullName = {
|
|
331
|
-
...route,
|
|
332
|
-
route_full_name: formatRouteName(route)
|
|
333
|
-
};
|
|
334
|
-
const directions = getDirectionsForRoute(routeWithFullName, config2);
|
|
335
|
-
if (directions.length === 0) {
|
|
336
|
-
logger.warn(messages.routeHasNoDirections(route.route_id));
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
const directionsWithData = directions.map((direction) => {
|
|
340
|
-
const directionStops = getStopsForDirection(
|
|
341
|
-
routeWithFullName,
|
|
342
|
-
direction,
|
|
343
|
-
config2,
|
|
344
|
-
stopCache
|
|
345
|
-
);
|
|
346
|
-
if (directionStops.length === 0) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
stops.push(...directionStops);
|
|
350
|
-
const trips = getTrips(
|
|
351
|
-
{
|
|
352
|
-
route_id: route.route_id,
|
|
353
|
-
direction_id: direction.direction_id,
|
|
354
|
-
service_id: calendars.map(
|
|
355
|
-
(calendar) => calendar.service_id
|
|
356
|
-
)
|
|
357
|
-
},
|
|
358
|
-
["trip_id"]
|
|
359
|
-
);
|
|
360
|
-
return {
|
|
361
|
-
...direction,
|
|
362
|
-
stopIds: directionStops.map((stop) => stop.stop_id),
|
|
363
|
-
tripIds: trips.map((trip) => trip.trip_id)
|
|
364
|
-
};
|
|
365
|
-
}).filter(Boolean);
|
|
366
|
-
if (directionsWithData.length === 0) {
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
filteredRoutes.push({
|
|
370
|
-
...routeWithFullName,
|
|
371
|
-
directions: directionsWithData
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
const sortedRoutes = [...filteredRoutes].sort((a, b) => {
|
|
375
|
-
const aShort = a.route_short_name ?? "";
|
|
376
|
-
const bShort = b.route_short_name ?? "";
|
|
377
|
-
const aNum = Number.parseInt(aShort, 10);
|
|
378
|
-
const bNum = Number.parseInt(bShort, 10);
|
|
379
|
-
if (!Number.isNaN(aNum) && !Number.isNaN(bNum) && aNum !== bNum) {
|
|
380
|
-
return aNum - bNum;
|
|
381
|
-
}
|
|
382
|
-
if (Number.isNaN(aNum) && !Number.isNaN(bNum)) {
|
|
383
|
-
return 1;
|
|
384
|
-
}
|
|
385
|
-
if (!Number.isNaN(aNum) && Number.isNaN(bNum)) {
|
|
386
|
-
return -1;
|
|
387
|
-
}
|
|
388
|
-
return aShort.localeCompare(bShort, void 0, {
|
|
389
|
-
numeric: true,
|
|
390
|
-
sensitivity: "base"
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
const parentStationIds = new Set(stops.map((stop) => stop.parent_station));
|
|
394
|
-
const parentStationStops = getStops(
|
|
395
|
-
{ stop_id: Array.from(parentStationIds) },
|
|
396
|
-
["stop_id", "stop_name", "stop_code", "parent_station"]
|
|
397
|
-
);
|
|
398
|
-
stops.push(
|
|
399
|
-
...parentStationStops.map((stop) => ({
|
|
400
|
-
...stop,
|
|
401
|
-
is_parent_station: true
|
|
402
|
-
}))
|
|
403
|
-
);
|
|
404
|
-
const sortedStops = sortBy(uniqBy(stops, "stop_id"), "stop_name");
|
|
405
|
-
return {
|
|
406
|
-
routes: arrayOfArrays(removeNulls(sortedRoutes)),
|
|
407
|
-
stops: arrayOfArrays(removeNulls(sortedStops))
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
function removeNulls(data) {
|
|
411
|
-
if (Array.isArray(data)) {
|
|
412
|
-
return data.map(removeNulls).filter((item) => item !== null && item !== void 0);
|
|
413
|
-
} else if (data !== null && typeof data === "object" && Object.getPrototypeOf(data) === Object.prototype) {
|
|
414
|
-
return Object.entries(data).reduce(
|
|
415
|
-
(acc, [key, value]) => {
|
|
416
|
-
const cleanedValue = removeNulls(value);
|
|
417
|
-
if (cleanedValue !== null && cleanedValue !== void 0) {
|
|
418
|
-
acc[key] = cleanedValue;
|
|
419
|
-
}
|
|
420
|
-
return acc;
|
|
421
|
-
},
|
|
422
|
-
{}
|
|
423
|
-
);
|
|
424
|
-
} else {
|
|
425
|
-
return data;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
function arrayOfArrays(array) {
|
|
429
|
-
if (array.length === 0) {
|
|
430
|
-
return { fields: [], rows: [] };
|
|
431
|
-
}
|
|
432
|
-
const fields = Array.from(
|
|
433
|
-
array.reduce((fieldSet, item) => {
|
|
434
|
-
Object.keys(item ?? {}).forEach((key) => fieldSet.add(key));
|
|
435
|
-
return fieldSet;
|
|
436
|
-
}, /* @__PURE__ */ new Set())
|
|
437
|
-
);
|
|
438
|
-
return {
|
|
439
|
-
fields,
|
|
440
|
-
rows: array.map((item) => fields.map((field) => item?.[field] ?? null))
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function formatWhereClause(key, value) {
|
|
444
|
-
if (Array.isArray(value)) {
|
|
445
|
-
let whereClause = `${sqlString.escapeId(key)} IN (${value.filter((v) => v !== null).map((v) => sqlString.escape(v)).join(", ")})`;
|
|
446
|
-
if (value.includes(null)) {
|
|
447
|
-
whereClause = `(${whereClause} OR ${sqlString.escapeId(key)} IS NULL)`;
|
|
448
|
-
}
|
|
449
|
-
return whereClause;
|
|
450
|
-
}
|
|
451
|
-
if (value === null) {
|
|
452
|
-
return `${sqlString.escapeId(key)} IS NULL`;
|
|
453
|
-
}
|
|
454
|
-
return `${sqlString.escapeId(key)} = ${sqlString.escape(value)}`;
|
|
455
|
-
}
|
|
456
|
-
function formatWhereClauses(query) {
|
|
457
|
-
if (Object.keys(query).length === 0) {
|
|
458
|
-
return "";
|
|
459
|
-
}
|
|
460
|
-
const whereClauses = Object.entries(query).map(
|
|
461
|
-
([key, value]) => formatWhereClause(key, value)
|
|
462
|
-
);
|
|
463
|
-
return `WHERE ${whereClauses.join(" AND ")}`;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/app/index.ts
|
|
467
|
-
var argv = yargs(hideBin(process.argv)).option("c", {
|
|
468
|
-
alias: "configPath",
|
|
469
|
-
describe: "Path to config file",
|
|
470
|
-
default: "./config.json",
|
|
471
|
-
type: "string"
|
|
12
|
+
//#region src/app/index.ts
|
|
13
|
+
const argv = yargs(hideBin(process.argv)).option("c", {
|
|
14
|
+
alias: "configPath",
|
|
15
|
+
describe: "Path to config file",
|
|
16
|
+
default: "./config.json",
|
|
17
|
+
type: "string"
|
|
472
18
|
}).parseSync();
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
var config = setDefaultConfig(selectedConfig);
|
|
19
|
+
const app = express();
|
|
20
|
+
const configPath = argv.configPath || join(process.cwd(), "config.json");
|
|
21
|
+
const config = setDefaultConfig(JSON.parse(readFileSync(configPath, "utf8")));
|
|
477
22
|
config.noHead = false;
|
|
478
23
|
config.assetPath = "/";
|
|
479
24
|
config.logFunction = console.log;
|
|
480
25
|
try {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
494
|
-
await importGtfs(gtfsImportConfig);
|
|
26
|
+
openDb(config);
|
|
27
|
+
const gtfsPath = config.agency.gtfs_static_path;
|
|
28
|
+
const gtfsUrl = config.agency.gtfs_static_url;
|
|
29
|
+
if (!gtfsPath && !gtfsUrl) throw new Error("Missing GTFS source. Set `agency.gtfs_static_path` or `agency.gtfs_static_url` in config.json.");
|
|
30
|
+
const agencyImportConfig = {
|
|
31
|
+
exclude: config.agency.exclude,
|
|
32
|
+
...gtfsPath ? { path: gtfsPath } : { url: gtfsUrl }
|
|
33
|
+
};
|
|
34
|
+
await importGtfs({
|
|
35
|
+
...clone(omit(config, "agency")),
|
|
36
|
+
agencies: [agencyImportConfig]
|
|
37
|
+
});
|
|
495
38
|
} catch (error) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
);
|
|
499
|
-
throw error;
|
|
39
|
+
console.error(`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.`);
|
|
40
|
+
throw error;
|
|
500
41
|
}
|
|
501
42
|
app.set("views", getPathToViewsFolder(config));
|
|
502
43
|
app.set("view engine", "pug");
|
|
503
44
|
app.use((req, res, next) => {
|
|
504
|
-
|
|
505
|
-
|
|
45
|
+
console.log(`${req.method} ${req.url}`);
|
|
46
|
+
next();
|
|
506
47
|
});
|
|
507
|
-
|
|
48
|
+
const staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify(config.templatePath);
|
|
508
49
|
app.use(express.static(staticAssetPath));
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
{ route: "/js", package: "accessible-autocomplete", subPath: "" },
|
|
513
|
-
{ route: "/css", package: "accessible-autocomplete", subPath: "" }
|
|
514
|
-
];
|
|
515
|
-
var resolvePackagePath = (packageName, subPath) => {
|
|
516
|
-
const packagePath = dirname2(fileURLToPath2(import.meta.resolve(packageName)));
|
|
517
|
-
return subPath ? join3(packagePath, subPath) : packagePath;
|
|
518
|
-
};
|
|
519
|
-
for (const { route, package: pkg, subPath } of frontendLibraryPaths) {
|
|
520
|
-
app.use(route, express.static(resolvePackagePath(pkg, subPath)));
|
|
521
|
-
}
|
|
50
|
+
const browserAssetsPath = join(dirname(fileURLToPath(import.meta.url)), "../browser");
|
|
51
|
+
app.use("/js", express.static(browserAssetsPath));
|
|
52
|
+
app.use("/css", express.static(browserAssetsPath));
|
|
522
53
|
app.get("/", async (request, response, next) => {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
54
|
+
try {
|
|
55
|
+
const html = await generateTransitDeparturesWidgetHtml(config);
|
|
56
|
+
response.send(html);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
next(error);
|
|
59
|
+
}
|
|
529
60
|
});
|
|
530
61
|
app.get("/data/routes.json", async (request, response, next) => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
62
|
+
try {
|
|
63
|
+
const { routes } = await generateTransitDeparturesWidgetJson(config);
|
|
64
|
+
response.json(routes);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
next(error);
|
|
67
|
+
}
|
|
537
68
|
});
|
|
538
69
|
app.get("/data/stops.json", async (request, response, next) => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
70
|
+
try {
|
|
71
|
+
const { stops } = await generateTransitDeparturesWidgetJson(config);
|
|
72
|
+
response.json(stops);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
next(error);
|
|
75
|
+
}
|
|
545
76
|
});
|
|
546
77
|
app.use((req, res) => {
|
|
547
|
-
|
|
78
|
+
res.status(404).send("Not Found");
|
|
548
79
|
});
|
|
549
|
-
app.use(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
} catch (err) {
|
|
572
|
-
console.error("Failed to start server:", err);
|
|
573
|
-
process.exit(1);
|
|
574
|
-
}
|
|
80
|
+
app.use((err, req, res, next) => {
|
|
81
|
+
console.error(err.stack);
|
|
82
|
+
res.status(500).send("Something broke!");
|
|
83
|
+
});
|
|
84
|
+
const startServer = async (port) => {
|
|
85
|
+
try {
|
|
86
|
+
await new Promise((resolve, reject) => {
|
|
87
|
+
const server = app.listen(port).once("listening", () => {
|
|
88
|
+
console.log(`Express server listening on port ${port}`);
|
|
89
|
+
resolve();
|
|
90
|
+
}).once("error", (err) => {
|
|
91
|
+
if (err.code === "EADDRINUSE") {
|
|
92
|
+
console.log(`Port ${port} is in use, trying ${port + 1}`);
|
|
93
|
+
server.close();
|
|
94
|
+
resolve(startServer(port + 1));
|
|
95
|
+
} else reject(err);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error("Failed to start server:", err);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
575
102
|
};
|
|
576
|
-
|
|
577
|
-
|
|
103
|
+
startServer(process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3);
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { };
|
|
578
107
|
//# sourceMappingURL=index.js.map
|