rclnodejs 1.5.2 → 1.6.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 +20 -3
- package/lib/message_serialization.js +171 -0
- package/lib/node.js +15 -0
- package/lib/parameter.js +4 -10
- package/lib/subscription.js +16 -1
- package/lib/utils.js +287 -0
- 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/index.d.ts +17 -0
- package/types/node.d.ts +16 -1
- 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/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const DistroUtils = require('./lib/distro.js');
|
|
|
18
18
|
const RMWUtils = require('./lib/rmw.js');
|
|
19
19
|
const { Clock, ROSClock } = require('./lib/clock.js');
|
|
20
20
|
const ClockType = require('./lib/clock_type.js');
|
|
21
|
-
const compareVersions = require('
|
|
21
|
+
const { compareVersions } = require('./lib/utils.js');
|
|
22
22
|
const Context = require('./lib/context.js');
|
|
23
23
|
const debug = require('debug')('rclnodejs');
|
|
24
24
|
const Duration = require('./lib/duration.js');
|
|
@@ -47,6 +47,7 @@ const ActionUuid = require('./lib/action/uuid.js');
|
|
|
47
47
|
const ClientGoalHandle = require('./lib/action/client_goal_handle.js');
|
|
48
48
|
const { CancelResponse, GoalResponse } = require('./lib/action/response.js');
|
|
49
49
|
const ServerGoalHandle = require('./lib/action/server_goal_handle.js');
|
|
50
|
+
const { toJSONSafe, toJSONString } = require('./lib/message_serialization.js');
|
|
50
51
|
const {
|
|
51
52
|
getActionClientNamesAndTypesByNode,
|
|
52
53
|
getActionServerNamesAndTypesByNode,
|
|
@@ -364,8 +365,7 @@ let rcl = {
|
|
|
364
365
|
|
|
365
366
|
const version = await getCurrentGeneratorVersion();
|
|
366
367
|
const forced =
|
|
367
|
-
version === null ||
|
|
368
|
-
compareVersions.compare(version, generator.version(), '<');
|
|
368
|
+
version === null || compareVersions(version, generator.version(), '<');
|
|
369
369
|
if (forced) {
|
|
370
370
|
debug(
|
|
371
371
|
'The generator will begin to create JavaScript code from ROS IDL files...'
|
|
@@ -539,6 +539,23 @@ let rcl = {
|
|
|
539
539
|
* @return {Promise<{process: ChildProcess}>} A Promise that resolves with the process.
|
|
540
540
|
*/
|
|
541
541
|
ros2Launch: ros2Launch,
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Convert a message object to be JSON-safe by converting TypedArrays to regular arrays
|
|
545
|
+
* and handling BigInt, Infinity, NaN, etc. for JSON serialization.
|
|
546
|
+
* @param {*} obj - The message object to convert
|
|
547
|
+
* @returns {*} A JSON-safe version of the object
|
|
548
|
+
*/
|
|
549
|
+
toJSONSafe: toJSONSafe,
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Convert a message object to a JSON string with proper handling of TypedArrays,
|
|
553
|
+
* BigInt, and other non-JSON-serializable values.
|
|
554
|
+
* @param {*} obj - The message object to convert
|
|
555
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
556
|
+
* @returns {string} The JSON string representation
|
|
557
|
+
*/
|
|
558
|
+
toJSONString: toJSONString,
|
|
542
559
|
};
|
|
543
560
|
|
|
544
561
|
const _sigHandler = () => {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if a value is a TypedArray
|
|
19
|
+
* @param {*} value - The value to check
|
|
20
|
+
* @returns {boolean} True if the value is a TypedArray
|
|
21
|
+
*/
|
|
22
|
+
function isTypedArray(value) {
|
|
23
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a value needs JSON conversion (BigInt, functions, etc.)
|
|
28
|
+
* @param {*} value - The value to check
|
|
29
|
+
* @returns {boolean} True if the value needs special JSON handling
|
|
30
|
+
*/
|
|
31
|
+
function needsJSONConversion(value) {
|
|
32
|
+
return (
|
|
33
|
+
typeof value === 'bigint' ||
|
|
34
|
+
typeof value === 'function' ||
|
|
35
|
+
typeof value === 'undefined' ||
|
|
36
|
+
value === Infinity ||
|
|
37
|
+
value === -Infinity ||
|
|
38
|
+
(typeof value === 'number' && isNaN(value))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert a message to plain arrays (TypedArray -> regular Array)
|
|
44
|
+
* @param {*} obj - The object to convert
|
|
45
|
+
* @returns {*} The converted object with plain arrays
|
|
46
|
+
*/
|
|
47
|
+
function toPlainArrays(obj) {
|
|
48
|
+
if (obj === null || obj === undefined) {
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isTypedArray(obj)) {
|
|
53
|
+
return Array.from(obj);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(obj)) {
|
|
57
|
+
return obj.map((item) => toPlainArrays(item));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
61
|
+
const result = {};
|
|
62
|
+
for (const key in obj) {
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
64
|
+
result[key] = toPlainArrays(obj[key]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return obj;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Convert a message to be fully JSON-safe
|
|
75
|
+
* @param {*} obj - The object to convert
|
|
76
|
+
* @returns {*} The JSON-safe converted object
|
|
77
|
+
*/
|
|
78
|
+
function toJSONSafe(obj) {
|
|
79
|
+
if (obj === null || obj === undefined) {
|
|
80
|
+
return obj;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isTypedArray(obj)) {
|
|
84
|
+
return Array.from(obj).map((item) => toJSONSafe(item));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (needsJSONConversion(obj)) {
|
|
88
|
+
if (typeof obj === 'bigint') {
|
|
89
|
+
// Convert BigInt to string with 'n' suffix to indicate it was a BigInt
|
|
90
|
+
return obj.toString() + 'n';
|
|
91
|
+
}
|
|
92
|
+
if (obj === Infinity) return 'Infinity';
|
|
93
|
+
if (obj === -Infinity) return '-Infinity';
|
|
94
|
+
if (typeof obj === 'number' && isNaN(obj)) return 'NaN';
|
|
95
|
+
if (typeof obj === 'undefined') return null;
|
|
96
|
+
if (typeof obj === 'function') return '[Function]';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(obj)) {
|
|
100
|
+
return obj.map((item) => toJSONSafe(item));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
104
|
+
const result = {};
|
|
105
|
+
for (const key in obj) {
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
107
|
+
result[key] = toJSONSafe(obj[key]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return obj;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Convert a message to a JSON string
|
|
118
|
+
* @param {*} obj - The object to convert
|
|
119
|
+
* @param {number} [space] - Space parameter for JSON.stringify formatting
|
|
120
|
+
* @returns {string} The JSON string representation
|
|
121
|
+
*/
|
|
122
|
+
function toJSONString(obj, space) {
|
|
123
|
+
const jsonSafeObj = toJSONSafe(obj);
|
|
124
|
+
return JSON.stringify(jsonSafeObj, null, space);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Apply serialization mode conversion to a message object
|
|
129
|
+
* @param {*} message - The message object to convert
|
|
130
|
+
* @param {string} serializationMode - The serialization mode ('default', 'plain', 'json')
|
|
131
|
+
* @returns {*} The converted message
|
|
132
|
+
*/
|
|
133
|
+
function applySerializationMode(message, serializationMode) {
|
|
134
|
+
switch (serializationMode) {
|
|
135
|
+
case 'default':
|
|
136
|
+
// No conversion needed - use native rclnodejs behavior
|
|
137
|
+
return message;
|
|
138
|
+
|
|
139
|
+
case 'plain':
|
|
140
|
+
// Convert TypedArrays to regular arrays
|
|
141
|
+
return toPlainArrays(message);
|
|
142
|
+
|
|
143
|
+
case 'json':
|
|
144
|
+
// Convert to fully JSON-safe format
|
|
145
|
+
return toJSONSafe(message);
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
`Invalid serializationMode: ${serializationMode}. Valid modes are: 'default', 'plain', 'json'`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validate serialization mode
|
|
156
|
+
* @param {string} mode - The serialization mode to validate
|
|
157
|
+
* @returns {boolean} True if valid
|
|
158
|
+
*/
|
|
159
|
+
function isValidSerializationMode(mode) {
|
|
160
|
+
return ['default', 'plain', 'json'].includes(mode);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
isTypedArray,
|
|
165
|
+
needsJSONConversion,
|
|
166
|
+
toPlainArrays,
|
|
167
|
+
toJSONSafe,
|
|
168
|
+
toJSONString,
|
|
169
|
+
applySerializationMode,
|
|
170
|
+
isValidSerializationMode,
|
|
171
|
+
};
|
package/lib/node.js
CHANGED
|
@@ -31,6 +31,7 @@ const {
|
|
|
31
31
|
Parameter,
|
|
32
32
|
ParameterDescriptor,
|
|
33
33
|
} = require('./parameter.js');
|
|
34
|
+
const { isValidSerializationMode } = require('./message_serialization.js');
|
|
34
35
|
const ParameterService = require('./parameter_service.js');
|
|
35
36
|
const Publisher = require('./publisher.js');
|
|
36
37
|
const QoS = require('./qos.js');
|
|
@@ -532,6 +533,14 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
532
533
|
options = Object.assign(options, { isRaw: false });
|
|
533
534
|
}
|
|
534
535
|
|
|
536
|
+
if (options.serializationMode === undefined) {
|
|
537
|
+
options = Object.assign(options, { serializationMode: 'default' });
|
|
538
|
+
} else if (!isValidSerializationMode(options.serializationMode)) {
|
|
539
|
+
throw new TypeError(
|
|
540
|
+
`Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
535
544
|
return options;
|
|
536
545
|
}
|
|
537
546
|
|
|
@@ -666,6 +675,10 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
666
675
|
* @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
|
|
667
676
|
* @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
|
|
668
677
|
* @param {boolean} options.isRaw - The topic is serialized when true, default: false.
|
|
678
|
+
* @param {string} [options.serializationMode='default'] - Controls message serialization format:
|
|
679
|
+
* 'default': Use native rclnodejs behavior (respects enableTypedArray setting),
|
|
680
|
+
* 'plain': Convert TypedArrays to regular arrays,
|
|
681
|
+
* 'json': Fully JSON-safe (handles TypedArrays, BigInt, etc.).
|
|
669
682
|
* @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
|
|
670
683
|
* Confirm that your RMW supports content-filtered topics before use.
|
|
671
684
|
* @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
|
|
@@ -1920,6 +1933,7 @@ class Node extends rclnodejs.ShadowNode {
|
|
|
1920
1933
|
* isRaw: false,
|
|
1921
1934
|
* qos: QoS.profileDefault,
|
|
1922
1935
|
* contentFilter: undefined,
|
|
1936
|
+
* serializationMode: 'default',
|
|
1923
1937
|
* }
|
|
1924
1938
|
*/
|
|
1925
1939
|
Node.getDefaultOptions = function () {
|
|
@@ -1928,6 +1942,7 @@ Node.getDefaultOptions = function () {
|
|
|
1928
1942
|
isRaw: false,
|
|
1929
1943
|
qos: QoS.profileDefault,
|
|
1930
1944
|
contentFilter: undefined,
|
|
1945
|
+
serializationMode: 'default',
|
|
1931
1946
|
};
|
|
1932
1947
|
};
|
|
1933
1948
|
|
package/lib/parameter.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const { isClose } = require('./utils.js');
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* The plus/minus tolerance for determining number equivalence.
|
|
@@ -623,8 +623,8 @@ class FloatingPointRange extends Range {
|
|
|
623
623
|
const max = Math.max(this.fromValue, this.toValue);
|
|
624
624
|
|
|
625
625
|
if (
|
|
626
|
-
|
|
627
|
-
|
|
626
|
+
isClose(value, min, this.tolerance) ||
|
|
627
|
+
isClose(value, max, this.tolerance)
|
|
628
628
|
) {
|
|
629
629
|
return true;
|
|
630
630
|
}
|
|
@@ -633,13 +633,7 @@ class FloatingPointRange extends Range {
|
|
|
633
633
|
}
|
|
634
634
|
if (this.step != 0.0) {
|
|
635
635
|
const distanceInSteps = Math.round((value - min) / this.step);
|
|
636
|
-
if (
|
|
637
|
-
!IsClose.isClose(
|
|
638
|
-
min + distanceInSteps * this.step,
|
|
639
|
-
value,
|
|
640
|
-
this.tolerance
|
|
641
|
-
)
|
|
642
|
-
) {
|
|
636
|
+
if (!isClose(min + distanceInSteps * this.step, value, this.tolerance)) {
|
|
643
637
|
return false;
|
|
644
638
|
}
|
|
645
639
|
}
|
package/lib/subscription.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const rclnodejs = require('./native_loader.js');
|
|
18
18
|
const Entity = require('./entity.js');
|
|
19
|
+
const { applySerializationMode } = require('./message_serialization.js');
|
|
19
20
|
const debug = require('debug')('rclnodejs:subscription');
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -42,6 +43,7 @@ class Subscription extends Entity {
|
|
|
42
43
|
this._topic = topic;
|
|
43
44
|
this._callback = callback;
|
|
44
45
|
this._isRaw = options.isRaw || false;
|
|
46
|
+
this._serializationMode = options.serializationMode || 'default';
|
|
45
47
|
this._node = node;
|
|
46
48
|
|
|
47
49
|
if (node && eventCallbacks) {
|
|
@@ -55,7 +57,13 @@ class Subscription extends Entity {
|
|
|
55
57
|
if (this._isRaw) {
|
|
56
58
|
this._callback(msg);
|
|
57
59
|
} else {
|
|
58
|
-
|
|
60
|
+
let message = msg.toPlainObject(this.typedArrayEnabled);
|
|
61
|
+
|
|
62
|
+
if (this._serializationMode !== 'default') {
|
|
63
|
+
message = applySerializationMode(message, this._serializationMode);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._callback(message);
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|
|
@@ -109,6 +117,13 @@ class Subscription extends Entity {
|
|
|
109
117
|
return this._isRaw;
|
|
110
118
|
}
|
|
111
119
|
|
|
120
|
+
/**
|
|
121
|
+
* @type {string}
|
|
122
|
+
*/
|
|
123
|
+
get serializationMode() {
|
|
124
|
+
return this._serializationMode;
|
|
125
|
+
}
|
|
126
|
+
|
|
112
127
|
/**
|
|
113
128
|
* Test if the RMW supports content-filtered topics and that this subscription
|
|
114
129
|
* has an active wellformed content-filter.
|
package/lib/utils.js
CHANGED
|
@@ -13,6 +13,156 @@
|
|
|
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
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Ensure directory exists, create recursively if needed (async)
|
|
21
|
+
* Replaces: fse.ensureDir() / fse.mkdirs()
|
|
22
|
+
* @param {string} dirPath - Path to directory
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async function ensureDir(dirPath) {
|
|
26
|
+
try {
|
|
27
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
// Ignore if directory already exists
|
|
30
|
+
if (err.code !== 'EEXIST') throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensure directory exists, create recursively if needed (sync)
|
|
36
|
+
* Replaces: fse.mkdirSync()
|
|
37
|
+
* @param {string} dirPath - Path to directory
|
|
38
|
+
*/
|
|
39
|
+
function ensureDirSync(dirPath) {
|
|
40
|
+
try {
|
|
41
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
// Ignore if directory already exists
|
|
44
|
+
if (err.code !== 'EEXIST') throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if path exists (async)
|
|
50
|
+
* Replaces: fse.exists()
|
|
51
|
+
* @param {string} filePath - Path to check
|
|
52
|
+
* @returns {Promise<boolean>}
|
|
53
|
+
*/
|
|
54
|
+
async function pathExists(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
await fsPromises.access(filePath);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Empty a directory (remove all contents but keep the directory)
|
|
65
|
+
* Replaces: fse.emptyDir()
|
|
66
|
+
* @param {string} dirPath - Path to directory
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
*/
|
|
69
|
+
async function emptyDir(dirPath) {
|
|
70
|
+
try {
|
|
71
|
+
const files = await fsPromises.readdir(dirPath);
|
|
72
|
+
await Promise.all(
|
|
73
|
+
files.map((file) =>
|
|
74
|
+
fsPromises.rm(path.join(dirPath, file), {
|
|
75
|
+
recursive: true,
|
|
76
|
+
force: true,
|
|
77
|
+
})
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// Ignore if directory doesn't exist
|
|
82
|
+
if (err.code !== 'ENOENT') throw err;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Copy file or directory recursively
|
|
88
|
+
* Replaces: fse.copy()
|
|
89
|
+
* @param {string} src - Source path
|
|
90
|
+
* @param {string} dest - Destination path
|
|
91
|
+
* @param {object} options - Copy options
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function copy(src, dest, options = {}) {
|
|
95
|
+
const opts = {
|
|
96
|
+
recursive: true,
|
|
97
|
+
force: options.overwrite !== false,
|
|
98
|
+
...options,
|
|
99
|
+
};
|
|
100
|
+
await fsPromises.cp(src, dest, opts);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read and parse JSON file synchronously
|
|
105
|
+
* Replaces: fse.readJsonSync()
|
|
106
|
+
* @param {string} filePath - Path to JSON file
|
|
107
|
+
* @param {object} options - Read options
|
|
108
|
+
* @returns {any} Parsed JSON data
|
|
109
|
+
*/
|
|
110
|
+
function readJsonSync(filePath, options = {}) {
|
|
111
|
+
const content = fs.readFileSync(filePath, options.encoding || 'utf8');
|
|
112
|
+
return JSON.parse(content);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove file or directory (async)
|
|
117
|
+
* Replaces: fse.remove()
|
|
118
|
+
* @param {string} filePath - Path to remove
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
async function remove(filePath) {
|
|
122
|
+
try {
|
|
123
|
+
await fsPromises.rm(filePath, { recursive: true, force: true });
|
|
124
|
+
} catch (err) {
|
|
125
|
+
// Ignore if path doesn't exist
|
|
126
|
+
if (err.code !== 'ENOENT') throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Remove file or directory (sync)
|
|
132
|
+
* Replaces: fse.removeSync()
|
|
133
|
+
* @param {string} filePath - Path to remove
|
|
134
|
+
*/
|
|
135
|
+
function removeSync(filePath) {
|
|
136
|
+
try {
|
|
137
|
+
fs.rmSync(filePath, { recursive: true, force: true });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// Ignore if path doesn't exist
|
|
140
|
+
if (err.code !== 'ENOENT') throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Write file with content (async)
|
|
146
|
+
* Replaces: fse.writeFile()
|
|
147
|
+
* @param {string} filePath - Path to file
|
|
148
|
+
* @param {string|Buffer} data - Content to write
|
|
149
|
+
* @param {object} options - Write options
|
|
150
|
+
* @returns {Promise<void>}
|
|
151
|
+
*/
|
|
152
|
+
async function writeFile(filePath, data, options = {}) {
|
|
153
|
+
await fsPromises.writeFile(filePath, data, options);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create directory (async)
|
|
158
|
+
* Replaces: fse.mkdir()
|
|
159
|
+
* @param {string} dirPath - Path to directory
|
|
160
|
+
* @param {object} options - mkdir options
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
*/
|
|
163
|
+
async function mkdir(dirPath, options = {}) {
|
|
164
|
+
await fsPromises.mkdir(dirPath, options);
|
|
165
|
+
}
|
|
16
166
|
|
|
17
167
|
/**
|
|
18
168
|
* Detect Ubuntu codename from /etc/os-release
|
|
@@ -32,6 +182,143 @@ function detectUbuntuCodename() {
|
|
|
32
182
|
}
|
|
33
183
|
}
|
|
34
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Check if two numbers are equal within a given tolerance.
|
|
187
|
+
*
|
|
188
|
+
* This function compares two numbers using both relative and absolute tolerance,
|
|
189
|
+
* matching the behavior of the 'is-close' npm package.
|
|
190
|
+
*
|
|
191
|
+
* The comparison uses the formula:
|
|
192
|
+
* abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)
|
|
193
|
+
*
|
|
194
|
+
* Implementation checks:
|
|
195
|
+
* 1. Absolute tolerance: abs(a - b) <= atol
|
|
196
|
+
* 2. Relative tolerance: abs(a - b) / max(abs(a), abs(b)) <= rtol
|
|
197
|
+
*
|
|
198
|
+
* @param {number} a - The first number to compare
|
|
199
|
+
* @param {number} b - The second number to compare
|
|
200
|
+
* @param {number} [rtol=1e-9] - The relative tolerance parameter (default: 1e-9)
|
|
201
|
+
* @param {number} [atol=0.0] - The absolute tolerance parameter (default: 0.0)
|
|
202
|
+
* @returns {boolean} True if the numbers are close within the tolerance
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* isClose(1.0, 1.0) // true - exact equality
|
|
206
|
+
* isClose(1.0, 1.1, 0.01) // false - relative diff: 0.1/1.1 ≈ 0.091 > 0.01
|
|
207
|
+
* isClose(10, 10.00001, 1e-6) // true - relative diff: 0.00001/10 = 1e-6 <= 1e-6
|
|
208
|
+
* isClose(0, 0.05, 0, 0.1) // true - absolute diff: 0.05 <= 0.1 (atol)
|
|
209
|
+
*/
|
|
210
|
+
function isClose(a, b, rtol = 1e-9, atol = 0.0) {
|
|
211
|
+
// Handle exact equality
|
|
212
|
+
if (a === b) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle non-finite numbers
|
|
217
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const absDiff = Math.abs(a - b);
|
|
222
|
+
|
|
223
|
+
// Check absolute tolerance first (optimization)
|
|
224
|
+
if (atol >= absDiff) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check relative tolerance
|
|
229
|
+
const relativeScaler = Math.max(Math.abs(a), Math.abs(b));
|
|
230
|
+
|
|
231
|
+
// Handle division by zero when both values are zero or very close to zero
|
|
232
|
+
if (relativeScaler === 0) {
|
|
233
|
+
return true; // Both are zero, already handled by absolute tolerance
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const relativeDiff = absDiff / relativeScaler;
|
|
237
|
+
|
|
238
|
+
return rtol >= relativeDiff;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Compare two semantic version strings.
|
|
243
|
+
*
|
|
244
|
+
* Supports version strings in the format: x.y.z or x.y.z.w
|
|
245
|
+
* where x, y, z, w are integers.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} version1 - First version string (e.g., '1.2.3')
|
|
248
|
+
* @param {string} version2 - Second version string (e.g., '1.2.4')
|
|
249
|
+
* @param {string} operator - Comparison operator: '<', '<=', '>', '>=', '==', '!='
|
|
250
|
+
* @returns {boolean} Result of the comparison
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* compareVersions('1.2.3', '1.2.4', '<') // true
|
|
254
|
+
* compareVersions('2.0.0', '1.9.9', '>') // true
|
|
255
|
+
* compareVersions('1.2.3', '1.2.3', '==') // true
|
|
256
|
+
* compareVersions('1.2.3', '1.2.3', '>=') // true
|
|
257
|
+
*/
|
|
258
|
+
function compareVersions(version1, version2, operator) {
|
|
259
|
+
// Parse version strings into arrays of integers
|
|
260
|
+
const v1Parts = version1.split('.').map((part) => parseInt(part, 10));
|
|
261
|
+
const v2Parts = version2.split('.').map((part) => parseInt(part, 10));
|
|
262
|
+
|
|
263
|
+
// Pad arrays to same length with zeros
|
|
264
|
+
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
|
265
|
+
while (v1Parts.length < maxLength) v1Parts.push(0);
|
|
266
|
+
while (v2Parts.length < maxLength) v2Parts.push(0);
|
|
267
|
+
|
|
268
|
+
// Compare each part
|
|
269
|
+
let cmp = 0;
|
|
270
|
+
for (let i = 0; i < maxLength; i++) {
|
|
271
|
+
if (v1Parts[i] > v2Parts[i]) {
|
|
272
|
+
cmp = 1;
|
|
273
|
+
break;
|
|
274
|
+
} else if (v1Parts[i] < v2Parts[i]) {
|
|
275
|
+
cmp = -1;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Apply operator
|
|
281
|
+
switch (operator) {
|
|
282
|
+
case '<':
|
|
283
|
+
return cmp < 0;
|
|
284
|
+
case '<=':
|
|
285
|
+
return cmp <= 0;
|
|
286
|
+
case '>':
|
|
287
|
+
return cmp > 0;
|
|
288
|
+
case '>=':
|
|
289
|
+
return cmp >= 0;
|
|
290
|
+
case '==':
|
|
291
|
+
case '===':
|
|
292
|
+
return cmp === 0;
|
|
293
|
+
case '!=':
|
|
294
|
+
case '!==':
|
|
295
|
+
return cmp !== 0;
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Invalid operator: ${operator}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
35
301
|
module.exports = {
|
|
302
|
+
// General utilities
|
|
36
303
|
detectUbuntuCodename,
|
|
304
|
+
isClose,
|
|
305
|
+
|
|
306
|
+
// File system utilities (async)
|
|
307
|
+
ensureDir,
|
|
308
|
+
mkdirs: ensureDir, // Alias for fs-extra compatibility
|
|
309
|
+
exists: pathExists, // Renamed to avoid conflict with deprecated fs.exists
|
|
310
|
+
pathExists,
|
|
311
|
+
emptyDir,
|
|
312
|
+
copy,
|
|
313
|
+
remove,
|
|
314
|
+
writeFile,
|
|
315
|
+
mkdir,
|
|
316
|
+
|
|
317
|
+
// File system utilities (sync)
|
|
318
|
+
ensureDirSync,
|
|
319
|
+
mkdirSync: ensureDirSync, // Alias for fs-extra compatibility
|
|
320
|
+
removeSync,
|
|
321
|
+
readJsonSync,
|
|
322
|
+
|
|
323
|
+
compareVersions,
|
|
37
324
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rclnodejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "ROS2.0 JavaScript client with Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"rebuild:dev": "npm run clean && node-gyp -j 16 rebuild --debug",
|
|
20
20
|
"generate-messages": "node scripts/generate_messages.js",
|
|
21
21
|
"generate-messages-idl": "node scripts/generate_messages.js --idl",
|
|
22
|
-
"generate-messages:dev": "node scripts/generate_messages.js --debug
|
|
22
|
+
"generate-messages:dev": "node scripts/generate_messages.js --debug",
|
|
23
23
|
"generate-tsd-messages": "node scripts/generate_tsd.js",
|
|
24
24
|
"clean": "node-gyp clean && npx rimraf ./generated",
|
|
25
25
|
"install": "node scripts/install.js",
|
|
@@ -76,14 +76,9 @@
|
|
|
76
76
|
"@rclnodejs/ref-array-di": "^1.2.2",
|
|
77
77
|
"@rclnodejs/ref-struct-di": "^1.1.1",
|
|
78
78
|
"bindings": "^1.5.0",
|
|
79
|
-
"compare-versions": "^6.1.1",
|
|
80
79
|
"debug": "^4.4.0",
|
|
81
|
-
"dot": "^1.1.3",
|
|
82
|
-
"fs-extra": "^11.2.0",
|
|
83
|
-
"is-close": "^1.3.3",
|
|
84
80
|
"json-bigint": "^1.0.0",
|
|
85
81
|
"node-addon-api": "^8.3.1",
|
|
86
|
-
"terser": "^5.39.0",
|
|
87
82
|
"walk": "^2.3.15"
|
|
88
83
|
},
|
|
89
84
|
"husky": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|