transit-departures-widget 2.7.3 → 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/dist/app/index.d.ts +1 -2
- package/dist/app/index.js +81 -555
- 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 -641
- package/dist/bin/transit-departures-widget.js.map +1 -1
- package/dist/browser/THIRD_PARTY_LICENSES.txt +87 -0
- package/dist/browser/accessible-autocomplete.min.css +3 -0
- package/dist/browser/accessible-autocomplete.min.js +2 -0
- package/dist/browser/gtfs-realtime.browser.proto.js +0 -0
- package/dist/browser/pbf.js +1 -0
- package/dist/index.d.ts +29 -26
- package/dist/index.js +2 -593
- 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 +10 -11
- package/dist/index.js.map +0 -1
package/dist/app/index.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { }
|
|
1
|
+
export { };
|
package/dist/app/index.js
CHANGED
|
@@ -1,581 +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
|
-
const gtfsImportConfig = {
|
|
494
|
-
...clone(omit(config, "agency")),
|
|
495
|
-
agencies: [agencyImportConfig]
|
|
496
|
-
};
|
|
497
|
-
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
|
+
});
|
|
498
38
|
} catch (error) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
);
|
|
502
|
-
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;
|
|
503
41
|
}
|
|
504
42
|
app.set("views", getPathToViewsFolder(config));
|
|
505
43
|
app.set("view engine", "pug");
|
|
506
44
|
app.use((req, res, next) => {
|
|
507
|
-
|
|
508
|
-
|
|
45
|
+
console.log(`${req.method} ${req.url}`);
|
|
46
|
+
next();
|
|
509
47
|
});
|
|
510
|
-
|
|
48
|
+
const staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify(config.templatePath);
|
|
511
49
|
app.use(express.static(staticAssetPath));
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
{ route: "/js", package: "accessible-autocomplete", subPath: "" },
|
|
516
|
-
{ route: "/css", package: "accessible-autocomplete", subPath: "" }
|
|
517
|
-
];
|
|
518
|
-
var resolvePackagePath = (packageName, subPath) => {
|
|
519
|
-
const packagePath = dirname2(fileURLToPath2(import.meta.resolve(packageName)));
|
|
520
|
-
return subPath ? join3(packagePath, subPath) : packagePath;
|
|
521
|
-
};
|
|
522
|
-
for (const { route, package: pkg, subPath } of frontendLibraryPaths) {
|
|
523
|
-
app.use(route, express.static(resolvePackagePath(pkg, subPath)));
|
|
524
|
-
}
|
|
50
|
+
const browserAssetsPath = join(dirname(fileURLToPath(import.meta.url)), "../browser");
|
|
51
|
+
app.use("/js", express.static(browserAssetsPath));
|
|
52
|
+
app.use("/css", express.static(browserAssetsPath));
|
|
525
53
|
app.get("/", async (request, response, next) => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
54
|
+
try {
|
|
55
|
+
const html = await generateTransitDeparturesWidgetHtml(config);
|
|
56
|
+
response.send(html);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
next(error);
|
|
59
|
+
}
|
|
532
60
|
});
|
|
533
61
|
app.get("/data/routes.json", async (request, response, next) => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
62
|
+
try {
|
|
63
|
+
const { routes } = await generateTransitDeparturesWidgetJson(config);
|
|
64
|
+
response.json(routes);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
next(error);
|
|
67
|
+
}
|
|
540
68
|
});
|
|
541
69
|
app.get("/data/stops.json", async (request, response, next) => {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
70
|
+
try {
|
|
71
|
+
const { stops } = await generateTransitDeparturesWidgetJson(config);
|
|
72
|
+
response.json(stops);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
next(error);
|
|
75
|
+
}
|
|
548
76
|
});
|
|
549
77
|
app.use((req, res) => {
|
|
550
|
-
|
|
78
|
+
res.status(404).send("Not Found");
|
|
551
79
|
});
|
|
552
|
-
app.use(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
} catch (err) {
|
|
575
|
-
console.error("Failed to start server:", err);
|
|
576
|
-
process.exit(1);
|
|
577
|
-
}
|
|
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
|
+
}
|
|
578
102
|
};
|
|
579
|
-
|
|
580
|
-
|
|
103
|
+
startServer(process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3);
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
export { };
|
|
581
107
|
//# sourceMappingURL=index.js.map
|