rclnodejs 1.5.2 → 1.7.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/index.js +79 -3
- package/lib/action/client.js +55 -9
- package/lib/action/deferred.js +8 -2
- package/lib/action/server.js +10 -1
- package/lib/action/uuid.js +4 -1
- package/lib/client.js +152 -3
- package/lib/clock.js +4 -1
- package/lib/context.js +12 -2
- package/lib/duration.js +37 -12
- package/lib/errors.js +571 -0
- package/lib/event_handler.js +21 -4
- package/lib/interface_loader.js +52 -12
- package/lib/lifecycle.js +8 -2
- package/lib/logging.js +12 -3
- package/lib/message_serialization.js +179 -0
- package/lib/native_loader.js +9 -4
- package/lib/node.js +283 -47
- package/lib/parameter.js +176 -45
- package/lib/parameter_client.js +506 -0
- package/lib/parameter_watcher.js +309 -0
- package/lib/qos.js +22 -5
- package/lib/rate.js +6 -1
- package/lib/serialization.js +7 -2
- package/lib/subscription.js +16 -1
- package/lib/time.js +136 -21
- package/lib/time_source.js +13 -4
- package/lib/utils.js +313 -0
- package/lib/validator.js +11 -12
- package/package.json +2 -7
- package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
- package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
- package/rosidl_convertor/idl_convertor.js +3 -2
- package/rosidl_gen/generate_worker.js +1 -1
- package/rosidl_gen/idl_generator.js +11 -24
- package/rosidl_gen/index.js +1 -1
- package/rosidl_gen/templates/action-template.js +68 -0
- package/rosidl_gen/templates/message-template.js +1113 -0
- package/rosidl_gen/templates/service-event-template.js +31 -0
- package/rosidl_gen/templates/service-template.js +44 -0
- package/rosidl_parser/rosidl_parser.js +2 -2
- package/third_party/ref-napi/lib/ref.js +0 -45
- package/types/base.d.ts +3 -0
- package/types/client.d.ts +36 -0
- package/types/errors.d.ts +447 -0
- package/types/index.d.ts +17 -0
- package/types/interfaces.d.ts +1910 -1
- package/types/node.d.ts +56 -1
- package/types/parameter_client.d.ts +252 -0
- package/types/parameter_watcher.d.ts +104 -0
- package/rosidl_gen/templates/CMakeLists.dot +0 -40
- package/rosidl_gen/templates/action.dot +0 -50
- package/rosidl_gen/templates/message.dot +0 -851
- package/rosidl_gen/templates/package.dot +0 -16
- package/rosidl_gen/templates/service.dot +0 -26
- package/rosidl_gen/templates/service_event.dot +0 -10
package/lib/time.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const Duration = require('./duration.js');
|
|
19
19
|
const ClockType = require('./clock_type.js');
|
|
20
|
+
const { TypeValidationError, RangeValidationError } = require('./errors.js');
|
|
20
21
|
const S_TO_NS = 10n ** 9n;
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -36,29 +37,49 @@ class Time {
|
|
|
36
37
|
clockType = ClockType.SYSTEM_TIME
|
|
37
38
|
) {
|
|
38
39
|
if (typeof seconds !== 'bigint') {
|
|
39
|
-
throw new
|
|
40
|
+
throw new TypeValidationError('seconds', seconds, 'bigint', {
|
|
41
|
+
entityType: 'time',
|
|
42
|
+
});
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
if (typeof nanoseconds !== 'bigint') {
|
|
43
|
-
throw new
|
|
46
|
+
throw new TypeValidationError('nanoseconds', nanoseconds, 'bigint', {
|
|
47
|
+
entityType: 'time',
|
|
48
|
+
});
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
if (typeof clockType !== 'number') {
|
|
47
|
-
throw new
|
|
52
|
+
throw new TypeValidationError('clockType', clockType, 'number', {
|
|
53
|
+
entityType: 'time',
|
|
54
|
+
});
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
if (seconds < 0n) {
|
|
51
|
-
throw new
|
|
58
|
+
throw new RangeValidationError('seconds', seconds, '>= 0', {
|
|
59
|
+
entityType: 'time',
|
|
60
|
+
});
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
if (nanoseconds < 0n) {
|
|
55
|
-
throw new
|
|
64
|
+
throw new RangeValidationError('nanoseconds', nanoseconds, '>= 0', {
|
|
65
|
+
entityType: 'time',
|
|
66
|
+
});
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
const total = seconds * S_TO_NS + nanoseconds;
|
|
59
70
|
if (total >= 2n ** 63n) {
|
|
60
|
-
throw new
|
|
61
|
-
'
|
|
71
|
+
throw new RangeValidationError(
|
|
72
|
+
'total nanoseconds',
|
|
73
|
+
total,
|
|
74
|
+
'< 2^63 (max C time point)',
|
|
75
|
+
{
|
|
76
|
+
entityType: 'time',
|
|
77
|
+
details: {
|
|
78
|
+
seconds: seconds,
|
|
79
|
+
nanoseconds: nanoseconds,
|
|
80
|
+
total: total,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
62
83
|
);
|
|
63
84
|
}
|
|
64
85
|
this._nanoseconds = total;
|
|
@@ -116,7 +137,9 @@ class Time {
|
|
|
116
137
|
this._clockType
|
|
117
138
|
);
|
|
118
139
|
}
|
|
119
|
-
throw new
|
|
140
|
+
throw new TypeValidationError('other', other, 'Duration', {
|
|
141
|
+
entityType: 'time',
|
|
142
|
+
});
|
|
120
143
|
}
|
|
121
144
|
|
|
122
145
|
/**
|
|
@@ -127,7 +150,18 @@ class Time {
|
|
|
127
150
|
sub(other) {
|
|
128
151
|
if (other instanceof Time) {
|
|
129
152
|
if (other._clockType !== this._clockType) {
|
|
130
|
-
throw new
|
|
153
|
+
throw new TypeValidationError(
|
|
154
|
+
'other',
|
|
155
|
+
other,
|
|
156
|
+
`Time with clock type ${this._clockType}`,
|
|
157
|
+
{
|
|
158
|
+
entityType: 'time',
|
|
159
|
+
details: {
|
|
160
|
+
expectedClockType: this._clockType,
|
|
161
|
+
providedClockType: other._clockType,
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
);
|
|
131
165
|
}
|
|
132
166
|
return new Duration(0n, this._nanoseconds - other._nanoseconds);
|
|
133
167
|
} else if (other instanceof Duration) {
|
|
@@ -137,7 +171,9 @@ class Time {
|
|
|
137
171
|
this._clockType
|
|
138
172
|
);
|
|
139
173
|
}
|
|
140
|
-
throw new
|
|
174
|
+
throw new TypeValidationError('other', other, 'Time or Duration', {
|
|
175
|
+
entityType: 'time',
|
|
176
|
+
});
|
|
141
177
|
}
|
|
142
178
|
|
|
143
179
|
/**
|
|
@@ -148,11 +184,24 @@ class Time {
|
|
|
148
184
|
eq(other) {
|
|
149
185
|
if (other instanceof Time) {
|
|
150
186
|
if (other._clockType !== this._clockType) {
|
|
151
|
-
throw new
|
|
187
|
+
throw new TypeValidationError(
|
|
188
|
+
'other',
|
|
189
|
+
other,
|
|
190
|
+
`Time with clock type ${this._clockType}`,
|
|
191
|
+
{
|
|
192
|
+
entityType: 'time',
|
|
193
|
+
details: {
|
|
194
|
+
expectedClockType: this._clockType,
|
|
195
|
+
providedClockType: other._clockType,
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
);
|
|
152
199
|
}
|
|
153
200
|
return this._nanoseconds === other.nanoseconds;
|
|
154
201
|
}
|
|
155
|
-
throw new
|
|
202
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
203
|
+
entityType: 'time',
|
|
204
|
+
});
|
|
156
205
|
}
|
|
157
206
|
|
|
158
207
|
/**
|
|
@@ -163,10 +212,24 @@ class Time {
|
|
|
163
212
|
ne(other) {
|
|
164
213
|
if (other instanceof Time) {
|
|
165
214
|
if (other._clockType !== this._clockType) {
|
|
166
|
-
throw new
|
|
215
|
+
throw new TypeValidationError(
|
|
216
|
+
'other',
|
|
217
|
+
other,
|
|
218
|
+
`Time with clock type ${this._clockType}`,
|
|
219
|
+
{
|
|
220
|
+
entityType: 'time',
|
|
221
|
+
details: {
|
|
222
|
+
expectedClockType: this._clockType,
|
|
223
|
+
providedClockType: other._clockType,
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
);
|
|
167
227
|
}
|
|
168
228
|
return this._nanoseconds !== other.nanoseconds;
|
|
169
229
|
}
|
|
230
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
231
|
+
entityType: 'time',
|
|
232
|
+
});
|
|
170
233
|
}
|
|
171
234
|
|
|
172
235
|
/**
|
|
@@ -177,11 +240,24 @@ class Time {
|
|
|
177
240
|
lt(other) {
|
|
178
241
|
if (other instanceof Time) {
|
|
179
242
|
if (other._clockType !== this._clockType) {
|
|
180
|
-
throw new
|
|
243
|
+
throw new TypeValidationError(
|
|
244
|
+
'other',
|
|
245
|
+
other,
|
|
246
|
+
`Time with clock type ${this._clockType}`,
|
|
247
|
+
{
|
|
248
|
+
entityType: 'time',
|
|
249
|
+
details: {
|
|
250
|
+
expectedClockType: this._clockType,
|
|
251
|
+
providedClockType: other._clockType,
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
);
|
|
181
255
|
}
|
|
182
256
|
return this._nanoseconds < other.nanoseconds;
|
|
183
257
|
}
|
|
184
|
-
throw new
|
|
258
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
259
|
+
entityType: 'time',
|
|
260
|
+
});
|
|
185
261
|
}
|
|
186
262
|
|
|
187
263
|
/**
|
|
@@ -192,11 +268,24 @@ class Time {
|
|
|
192
268
|
lte(other) {
|
|
193
269
|
if (other instanceof Time) {
|
|
194
270
|
if (other._clockType !== this._clockType) {
|
|
195
|
-
throw new
|
|
271
|
+
throw new TypeValidationError(
|
|
272
|
+
'other',
|
|
273
|
+
other,
|
|
274
|
+
`Time with clock type ${this._clockType}`,
|
|
275
|
+
{
|
|
276
|
+
entityType: 'time',
|
|
277
|
+
details: {
|
|
278
|
+
expectedClockType: this._clockType,
|
|
279
|
+
providedClockType: other._clockType,
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
);
|
|
196
283
|
}
|
|
197
284
|
return this._nanoseconds <= other.nanoseconds;
|
|
198
285
|
}
|
|
199
|
-
throw new
|
|
286
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
287
|
+
entityType: 'time',
|
|
288
|
+
});
|
|
200
289
|
}
|
|
201
290
|
|
|
202
291
|
/**
|
|
@@ -207,11 +296,24 @@ class Time {
|
|
|
207
296
|
gt(other) {
|
|
208
297
|
if (other instanceof Time) {
|
|
209
298
|
if (other._clockType !== this._clockType) {
|
|
210
|
-
throw new
|
|
299
|
+
throw new TypeValidationError(
|
|
300
|
+
'other',
|
|
301
|
+
other,
|
|
302
|
+
`Time with clock type ${this._clockType}`,
|
|
303
|
+
{
|
|
304
|
+
entityType: 'time',
|
|
305
|
+
details: {
|
|
306
|
+
expectedClockType: this._clockType,
|
|
307
|
+
providedClockType: other._clockType,
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
);
|
|
211
311
|
}
|
|
212
312
|
return this._nanoseconds > other.nanoseconds;
|
|
213
313
|
}
|
|
214
|
-
throw new
|
|
314
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
315
|
+
entityType: 'time',
|
|
316
|
+
});
|
|
215
317
|
}
|
|
216
318
|
|
|
217
319
|
/**
|
|
@@ -222,11 +324,24 @@ class Time {
|
|
|
222
324
|
gte(other) {
|
|
223
325
|
if (other instanceof Time) {
|
|
224
326
|
if (other._clockType !== this._clockType) {
|
|
225
|
-
throw new
|
|
327
|
+
throw new TypeValidationError(
|
|
328
|
+
'other',
|
|
329
|
+
other,
|
|
330
|
+
`Time with clock type ${this._clockType}`,
|
|
331
|
+
{
|
|
332
|
+
entityType: 'time',
|
|
333
|
+
details: {
|
|
334
|
+
expectedClockType: this._clockType,
|
|
335
|
+
providedClockType: other._clockType,
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
);
|
|
226
339
|
}
|
|
227
340
|
return this._nanoseconds >= other.nanoseconds;
|
|
228
341
|
}
|
|
229
|
-
throw new
|
|
342
|
+
throw new TypeValidationError('other', other, 'Time', {
|
|
343
|
+
entityType: 'time',
|
|
344
|
+
});
|
|
230
345
|
}
|
|
231
346
|
|
|
232
347
|
/**
|
package/lib/time_source.js
CHANGED
|
@@ -19,6 +19,7 @@ const { Clock, ROSClock } = require('./clock.js');
|
|
|
19
19
|
const { ClockType } = Clock;
|
|
20
20
|
const { Parameter, ParameterType } = require('./parameter.js');
|
|
21
21
|
const Time = require('./time.js');
|
|
22
|
+
const { TypeValidationError, OperationError } = require('./errors.js');
|
|
22
23
|
|
|
23
24
|
const USE_SIM_TIME_PARAM = 'use_sim_time';
|
|
24
25
|
const CLOCK_TOPIC = '/clock';
|
|
@@ -102,7 +103,9 @@ class TimeSource {
|
|
|
102
103
|
*/
|
|
103
104
|
attachNode(node) {
|
|
104
105
|
if ((!node) instanceof rclnodejs.ShadowNode) {
|
|
105
|
-
throw new
|
|
106
|
+
throw new TypeValidationError('node', node, 'Node', {
|
|
107
|
+
entityType: 'time source',
|
|
108
|
+
});
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
if (this._node) {
|
|
@@ -150,8 +153,12 @@ class TimeSource {
|
|
|
150
153
|
detachNode() {
|
|
151
154
|
if (this._clockSubscription) {
|
|
152
155
|
if (!this._node) {
|
|
153
|
-
throw new
|
|
154
|
-
'Unable to destroy previously created clock subscription'
|
|
156
|
+
throw new OperationError(
|
|
157
|
+
'Unable to destroy previously created clock subscription',
|
|
158
|
+
{
|
|
159
|
+
code: 'NO_NODE_ATTACHED',
|
|
160
|
+
entityType: 'time source',
|
|
161
|
+
}
|
|
155
162
|
);
|
|
156
163
|
}
|
|
157
164
|
this._node.destroySubscription(this._clockSubscription);
|
|
@@ -167,7 +174,9 @@ class TimeSource {
|
|
|
167
174
|
*/
|
|
168
175
|
attachClock(clock) {
|
|
169
176
|
if (!(clock instanceof ROSClock)) {
|
|
170
|
-
throw new
|
|
177
|
+
throw new TypeValidationError('clock', clock, 'ROSClock', {
|
|
178
|
+
entityType: 'time source',
|
|
179
|
+
});
|
|
171
180
|
}
|
|
172
181
|
clock.rosTimeOverride = this._lastTimeSet;
|
|
173
182
|
clock.isRosTimeActive = this._isRosTimeActive;
|
package/lib/utils.js
CHANGED
|
@@ -13,6 +13,157 @@
|
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
15
|
const fs = require('fs');
|
|
16
|
+
const fsPromises = require('fs/promises');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { ValidationError } = require('./errors.js');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Ensure directory exists, create recursively if needed (async)
|
|
22
|
+
* Replaces: fse.ensureDir() / fse.mkdirs()
|
|
23
|
+
* @param {string} dirPath - Path to directory
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
async function ensureDir(dirPath) {
|
|
27
|
+
try {
|
|
28
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// Ignore if directory already exists
|
|
31
|
+
if (err.code !== 'EEXIST') throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ensure directory exists, create recursively if needed (sync)
|
|
37
|
+
* Replaces: fse.mkdirSync()
|
|
38
|
+
* @param {string} dirPath - Path to directory
|
|
39
|
+
*/
|
|
40
|
+
function ensureDirSync(dirPath) {
|
|
41
|
+
try {
|
|
42
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
43
|
+
} catch (err) {
|
|
44
|
+
// Ignore if directory already exists
|
|
45
|
+
if (err.code !== 'EEXIST') throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if path exists (async)
|
|
51
|
+
* Replaces: fse.exists()
|
|
52
|
+
* @param {string} filePath - Path to check
|
|
53
|
+
* @returns {Promise<boolean>}
|
|
54
|
+
*/
|
|
55
|
+
async function pathExists(filePath) {
|
|
56
|
+
try {
|
|
57
|
+
await fsPromises.access(filePath);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Empty a directory (remove all contents but keep the directory)
|
|
66
|
+
* Replaces: fse.emptyDir()
|
|
67
|
+
* @param {string} dirPath - Path to directory
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
async function emptyDir(dirPath) {
|
|
71
|
+
try {
|
|
72
|
+
const files = await fsPromises.readdir(dirPath);
|
|
73
|
+
await Promise.all(
|
|
74
|
+
files.map((file) =>
|
|
75
|
+
fsPromises.rm(path.join(dirPath, file), {
|
|
76
|
+
recursive: true,
|
|
77
|
+
force: true,
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
// Ignore if directory doesn't exist
|
|
83
|
+
if (err.code !== 'ENOENT') throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Copy file or directory recursively
|
|
89
|
+
* Replaces: fse.copy()
|
|
90
|
+
* @param {string} src - Source path
|
|
91
|
+
* @param {string} dest - Destination path
|
|
92
|
+
* @param {object} options - Copy options
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
async function copy(src, dest, options = {}) {
|
|
96
|
+
const opts = {
|
|
97
|
+
recursive: true,
|
|
98
|
+
force: options.overwrite !== false,
|
|
99
|
+
...options,
|
|
100
|
+
};
|
|
101
|
+
await fsPromises.cp(src, dest, opts);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Read and parse JSON file synchronously
|
|
106
|
+
* Replaces: fse.readJsonSync()
|
|
107
|
+
* @param {string} filePath - Path to JSON file
|
|
108
|
+
* @param {object} options - Read options
|
|
109
|
+
* @returns {any} Parsed JSON data
|
|
110
|
+
*/
|
|
111
|
+
function readJsonSync(filePath, options = {}) {
|
|
112
|
+
const content = fs.readFileSync(filePath, options.encoding || 'utf8');
|
|
113
|
+
return JSON.parse(content);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Remove file or directory (async)
|
|
118
|
+
* Replaces: fse.remove()
|
|
119
|
+
* @param {string} filePath - Path to remove
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
122
|
+
async function remove(filePath) {
|
|
123
|
+
try {
|
|
124
|
+
await fsPromises.rm(filePath, { recursive: true, force: true });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
// Ignore if path doesn't exist
|
|
127
|
+
if (err.code !== 'ENOENT') throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove file or directory (sync)
|
|
133
|
+
* Replaces: fse.removeSync()
|
|
134
|
+
* @param {string} filePath - Path to remove
|
|
135
|
+
*/
|
|
136
|
+
function removeSync(filePath) {
|
|
137
|
+
try {
|
|
138
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// Ignore if path doesn't exist
|
|
141
|
+
if (err.code !== 'ENOENT') throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Write file with content (async)
|
|
147
|
+
* Replaces: fse.writeFile()
|
|
148
|
+
* @param {string} filePath - Path to file
|
|
149
|
+
* @param {string|Buffer} data - Content to write
|
|
150
|
+
* @param {object} options - Write options
|
|
151
|
+
* @returns {Promise<void>}
|
|
152
|
+
*/
|
|
153
|
+
async function writeFile(filePath, data, options = {}) {
|
|
154
|
+
await fsPromises.writeFile(filePath, data, options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create directory (async)
|
|
159
|
+
* Replaces: fse.mkdir()
|
|
160
|
+
* @param {string} dirPath - Path to directory
|
|
161
|
+
* @param {object} options - mkdir options
|
|
162
|
+
* @returns {Promise<void>}
|
|
163
|
+
*/
|
|
164
|
+
async function mkdir(dirPath, options = {}) {
|
|
165
|
+
await fsPromises.mkdir(dirPath, options);
|
|
166
|
+
}
|
|
16
167
|
|
|
17
168
|
/**
|
|
18
169
|
* Detect Ubuntu codename from /etc/os-release
|
|
@@ -32,6 +183,168 @@ function detectUbuntuCodename() {
|
|
|
32
183
|
}
|
|
33
184
|
}
|
|
34
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Normalize a ROS 2 node name by removing the leading slash if present.
|
|
188
|
+
*
|
|
189
|
+
* ROS 2 node names may be specified with or without a leading slash depending
|
|
190
|
+
* on the context. This utility ensures consistent representation without the
|
|
191
|
+
* leading slash, which is the standard format for most ROS 2 APIs.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} nodeName - The node name to normalize
|
|
194
|
+
* @returns {string} The normalized node name without leading slash
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* normalizeNodeName('my_node') // 'my_node'
|
|
198
|
+
* normalizeNodeName('/my_node') // 'my_node'
|
|
199
|
+
* normalizeNodeName('/ns/my_node') // 'ns/my_node'
|
|
200
|
+
*/
|
|
201
|
+
function normalizeNodeName(nodeName) {
|
|
202
|
+
return nodeName.startsWith('/') ? nodeName.substring(1) : nodeName;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if two numbers are equal within a given tolerance.
|
|
207
|
+
*
|
|
208
|
+
* This function compares two numbers using both relative and absolute tolerance,
|
|
209
|
+
* matching the behavior of the 'is-close' npm package.
|
|
210
|
+
*
|
|
211
|
+
* The comparison uses the formula:
|
|
212
|
+
* abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)
|
|
213
|
+
*
|
|
214
|
+
* Implementation checks:
|
|
215
|
+
* 1. Absolute tolerance: abs(a - b) <= atol
|
|
216
|
+
* 2. Relative tolerance: abs(a - b) / max(abs(a), abs(b)) <= rtol
|
|
217
|
+
*
|
|
218
|
+
* @param {number} a - The first number to compare
|
|
219
|
+
* @param {number} b - The second number to compare
|
|
220
|
+
* @param {number} [rtol=1e-9] - The relative tolerance parameter (default: 1e-9)
|
|
221
|
+
* @param {number} [atol=0.0] - The absolute tolerance parameter (default: 0.0)
|
|
222
|
+
* @returns {boolean} True if the numbers are close within the tolerance
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* isClose(1.0, 1.0) // true - exact equality
|
|
226
|
+
* isClose(1.0, 1.1, 0.01) // false - relative diff: 0.1/1.1 ≈ 0.091 > 0.01
|
|
227
|
+
* isClose(10, 10.00001, 1e-6) // true - relative diff: 0.00001/10 = 1e-6 <= 1e-6
|
|
228
|
+
* isClose(0, 0.05, 0, 0.1) // true - absolute diff: 0.05 <= 0.1 (atol)
|
|
229
|
+
*/
|
|
230
|
+
function isClose(a, b, rtol = 1e-9, atol = 0.0) {
|
|
231
|
+
// Handle exact equality
|
|
232
|
+
if (a === b) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Handle non-finite numbers
|
|
237
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const absDiff = Math.abs(a - b);
|
|
242
|
+
|
|
243
|
+
// Check absolute tolerance first (optimization)
|
|
244
|
+
if (atol >= absDiff) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check relative tolerance
|
|
249
|
+
const relativeScaler = Math.max(Math.abs(a), Math.abs(b));
|
|
250
|
+
|
|
251
|
+
// Handle division by zero when both values are zero or very close to zero
|
|
252
|
+
if (relativeScaler === 0) {
|
|
253
|
+
return true; // Both are zero, already handled by absolute tolerance
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const relativeDiff = absDiff / relativeScaler;
|
|
257
|
+
|
|
258
|
+
return rtol >= relativeDiff;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Compare two semantic version strings.
|
|
263
|
+
*
|
|
264
|
+
* Supports version strings in the format: x.y.z or x.y.z.w
|
|
265
|
+
* where x, y, z, w are integers.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} version1 - First version string (e.g., '1.2.3')
|
|
268
|
+
* @param {string} version2 - Second version string (e.g., '1.2.4')
|
|
269
|
+
* @param {string} operator - Comparison operator: '<', '<=', '>', '>=', '==', '!='
|
|
270
|
+
* @returns {boolean} Result of the comparison
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* compareVersions('1.2.3', '1.2.4', '<') // true
|
|
274
|
+
* compareVersions('2.0.0', '1.9.9', '>') // true
|
|
275
|
+
* compareVersions('1.2.3', '1.2.3', '==') // true
|
|
276
|
+
* compareVersions('1.2.3', '1.2.3', '>=') // true
|
|
277
|
+
*/
|
|
278
|
+
function compareVersions(version1, version2, operator) {
|
|
279
|
+
// Parse version strings into arrays of integers
|
|
280
|
+
const v1Parts = version1.split('.').map((part) => parseInt(part, 10));
|
|
281
|
+
const v2Parts = version2.split('.').map((part) => parseInt(part, 10));
|
|
282
|
+
|
|
283
|
+
// Pad arrays to same length with zeros
|
|
284
|
+
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
|
285
|
+
while (v1Parts.length < maxLength) v1Parts.push(0);
|
|
286
|
+
while (v2Parts.length < maxLength) v2Parts.push(0);
|
|
287
|
+
|
|
288
|
+
// Compare each part
|
|
289
|
+
let cmp = 0;
|
|
290
|
+
for (let i = 0; i < maxLength; i++) {
|
|
291
|
+
if (v1Parts[i] > v2Parts[i]) {
|
|
292
|
+
cmp = 1;
|
|
293
|
+
break;
|
|
294
|
+
} else if (v1Parts[i] < v2Parts[i]) {
|
|
295
|
+
cmp = -1;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Apply operator
|
|
301
|
+
switch (operator) {
|
|
302
|
+
case '<':
|
|
303
|
+
return cmp < 0;
|
|
304
|
+
case '<=':
|
|
305
|
+
return cmp <= 0;
|
|
306
|
+
case '>':
|
|
307
|
+
return cmp > 0;
|
|
308
|
+
case '>=':
|
|
309
|
+
return cmp >= 0;
|
|
310
|
+
case '==':
|
|
311
|
+
case '===':
|
|
312
|
+
return cmp === 0;
|
|
313
|
+
case '!=':
|
|
314
|
+
case '!==':
|
|
315
|
+
return cmp !== 0;
|
|
316
|
+
default:
|
|
317
|
+
throw new ValidationError(`Invalid operator: ${operator}`, {
|
|
318
|
+
code: 'INVALID_OPERATOR',
|
|
319
|
+
argumentName: 'operator',
|
|
320
|
+
providedValue: operator,
|
|
321
|
+
expectedType: "'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
35
326
|
module.exports = {
|
|
327
|
+
// General utilities
|
|
36
328
|
detectUbuntuCodename,
|
|
329
|
+
isClose,
|
|
330
|
+
normalizeNodeName,
|
|
331
|
+
|
|
332
|
+
// File system utilities (async)
|
|
333
|
+
ensureDir,
|
|
334
|
+
mkdirs: ensureDir, // Alias for fs-extra compatibility
|
|
335
|
+
exists: pathExists, // Renamed to avoid conflict with deprecated fs.exists
|
|
336
|
+
pathExists,
|
|
337
|
+
emptyDir,
|
|
338
|
+
copy,
|
|
339
|
+
remove,
|
|
340
|
+
writeFile,
|
|
341
|
+
mkdir,
|
|
342
|
+
|
|
343
|
+
// File system utilities (sync)
|
|
344
|
+
ensureDirSync,
|
|
345
|
+
mkdirSync: ensureDirSync, // Alias for fs-extra compatibility
|
|
346
|
+
removeSync,
|
|
347
|
+
readJsonSync,
|
|
348
|
+
|
|
349
|
+
compareVersions,
|
|
37
350
|
};
|