node-red-contrib-ntrip 0.2.10 → 0.2.11

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/examples/watchdog.json +1 -1
  4. package/ntrip/lib/ntrip-client.js +70 -72
  5. package/ntrip/nodes/nmea-decoder-node.js +36 -38
  6. package/ntrip/nodes/nmea-encoder-node.js +97 -97
  7. package/ntrip/nodes/ntrip-client-node.js +153 -148
  8. package/ntrip/nodes/rtcm-decoder-node.js +50 -47
  9. package/ntrip/nodes/rtcm-encoder-node.js +33 -32
  10. package/package.json +16 -6
  11. package/.github/dependabot.yml +0 -23
  12. package/.github/workflows/node.js.yml +0 -32
  13. package/.github/workflows/release.yml +0 -65
  14. package/.prettierignore +0 -20
  15. package/.prettierrc.json +0 -9
  16. package/CLAUDE.md +0 -73
  17. package/doc/architecture/README.md +0 -19
  18. package/doc/architecture/adr/0001-single-registration-file.md +0 -64
  19. package/doc/architecture/adr/0002-ntrip-uploader-extension.md +0 -71
  20. package/doc/architecture/adr/0003-two-output-decoder-design.md +0 -73
  21. package/doc/architecture/adr/0004-stateful-handshake-interception.md +0 -74
  22. package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +0 -87
  23. package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +0 -66
  24. package/doc/architecture/adr/0007-mocha-test-helper-stack.md +0 -67
  25. package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +0 -76
  26. package/doc/architecture/adr/README.md +0 -18
  27. package/doc/architecture/architecture-decisions.md +0 -60
  28. package/doc/architecture/behavioural-design.md +0 -226
  29. package/doc/architecture/errors-and-weaknesses.md +0 -71
  30. package/doc/architecture/future-improvements.md +0 -130
  31. package/doc/architecture/overview.md +0 -77
  32. package/doc/architecture/refactoring-recommendations.md +0 -114
  33. package/doc/architecture/statistics.md +0 -118
  34. package/doc/architecture/structural-design.md +0 -141
  35. package/eslint.config.js +0 -36
  36. package/test/nmea-decoder.spec.js +0 -146
  37. package/test/nmea-encoder.spec.js +0 -104
  38. package/test/rtcm-decoder.spec.js +0 -116
  39. package/test/rtcm-encoder.spec.js +0 -144
@@ -8,13 +8,15 @@ module.exports = function (RED) {
8
8
  const HandshakePrefixBytes = 64;
9
9
 
10
10
  function formatBps(bps) {
11
+ let text;
11
12
  if (bps < 1000) {
12
- return Math.round(bps) + ' bps';
13
- }
14
- if (bps < 1000000) {
15
- return (bps / 1000).toFixed(1) + ' kbps';
13
+ text = Math.round(bps) + ' bps';
14
+ } else if (bps < 1000000) {
15
+ text = (bps / 1000).toFixed(1) + ' kbps';
16
+ } else {
17
+ text = (bps / 1000000).toFixed(2) + ' Mbps';
16
18
  }
17
- return (bps / 1000000).toFixed(2) + ' Mbps';
19
+ return text;
18
20
  }
19
21
 
20
22
  function NtripClientNode(config) {
@@ -33,168 +35,171 @@ module.exports = function (RED) {
33
35
  let authmode = config.authmode || 'legacy';
34
36
  let interval = parseInt(config.interval, 10) || 1000;
35
37
 
38
+ let client = null;
39
+
36
40
  if (host === '') {
37
41
  node.status({ fill: 'red', shape: 'ring', text: 'Host not configured' });
38
- return;
39
- }
40
-
41
- let options = {
42
- host: host,
43
- port: port,
44
- mountpoint: mountpoint,
45
- username: node.credentials.username,
46
- password: node.credentials.password,
47
- interval: interval,
48
- };
49
-
50
- // Depending on the caster the clients need to provide the location via GGA sentence.
51
- let x = parseFloat(config.xcoordinate);
52
- let y = parseFloat(config.ycoordinate);
53
- let z = parseFloat(config.zcoordinate);
54
-
55
- if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && !(x === 0 && y === 0 && z === 0)) {
56
- options.xyz = [x, y, z];
57
- }
58
-
59
- let client;
60
- if (mode === 'download') {
61
- client = NtripClient.createDownloader(options);
62
- } else if (mode === 'upload') {
63
- options.authmode = authmode;
64
- try {
65
- client = NtripClient.createUploader(options);
66
- } catch (error) {
67
- node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
68
- node.error('Failed to create uploader: ' + (error.message || error));
69
- return;
70
- }
71
42
  } else {
72
- node.status({ fill: 'red', shape: 'ring', text: 'Unsupported mode: ' + mode });
73
- node.error('Mode not supported: ' + mode);
74
- return;
75
- }
76
-
77
- // Track handshake state so we only intercept control replies during the handshake,
78
- // not on every subsequent data packet. Reset on socket close so reconnects work.
79
- let connected = false;
80
-
81
- function updateStatus() {
82
- node.status({
83
- fill: 'green',
84
- shape: 'ring',
85
- text: formatBps(node.currentBps) + ' Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
86
- });
87
- }
88
-
89
- // Sample inbound byte counter once per second to compute the bps shown in the
90
- // status badge. Counter is reset each tick — currentBps reflects the most
91
- // recent 1-second window.
92
- const bpsSampler = setInterval(() => {
93
- node.currentBps = node.bytesAccum * 8;
94
- node.bytesAccum = 0;
95
- updateStatus();
96
- }, 1000);
97
-
98
- client.on('data', (data) => {
99
- node.bytesAccum += data.length;
100
- if (!connected) {
101
- let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
102
- if (prefix.startsWith(NtripServerOkReply)) {
103
- connected = true;
104
- node.status({ fill: 'green', shape: 'ring', text: 'NTRIP server connected.' });
105
- // Forward any RTCM bytes that arrived in the same TCP segment as the handshake reply.
106
- let headerEnd = data.indexOf('\r\n\r\n');
107
- if (headerEnd !== -1 && headerEnd + 4 < data.length) {
108
- node.messagesReceived++;
109
- node.send({ payload: data.slice(headerEnd + 4) });
110
- updateStatus();
111
- }
112
- return;
113
- }
114
- if (prefix.startsWith(NtripServerNotOkReply)) {
115
- node.status({ fill: 'red', shape: 'ring', text: 'NTRIP server rejected connection.' });
116
- node.error('Server rejected connect: ' + prefix);
117
- return;
118
- }
119
- if (prefix.startsWith(NtripServerMissingMountpoint)) {
120
- node.status({ fill: 'red', shape: 'ring', text: 'No mountpoint ' + mountpoint });
121
- node.error('No mountpoint: ' + prefix);
122
- return;
123
- }
124
- // Unexpected: data arrived before any recognised handshake reply.
125
- // Treat as connected so we don't drop further frames.
126
- connected = true;
43
+ let options = {
44
+ host: host,
45
+ port: port,
46
+ mountpoint: mountpoint,
47
+ username: node.credentials.username,
48
+ password: node.credentials.password,
49
+ interval: interval,
50
+ };
51
+
52
+ // Depending on the caster the clients need to provide the location via GGA sentence.
53
+ let x = parseFloat(config.xcoordinate);
54
+ let y = parseFloat(config.ycoordinate);
55
+ let z = parseFloat(config.zcoordinate);
56
+
57
+ if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && !(x === 0 && y === 0 && z === 0)) {
58
+ options.xyz = [x, y, z];
127
59
  }
128
60
 
129
- node.messagesReceived++;
130
- node.send({ payload: data });
131
- updateStatus();
132
- });
133
-
134
- client.on('close', () => {
135
- connected = false;
136
- node.log('Closed NTRIP connection.');
137
- });
138
-
139
- client.on('error', (error) => {
140
- connected = false;
141
- if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
142
- error.errors.forEach((e, i) => {
143
- node.log('Error ' + (i + 1) + ': ' + e.message);
144
- node.error('Error: ' + e.message);
145
- });
61
+ if (mode === 'download') {
62
+ client = NtripClient.createDownloader(options);
63
+ } else if (mode === 'upload') {
64
+ options.authmode = authmode;
65
+ try {
66
+ client = NtripClient.createUploader(options);
67
+ } catch (error) {
68
+ node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
69
+ node.error('Failed to create uploader: ' + (error.message || error));
70
+ }
146
71
  } else {
147
- let text = error && error.message ? error.message : String(error);
148
- node.log(text);
149
- node.error('Error: ' + text);
72
+ node.status({ fill: 'red', shape: 'ring', text: 'Unsupported mode: ' + mode });
73
+ node.error('Mode not supported: ' + mode);
150
74
  }
151
- });
152
-
153
- try {
154
- client.run();
155
- } catch (error) {
156
- node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
157
- node.error('Failed to start client: ' + (error.message || error));
158
- return;
159
75
  }
160
76
 
161
- node.status({ fill: 'yellow', shape: 'ring', text: 'connecting...' });
77
+ if (client !== null) {
78
+ // Track handshake state so we only intercept control replies during the handshake,
79
+ // not on every subsequent data packet. Reset on socket close so reconnects work.
80
+ let connected = false;
162
81
 
163
- this.on('input', function (msg) {
164
- let payload = msg.payload;
165
- if (payload === undefined || payload === null) {
166
- return;
82
+ function updateStatus() {
83
+ node.status({
84
+ fill: 'green',
85
+ shape: 'ring',
86
+ text: formatBps(node.currentBps) + ' Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
87
+ });
167
88
  }
168
89
 
169
- try {
170
- if (Array.isArray(payload)) {
171
- client.setXYZ(payload);
172
- } else {
173
- client.write(payload);
174
- node.messagesSent++;
175
- if (node.passthrough) {
176
- node.send(msg);
90
+ // Sample inbound byte counter once per second to compute the bps shown in the
91
+ // status badge. Counter is reset each tick — currentBps reflects the most
92
+ // recent 1-second window.
93
+ const bpsSampler = setInterval(() => {
94
+ node.currentBps = node.bytesAccum * 8;
95
+ node.bytesAccum = 0;
96
+ updateStatus();
97
+ }, 1000);
98
+
99
+ client.on('data', (data) => {
100
+ node.bytesAccum += data.length;
101
+ let handled = false;
102
+ if (!connected) {
103
+ let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
104
+ if (prefix.startsWith(NtripServerOkReply)) {
105
+ connected = true;
106
+ node.status({ fill: 'green', shape: 'ring', text: 'NTRIP server connected.' });
107
+ // Forward any RTCM bytes that arrived in the same TCP segment as the handshake reply.
108
+ let headerEnd = data.indexOf('\r\n\r\n');
109
+ if (headerEnd !== -1 && headerEnd + 4 < data.length) {
110
+ node.messagesReceived++;
111
+ node.send({ payload: data.slice(headerEnd + 4) });
112
+ updateStatus();
113
+ }
114
+ handled = true;
115
+ } else if (prefix.startsWith(NtripServerNotOkReply)) {
116
+ node.status({ fill: 'red', shape: 'ring', text: 'NTRIP server rejected connection.' });
117
+ node.error('Server rejected connect: ' + prefix);
118
+ handled = true;
119
+ } else if (prefix.startsWith(NtripServerMissingMountpoint)) {
120
+ node.status({ fill: 'red', shape: 'ring', text: 'No mountpoint ' + mountpoint });
121
+ node.error('No mountpoint: ' + prefix);
122
+ handled = true;
123
+ } else {
124
+ // Unexpected: data arrived before any recognised handshake reply.
125
+ // Treat as connected so we don't drop further frames.
126
+ connected = true;
177
127
  }
128
+ }
129
+
130
+ if (!handled) {
131
+ node.messagesReceived++;
132
+ node.send({ payload: data });
178
133
  updateStatus();
179
134
  }
135
+ });
136
+
137
+ client.on('close', () => {
138
+ connected = false;
139
+ node.log('Closed NTRIP connection.');
140
+ });
141
+
142
+ client.on('error', (error) => {
143
+ connected = false;
144
+ if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
145
+ error.errors.forEach((e, i) => {
146
+ node.log('Error ' + (i + 1) + ': ' + e.message);
147
+ node.error('Error: ' + e.message);
148
+ });
149
+ } else {
150
+ let text = error && error.message ? error.message : String(error);
151
+ node.log(text);
152
+ node.error('Error: ' + text);
153
+ }
154
+ });
155
+
156
+ let running = true;
157
+ try {
158
+ client.run();
180
159
  } catch (error) {
181
- let text = error && error.message ? error.message : String(error);
182
- node.status({ fill: 'red', shape: 'ring', text: text });
183
- node.error('Failed to write data: ' + text, msg);
160
+ running = false;
161
+ node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
162
+ node.error('Failed to start client: ' + (error.message || error));
184
163
  }
185
- });
186
164
 
187
- this.on('close', function (done) {
188
- clearInterval(bpsSampler);
189
- try {
190
- client.removeAllListeners();
191
- client.close();
192
- } catch {
193
- // ignore — node is shutting down anyway
165
+ if (running) {
166
+ node.status({ fill: 'yellow', shape: 'ring', text: 'connecting...' });
167
+
168
+ this.on('input', function (msg) {
169
+ let payload = msg.payload;
170
+ if (payload !== undefined && payload !== null) {
171
+ try {
172
+ if (Array.isArray(payload)) {
173
+ client.setXYZ(payload);
174
+ } else {
175
+ client.write(payload);
176
+ node.messagesSent++;
177
+ if (node.passthrough) {
178
+ node.send(msg);
179
+ }
180
+ updateStatus();
181
+ }
182
+ } catch (error) {
183
+ let text = error && error.message ? error.message : String(error);
184
+ node.status({ fill: 'red', shape: 'ring', text: text });
185
+ node.error('Failed to write data: ' + text, msg);
186
+ }
187
+ }
188
+ });
189
+
190
+ this.on('close', function (done) {
191
+ clearInterval(bpsSampler);
192
+ try {
193
+ client.removeAllListeners();
194
+ client.close();
195
+ } catch {
196
+ // ignore — node is shutting down anyway
197
+ }
198
+ node.status({});
199
+ done();
200
+ });
194
201
  }
195
- node.status({});
196
- done();
197
- });
202
+ }
198
203
  }
199
204
 
200
205
  return NtripClientNode;
@@ -23,59 +23,62 @@ module.exports = function (RED) {
23
23
  }
24
24
 
25
25
  this.on('input', function (msg) {
26
- if (msg.payload === undefined || msg.payload === null) {
27
- return;
28
- }
29
-
30
- let chunk = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
31
- let buffer = node.pendingBuffer.length > 0 ? Buffer.concat([node.pendingBuffer, chunk]) : chunk;
26
+ if (msg.payload !== undefined && msg.payload !== null) {
27
+ let chunk = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
28
+ let buffer = node.pendingBuffer.length > 0 ? Buffer.concat([node.pendingBuffer, chunk]) : chunk;
32
29
 
33
- while (buffer.length > 0) {
34
- let message;
35
- let length;
36
- try {
37
- [message, length] = RtcmTransport.decode(buffer);
38
- } catch (ex) {
39
- // Likely a partial frame at the tail of this chunk — keep the bytes
40
- // for the next input event. If we've accumulated too much without a
41
- // successful decode, drop and emit an error.
42
- if (buffer.length > MaxPendingBytes) {
43
- let errMsg = {
44
- payload: {
45
- error: ex,
46
- input: buffer,
47
- inputString: buffer.toString(),
48
- },
49
- };
50
- node.send([null, errMsg]);
51
- node.invalidMessagesReceived++;
52
- buffer = Buffer.alloc(0);
30
+ let keepLooping = true;
31
+ while (keepLooping && buffer.length > 0) {
32
+ let message;
33
+ let length;
34
+ let decoded = true;
35
+ try {
36
+ [message, length] = RtcmTransport.decode(buffer);
37
+ } catch (ex) {
38
+ decoded = false;
39
+ // Likely a partial frame at the tail of this chunk — keep the bytes
40
+ // for the next input event. If we've accumulated too much without a
41
+ // successful decode, drop and emit an error.
42
+ if (buffer.length > MaxPendingBytes) {
43
+ let errMsg = {
44
+ payload: {
45
+ error: ex,
46
+ input: buffer,
47
+ inputString: buffer.toString(),
48
+ },
49
+ };
50
+ node.send([null, errMsg]);
51
+ node.invalidMessagesReceived++;
52
+ buffer = Buffer.alloc(0);
53
+ }
54
+ keepLooping = false;
53
55
  }
54
- break;
55
- }
56
56
 
57
- if (!Number.isInteger(length) || length <= 0 || length > buffer.length) {
58
- // Defensive guarantees forward progress even if the decoder ever
59
- // returns a non-positive length.
60
- break;
57
+ if (decoded) {
58
+ if (!Number.isInteger(length) || length <= 0 || length > buffer.length) {
59
+ // Defensive guarantees forward progress even if the decoder ever
60
+ // returns a non-positive length.
61
+ keepLooping = false;
62
+ } else {
63
+ let messageType = message.constructor.name.replace('RtcmMessage', '');
64
+ let outMsg = {
65
+ payload: {
66
+ rtcm: message.messageType,
67
+ messageType: messageType,
68
+ message: message,
69
+ input: buffer.slice(0, length),
70
+ },
71
+ };
72
+ node.send([outMsg, null]);
73
+ node.rtcmMessagesReceived++;
74
+ buffer = buffer.slice(length);
75
+ }
76
+ }
61
77
  }
62
78
 
63
- let messageType = message.constructor.name.replace('RtcmMessage', '');
64
- let outMsg = {
65
- payload: {
66
- rtcm: message.messageType,
67
- messageType: messageType,
68
- message: message,
69
- input: buffer.slice(0, length),
70
- },
71
- };
72
- node.send([outMsg, null]);
73
- node.rtcmMessagesReceived++;
74
- buffer = buffer.slice(length);
79
+ node.pendingBuffer = buffer;
80
+ updateStatus();
75
81
  }
76
-
77
- node.pendingBuffer = buffer;
78
- updateStatus();
79
82
  });
80
83
 
81
84
  this.on('close', function (done) {
@@ -31,7 +31,9 @@ module.exports = function (RED) {
31
31
  message = payload;
32
32
  } else if (payload != null && payload.message instanceof RtcmMessage) {
33
33
  message = payload.message;
34
- } else {
34
+ }
35
+
36
+ if (message === undefined) {
35
37
  node.send([
36
38
  null,
37
39
  {
@@ -43,40 +45,39 @@ module.exports = function (RED) {
43
45
  ]);
44
46
  node.invalidMessagesReceived++;
45
47
  updateStatus(false);
46
- return;
47
- }
48
-
49
- try {
50
- let buffer = Buffer.alloc(MaxFrameBytes);
51
- let length = RtcmTransport.encode(message, buffer);
52
- let encoded = buffer.slice(0, length);
53
- let messageType = message.constructor.name.replace('RtcmMessage', '');
48
+ } else {
49
+ try {
50
+ let buffer = Buffer.alloc(MaxFrameBytes);
51
+ let length = RtcmTransport.encode(message, buffer);
52
+ let encoded = buffer.slice(0, length);
53
+ let messageType = message.constructor.name.replace('RtcmMessage', '');
54
54
 
55
- node.send([
56
- {
57
- payload: {
58
- rtcmMessage: encoded,
59
- rtcm: message.messageType,
60
- messageType: messageType,
61
- input: payload,
55
+ node.send([
56
+ {
57
+ payload: {
58
+ rtcmMessage: encoded,
59
+ rtcm: message.messageType,
60
+ messageType: messageType,
61
+ input: payload,
62
+ },
62
63
  },
63
- },
64
- null,
65
- ]);
66
- node.rtcmMessagesReceived++;
67
- updateStatus(true);
68
- } catch (ex) {
69
- node.send([
70
- null,
71
- {
72
- payload: {
73
- error: ex,
74
- input: payload,
64
+ null,
65
+ ]);
66
+ node.rtcmMessagesReceived++;
67
+ updateStatus(true);
68
+ } catch (ex) {
69
+ node.send([
70
+ null,
71
+ {
72
+ payload: {
73
+ error: ex,
74
+ input: payload,
75
+ },
75
76
  },
76
- },
77
- ]);
78
- node.invalidMessagesReceived++;
79
- updateStatus(false);
77
+ ]);
78
+ node.invalidMessagesReceived++;
79
+ updateStatus(false);
80
+ }
80
81
  }
81
82
  });
82
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ntrip",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Node for ntrip and rtcm handling.",
5
5
  "node-red": {
6
6
  "version": ">=0.1.0",
@@ -9,7 +9,7 @@
9
9
  }
10
10
  },
11
11
  "engines": {
12
- "node": ">=18.0.0"
12
+ "node": ">=20.0.0"
13
13
  },
14
14
  "keywords": [
15
15
  "node-red",
@@ -18,6 +18,12 @@
18
18
  "gnss",
19
19
  "mmea"
20
20
  ],
21
+ "files": [
22
+ "ntrip/",
23
+ "examples/",
24
+ "images/",
25
+ "CHANGELOG.md"
26
+ ],
21
27
  "repository": {
22
28
  "type": "git",
23
29
  "url": "git+https://github.com/windkh/node-red-contrib-ntrip.git"
@@ -25,11 +31,15 @@
25
31
  "author": "Karl-Heinz Wind",
26
32
  "license": "MIT",
27
33
  "scripts": {
28
- "test": "mocha test/**/*.spec.js --timeout 5000",
34
+ "test": "node --test --test-force-exit --test-timeout=30000 --test-concurrency=1",
29
35
  "lint": "eslint .",
30
36
  "lint:fix": "eslint . --fix",
31
37
  "format": "prettier --write .",
32
- "format:check": "prettier --check ."
38
+ "format:check": "prettier --check .",
39
+ "coverage": "c8 npm test",
40
+ "coverage:check": "c8 --check-coverage npm test",
41
+ "standards:audit": "nrstd audit",
42
+ "standards:sync": "nrstd sync"
33
43
  },
34
44
  "dependencies": {
35
45
  "@gnss/nmea": "^0.1.2",
@@ -38,13 +48,13 @@
38
48
  },
39
49
  "devDependencies": {
40
50
  "@eslint/js": "^10.0.1",
41
- "chai": "^4.5.0",
51
+ "c8": "^10.0.0",
42
52
  "eslint": "^10.3.0",
43
53
  "eslint-config-prettier": "^10.1.8",
44
54
  "globals": "^17.6.0",
45
- "mocha": "^11.7.5",
46
55
  "node-red": "^5.0.1",
47
56
  "node-red-node-test-helper": "^0.3.6",
57
+ "node-red-standards": "https://codeload.github.com/windkh/node-red-standards/tar.gz/refs/heads/main",
48
58
  "prettier": "^3.8.3"
49
59
  }
50
60
  }
@@ -1,23 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: 'npm'
4
- directory: '/'
5
- schedule:
6
- interval: 'weekly'
7
- open-pull-requests-limit: 5
8
- # The following packages are transitive devDependencies pulled in by
9
- # `node-red` / `node-red-node-test-helper`. They have known advisories
10
- # but no patched version is currently resolvable, so Dependabot's
11
- # security-update PRs fail noisily on every retry. Since these packages
12
- # are dev-only (not reachable from the published runtime tree of
13
- # @gnss/nmea, @gnss/rtcm, ntrip-client), the advisories don't affect
14
- # end users. Remove the ignore once upstream ships a fixed version.
15
- ignore:
16
- - dependency-name: 'picomatch'
17
- - dependency-name: 'ip-address'
18
- - dependency-name: 'serialize-javascript'
19
-
20
- - package-ecosystem: 'github-actions'
21
- directory: '/'
22
- schedule:
23
- interval: 'monthly'
@@ -1,32 +0,0 @@
1
- # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
- # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3
-
4
- name: Node.js CI
5
-
6
- on:
7
- push:
8
- branches: ['main']
9
- pull_request:
10
- branches: ['main']
11
-
12
- jobs:
13
- build:
14
- runs-on: ubuntu-latest
15
-
16
- strategy:
17
- matrix:
18
- node-version: [18.x, 20.x, 22.x]
19
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20
-
21
- steps:
22
- - uses: actions/checkout@v7
23
- - name: Use Node.js ${{ matrix.node-version }}
24
- uses: actions/setup-node@v6
25
- with:
26
- node-version: ${{ matrix.node-version }}
27
- cache: 'npm'
28
- - run: npm ci
29
- - run: npm run build --if-present
30
- - run: npm run lint
31
- - run: npm run format:check
32
- - run: npm test