tileserver-gl 5.5.0 → 5.6.0-pre.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/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 5.6.0-pre.0
11
+ ### ✨ Features and improvements
12
+ - feat: Add ignore-missing-files cli option to avoid crashing at startup ([#1896](https://github.com/maptiler/tileserver-gl/pull/1896)) (by [andrewlaguna824](https://github.com/andrewlaguna824))
13
+
10
14
  ## 5.5.0
11
15
  - Add S3 support for PMTiles with multiple AWS credential profiles (https://github.com/maptiler/tileserver-gl/pull/1779) by @acalcutt
12
16
  - Create .aws directory passthrough folder in Dockerfile (https://github.com/maptiler/tileserver-gl/pull/1784) by @acalcutt
package/docs/usage.rst CHANGED
@@ -20,6 +20,7 @@ Getting started
20
20
  -C|--no-cors Disable Cross-origin resource sharing headers
21
21
  -u|--public_url <url> Enable exposing the server on subpaths, not necessarily the root of the domain
22
22
  --fetch-timeout <ms> Timeout in milliseconds for fetching remote tiles (default: 15000)
23
+ --ignore-missing-files Do not exit when referenced data files or remote sources are missing at startup; log a warning and continue running (useful when styles reference optional or not-yet-available sources)
23
24
  -V, --verbose [level] More verbose output (level 1-3)
24
25
  -V, --verbose, -V 1, or --verbose 1: Important operations
25
26
  -V 2 or --verbose 2: Detailed operations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileserver-gl",
3
- "version": "5.5.0",
3
+ "version": "5.6.0-pre.0",
4
4
  "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "docker": "docker build . && docker run --rm -i -p 8080:8080 $(docker build -q .)"
38
38
  },
39
39
  "dependencies": {
40
- "@aws-sdk/client-s3": "^3.962.0",
40
+ "@aws-sdk/client-s3": "^3.966.0",
41
41
  "@jsse/pbfont": "^0.3.0",
42
42
  "@mapbox/mapbox-gl-rtl-text": "0.3.0",
43
43
  "@mapbox/mbtiles": "0.12.1",
@@ -72,10 +72,10 @@
72
72
  "tileserver-gl-styles": "2.0.0"
73
73
  },
74
74
  "devDependencies": {
75
- "@commitlint/cli": "^20.3.0",
75
+ "@commitlint/cli": "^20.3.1",
76
76
  "@commitlint/config-conventional": "^20.3.0",
77
- "@typescript-eslint/eslint-plugin": "^8.51.0",
78
- "@typescript-eslint/parser": "^8.51.0",
77
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
78
+ "@typescript-eslint/parser": "^8.52.0",
79
79
  "chai": "6.2.2",
80
80
  "cross-env": "^10.1.0",
81
81
  "eslint": "^9.39.2",
@@ -89,7 +89,7 @@
89
89
  "pixelmatch": "^7.1.0",
90
90
  "prettier": "^3.7.4",
91
91
  "should": "^13.2.3",
92
- "supertest": "^7.1.4",
92
+ "supertest": "^7.2.2",
93
93
  "yaml-lint": "^1.7.0"
94
94
  },
95
95
  "keywords": [
package/src/main.js CHANGED
@@ -109,6 +109,10 @@ program
109
109
  '-f|--log_format <format>',
110
110
  'define the log format: https://github.com/expressjs/morgan#morganformat-options',
111
111
  )
112
+ .option(
113
+ '--ignore-missing-files',
114
+ 'Continue startup even if configured mbtiles/pmtiles files are missing',
115
+ )
112
116
  .version(packageJson.version, '-v, --version');
113
117
  program.parse(process.argv);
114
118
  const opts = program.opts();
@@ -132,6 +136,7 @@ const startServer = (configPath, config) => {
132
136
  logFormat: opts.log_format,
133
137
  fetchTimeout: opts.fetchTimeout,
134
138
  publicUrl,
139
+ ignoreMissingFiles: opts.ignoreMissingFiles,
135
140
  });
136
141
  };
137
142
 
package/src/serve_data.js CHANGED
@@ -508,7 +508,7 @@ export const serve_data = {
508
508
  * @returns {Promise<void>}
509
509
  */
510
510
  add: async function (options, repo, params, id, programOpts) {
511
- const { publicUrl, verbose } = programOpts;
511
+ const { publicUrl, verbose, ignoreMissingFiles } = programOpts;
512
512
  let inputFile;
513
513
  let inputType;
514
514
  if (params.pmtiles) {
@@ -542,8 +542,18 @@ export const serve_data = {
542
542
 
543
543
  // Only check file stats for local files, not remote URLs
544
544
  if (!isValidRemoteUrl(inputFile)) {
545
- const inputFileStats = await fsp.stat(inputFile);
546
- if (!inputFileStats.isFile() || inputFileStats.size === 0) {
545
+ try {
546
+ const inputFileStats = await fsp.stat(inputFile);
547
+ if (!inputFileStats.isFile() || inputFileStats.size === 0) {
548
+ throw Error(`Not valid input file: "${inputFile}"`);
549
+ }
550
+ } catch (err) {
551
+ if (ignoreMissingFiles) {
552
+ console.log(
553
+ `WARN: Data source '${id}' file not found: "${inputFile}" - skipping`,
554
+ );
555
+ return;
556
+ }
547
557
  throw Error(`Not valid input file: "${inputFile}"`);
548
558
  }
549
559
  }
@@ -555,24 +565,34 @@ export const serve_data = {
555
565
  tileJSON['encoding'] = params['encoding'];
556
566
  tileJSON['tileSize'] = params['tileSize'];
557
567
 
558
- if (inputType === 'pmtiles') {
559
- source = openPMtiles(
560
- inputFile,
561
- params.s3Profile,
562
- params.requestPayer,
563
- params.s3Region,
564
- params.s3UrlFormat,
565
- verbose,
566
- );
567
- sourceType = 'pmtiles';
568
- const metadata = await getPMtilesInfo(source, inputFile);
569
- Object.assign(tileJSON, metadata);
570
- } else if (inputType === 'mbtiles') {
571
- sourceType = 'mbtiles';
572
- const mbw = await openMbTilesWrapper(inputFile);
573
- const info = await mbw.getInfo();
574
- source = mbw.getMbTiles();
575
- Object.assign(tileJSON, info);
568
+ try {
569
+ if (inputType === 'pmtiles') {
570
+ source = openPMtiles(
571
+ inputFile,
572
+ params.s3Profile,
573
+ params.requestPayer,
574
+ params.s3Region,
575
+ params.s3UrlFormat,
576
+ verbose,
577
+ );
578
+ sourceType = 'pmtiles';
579
+ const metadata = await getPMtilesInfo(source, inputFile);
580
+ Object.assign(tileJSON, metadata);
581
+ } else if (inputType === 'mbtiles') {
582
+ sourceType = 'mbtiles';
583
+ const mbw = await openMbTilesWrapper(inputFile);
584
+ const info = await mbw.getInfo();
585
+ source = mbw.getMbTiles();
586
+ Object.assign(tileJSON, info);
587
+ }
588
+ } catch (err) {
589
+ if (ignoreMissingFiles) {
590
+ console.log(
591
+ `WARN: Unable to open data source '${id}' from "${inputFile}": ${err.message} - skipping (requests will return 404)`,
592
+ );
593
+ return;
594
+ }
595
+ throw err;
576
596
  }
577
597
 
578
598
  delete tileJSON['filesize'];
@@ -213,7 +213,7 @@ export const serve_style = {
213
213
  reportTiles,
214
214
  reportFont,
215
215
  ) {
216
- const { publicUrl } = programOpts;
216
+ const { publicUrl, ignoreMissingFiles } = programOpts;
217
217
  const styleFile = path.resolve(options.paths.styles, params.style);
218
218
  const styleJSON = clone(style);
219
219
 
@@ -247,6 +247,9 @@ export const serve_style = {
247
247
  return false;
248
248
  }
249
249
 
250
+ // Track missing sources
251
+ const missingSources = [];
252
+
250
253
  for (const name of Object.keys(styleJSON.sources)) {
251
254
  // eslint-disable-next-line security/detect-object-injection -- name is from Object.keys of style sources
252
255
  const source = styleJSON.sources[name];
@@ -270,7 +273,9 @@ export const serve_style = {
270
273
 
271
274
  const identifier = reportTiles(dataId, protocol);
272
275
  if (!identifier) {
273
- return false;
276
+ // This datasource is missing or invalid in some way
277
+ missingSources.push(name);
278
+ continue;
274
279
  }
275
280
  source.url = `local://data/${identifier}.json`;
276
281
  }
@@ -286,6 +291,20 @@ export const serve_style = {
286
291
  }
287
292
  }
288
293
 
294
+ // Check if any sources are missing after processing all of them
295
+ if (missingSources.length > 0) {
296
+ if (ignoreMissingFiles) {
297
+ console.log(
298
+ `WARN: Style '${id}' references ${missingSources.length} missing data source(s): [${missingSources.join(', ')}] - not adding style`,
299
+ );
300
+ } else {
301
+ console.log(
302
+ `ERROR: Style '${id}' references missing data source(s): [${missingSources.join(', ')}]`,
303
+ );
304
+ }
305
+ return false;
306
+ }
307
+
289
308
  for (const obj of styleJSON.layers) {
290
309
  if (obj['type'] === 'symbol') {
291
310
  const fonts = (obj['layout'] || {})['text-font'];
package/src/server.js CHANGED
@@ -241,7 +241,36 @@ async function start(opts) {
241
241
  }
242
242
  }
243
243
  if (dataItemId) {
244
- // input files exists in the data config, return found id
244
+ // Data source exists in config, now validate file exists
245
+ // eslint-disable-next-line security/detect-object-injection -- dataItemId is validated above
246
+ const dataSource = data[dataItemId];
247
+ const fileType = dataSource.pmtiles ? 'pmtiles' : 'mbtiles';
248
+ const fileName = dataSource[fileType];
249
+
250
+ // Skip validation for remote URLs
251
+ if (fileName && !isValidRemoteUrl(fileName)) {
252
+ const filePath = path.resolve(options.paths[fileType], fileName);
253
+ try {
254
+ const stats = fs.statSync(filePath);
255
+ if (!stats.isFile() || stats.size === 0) {
256
+ if (opts.ignoreMissingFiles) {
257
+ // File doesn't exist or is empty - return undefined to skip
258
+ return undefined;
259
+ }
260
+ // File missing but flag not set - let it fail later
261
+ return dataItemId;
262
+ }
263
+ } catch (err) {
264
+ // File doesn't exist
265
+ if (opts.ignoreMissingFiles) {
266
+ return undefined;
267
+ }
268
+ // File missing but flag not set - let it fail later
269
+ return dataItemId;
270
+ }
271
+ }
272
+
273
+ // File exists or is remote URL, return the id
245
274
  return dataItemId;
246
275
  } else {
247
276
  if (!allowMoreData) {
@@ -257,6 +286,21 @@ async function start(opts) {
257
286
  if (isValidRemoteUrl(styleSourceId)) {
258
287
  id =
259
288
  fnv1a(styleSourceId) + '_' + id.replace(/^.*\/(.*)$/, '$1');
289
+ } else {
290
+ try {
291
+ const stats = fs.statSync(styleSourceId);
292
+ if (!stats.isFile() || stats.size === 0) {
293
+ if (opts.ignoreMissingFiles) {
294
+ // File doesn't exist or is empty - return undefined to skip
295
+ return undefined;
296
+ }
297
+ }
298
+ } catch (err) {
299
+ // File doesn't exist
300
+ if (opts.ignoreMissingFiles) {
301
+ return undefined;
302
+ }
303
+ }
260
304
  }
261
305
  // eslint-disable-next-line security/detect-object-injection -- id is being checked for existence before modification
262
306
  while (data[id]) id += '_'; //if the data source id already exists, add a "_" untill it doesn't