@gjsify/os 0.0.4 → 0.1.1

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/src/index.spec.ts CHANGED
@@ -1,9 +1,393 @@
1
1
  import { describe, it, expect } from '@gjsify/unit';
2
+ import * as os from 'node:os';
3
+
4
+ // Ported from refs/node/test/parallel/test-os.js
5
+ // Original: MIT license, Node.js contributors
2
6
 
3
7
  export default async () => {
4
- await describe('true', async () => {
5
- await it('should be true', async () => {
6
- expect(true).toBeTruthy();
8
+ // ==================== basic return types ====================
9
+
10
+ await describe('os: basic return types', async () => {
11
+ await it('homedir() should return a non-empty string', async () => {
12
+ const home = os.homedir();
13
+ expect(typeof home).toBe('string');
14
+ expect(home.length > 0).toBeTruthy();
15
+ });
16
+
17
+ await it('homedir() should return an absolute path', async () => {
18
+ const home = os.homedir();
19
+ expect(home.startsWith('/')).toBeTruthy();
20
+ });
21
+
22
+ await it('hostname() should return a non-empty string', async () => {
23
+ const hostname = os.hostname();
24
+ expect(typeof hostname).toBe('string');
25
+ expect(hostname.length > 0).toBeTruthy();
26
+ });
27
+
28
+ await it('hostname() should not contain spaces', async () => {
29
+ const hostname = os.hostname();
30
+ expect(hostname.includes(' ')).toBe(false);
31
+ });
32
+
33
+ await it('tmpdir() should return a non-empty string', async () => {
34
+ const tmp = os.tmpdir();
35
+ expect(typeof tmp).toBe('string');
36
+ expect(tmp.length > 0).toBeTruthy();
37
+ });
38
+
39
+ await it('tmpdir() should return an absolute path', async () => {
40
+ const tmp = os.tmpdir();
41
+ expect(tmp.startsWith('/')).toBeTruthy();
42
+ });
43
+
44
+ await it('type() should return a non-empty string', async () => {
45
+ const type = os.type();
46
+ expect(typeof type).toBe('string');
47
+ expect(type.length > 0).toBeTruthy();
48
+ });
49
+
50
+ await it('type() should return Linux on Linux', async () => {
51
+ const type = os.type();
52
+ expect(type).toBe('Linux');
53
+ });
54
+
55
+ await it('release() should return a non-empty string', async () => {
56
+ const release = os.release();
57
+ expect(typeof release).toBe('string');
58
+ expect(release.length > 0).toBeTruthy();
59
+ });
60
+
61
+ await it('platform() should return a non-empty string', async () => {
62
+ const platform = os.platform();
63
+ expect(typeof platform).toBe('string');
64
+ expect(platform.length > 0).toBeTruthy();
65
+ });
66
+
67
+ await it('platform() should return linux on Linux', async () => {
68
+ expect(os.platform()).toBe('linux');
69
+ });
70
+
71
+ await it('arch() should return a non-empty string', async () => {
72
+ const arch = os.arch();
73
+ expect(typeof arch).toBe('string');
74
+ expect(arch.length > 0).toBeTruthy();
75
+ });
76
+
77
+ await it('arch() should be one of known architectures', async () => {
78
+ const known = ['x64', 'arm64', 'arm', 'ia32', 'ppc64', 's390x', 'mips', 'mipsel', 'riscv64', 'loong64'];
79
+ expect(known.includes(os.arch())).toBeTruthy();
80
+ });
81
+ });
82
+
83
+ // ==================== endianness ====================
84
+
85
+ await describe('os: endianness', async () => {
86
+ await it('should return BE or LE', async () => {
87
+ const endianness = os.endianness();
88
+ expect(typeof endianness).toBe('string');
89
+ expect(endianness === 'BE' || endianness === 'LE').toBeTruthy();
90
+ });
91
+ });
92
+
93
+ // ==================== EOL ====================
94
+
95
+ await describe('os: EOL', async () => {
96
+ await it('should be \\n on non-Windows', async () => {
97
+ expect(os.EOL).toBe('\n');
98
+ });
99
+ });
100
+
101
+ // ==================== cpus ====================
102
+
103
+ await describe('os: cpus', async () => {
104
+ await it('should return a non-empty array', async () => {
105
+ const cpus = os.cpus();
106
+ expect(Array.isArray(cpus)).toBeTruthy();
107
+ expect(cpus.length > 0).toBeTruthy();
108
+ });
109
+
110
+ await it('each cpu should have model, speed, and times', async () => {
111
+ const cpus = os.cpus();
112
+ for (const cpu of cpus) {
113
+ expect(typeof cpu.model).toBe('string');
114
+ expect(typeof cpu.speed).toBe('number');
115
+ expect(typeof cpu.times).toBe('object');
116
+ expect(typeof cpu.times.user).toBe('number');
117
+ expect(typeof cpu.times.nice).toBe('number');
118
+ expect(typeof cpu.times.sys).toBe('number');
119
+ expect(typeof cpu.times.idle).toBe('number');
120
+ expect(typeof cpu.times.irq).toBe('number');
121
+ }
122
+ });
123
+
124
+ await it('cpus() length should match availableParallelism()', async () => {
125
+ const cpus = os.cpus();
126
+ expect(cpus.length).toBe(os.availableParallelism());
127
+ });
128
+
129
+ await it('cpu times should have non-zero values', async () => {
130
+ const cpus = os.cpus();
131
+ // At least one CPU should have non-zero idle time
132
+ const hasNonZeroIdle = cpus.some(cpu => cpu.times.idle > 0);
133
+ expect(hasNonZeroIdle).toBeTruthy();
134
+ });
135
+ });
136
+
137
+ // ==================== memory ====================
138
+
139
+ await describe('os: memory', async () => {
140
+ await it('freemem() should return a positive number', async () => {
141
+ const free = os.freemem();
142
+ expect(typeof free).toBe('number');
143
+ expect(free > 0).toBeTruthy();
144
+ });
145
+
146
+ await it('totalmem() should return a positive number', async () => {
147
+ const total = os.totalmem();
148
+ expect(typeof total).toBe('number');
149
+ expect(total > 0).toBeTruthy();
150
+ });
151
+
152
+ await it('totalmem() should return at least 1 MB', async () => {
153
+ expect(os.totalmem() >= 1024 * 1024).toBeTruthy();
154
+ });
155
+
156
+ await it('freemem() should return at least 1 MB', async () => {
157
+ expect(os.freemem() >= 1024 * 1024).toBeTruthy();
158
+ });
159
+
160
+ await it('freemem should be less than totalmem', async () => {
161
+ expect(os.freemem() <= os.totalmem()).toBeTruthy();
162
+ });
163
+ });
164
+
165
+ // ==================== loadavg ====================
166
+
167
+ await describe('os: loadavg', async () => {
168
+ await it('should return an array with 3 elements', async () => {
169
+ const avg = os.loadavg();
170
+ expect(Array.isArray(avg)).toBeTruthy();
171
+ expect(avg.length).toBe(3);
172
+ });
173
+
174
+ await it('each element should be a number', async () => {
175
+ const avg = os.loadavg();
176
+ for (const v of avg) {
177
+ expect(typeof v).toBe('number');
178
+ }
179
+ });
180
+
181
+ await it('all values should be >= 0', async () => {
182
+ const avg = os.loadavg();
183
+ for (const v of avg) {
184
+ expect(v >= 0).toBeTruthy();
185
+ }
186
+ });
187
+ });
188
+
189
+ // ==================== uptime ====================
190
+
191
+ await describe('os: uptime', async () => {
192
+ await it('should return a positive number', async () => {
193
+ const uptime = os.uptime();
194
+ expect(typeof uptime).toBe('number');
195
+ expect(uptime > 0).toBeTruthy();
196
+ });
197
+
198
+ await it('should be a reasonable value (less than 10 years in seconds)', async () => {
199
+ const tenYearsInSeconds = 10 * 365 * 24 * 60 * 60;
200
+ expect(os.uptime() < tenYearsInSeconds).toBeTruthy();
201
+ });
202
+ });
203
+
204
+ // ==================== version ====================
205
+
206
+ await describe('os: version', async () => {
207
+ await it('should return a non-empty string', async () => {
208
+ const version = os.version();
209
+ expect(typeof version).toBe('string');
210
+ expect(version.length > 0).toBeTruthy();
211
+ });
212
+
213
+ await it('should contain a version-like pattern', async () => {
214
+ const version = os.version();
215
+ // Linux version strings typically start with # or contain version info
216
+ expect(version.length > 2).toBeTruthy();
217
+ });
218
+ });
219
+
220
+ // ==================== machine ====================
221
+
222
+ await describe('os: machine', async () => {
223
+ await it('should return a non-empty string', async () => {
224
+ const machine = os.machine();
225
+ expect(typeof machine).toBe('string');
226
+ expect(machine.length > 0).toBeTruthy();
227
+ });
228
+
229
+ await it('should be one of known machine types', async () => {
230
+ const known = ['x86_64', 'aarch64', 'arm', 'armv7l', 'i686', 'ppc64', 'ppc64le', 's390x', 'mips', 'mipsel', 'mips64el', 'riscv64', 'loongarch64'];
231
+ expect(known.includes(os.machine())).toBeTruthy();
232
+ });
233
+ });
234
+
235
+ // ==================== devNull ====================
236
+
237
+ await describe('os: devNull', async () => {
238
+ await it('should be /dev/null on Linux', async () => {
239
+ expect(os.devNull).toBe('/dev/null');
240
+ });
241
+ });
242
+
243
+ // ==================== availableParallelism ====================
244
+
245
+ await describe('os: availableParallelism', async () => {
246
+ await it('should return a positive number', async () => {
247
+ const n = os.availableParallelism();
248
+ expect(typeof n).toBe('number');
249
+ expect(n > 0).toBeTruthy();
250
+ });
251
+ });
252
+
253
+ // ==================== userInfo ====================
254
+
255
+ await describe('os: userInfo', async () => {
256
+ await it('should return an object', async () => {
257
+ const info = os.userInfo();
258
+ expect(typeof info).toBe('object');
259
+ });
260
+
261
+ await it('should have uid as number', async () => {
262
+ expect(typeof os.userInfo().uid).toBe('number');
263
+ });
264
+
265
+ await it('should have gid as number', async () => {
266
+ expect(typeof os.userInfo().gid).toBe('number');
267
+ });
268
+
269
+ await it('should have username as string', async () => {
270
+ expect(typeof os.userInfo().username).toBe('string');
271
+ expect(os.userInfo().username.length > 0).toBeTruthy();
272
+ });
273
+
274
+ await it('should have homedir as string', async () => {
275
+ expect(typeof os.userInfo().homedir).toBe('string');
276
+ expect(os.userInfo().homedir.length > 0).toBeTruthy();
277
+ });
278
+
279
+ await it('should have shell as string', async () => {
280
+ expect(typeof os.userInfo().shell).toBe('string');
281
+ });
282
+
283
+ await it('uid should be >= 0', async () => {
284
+ expect(os.userInfo().uid >= 0).toBeTruthy();
285
+ });
286
+
287
+ await it('gid should be >= 0', async () => {
288
+ expect(os.userInfo().gid >= 0).toBeTruthy();
289
+ });
290
+
291
+ await it('homedir should be an absolute path', async () => {
292
+ expect(os.userInfo().homedir.startsWith('/')).toBeTruthy();
293
+ });
294
+
295
+ await it('username should match current user', async () => {
296
+ const info = os.userInfo();
297
+ // username should not contain slashes or spaces
298
+ expect(info.username.includes('/')).toBe(false);
299
+ expect(info.username.includes(' ')).toBe(false);
300
+ });
301
+ });
302
+
303
+ // ==================== networkInterfaces ====================
304
+
305
+ await describe('os: networkInterfaces', async () => {
306
+ await it('should return an object', async () => {
307
+ const ifaces = os.networkInterfaces();
308
+ expect(typeof ifaces).toBe('object');
309
+ });
310
+
311
+ await it('should have at least one interface', async () => {
312
+ const ifaces = os.networkInterfaces();
313
+ expect(Object.keys(ifaces).length > 0).toBeTruthy();
314
+ });
315
+
316
+ await it('each interface entry should have required fields', async () => {
317
+ const ifaces = os.networkInterfaces();
318
+ for (const [, entries] of Object.entries(ifaces)) {
319
+ for (const entry of entries as any[]) {
320
+ expect(typeof entry.address).toBe('string');
321
+ expect(typeof entry.netmask).toBe('string');
322
+ expect(entry.family === 'IPv4' || entry.family === 'IPv6' ||
323
+ entry.family === 4 || entry.family === 6).toBeTruthy();
324
+ expect(typeof entry.mac).toBe('string');
325
+ expect(typeof entry.internal).toBe('boolean');
326
+ }
327
+ }
328
+ });
329
+ });
330
+
331
+ // ==================== constants ====================
332
+
333
+ await describe('os: constants', async () => {
334
+ await it('should have signals object', async () => {
335
+ expect(typeof os.constants.signals).toBe('object');
336
+ });
337
+
338
+ await it('should have errno object', async () => {
339
+ expect(typeof os.constants.errno).toBe('object');
340
+ });
341
+
342
+ await it('signals.SIGTERM should be a number', async () => {
343
+ expect(typeof os.constants.signals.SIGTERM).toBe('number');
344
+ });
345
+
346
+ await it('signals.SIGKILL should be a number', async () => {
347
+ expect(typeof os.constants.signals.SIGKILL).toBe('number');
348
+ });
349
+
350
+ await it('signals.SIGINT should be a number', async () => {
351
+ expect(typeof os.constants.signals.SIGINT).toBe('number');
352
+ });
353
+
354
+ await it('signals.SIGTERM should be 15', async () => {
355
+ expect(os.constants.signals.SIGTERM).toBe(15);
356
+ });
357
+
358
+ await it('signals.SIGKILL should be 9', async () => {
359
+ expect(os.constants.signals.SIGKILL).toBe(9);
360
+ });
361
+
362
+ await it('signals.SIGINT should be 2', async () => {
363
+ expect(os.constants.signals.SIGINT).toBe(2);
364
+ });
365
+
366
+ await it('errno.ENOENT should be a number', async () => {
367
+ expect(typeof os.constants.errno.ENOENT).toBe('number');
368
+ });
369
+
370
+ await it('errno.EACCES should be a number', async () => {
371
+ expect(typeof os.constants.errno.EACCES).toBe('number');
372
+ });
373
+
374
+ await it('errno.EEXIST should be a number', async () => {
375
+ expect(typeof os.constants.errno.EEXIST).toBe('number');
376
+ });
377
+
378
+ await it('errno.ENOENT should be a positive integer', async () => {
379
+ expect(os.constants.errno.ENOENT > 0).toBeTruthy();
380
+ expect(Number.isInteger(os.constants.errno.ENOENT)).toBeTruthy();
381
+ });
382
+
383
+ await it('errno.EACCES should be a positive integer', async () => {
384
+ expect(os.constants.errno.EACCES > 0).toBeTruthy();
385
+ expect(Number.isInteger(os.constants.errno.EACCES)).toBeTruthy();
386
+ });
387
+
388
+ await it('errno.EEXIST should be a positive integer', async () => {
389
+ expect(os.constants.errno.EEXIST > 0).toBeTruthy();
390
+ expect(Number.isInteger(os.constants.errno.EEXIST)).toBeTruthy();
7
391
  });
8
392
  });
9
- }
393
+ };
package/src/index.ts CHANGED
@@ -1,4 +1,30 @@
1
- import { cli, getPathSeparator, getOs } from '@gjsify/utils';
1
+ // Reference: Node.js lib/os.js
2
+ // Reimplemented for GJS using GLib (get_home_dir, get_host_name, etc.)
3
+
4
+ import { cli, getPathSeparator } from '@gjsify/utils';
5
+ import Gio from '@girs/gio-2.0';
6
+
7
+ /** Cached OS detection result */
8
+ let _os = '';
9
+
10
+ /** Get the OS name (darwin, linux, win32) via uname */
11
+ const getOs = () => {
12
+ if (_os) return _os;
13
+ const os = cli('uname -o').trim();
14
+ if (/\bDarwin\b/i.test(os)) { _os = 'darwin'; return _os; }
15
+ if (/\bLinux\b/i.test(os)) { _os = 'linux'; return _os; }
16
+ _os = 'win32';
17
+ return _os;
18
+ };
19
+
20
+ /** Cached PID */
21
+ let _pid = 0;
22
+
23
+ /** Get the current process ID via Gio.Credentials */
24
+ const getPid = () => {
25
+ if (!_pid) _pid = new Gio.Credentials().get_unix_pid();
26
+ return _pid;
27
+ };
2
28
 
3
29
  export { constants }
4
30
 
@@ -10,28 +36,82 @@ import constants from './constants.js';
10
36
 
11
37
  export const EOL = getPathSeparator() === '/' ? '\n' : '\r\n';
12
38
 
13
- // Ported to packages/deno/std/node/os.ts
39
+ export const devNull = getPathSeparator() === '/' ? '/dev/null' : '\\\\.\\nul';
40
+
14
41
  export const homedir = () => GLib.get_home_dir();
15
42
 
16
- // Ported to deno runtime
17
43
  export const hostname = () => GLib.get_host_name();
18
44
 
19
- // Ported to deno runtime
20
- export const release = () => cli('uname -r');
45
+ export const release = () => cli('uname -r').trim();
21
46
 
22
- // Ported to packages/deno/std/node/os.ts
23
47
  export const tmpdir = () => GLib.get_tmp_dir();
24
48
 
25
- // Existing replacement in packages/deno/std/node/os.ts
26
- export const type = () => cli('uname');
49
+ export const type = () => cli('uname').trim();
27
50
 
28
- // Ported to packages/deno/std/node/os.ts
29
- export const userInfo = () => ({
30
- uid: 1000,
31
- gid: 100,
32
- username: GLib.get_user_name(),
33
- homedir: GLib.get_home_dir()
34
- });
51
+ export const platform = () => cli('uname -s').trim().toLowerCase() as NodeJS.Platform;
52
+
53
+ export const arch = () => {
54
+ const machine = cli('uname -m').trim();
55
+ // Map uname -m to Node.js arch names
56
+ if (machine === 'x86_64' || machine === 'amd64') return 'x64';
57
+ if (machine === 'aarch64' || machine === 'arm64') return 'arm64';
58
+ if (machine === 'i686' || machine === 'i386') return 'ia32';
59
+ if (machine.startsWith('arm')) return 'arm';
60
+ return machine;
61
+ };
62
+
63
+ export const machine = () => cli('uname -m').trim();
64
+
65
+ export const version = () => cli('uname -v').trim();
66
+
67
+ export const uptime = () => {
68
+ const _os = getOs();
69
+ switch (_os) {
70
+ case "darwin":
71
+ return darwin.uptime();
72
+ case "linux":
73
+ return linux.uptime();
74
+ default:
75
+ return 0;
76
+ }
77
+ };
78
+
79
+ export const totalmem = () => {
80
+ const _os = getOs();
81
+ switch (_os) {
82
+ case "darwin":
83
+ return darwin.totalmem();
84
+ case "linux":
85
+ return linux.totalmem();
86
+ default:
87
+ return 0;
88
+ }
89
+ };
90
+
91
+ export const availableParallelism = () => {
92
+ const c = cpus();
93
+ return c ? c.length : 1;
94
+ };
95
+
96
+ export const userInfo = () => {
97
+ let uid = 1000;
98
+ let gid = 100;
99
+ let shell = '';
100
+ try {
101
+ uid = parseInt(cli('id -u'), 10);
102
+ gid = parseInt(cli('id -g'), 10);
103
+ shell = GLib.getenv('SHELL') || '';
104
+ } catch {
105
+ // fallback to defaults
106
+ }
107
+ return {
108
+ uid,
109
+ gid,
110
+ username: GLib.get_user_name(),
111
+ homedir: GLib.get_home_dir(),
112
+ shell,
113
+ };
114
+ };
35
115
 
36
116
  // Ported to packages/deno/std/node/os.ts
37
117
  export const cpus = () => {
@@ -101,3 +181,78 @@ export const networkInterfaces = () => {
101
181
  break;
102
182
  }
103
183
  };
184
+
185
+ /**
186
+ * Get process scheduling priority.
187
+ * Uses `ps -o ni=` to read the nice value for a given process.
188
+ * pid 0 (or omitted) means the current process.
189
+ */
190
+ export const getPriority = (pid?: number): number => {
191
+ const targetPid = (pid === undefined || pid === 0) ? getPid() : pid;
192
+ try {
193
+ const nice = cli(`ps -o ni= -p ${targetPid}`).trim();
194
+ const val = parseInt(nice, 10);
195
+ if (!isNaN(val)) return val;
196
+ } catch {
197
+ // fallback
198
+ }
199
+ return 0;
200
+ };
201
+
202
+ /**
203
+ * Set process scheduling priority.
204
+ * Uses `renice` command. Requires appropriate permissions for other processes.
205
+ */
206
+ export const setPriority = (pidOrPriority: number, priority?: number): void => {
207
+ let pid: number;
208
+ let prio: number;
209
+ if (priority === undefined) {
210
+ prio = pidOrPriority;
211
+ pid = 0;
212
+ } else {
213
+ pid = pidOrPriority;
214
+ prio = priority;
215
+ }
216
+
217
+ if (typeof pid !== 'number' || !Number.isInteger(pid)) {
218
+ throw new TypeError('The "pid" argument must be an integer');
219
+ }
220
+ if (typeof prio !== 'number' || !Number.isInteger(prio) || prio < -20 || prio > 19) {
221
+ throw new RangeError('The "priority" argument must be an integer between -20 and 19');
222
+ }
223
+
224
+ try {
225
+ const actualPid = pid === 0 ? getPid() : pid;
226
+ cli(`renice -n ${prio} -p ${actualPid}`);
227
+ } catch (err) {
228
+ const error = new Error(`A system error occurred: priority could not be set`);
229
+ (error as any).code = 'ERR_SYSTEM_ERROR';
230
+ throw error;
231
+ }
232
+ };
233
+
234
+ export default {
235
+ EOL,
236
+ arch,
237
+ availableParallelism,
238
+ constants,
239
+ cpus,
240
+ devNull,
241
+ endianness,
242
+ freemem,
243
+ getPriority,
244
+ homedir,
245
+ hostname,
246
+ loadavg,
247
+ machine,
248
+ networkInterfaces,
249
+ platform,
250
+ release,
251
+ setPriority,
252
+ tmpdir,
253
+ totalmem,
254
+ type,
255
+ uptime,
256
+ userInfo,
257
+ version,
258
+ };