signalk-charts-provider-simple 2.1.0 → 2.2.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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +71 -35
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +16 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/catalog-manager.d.ts +15 -1
- package/dist/utils/catalog-manager.d.ts.map +1 -1
- package/dist/utils/catalog-manager.js +193 -4
- package/dist/utils/catalog-manager.js.map +1 -1
- package/dist/utils/catalog-schemas.d.ts +6 -4
- package/dist/utils/catalog-schemas.d.ts.map +1 -1
- package/dist/utils/catalog-schemas.js +10 -3
- package/dist/utils/catalog-schemas.js.map +1 -1
- package/dist/utils/container-fs.d.ts +17 -0
- package/dist/utils/container-fs.d.ts.map +1 -0
- package/dist/utils/container-fs.js +40 -0
- package/dist/utils/container-fs.js.map +1 -0
- package/dist/utils/container-images.d.ts +1 -1
- package/dist/utils/container-images.js +1 -1
- package/dist/utils/download-manager.d.ts +5 -0
- package/dist/utils/download-manager.d.ts.map +1 -1
- package/dist/utils/download-manager.js +97 -13
- package/dist/utils/download-manager.js.map +1 -1
- package/dist/utils/quarantine.d.ts.map +1 -1
- package/dist/utils/quarantine.js +2 -0
- package/dist/utils/quarantine.js.map +1 -1
- package/dist/utils/rnc-converter.d.ts.map +1 -1
- package/dist/utils/rnc-converter.js +2 -2
- package/dist/utils/rnc-converter.js.map +1 -1
- package/dist/utils/s57-converter.d.ts +1 -1
- package/dist/utils/s57-converter.d.ts.map +1 -1
- package/dist/utils/s57-converter.js +13 -6
- package/dist/utils/s57-converter.js.map +1 -1
- package/package.json +1 -1
- package/public/css/chart-catalog.css +171 -1
- package/public/js/chart-catalog.js +526 -18
- package/public/js/chart-catalog.ts +591 -18
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ systemctl --user enable --now podman.socket
|
|
|
125
125
|
|
|
126
126
|
The plugin uses one combined image that `signalk-container` pulls automatically on first conversion:
|
|
127
127
|
|
|
128
|
-
- `ghcr.io/dirkwa/signalk-charts-provider-simple/charts-toolbox:1.
|
|
128
|
+
- `ghcr.io/dirkwa/signalk-charts-provider-simple/charts-toolbox:1.1.0` — GDAL + tippecanoe + tile-join + helpers in a single image (multi-arch: amd64 + arm64). The plugin pins to a specific `:VERSION` tag rather than `:latest` so a published image tag is permanent for any host that pulled it; bumping the toolbox image is a co-ordinated edit of `docker/charts-toolbox/VERSION` and the matching constant in `src/utils/container-images.ts`.
|
|
129
129
|
|
|
130
130
|
**Why `signalk-container`?** It transparently handles the three deployment topologies that 1.x got wrong:
|
|
131
131
|
|
|
@@ -144,7 +144,7 @@ The same `charts-toolbox` image the plugin uses internally is also available for
|
|
|
144
144
|
Pull it directly from GHCR:
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
docker pull ghcr.io/dirkwa/signalk-charts-provider-simple/charts-toolbox:1.
|
|
147
|
+
docker pull ghcr.io/dirkwa/signalk-charts-provider-simple/charts-toolbox:1.1.0
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
See [docs/charts-toolbox-image.md](docs/charts-toolbox-image.md) for a quickstart, the `--user` / `--userns=keep-id` flag forms per runtime, and reproducibility notes.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAuExD,OAAO,KAAK,EAGV,iBAAiB,EAMlB,MAAM,YAAY,CAAC;AAepB,QAAA,MAAM,iBAAiB,GAAI,KAAK,iBAAiB,KAAG,MA+0FnD,CAAC;AAiHF,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SKVersion } from '@signalk/server-api';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import Busboy from 'busboy';
|
|
2
4
|
import fs from 'fs';
|
|
3
5
|
import https from 'https';
|
|
4
|
-
import {
|
|
6
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
7
|
+
import path from 'path';
|
|
5
8
|
import { findCharts, findRepairableCharts } from './charts-loader.js';
|
|
6
|
-
import {
|
|
9
|
+
import { checkForUpdates, classifyUrl, fetchCatalog, fetchCatalogRegistry, getCachedCatalog, getCatalogRegistry, getCatalogsWithInstalledCharts, getConvertingCharts, getConvertingCount, getInstalledCatalogCharts, getRegistryStatus, initCatalogManager, pruneStaleInstalls, removeInstall, removeInstallByFilename, renameInstallFilename, rollbackInstall, setConvertingState, setInstallFilename, trackInstall } from './utils/catalog-manager.js';
|
|
10
|
+
import { cleanCatalogTitle } from './utils/catalog-title.js';
|
|
7
11
|
import { initChartState, isChartEnabled, setChartEnabled } from './utils/chart-state.js';
|
|
8
|
-
import {
|
|
9
|
-
import { initCatalogManager, getCatalogRegistry, fetchCatalog, getCachedCatalog, classifyUrl, trackInstall, setInstallFilename, renameInstallFilename, removeInstall, removeInstallByFilename, getInstalledCatalogCharts, pruneStaleInstalls, setConvertingState, getConvertingCharts, getConvertingCount, checkForUpdates, getCatalogsWithInstalledCharts } from './utils/catalog-manager.js';
|
|
10
|
-
import { initS57Converter, processS57Zip, getAllConversionProgress as getAllS57Progress, getConversionProgress as getS57Progress, setConversionFailed as setS57Failed } from './utils/s57-converter.js';
|
|
11
|
-
import { getContainerManager, waitForContainerManager } from './utils/container-manager.js';
|
|
12
|
+
import { getCpuBudget, setCpuBudget } from './utils/concurrency.js';
|
|
12
13
|
import { PLUGIN_OWNER_ID } from './utils/container-jobs.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { getContainerManager, waitForContainerManager } from './utils/container-manager.js';
|
|
15
|
+
import { downloadManager } from './utils/download-manager.js';
|
|
16
|
+
import { scanAllFolders, scanChartsRecursively } from './utils/file-scanner.js';
|
|
17
|
+
import { repairMbtilesMetadata, setMbtilesDisplayName } from './utils/mbtiles-metadata.js';
|
|
16
18
|
import { open as openMbtilesReader } from './utils/mbtiles-reader.js';
|
|
17
|
-
import { initRncConverter, processRncZip, processPilotTar, getAllConversionProgress as getAllRncProgress, getConversionProgress as getRncProgress, setConversionFailed as setRncFailed } from './utils/rnc-converter.js';
|
|
18
|
-
import { processGshhg, processShpBasemap } from './utils/s57-converter.js';
|
|
19
|
-
import { getCpuBudget, setCpuBudget } from './utils/concurrency.js';
|
|
20
19
|
import { writeChartPathMarker } from './utils/path-marker.js';
|
|
20
|
+
import { arePairWithinBase, isWithinBase, validateChartName } from './utils/path-safety.js';
|
|
21
21
|
import { parsePluginConfig } from './utils/plugin-config-schema.js';
|
|
22
|
-
import {
|
|
22
|
+
import { cleanupQuarantineDir, makeQuarantineDir, promoteQuarantine, sweepStaleQuarantineDirs } from './utils/quarantine.js';
|
|
23
23
|
import { parseBody, parseShape } from './utils/rest-validation.js';
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
26
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
24
|
+
import { getAllConversionProgress as getAllRncProgress, getConversionProgress as getRncProgress, initRncConverter, processPilotTar, processRncZip, setConversionFailed as setRncFailed } from './utils/rnc-converter.js';
|
|
25
|
+
import { getAllConversionProgress as getAllS57Progress, getConversionProgress as getS57Progress, initS57Converter, processGshhg, processS57Zip, processShpBasemap, setConversionFailed as setS57Failed } from './utils/s57-converter.js';
|
|
27
26
|
// JSON import with type attribute (NodeNext ESM). package.json's `version`
|
|
28
27
|
// is what the marker file records so users can confirm the running build.
|
|
29
28
|
import packageJson from '../package.json' with { type: 'json' };
|
|
@@ -320,7 +319,14 @@ const pluginConstructor = (app) => {
|
|
|
320
319
|
`rolling back chart ${chartNumber ?? '<unknown>'}`);
|
|
321
320
|
if (chartNumber) {
|
|
322
321
|
setConvertingState(chartNumber, false);
|
|
323
|
-
|
|
322
|
+
// A restart/crash can land here mid-update. rollbackInstall
|
|
323
|
+
// restores the prior on-disk version (UPDATE) or drops the
|
|
324
|
+
// pending record (FRESH), reading the snapshot trackInstall
|
|
325
|
+
// persisted in the record — so a still-pending update keeps
|
|
326
|
+
// surfacing in checkForUpdates(). For a committed record (no
|
|
327
|
+
// snapshot marker) it's a no-op, so a spurious reap of an
|
|
328
|
+
// already-installed chart doesn't delete it.
|
|
329
|
+
rollbackInstall(chartNumber);
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
if (cleanup.reaped.length > 0) {
|
|
@@ -1467,13 +1473,43 @@ const pluginConstructor = (app) => {
|
|
|
1467
1473
|
const registry = getCatalogRegistry();
|
|
1468
1474
|
const installed = getInstalledCatalogCharts();
|
|
1469
1475
|
const convertingCharts = getConvertingCharts();
|
|
1470
|
-
res.json({
|
|
1476
|
+
res.json({
|
|
1477
|
+
registry,
|
|
1478
|
+
installed,
|
|
1479
|
+
converting: convertingCharts,
|
|
1480
|
+
registryStatus: getRegistryStatus()
|
|
1481
|
+
});
|
|
1471
1482
|
}
|
|
1472
1483
|
catch (error) {
|
|
1473
1484
|
console.error('Error fetching catalog registry:', error);
|
|
1474
1485
|
res.status(500).json({ error: 'Failed to fetch catalog registry' });
|
|
1475
1486
|
}
|
|
1476
1487
|
});
|
|
1488
|
+
// Force a re-fetch of the catalog index from GitHub (the Refresh button).
|
|
1489
|
+
// Awaits the fetch so the response reflects the just-attempted result, but
|
|
1490
|
+
// swallows the rejection — registryStatus carries the reason (rate-limited
|
|
1491
|
+
// / offline / error) so the UI can message accurately.
|
|
1492
|
+
router.post('/catalog-registry/refresh', (_req, res) => {
|
|
1493
|
+
void (async () => {
|
|
1494
|
+
try {
|
|
1495
|
+
await fetchCatalogRegistry().catch((err) => {
|
|
1496
|
+
app.debug(`Catalog registry refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1497
|
+
});
|
|
1498
|
+
res.json({
|
|
1499
|
+
registry: getCatalogRegistry(),
|
|
1500
|
+
installed: getInstalledCatalogCharts(),
|
|
1501
|
+
converting: getConvertingCharts(),
|
|
1502
|
+
registryStatus: getRegistryStatus()
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
catch (error) {
|
|
1506
|
+
// The refresh itself is awaited+swallowed above; this only fires if
|
|
1507
|
+
// building the response throws.
|
|
1508
|
+
console.error('Error building catalog refresh response:', error);
|
|
1509
|
+
res.status(500).json({ error: 'Failed to build catalog refresh response' });
|
|
1510
|
+
}
|
|
1511
|
+
})();
|
|
1512
|
+
});
|
|
1477
1513
|
router.get('/catalog-s57-status', (_req, res) => {
|
|
1478
1514
|
// Runtime info is owned by signalk-container in 2.0+; we report
|
|
1479
1515
|
// whatever its manager exposes. Available = manager present AND
|
|
@@ -1791,14 +1827,14 @@ const pluginConstructor = (app) => {
|
|
|
1791
1827
|
void (async () => {
|
|
1792
1828
|
try {
|
|
1793
1829
|
if (!job.extractedFiles || job.extractedFiles.length === 0) {
|
|
1794
|
-
|
|
1830
|
+
rollbackInstall(chartNumber);
|
|
1795
1831
|
app.debug(`Removed catalog tracking for ${chartNumber}: no .mbtiles extracted`);
|
|
1796
1832
|
return;
|
|
1797
1833
|
}
|
|
1798
1834
|
if (job.status !== 'completed') {
|
|
1799
1835
|
// Failed downloads leak nothing into the live target;
|
|
1800
1836
|
// the quarantine wipe in `finally` handles cleanup.
|
|
1801
|
-
|
|
1837
|
+
rollbackInstall(chartNumber);
|
|
1802
1838
|
return;
|
|
1803
1839
|
}
|
|
1804
1840
|
try {
|
|
@@ -1806,7 +1842,7 @@ const pluginConstructor = (app) => {
|
|
|
1806
1842
|
}
|
|
1807
1843
|
catch (promoteErr) {
|
|
1808
1844
|
app.error(`Promotion failed for ${chartNumber}: ${promoteErr instanceof Error ? promoteErr.message : String(promoteErr)}`);
|
|
1809
|
-
|
|
1845
|
+
rollbackInstall(chartNumber);
|
|
1810
1846
|
return;
|
|
1811
1847
|
}
|
|
1812
1848
|
// The global `job-completed` handler enables charts and
|
|
@@ -1890,7 +1926,7 @@ const pluginConstructor = (app) => {
|
|
|
1890
1926
|
const zipPath = zipFileName ? path.join(tmpDownloadDir, zipFileName) : null;
|
|
1891
1927
|
if (!zipPath || !fs.existsSync(zipPath)) {
|
|
1892
1928
|
app.debug(`S-57: no ZIP file found after download for ${chartNumber}`);
|
|
1893
|
-
|
|
1929
|
+
rollbackInstall(chartNumber);
|
|
1894
1930
|
cleanupDir(tmpDownloadDir);
|
|
1895
1931
|
return;
|
|
1896
1932
|
}
|
|
@@ -1937,7 +1973,7 @@ const pluginConstructor = (app) => {
|
|
|
1937
1973
|
}
|
|
1938
1974
|
catch (convError) {
|
|
1939
1975
|
app.error(`S-57 conversion failed for ${chartNumber}: ${convError instanceof Error ? convError.message : String(convError)}`);
|
|
1940
|
-
|
|
1976
|
+
rollbackInstall(chartNumber);
|
|
1941
1977
|
setConvertingState(chartNumber, false);
|
|
1942
1978
|
if (quarantineDir !== null) {
|
|
1943
1979
|
cleanupQuarantineDir(quarantineDir);
|
|
@@ -1951,7 +1987,7 @@ const pluginConstructor = (app) => {
|
|
|
1951
1987
|
}
|
|
1952
1988
|
downloadManager.removeListener('job-completed', s57Listener);
|
|
1953
1989
|
downloadManager.removeListener('job-failed', s57FailListener);
|
|
1954
|
-
|
|
1990
|
+
rollbackInstall(chartNumber);
|
|
1955
1991
|
setConvertingState(chartNumber, false);
|
|
1956
1992
|
setS57Failed(chartNumber, job.error ?? 'Download failed');
|
|
1957
1993
|
cleanupDir(tmpDownloadDir);
|
|
@@ -1978,7 +2014,7 @@ const pluginConstructor = (app) => {
|
|
|
1978
2014
|
const zipPath = zipFileName ? path.join(tmpDownloadDir, zipFileName) : null;
|
|
1979
2015
|
if (!zipPath || !fs.existsSync(zipPath)) {
|
|
1980
2016
|
app.debug(`RNC: no file found after download for ${chartNumber}`);
|
|
1981
|
-
|
|
2017
|
+
rollbackInstall(chartNumber);
|
|
1982
2018
|
cleanupDir(tmpDownloadDir);
|
|
1983
2019
|
return;
|
|
1984
2020
|
}
|
|
@@ -2020,7 +2056,7 @@ const pluginConstructor = (app) => {
|
|
|
2020
2056
|
}
|
|
2021
2057
|
catch (convError) {
|
|
2022
2058
|
app.error(`RNC conversion failed for ${chartNumber}: ${convError instanceof Error ? convError.message : String(convError)}`);
|
|
2023
|
-
|
|
2059
|
+
rollbackInstall(chartNumber);
|
|
2024
2060
|
setConvertingState(chartNumber, false);
|
|
2025
2061
|
if (quarantineDir !== null) {
|
|
2026
2062
|
cleanupQuarantineDir(quarantineDir);
|
|
@@ -2034,7 +2070,7 @@ const pluginConstructor = (app) => {
|
|
|
2034
2070
|
}
|
|
2035
2071
|
downloadManager.removeListener('job-completed', rncListener);
|
|
2036
2072
|
downloadManager.removeListener('job-failed', rncFailListener);
|
|
2037
|
-
|
|
2073
|
+
rollbackInstall(chartNumber);
|
|
2038
2074
|
setConvertingState(chartNumber, false);
|
|
2039
2075
|
setRncFailed(chartNumber, job.error ?? 'Download failed');
|
|
2040
2076
|
cleanupDir(tmpDownloadDir);
|
|
@@ -2060,7 +2096,7 @@ const pluginConstructor = (app) => {
|
|
|
2060
2096
|
const dlFileName = job.extractedFiles?.[0] ?? job.targetFiles?.[0];
|
|
2061
2097
|
const dlPath = dlFileName ? path.join(tmpDownloadDir, dlFileName) : null;
|
|
2062
2098
|
if (!dlPath || !fs.existsSync(dlPath)) {
|
|
2063
|
-
|
|
2099
|
+
rollbackInstall(chartNumber);
|
|
2064
2100
|
cleanupDir(tmpDownloadDir);
|
|
2065
2101
|
return;
|
|
2066
2102
|
}
|
|
@@ -2091,7 +2127,7 @@ const pluginConstructor = (app) => {
|
|
|
2091
2127
|
}
|
|
2092
2128
|
catch (convError) {
|
|
2093
2129
|
app.error(`Pilot conversion failed: ${convError instanceof Error ? convError.message : String(convError)}`);
|
|
2094
|
-
|
|
2130
|
+
rollbackInstall(chartNumber);
|
|
2095
2131
|
setConvertingState(chartNumber, false);
|
|
2096
2132
|
if (quarantineDir !== null) {
|
|
2097
2133
|
cleanupQuarantineDir(quarantineDir);
|
|
@@ -2105,7 +2141,7 @@ const pluginConstructor = (app) => {
|
|
|
2105
2141
|
}
|
|
2106
2142
|
downloadManager.removeListener('job-completed', pilotListener);
|
|
2107
2143
|
downloadManager.removeListener('job-failed', pilotFailListener);
|
|
2108
|
-
|
|
2144
|
+
rollbackInstall(chartNumber);
|
|
2109
2145
|
setConvertingState(chartNumber, false);
|
|
2110
2146
|
setRncFailed(chartNumber, job.error ?? 'Download failed');
|
|
2111
2147
|
cleanupDir(tmpDownloadDir);
|
|
@@ -2131,7 +2167,7 @@ const pluginConstructor = (app) => {
|
|
|
2131
2167
|
const dlFileName = job.extractedFiles?.[0] ?? job.targetFiles?.[0];
|
|
2132
2168
|
const dlPath = dlFileName ? path.join(tmpDownloadDir, dlFileName) : null;
|
|
2133
2169
|
if (!dlPath || !fs.existsSync(dlPath)) {
|
|
2134
|
-
|
|
2170
|
+
rollbackInstall(chartNumber);
|
|
2135
2171
|
cleanupDir(tmpDownloadDir);
|
|
2136
2172
|
return;
|
|
2137
2173
|
}
|
|
@@ -2158,7 +2194,7 @@ const pluginConstructor = (app) => {
|
|
|
2158
2194
|
}
|
|
2159
2195
|
catch (convError) {
|
|
2160
2196
|
app.error(`ShpBasemap conversion failed: ${convError instanceof Error ? convError.message : String(convError)}`);
|
|
2161
|
-
|
|
2197
|
+
rollbackInstall(chartNumber);
|
|
2162
2198
|
setConvertingState(chartNumber, false);
|
|
2163
2199
|
if (quarantineDir !== null) {
|
|
2164
2200
|
cleanupQuarantineDir(quarantineDir);
|
|
@@ -2172,7 +2208,7 @@ const pluginConstructor = (app) => {
|
|
|
2172
2208
|
}
|
|
2173
2209
|
downloadManager.removeListener('job-completed', shpListener);
|
|
2174
2210
|
downloadManager.removeListener('job-failed', shpFailListener);
|
|
2175
|
-
|
|
2211
|
+
rollbackInstall(chartNumber);
|
|
2176
2212
|
setConvertingState(chartNumber, false);
|
|
2177
2213
|
cleanupDir(tmpDownloadDir);
|
|
2178
2214
|
};
|
|
@@ -2206,7 +2242,7 @@ const pluginConstructor = (app) => {
|
|
|
2206
2242
|
}
|
|
2207
2243
|
catch (error) {
|
|
2208
2244
|
app.error(`GSHHG conversion failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2209
|
-
|
|
2245
|
+
rollbackInstall(chartNumber);
|
|
2210
2246
|
setConvertingState(chartNumber, false);
|
|
2211
2247
|
if (quarantineDir !== null) {
|
|
2212
2248
|
cleanupQuarantineDir(quarantineDir);
|