• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

RobotWebTools / rclnodejs / 18931458458

30 Oct 2025 06:01AM UTC coverage: 81.767% (-1.4%) from 83.209%
18931458458

push

github

minggangw
Pump to 1.6.0 (#1316)

858 of 1164 branches covered (73.71%)

Branch coverage included in aggregate %.

2057 of 2401 relevant lines covered (85.67%)

466.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.21
/lib/utils.js
1
// Copyright (c) 2025, The Robot Web Tools Contributors
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
const fs = require('fs');
27✔
16
const fsPromises = require('fs/promises');
27✔
17
const path = require('path');
27✔
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 {
2,329✔
27
    await fsPromises.mkdir(dirPath, { recursive: true });
2,329✔
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 {
93✔
41
    fs.mkdirSync(dirPath, { recursive: true });
93✔
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 {
26✔
56
    await fsPromises.access(filePath);
26✔
57
    return true;
23✔
58
  } catch {
59
    return false;
3✔
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 = {}) {
3✔
95
  const opts = {
3✔
96
    recursive: true,
97
    force: options.overwrite !== false,
98
    ...options,
99
  };
100
  await fsPromises.cp(src, dest, opts);
3✔
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 = {}) {
23✔
111
  const content = fs.readFileSync(filePath, options.encoding || 'utf8');
23✔
112
  return JSON.parse(content);
23✔
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 {
1✔
123
    await fsPromises.rm(filePath, { recursive: true, force: true });
1✔
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 {
248✔
137
    fs.rmSync(filePath, { recursive: true, force: true });
248✔
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 = {}) {
2,325✔
153
  await fsPromises.writeFile(filePath, data, options);
2,325✔
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 = {}) {
3✔
164
  await fsPromises.mkdir(dirPath, options);
3✔
165
}
166

167
/**
168
 * Detect Ubuntu codename from /etc/os-release
169
 * @returns {string|null} Ubuntu codename (e.g., 'noble', 'jammy') or null if not detectable
170
 */
171
function detectUbuntuCodename() {
172
  if (process.platform !== 'linux') {
52!
173
    return null;
×
174
  }
175

176
  try {
52✔
177
    const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
52✔
178
    const match = osRelease.match(/^VERSION_CODENAME=(.*)$/m);
52✔
179
    return match ? match[1].trim() : null;
52!
180
  } catch {
181
    return null;
×
182
  }
183
}
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) {
25!
211
  // Handle exact equality
212
  if (a === b) {
25✔
213
    return true;
6✔
214
  }
215

216
  // Handle non-finite numbers
217
  if (!Number.isFinite(a) || !Number.isFinite(b)) {
19!
218
    return false;
×
219
  }
220

221
  const absDiff = Math.abs(a - b);
19✔
222

223
  // Check absolute tolerance first (optimization)
224
  if (atol >= absDiff) {
19!
225
    return true;
×
226
  }
227

228
  // Check relative tolerance
229
  const relativeScaler = Math.max(Math.abs(a), Math.abs(b));
19✔
230

231
  // Handle division by zero when both values are zero or very close to zero
232
  if (relativeScaler === 0) {
19!
233
    return true; // Both are zero, already handled by absolute tolerance
×
234
  }
235

236
  const relativeDiff = absDiff / relativeScaler;
19✔
237

238
  return rtol >= relativeDiff;
19✔
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));
150✔
261
  const v2Parts = version2.split('.').map((part) => parseInt(part, 10));
177✔
262

263
  // Pad arrays to same length with zeros
264
  const maxLength = Math.max(v1Parts.length, v2Parts.length);
50✔
265
  while (v1Parts.length < maxLength) v1Parts.push(0);
50✔
266
  while (v2Parts.length < maxLength) v2Parts.push(0);
50✔
267

268
  // Compare each part
269
  let cmp = 0;
50✔
270
  for (let i = 0; i < maxLength; i++) {
50✔
271
    if (v1Parts[i] > v2Parts[i]) {
96✔
272
      cmp = 1;
27✔
273
      break;
27✔
274
    } else if (v1Parts[i] < v2Parts[i]) {
69!
275
      cmp = -1;
×
276
      break;
×
277
    }
278
  }
279

280
  // Apply operator
281
  switch (operator) {
50!
282
    case '<':
283
      return cmp < 0;
23✔
284
    case '<=':
285
      return cmp <= 0;
×
286
    case '>':
287
      return cmp > 0;
×
288
    case '>=':
289
      return cmp >= 0;
27✔
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

301
module.exports = {
27✔
302
  // General utilities
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,
324
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc