signalk-tides 1.4.0 → 1.5.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/.github/workflows/ci.yml +39 -0
- package/README.md +13 -4
- package/dist/cache.js +8 -11
- package/dist/calculations.js +1 -4
- package/dist/index.js +29 -36
- package/dist/sources/index.js +7 -0
- package/dist/sources/neaps.js +39 -0
- package/dist/sources/noaa.js +31 -30
- package/dist/sources/stormglass.js +5 -11
- package/dist/sources/worldtides.js +4 -10
- package/dist/types.js +1 -2
- package/package.json +8 -5
- package/public/assets/index-ClNCI7kN.js +9 -0
- package/public/assets/index-D8qkCL0i.css +1 -0
- package/public/index.html +2 -2
- package/tsconfig.node.json +2 -2
- package/public/assets/index-DDL3NhwZ.js +0 -49
- package/public/assets/index-v5sE8dbG.css +0 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push: {}
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
lint:
|
|
8
|
+
name: Lint
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout repository
|
|
12
|
+
uses: actions/checkout@v6
|
|
13
|
+
|
|
14
|
+
- name: Setup Node.js
|
|
15
|
+
uses: actions/setup-node@v6
|
|
16
|
+
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: npm install
|
|
19
|
+
|
|
20
|
+
- name: Run linter
|
|
21
|
+
run: npm run lint
|
|
22
|
+
|
|
23
|
+
build:
|
|
24
|
+
name: Build
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
env:
|
|
27
|
+
CI: true
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v6
|
|
31
|
+
|
|
32
|
+
- name: Setup Node.js
|
|
33
|
+
uses: actions/setup-node@v6
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: npm install
|
|
37
|
+
|
|
38
|
+
- name: Run build
|
|
39
|
+
run: npm run build
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# signalk-tides
|
|
2
2
|
|
|
3
|
-
A SignalK plugin that provides tidal predictions for the vessel's position from various
|
|
3
|
+
A SignalK plugin that provides tidal predictions for the vessel's position from various [sources](#sources).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -206,9 +206,18 @@ $ curl http://localhost:3000/signalk/v2/api/resources/tides
|
|
|
206
206
|
|
|
207
207
|
## Sources
|
|
208
208
|
|
|
209
|
-
- [
|
|
210
|
-
-
|
|
211
|
-
-
|
|
209
|
+
- [Neaps](https://github.com/neaps/neaps)
|
|
210
|
+
- Offline, open source, runs locally
|
|
211
|
+
- Coverage: Regional — United States and territories (currently)
|
|
212
|
+
- [NOAA](https://tidesandcurrents.noaa.gov/web_services_info.html)
|
|
213
|
+
- Online (NOAA web services)
|
|
214
|
+
- Regional — United States and territories
|
|
215
|
+
- [WorldTides API](https://www.worldtides.info/)
|
|
216
|
+
- Online (requires API key, subject to API quotas)
|
|
217
|
+
- Coverage: Global - station and model coverage varies by location
|
|
218
|
+
- [StormGlass.io](https://stormglass.io/)
|
|
219
|
+
- Online (requires API key)
|
|
220
|
+
- Coverage: Global - aggregated marine data; tide availability varies
|
|
212
221
|
|
|
213
222
|
## License
|
|
214
223
|
|
package/dist/cache.js
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const promises_1 = require("fs/promises");
|
|
5
|
-
class FileCache {
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { writeFile, readFile, mkdir } from "fs/promises";
|
|
3
|
+
export default class FileCache {
|
|
6
4
|
path;
|
|
7
5
|
constructor(path) {
|
|
8
6
|
this.path = path;
|
|
9
7
|
}
|
|
10
8
|
async get(key) {
|
|
11
9
|
try {
|
|
12
|
-
const content = await
|
|
10
|
+
const content = await readFile(this.getKey(key), "utf-8");
|
|
13
11
|
return JSON.parse(content);
|
|
14
12
|
}
|
|
15
|
-
catch
|
|
13
|
+
catch {
|
|
16
14
|
return undefined;
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
17
|
async set(key, value) {
|
|
20
18
|
// Ensure directory exists
|
|
21
|
-
await
|
|
22
|
-
return
|
|
19
|
+
await mkdir(this.path, { recursive: true });
|
|
20
|
+
return writeFile(this.getKey(key), JSON.stringify(value));
|
|
23
21
|
}
|
|
24
22
|
getKey(key) {
|
|
25
|
-
return
|
|
23
|
+
return join(this.path, `${key}.json`);
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
exports.default = FileCache;
|
package/dist/calculations.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.approximateTideHeightAt = approximateTideHeightAt;
|
|
4
1
|
/** Given a list of tide extremes, estimate the height at a specific time. */
|
|
5
|
-
function approximateTideHeightAt(extremes, time) {
|
|
2
|
+
export function approximateTideHeightAt(extremes, time) {
|
|
6
3
|
const sorted = extremes.slice().sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
7
4
|
const prev = sorted.filter(h => new Date(h.time) <= time).at(-1);
|
|
8
5
|
const next = sorted.filter(h => new Date(h.time) >= time).at(0);
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*
|
|
3
2
|
* Copyright 2017 Scott Bender <scott@scottbender.net> and Joachim Bakke
|
|
4
3
|
* Copyright 2025 Brandon Keepers <brandon@opensoul.org>
|
|
@@ -14,27 +13,17 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const stormglass_js_1 = __importDefault(require("./sources/stormglass.js"));
|
|
22
|
-
const worldtides_js_1 = __importDefault(require("./sources/worldtides.js"));
|
|
23
|
-
const calculations_js_1 = require("./calculations.js");
|
|
24
|
-
const cache_js_1 = __importDefault(require("./cache.js"));
|
|
25
|
-
module.exports = function (app) {
|
|
16
|
+
import { approximateTideHeightAt } from "./calculations.js";
|
|
17
|
+
import FileCache from "./cache.js";
|
|
18
|
+
import createSources from "./sources/index.js";
|
|
19
|
+
export default function (app) {
|
|
26
20
|
// Interval to update tide data
|
|
27
21
|
const defaultPeriod = 60; // 1 hour
|
|
28
22
|
let unsubscribes = [];
|
|
29
|
-
const sources =
|
|
30
|
-
(0, noaa_js_1.default)(app),
|
|
31
|
-
(0, stormglass_js_1.default)(app),
|
|
32
|
-
(0, worldtides_js_1.default)(app),
|
|
33
|
-
];
|
|
23
|
+
const sources = createSources(app);
|
|
34
24
|
const plugin = {
|
|
35
25
|
id: "tides",
|
|
36
26
|
name: "Tides",
|
|
37
|
-
// @ts-expect-error: TODO[TS]: fix Plugin type upstream
|
|
38
27
|
description: "Tidal predictions for the vessel's position from various online sources.",
|
|
39
28
|
schema: () => ({
|
|
40
29
|
title: "Tides API",
|
|
@@ -43,9 +32,9 @@ module.exports = function (app) {
|
|
|
43
32
|
source: {
|
|
44
33
|
title: "Data source",
|
|
45
34
|
type: "string",
|
|
46
|
-
|
|
35
|
+
anyOf: sources.map(({ id, title }) => ({
|
|
47
36
|
const: id,
|
|
48
|
-
title
|
|
37
|
+
title,
|
|
49
38
|
})),
|
|
50
39
|
default: sources[0].id,
|
|
51
40
|
},
|
|
@@ -58,20 +47,21 @@ module.exports = function (app) {
|
|
|
58
47
|
default: 60,
|
|
59
48
|
minimum: 1,
|
|
60
49
|
},
|
|
61
|
-
}
|
|
50
|
+
},
|
|
62
51
|
}),
|
|
52
|
+
start,
|
|
63
53
|
stop() {
|
|
64
54
|
unsubscribes.forEach((f) => f());
|
|
65
55
|
unsubscribes = [];
|
|
66
|
-
}
|
|
56
|
+
},
|
|
67
57
|
};
|
|
68
|
-
|
|
58
|
+
async function start(props) {
|
|
69
59
|
app.debug("Starting tides-api: " + JSON.stringify(props));
|
|
70
60
|
let lastForecast = null;
|
|
71
61
|
let lastPosition = null;
|
|
72
|
-
const cache = new
|
|
62
|
+
const cache = new FileCache(app.getDataDirPath());
|
|
73
63
|
// Use the selected source, or the first one if not specified
|
|
74
|
-
const source = sources.find(source => source.id === props.source) || sources[0];
|
|
64
|
+
const source = sources.find((source) => source.id === props.source) || sources[0];
|
|
75
65
|
// Load the selected source
|
|
76
66
|
const provider = await source.start(props);
|
|
77
67
|
// Register the source as a resource provider
|
|
@@ -91,11 +81,11 @@ module.exports = function (app) {
|
|
|
91
81
|
},
|
|
92
82
|
deleteResource() {
|
|
93
83
|
throw new Error("Not implemented");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
96
86
|
});
|
|
97
87
|
app.subscriptionmanager.subscribe({
|
|
98
|
-
context: "vessels." + app.selfId,
|
|
88
|
+
context: ("vessels." + app.selfId),
|
|
99
89
|
subscribe: [
|
|
100
90
|
{
|
|
101
91
|
path: "navigation.position",
|
|
@@ -107,9 +97,10 @@ module.exports = function (app) {
|
|
|
107
97
|
app.error("Error:" + subscriptionError);
|
|
108
98
|
}, updatePosition);
|
|
109
99
|
async function updatePosition() {
|
|
110
|
-
lastPosition =
|
|
100
|
+
lastPosition =
|
|
101
|
+
app.getSelfPath("navigation.position.value") || (await cache.get("position")) || null;
|
|
111
102
|
if (lastPosition) {
|
|
112
|
-
await cache.set(
|
|
103
|
+
await cache.set("position", lastPosition);
|
|
113
104
|
await updateForecast();
|
|
114
105
|
}
|
|
115
106
|
}
|
|
@@ -134,28 +125,30 @@ module.exports = function (app) {
|
|
|
134
125
|
if (!lastForecast)
|
|
135
126
|
return;
|
|
136
127
|
// Get the next two upcoming extremes
|
|
137
|
-
const nextTides = lastForecast.extremes
|
|
128
|
+
const nextTides = lastForecast.extremes
|
|
129
|
+
.filter(({ time }) => new Date(time) >= now)
|
|
130
|
+
.slice(0, 2);
|
|
138
131
|
const delta = {
|
|
139
|
-
context: "vessels." + app.selfId,
|
|
132
|
+
context: ("vessels." + app.selfId),
|
|
140
133
|
updates: [
|
|
141
134
|
{
|
|
142
135
|
timestamp: now.toISOString(),
|
|
143
136
|
values: [
|
|
144
137
|
{
|
|
145
138
|
path: "environment.tide.stationName",
|
|
146
|
-
value: lastForecast.station.name
|
|
139
|
+
value: lastForecast.station.name,
|
|
147
140
|
},
|
|
148
141
|
{
|
|
149
142
|
path: "environment.tide.heightNow",
|
|
150
|
-
value:
|
|
143
|
+
value: approximateTideHeightAt(lastForecast.extremes, now),
|
|
151
144
|
},
|
|
152
145
|
...nextTides.flatMap(({ type, time, value }) => {
|
|
153
146
|
return [
|
|
154
147
|
{ path: `environment.tide.height${type}`, value },
|
|
155
148
|
{ path: `environment.tide.time${type}`, value: time },
|
|
156
149
|
];
|
|
157
|
-
})
|
|
158
|
-
]
|
|
150
|
+
}),
|
|
151
|
+
],
|
|
159
152
|
},
|
|
160
153
|
],
|
|
161
154
|
};
|
|
@@ -166,9 +159,9 @@ module.exports = function (app) {
|
|
|
166
159
|
delay(4000).then(updatePosition);
|
|
167
160
|
// Update every minute
|
|
168
161
|
setInterval(updateTides, 60 * 1000);
|
|
169
|
-
}
|
|
162
|
+
}
|
|
170
163
|
function delay(time) {
|
|
171
164
|
return new Promise((resolve) => setTimeout(resolve, time));
|
|
172
165
|
}
|
|
173
166
|
return plugin;
|
|
174
|
-
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getExtremesPrediction } from "neaps";
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
export default function (app) {
|
|
4
|
+
return {
|
|
5
|
+
id: 'neaps',
|
|
6
|
+
title: 'Neaps',
|
|
7
|
+
properties: {},
|
|
8
|
+
start() {
|
|
9
|
+
app.debug("Using Neaps");
|
|
10
|
+
return async ({ position, date = moment().subtract(1, "days").toISOString() }) => {
|
|
11
|
+
const { station, extremes } = getExtremesPrediction({
|
|
12
|
+
...position,
|
|
13
|
+
start: new Date(date),
|
|
14
|
+
end: moment(date).add(7, "days").toDate(),
|
|
15
|
+
labels: {
|
|
16
|
+
high: "High",
|
|
17
|
+
low: "Low"
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
station: {
|
|
22
|
+
name: station.name,
|
|
23
|
+
position: {
|
|
24
|
+
latitude: station.latitude,
|
|
25
|
+
longitude: station.longitude,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
extremes: extremes.map(({ time, level, label }) => {
|
|
29
|
+
return {
|
|
30
|
+
type: label,
|
|
31
|
+
value: level,
|
|
32
|
+
time: time.toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
package/dist/sources/noaa.js
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const promises_1 = require("fs/promises");
|
|
9
|
-
const geolib_1 = require("geolib");
|
|
10
|
-
const moment_1 = __importDefault(require("moment"));
|
|
11
|
-
const cache_js_1 = __importDefault(require("../cache.js"));
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { unlink } from "fs/promises";
|
|
3
|
+
import { getDistance } from "geolib";
|
|
4
|
+
import moment from "moment";
|
|
5
|
+
import FileCache from "../cache.js";
|
|
12
6
|
const stationsUrl = `https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations.json?type=tidepredictions`;
|
|
13
|
-
const dataGetterUrl =
|
|
14
|
-
const datum =
|
|
15
|
-
function
|
|
7
|
+
const dataGetterUrl = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter";
|
|
8
|
+
const datum = "MLLW";
|
|
9
|
+
export default function (app) {
|
|
16
10
|
return {
|
|
17
|
-
id:
|
|
18
|
-
title:
|
|
11
|
+
id: "noaa",
|
|
12
|
+
title: "NOAA (US only)",
|
|
19
13
|
async start() {
|
|
20
|
-
const cache = new
|
|
14
|
+
const cache = new FileCache(path.join(app.getDataDirPath(), "noaa"));
|
|
21
15
|
const stations = await StationList.load(cache, app);
|
|
22
16
|
// Remove old cache file
|
|
23
|
-
|
|
17
|
+
// @ts-expect-error: configPath exists, just not part of the types
|
|
18
|
+
unlink(path.join(app.config.configPath, "noaastations.json")).catch(() => {
|
|
19
|
+
/* ignore */
|
|
20
|
+
});
|
|
24
21
|
return async (params) => {
|
|
25
|
-
const { position, date = (
|
|
22
|
+
const { position, date = moment().subtract(1, "days") } = params;
|
|
26
23
|
const station = stations.closestTo(position);
|
|
27
24
|
const endpoint = new URL(dataGetterUrl);
|
|
28
25
|
endpoint.search = new URLSearchParams({
|
|
29
26
|
product: "predictions",
|
|
30
27
|
application: "signalk.org/node-server",
|
|
31
|
-
begin_date: (
|
|
32
|
-
end_date: (
|
|
28
|
+
begin_date: moment(date).format("YYYYMMDD"),
|
|
29
|
+
end_date: moment(date).add(7, "days").format("YYYYMMDD"),
|
|
33
30
|
datum,
|
|
34
31
|
station: station.id,
|
|
35
32
|
time_zone: "gmt",
|
|
@@ -42,7 +39,7 @@ function default_1(app) {
|
|
|
42
39
|
const res = await fetch(endpoint.toString());
|
|
43
40
|
if (!res.ok)
|
|
44
41
|
throw new Error("Failed to fetch NOAA tides: " + res.statusText);
|
|
45
|
-
const body = await res.json();
|
|
42
|
+
const body = (await res.json());
|
|
46
43
|
app.debug("NOAA response: \n" + JSON.stringify(body, null, 2));
|
|
47
44
|
if (body.error)
|
|
48
45
|
throw new Error(body.error.message);
|
|
@@ -68,23 +65,22 @@ function default_1(app) {
|
|
|
68
65
|
throw err;
|
|
69
66
|
}
|
|
70
67
|
};
|
|
71
|
-
}
|
|
68
|
+
},
|
|
72
69
|
};
|
|
73
70
|
}
|
|
74
|
-
;
|
|
75
71
|
class StationList extends Map {
|
|
76
72
|
static async load(cache, app) {
|
|
77
|
-
let data = await cache.get(
|
|
73
|
+
let data = (await cache.get("stations"));
|
|
78
74
|
if (data) {
|
|
79
75
|
app.debug("NOAA: Loaded cached tide stations");
|
|
80
76
|
}
|
|
81
77
|
else {
|
|
82
|
-
app.debug(
|
|
78
|
+
app.debug("NOAA: Downloading tide stations");
|
|
83
79
|
const res = await fetch(stationsUrl);
|
|
84
80
|
if (!res.ok)
|
|
85
81
|
throw new Error(`Failed to download stations: ${res.statusText}`);
|
|
86
|
-
data = await res.json();
|
|
87
|
-
await cache.set(
|
|
82
|
+
data = (await res.json());
|
|
83
|
+
await cache.set("stations", data);
|
|
88
84
|
}
|
|
89
85
|
return new this(data.stations);
|
|
90
86
|
}
|
|
@@ -97,8 +93,13 @@ class StationList extends Map {
|
|
|
97
93
|
near(position, limit = 10) {
|
|
98
94
|
const stationsWithDistances = Array.from(this.values()).map((station) => ({
|
|
99
95
|
...station,
|
|
100
|
-
distance:
|
|
96
|
+
distance: getDistance(position, {
|
|
97
|
+
latitude: station.lat,
|
|
98
|
+
longitude: station.lng,
|
|
99
|
+
}),
|
|
101
100
|
}));
|
|
102
|
-
return stationsWithDistances
|
|
101
|
+
return stationsWithDistances
|
|
102
|
+
.sort((a, b) => a.distance - b.distance)
|
|
103
|
+
.slice(0, limit);
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const moment_1 = __importDefault(require("moment"));
|
|
8
|
-
function default_1(app) {
|
|
1
|
+
import moment from 'moment';
|
|
2
|
+
export default function (app) {
|
|
9
3
|
return {
|
|
10
4
|
id: 'stormglass',
|
|
11
5
|
title: 'StormGlass.io',
|
|
@@ -17,11 +11,11 @@ function default_1(app) {
|
|
|
17
11
|
},
|
|
18
12
|
start({ stormglassApiKey } = {}) {
|
|
19
13
|
app.debug("Using StormGlass.io API");
|
|
20
|
-
return async ({ position, date = (
|
|
14
|
+
return async ({ position, date = moment().subtract(1, "days").toISOString() }) => {
|
|
21
15
|
const endPoint = new URL("https://api.stormglass.io/v2/tide/extremes/point");
|
|
22
16
|
endPoint.search = new URLSearchParams({
|
|
23
|
-
start: (
|
|
24
|
-
end: (
|
|
17
|
+
start: moment(date).format("YYYY-MM-DD"),
|
|
18
|
+
end: moment(date).add(7, "days").format("YYYY-MM-DD"),
|
|
25
19
|
// datum: "CD",
|
|
26
20
|
lat: position.latitude.toString(),
|
|
27
21
|
lng: position.longitude.toString()
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.default = default_1;
|
|
7
|
-
const moment_1 = __importDefault(require("moment"));
|
|
8
|
-
function default_1(app) {
|
|
1
|
+
import moment from 'moment';
|
|
2
|
+
export default function (app) {
|
|
9
3
|
return {
|
|
10
4
|
id: 'WorldTides.info',
|
|
11
5
|
title: 'worldtides.info',
|
|
@@ -18,10 +12,10 @@ function default_1(app) {
|
|
|
18
12
|
start({ worldtidesApiKey = '' } = {}) {
|
|
19
13
|
app.debug("Using WorldTides API");
|
|
20
14
|
return async (params) => {
|
|
21
|
-
const { position, date = (
|
|
15
|
+
const { position, date = moment().subtract(1, "days") } = params;
|
|
22
16
|
const endPoint = new URL("https://www.worldtides.info/api/v3");
|
|
23
17
|
endPoint.search = new URLSearchParams({
|
|
24
|
-
date: (
|
|
18
|
+
date: moment(date).format("YYYY-MM-DD"),
|
|
25
19
|
datum: "CD",
|
|
26
20
|
days: "7",
|
|
27
21
|
extremes: "true",
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signalk-tides",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Tidal predictions for the vessel's position from various online sources.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "./dist/index.js",
|
|
7
|
+
"exports": "./dist/index.js",
|
|
6
8
|
"scripts": {
|
|
7
9
|
"build": "tsc -b && vite build",
|
|
8
10
|
"dev": "vite",
|
|
@@ -29,22 +31,23 @@
|
|
|
29
31
|
],
|
|
30
32
|
"license": "Apache-2.0",
|
|
31
33
|
"dependencies": {
|
|
32
|
-
"@react-hook/resize-observer": "^2.0.2",
|
|
33
34
|
"@signalk/server-api": "^2.0",
|
|
34
35
|
"geolib": "^3.3.4",
|
|
35
|
-
"moment": "^2.30.1"
|
|
36
|
+
"moment": "^2.30.1",
|
|
37
|
+
"neaps": "^0.2.0"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
40
|
"@eslint/js": "^9.21.0",
|
|
41
|
+
"@react-hook/resize-observer": "^2.0.2",
|
|
39
42
|
"@tailwindcss/vite": "^4.1.3",
|
|
40
43
|
"@types/d3": "^7.4.3",
|
|
41
|
-
"@types/node": "^
|
|
44
|
+
"@types/node": "^25.0.3",
|
|
42
45
|
"@types/react": "^19.0.12",
|
|
43
46
|
"@types/react-dom": "^19.0.4",
|
|
44
47
|
"@vitejs/plugin-react": "^5.0.2",
|
|
45
48
|
"d3": "^7.9.0",
|
|
46
49
|
"eslint": "^9.21.0",
|
|
47
|
-
"eslint-plugin-react-hooks": "^
|
|
50
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
48
51
|
"eslint-plugin-react-refresh": "^0.4.19",
|
|
49
52
|
"globals": "^16.3.0",
|
|
50
53
|
"react": "^19.1.0",
|