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/CHANGELOG.md +3 -2
- package/docs/config.rst +158 -2
- package/docs/usage.rst +66 -3
- package/package.json +7 -6
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/src/main.js +89 -14
- package/src/mbtiles_wrapper.js +5 -6
- package/src/pmtiles_adapter.js +413 -60
- package/src/render.js +48 -13
- package/src/serve_data.js +28 -9
- package/src/serve_rendered.js +78 -27
- package/src/serve_style.js +13 -8
- package/src/server.js +115 -25
- package/src/utils.js +79 -11
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 {
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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}`);
|
package/src/mbtiles_wrapper.js
CHANGED
|
@@ -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
|
-
*
|
|
33
|
-
* @
|
|
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) => {
|