tileserver-gl-light 5.4.1-pre.0 → 5.5.0-pre.1

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/src/main.js CHANGED
@@ -14,12 +14,34 @@ import path from 'path';
14
14
  import { fileURLToPath } from 'url';
15
15
  import axios from 'axios';
16
16
  import { server } from './server.js';
17
- import { isValidHttpUrl } from './utils.js';
17
+ import { isValidRemoteUrl } from './utils.js';
18
18
  import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
19
19
  import { program } from 'commander';
20
20
  import { existsP } from './promises.js';
21
21
  import { openMbTilesWrapper } from './mbtiles_wrapper.js';
22
22
 
23
+ // ============================================================================
24
+ // Global Error Handlers - Prevent server crashes from unhandled errors
25
+ // ============================================================================
26
+
27
+ // Prevent unhandled promise rejections from crashing the server
28
+ process.on('unhandledRejection', (reason, promise) => {
29
+ console.error('Unhandled Promise Rejection at:', promise);
30
+ console.error('Reason:', reason);
31
+ // Don't exit - keep server running
32
+ });
33
+
34
+ // Prevent uncaught exceptions from crashing the server
35
+ process.on('uncaughtException', (error) => {
36
+ console.error('Uncaught Exception:', error);
37
+ console.error('Stack:', error.stack);
38
+ // Don't exit - keep server running
39
+ });
40
+
41
+ // ============================================================================
42
+ // Continue with normal startup
43
+ // ============================================================================
44
+
23
45
  const __filename = fileURLToPath(import.meta.url);
24
46
  const __dirname = path.dirname(__filename);
25
47
  const packageJson = JSON.parse(
@@ -36,7 +58,7 @@ program
36
58
  .usage('tileserver-gl [mbtiles] [options]')
37
59
  .option(
38
60
  '--file <file>',
39
- 'MBTiles or PMTiles file\n' +
61
+ 'MBTiles or PMTiles file (local path, http(s)://, or s3:// URL)\n' +
40
62
  '\t ignored if the configuration file is also specified',
41
63
  )
42
64
  .option(
@@ -57,7 +79,18 @@ program
57
79
  '-u|--public_url <url>',
58
80
  'Enable exposing the server on subpaths, not necessarily the root of the domain',
59
81
  )
60
- .option('-V, --verbose', 'More verbose output')
82
+ .option(
83
+ '-V, --verbose [level]',
84
+ 'More verbose output (can specify level 1-3, default 1)',
85
+ (value) => {
86
+ // If no value provided, return 1 (boolean true case)
87
+ if (value === undefined || value === true) return 1;
88
+ // Parse the numeric value
89
+ const level = parseInt(value, 10);
90
+ // Validate level is between 1-3
91
+ return isNaN(level) ? 1 : Math.min(Math.max(level, 1), 3);
92
+ },
93
+ )
61
94
  .option('-s, --silent', 'Less verbose output')
62
95
  .option('-l|--log_file <file>', 'output log file (defaults to standard out)')
63
96
  .option(
@@ -96,19 +129,48 @@ const startWithInputFile = async (inputFile) => {
96
129
  `[INFO] See documentation to learn how to create config.json file.`,
97
130
  );
98
131
 
132
+ // Determine file type from prefix or extension
133
+ let fileType = null;
134
+
135
+ if (inputFile.startsWith('pmtiles://')) {
136
+ fileType = 'pmtiles';
137
+ inputFile = inputFile.replace('pmtiles://', '');
138
+ } else if (inputFile.startsWith('mbtiles://')) {
139
+ fileType = 'mbtiles';
140
+ inputFile = inputFile.replace('mbtiles://', '');
141
+ } else {
142
+ // Determine by extension (remove query parameters first)
143
+ const extension = inputFile.split('?')[0].split('.').pop().toLowerCase();
144
+ if (extension === 'pmtiles' || extension === 'mbtiles') {
145
+ fileType = extension;
146
+ }
147
+ }
148
+
149
+ if (!fileType) {
150
+ console.log(`ERROR: Unable to determine file type for "${inputFile}".`);
151
+ console.log(
152
+ `File must end with .pmtiles or .mbtiles, or use pmtiles:// or mbtiles:// prefix.`,
153
+ );
154
+ process.exit(1);
155
+ }
156
+
99
157
  let inputFilePath;
100
- if (isValidHttpUrl(inputFile)) {
158
+ // Check if input is a remote URL (HTTP, HTTPS, or S3)
159
+ if (isValidRemoteUrl(inputFile)) {
101
160
  inputFilePath = process.cwd();
102
161
  } else {
162
+ // Local file path
103
163
  inputFile = path.resolve(process.cwd(), inputFile);
104
164
  inputFilePath = path.dirname(inputFile);
105
165
 
166
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Validating local file from CLI argument
106
167
  const inputFileStats = await fsp.stat(inputFile);
107
168
  if (!inputFileStats.isFile() || inputFileStats.size === 0) {
108
- console.log(`ERROR: Not a valid input file: `);
169
+ console.log(`ERROR: Not a valid input file: ${inputFile}`);
109
170
  process.exit(1);
110
171
  }
111
172
  }
173
+ console.log(`[INFO] Loading data source from: ${inputFile}`);
112
174
 
113
175
  const styleDir = path.resolve(
114
176
  __dirname,
@@ -129,16 +191,22 @@ const startWithInputFile = async (inputFile) => {
129
191
  data: {},
130
192
  };
131
193
 
132
- const extension = inputFile.split('.').pop().toLowerCase();
133
- if (extension === 'pmtiles') {
134
- const fileOpenInfo = openPMtiles(inputFile);
135
- const metadata = await getPMtilesInfo(fileOpenInfo);
194
+ if (fileType === 'pmtiles') {
195
+ const fileOpenInfo = openPMtiles(
196
+ inputFile,
197
+ undefined,
198
+ undefined,
199
+ undefined,
200
+ opts.verbose,
201
+ );
202
+ const metadata = await getPMtilesInfo(fileOpenInfo, inputFile);
136
203
 
137
204
  if (
138
205
  metadata.format === 'pbf' &&
139
206
  metadata.name.toLowerCase().indexOf('openmaptiles') > -1
140
207
  ) {
141
- if (isValidHttpUrl(inputFile)) {
208
+ // Use inputFile directly for remote URLs (HTTP or S3)
209
+ if (isValidRemoteUrl(inputFile)) {
142
210
  config['data'][`v3`] = {
143
211
  pmtiles: inputFile,
144
212
  };
@@ -153,6 +221,7 @@ const startWithInputFile = async (inputFile) => {
153
221
  const styleFileRel = styleName + '/style.json';
154
222
  const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
155
223
  if (await existsP(styleFile)) {
224
+ // eslint-disable-next-line security/detect-object-injection -- styleName is from trusted filesystem readdir, not user input
156
225
  config['styles'][styleName] = {
157
226
  style: styleFileRel,
158
227
  tilejson: {
@@ -165,7 +234,8 @@ const startWithInputFile = async (inputFile) => {
165
234
  console.log(
166
235
  `WARN: PMTiles not in "openmaptiles" format. Serving raw data only...`,
167
236
  );
168
- if (isValidHttpUrl(inputFile)) {
237
+ // Use inputFile directly for remote URLs (HTTP or S3)
238
+ if (isValidRemoteUrl(inputFile)) {
169
239
  config['data'][(metadata.id || 'pmtiles').replace(/[?/:]/g, '_')] = {
170
240
  pmtiles: inputFile,
171
241
  };
@@ -183,10 +253,11 @@ const startWithInputFile = async (inputFile) => {
183
253
  }
184
254
 
185
255
  return startServer(null, config);
186
- } else {
187
- if (isValidHttpUrl(inputFile)) {
256
+ } else if (fileType === 'mbtiles') {
257
+ // MBTiles handling - reject remote URLs
258
+ if (isValidRemoteUrl(inputFile)) {
188
259
  console.log(
189
- `ERROR: MBTiles does not support web based files. "${inputFile}" is not a valid data file.`,
260
+ `ERROR: MBTiles does not support remote files. "${inputFile}" is not a valid data file.`,
190
261
  );
191
262
  process.exit(1);
192
263
  }
@@ -215,6 +286,7 @@ const startWithInputFile = async (inputFile) => {
215
286
  const styleFileRel = styleName + '/style.json';
216
287
  const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
217
288
  if (await existsP(styleFile)) {
289
+ // eslint-disable-next-line security/detect-object-injection -- styleName is from trusted filesystem readdir, not user input
218
290
  config['styles'][styleName] = {
219
291
  style: styleFileRel,
220
292
  tilejson: {
@@ -242,6 +314,7 @@ const startWithInputFile = async (inputFile) => {
242
314
  }
243
315
  };
244
316
 
317
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Config path from CLI argument is expected behavior
245
318
  fs.stat(path.resolve(opts.config), async (err, stats) => {
246
319
  if (err || !stats.isFile() || stats.size === 0) {
247
320
  let inputFile;
@@ -258,6 +331,7 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
258
331
  const files = await fsp.readdir(process.cwd());
259
332
  for (const filename of files) {
260
333
  if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) {
334
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Scanning current directory for tile files
261
335
  const inputFilesStats = await fsp.stat(filename);
262
336
  if (inputFilesStats.isFile() && inputFilesStats.size > 0) {
263
337
  inputFile = filename;
@@ -272,6 +346,7 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
272
346
  const url =
273
347
  'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
274
348
  const filename = 'zurich_switzerland.mbtiles';
349
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Writing demo file to known filename
275
350
  const writer = fs.createWriteStream(filename);
276
351
  console.log(`No input file found`);
277
352
  console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
@@ -12,7 +12,7 @@ class MBTilesWrapper {
12
12
 
13
13
  /**
14
14
  * Get the underlying MBTiles object.
15
- * @returns {MBTiles}
15
+ * @returns {MBTiles} The MBTiles instance.
16
16
  */
17
17
  getMbTiles() {
18
18
  return this._mbtiles;
@@ -20,7 +20,7 @@ class MBTilesWrapper {
20
20
 
21
21
  /**
22
22
  * Get the MBTiles metadata object.
23
- * @returns {Promise<object>}
23
+ * @returns {Promise<object>} A promise that resolves with the MBTiles metadata object.
24
24
  */
25
25
  getInfo() {
26
26
  return this._getInfoP();
@@ -28,10 +28,9 @@ class MBTilesWrapper {
28
28
  }
29
29
 
30
30
  /**
31
- * Open the given MBTiles file and return a promise that resolves with a
32
- * MBTilesWrapper instance.
33
- * @param inputFile Input file
34
- * @returns {Promise<MBTilesWrapper>}
31
+ * Open the given MBTiles file and return a promise that resolves with a MBTilesWrapper instance.
32
+ * @param {string} inputFile - The path to the input MBTiles file.
33
+ * @returns {Promise<MBTilesWrapper>} A promise that resolves with a MBTilesWrapper instance or rejects with an error.
35
34
  */
36
35
  export function openMbTilesWrapper(inputFile) {
37
36
  return new Promise((resolve, reject) => {