@gjsify/process 0.4.0 → 0.4.3

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.ts DELETED
@@ -1,790 +0,0 @@
1
- // Reference: Node.js lib/internal/process/*.js
2
- // Reimplemented for GJS using GLib (env, paths, platform detection) and EventEmitter
3
-
4
- import { EventEmitter } from '@gjsify/events';
5
- import { ensureMainLoop, quitMainLoop } from '@gjsify/utils';
6
- import { nativeTerminal } from '@gjsify/terminal-native';
7
-
8
- const _encoder = new TextEncoder();
9
-
10
- type ProcessPlatform = NodeJS.Platform;
11
- type ProcessArch = NodeJS.Architecture;
12
-
13
- // GJS-specific global type for accessing GNOME libraries
14
- interface GjsGlobalThis {
15
- imports?: {
16
- gi?: {
17
- GLib?: Record<string, Function>;
18
- [key: string]: unknown;
19
- };
20
- system?: {
21
- programArgs?: string[];
22
- programInvocationName?: string;
23
- exit?: (code: number) => never;
24
- version?: number;
25
- [key: string]: unknown;
26
- };
27
- [key: string]: unknown;
28
- };
29
- }
30
-
31
- function getGjsGlobal(): GjsGlobalThis {
32
- return globalThis as unknown as GjsGlobalThis;
33
- }
34
-
35
- function detectGjsVersion(): string | undefined {
36
- try {
37
- const system = getGjsGlobal().imports?.system;
38
- if (system?.version !== undefined) {
39
- const v = Number(system.version);
40
- const major = Math.floor(v / 10000);
41
- const minor = Math.floor((v % 10000) / 100);
42
- const patch = v % 100;
43
- return `${major}.${minor}.${patch}`;
44
- }
45
- } catch { /* ignore */ }
46
- return undefined;
47
- }
48
-
49
- function detectNodeVersion(): string | undefined {
50
- if (typeof globalThis.process?.versions?.node === 'string') {
51
- return globalThis.process.versions.node;
52
- }
53
- return undefined;
54
- }
55
-
56
- function detectVersionInfo(): { version: string; versions: Record<string, string>; title: string } {
57
- const nodeVersion = detectNodeVersion();
58
-
59
- if (nodeVersion) {
60
- // Running on Node.js — use native values
61
- return {
62
- version: globalThis.process.version,
63
- versions: { ...globalThis.process.versions } as Record<string, string>,
64
- title: globalThis.process?.title || 'node',
65
- };
66
- }
67
-
68
- // Running on GJS
69
- const gjsVersion = detectGjsVersion();
70
- const versions: Record<string, string> = {
71
- node: '20.0.0', // Compatibility version — many npm packages check process.versions.node
72
- };
73
- if (gjsVersion) versions.gjs = gjsVersion;
74
-
75
- return {
76
- version: 'v20.0.0', // Compatibility version for Node.js API level checks
77
- versions,
78
- title: 'gjs',
79
- };
80
- }
81
-
82
- function detectPpid(): number {
83
- if (typeof globalThis.process?.ppid === 'number') {
84
- return globalThis.process.ppid;
85
- }
86
- try {
87
- const GLib = getGjsGlobal().imports?.gi?.GLib;
88
- if (GLib) {
89
- const [, contents] = GLib.file_get_contents('/proc/self/status');
90
- if (contents) {
91
- const str = new TextDecoder().decode(contents);
92
- const match = str.match(/PPid:\s+(\d+)/);
93
- if (match) return parseInt(match[1], 10);
94
- }
95
- }
96
- } catch { /* ignore */ }
97
- return 0;
98
- }
99
-
100
- function detectPlatform(): ProcessPlatform {
101
- try {
102
- const GLib = getGjsGlobal().imports?.gi?.GLib;
103
- if (GLib) {
104
- const osInfo = GLib.get_os_info('ID');
105
- if (osInfo) return 'linux';
106
- }
107
- } catch { /* ignore */ }
108
-
109
- if (typeof getGjsGlobal().imports?.system !== 'undefined') {
110
- return 'linux';
111
- }
112
-
113
- if (typeof globalThis.process?.platform === 'string') {
114
- return globalThis.process.platform as ProcessPlatform;
115
- }
116
-
117
- return 'linux';
118
- }
119
-
120
- function detectArch(): ProcessArch {
121
- if (typeof globalThis.process?.arch === 'string') {
122
- return globalThis.process.arch as ProcessArch;
123
- }
124
- return 'x64';
125
- }
126
-
127
- function getCwd(): string {
128
- // Try GLib first to avoid recursion — under GJS, globalThis.process.cwd
129
- // is our own method which calls getCwd(), causing infinite recursion.
130
- try {
131
- const GLib = getGjsGlobal().imports?.gi?.GLib;
132
- if (GLib?.get_current_dir) return GLib.get_current_dir();
133
- } catch { /* ignore */ }
134
- return '/';
135
- }
136
-
137
- function getEnvProxy(): Record<string, string | undefined> {
138
- // On Node.js, just return process.env
139
- if (typeof globalThis.process?.env === 'object') {
140
- return globalThis.process.env;
141
- }
142
-
143
- // On GJS, create a Proxy that uses GLib.getenv/setenv
144
- try {
145
- const GLib = getGjsGlobal().imports?.gi?.GLib;
146
- if (GLib) {
147
- return new Proxy({} as Record<string, string | undefined>, {
148
- get(_target, prop: string) {
149
- if (typeof prop !== 'string') return undefined;
150
- return GLib.getenv(prop) ?? undefined;
151
- },
152
- set(_target, prop: string, value: string) {
153
- if (typeof prop !== 'string') return false;
154
- GLib.setenv(prop, String(value), true);
155
- return true;
156
- },
157
- deleteProperty(_target, prop: string) {
158
- if (typeof prop !== 'string') return false;
159
- GLib.unsetenv(prop);
160
- return true;
161
- },
162
- has(_target, prop: string) {
163
- if (typeof prop !== 'string') return false;
164
- return GLib.getenv(prop) !== null;
165
- },
166
- ownKeys(_target) {
167
- const envp: string[] = GLib.listenv();
168
- return envp;
169
- },
170
- getOwnPropertyDescriptor(_target, prop: string) {
171
- if (typeof prop !== 'string') return undefined;
172
- const val = GLib.getenv(prop);
173
- if (val === null) return undefined;
174
- return { configurable: true, enumerable: true, writable: true, value: val };
175
- },
176
- });
177
- }
178
- } catch { /* ignore */ }
179
-
180
- return {};
181
- }
182
-
183
- function getArgv(): string[] {
184
- if (typeof globalThis.process?.argv !== 'undefined') {
185
- return globalThis.process.argv;
186
- }
187
- try {
188
- const system = getGjsGlobal().imports?.system;
189
- if (system?.programArgs) {
190
- // Node.js convention: argv = [executable, script, ...userArgs].
191
- // GJS `system.programInvocationName` holds the script path, so prepend
192
- // 'gjs' so consumers like yargs' `hideBin()` (which slices(2)) work.
193
- return ['gjs', system.programInvocationName || '', ...system.programArgs];
194
- }
195
- } catch { /* ignore */ }
196
- return ['gjs'];
197
- }
198
-
199
- function getExecPath(): string {
200
- if (typeof globalThis.process?.execPath === 'string') {
201
- return globalThis.process.execPath;
202
- }
203
- try {
204
- const system = getGjsGlobal().imports?.system;
205
- if (system?.programInvocationName) return system.programInvocationName;
206
- } catch { /* ignore */ }
207
- return '/usr/bin/gjs';
208
- }
209
-
210
- function getPid(): number {
211
- if (typeof globalThis.process?.pid === 'number') {
212
- return globalThis.process.pid;
213
- }
214
- try {
215
- const GLib = getGjsGlobal().imports?.gi?.GLib;
216
- if (GLib) {
217
- // GLib doesn't have a direct getpid, read from /proc/self
218
- const [, contents] = GLib.file_get_contents('/proc/self/stat');
219
- if (contents) {
220
- const str = new TextDecoder().decode(contents);
221
- const pid = parseInt(str, 10);
222
- if (!isNaN(pid)) return pid;
223
- }
224
- }
225
- } catch { /* ignore */ }
226
- return 0;
227
- }
228
-
229
- const startTime = Date.now();
230
-
231
- function getGioNamespace(): any {
232
- const _gi: Record<string, unknown> | undefined = (globalThis as any).imports?.gi;
233
- if (!_gi) return null;
234
- let gio: any = null;
235
- try { gio = (_gi as any)['GioUnix']; } catch { /* try Gio */ }
236
- if (!gio) { try { gio = (_gi as any)['Gio']; } catch { /* absent */ } }
237
- return gio;
238
- }
239
-
240
- class ProcessWriteStream extends EventEmitter {
241
- readonly fd: number;
242
- // Required by Stream.pipe(): without this, pipe skips dest.write() entirely.
243
- writable = true;
244
- private _outGio: any = null;
245
-
246
- constructor(fd: number) {
247
- super();
248
- this.fd = fd;
249
- const gio = getGioNamespace();
250
- if (gio) {
251
- const Cls = gio.UnixOutputStream ?? gio.OutputStream;
252
- if (Cls) { try { this._outGio = Cls.new(this.fd, false); } catch { /* fallback */ } }
253
- }
254
- }
255
-
256
- write(data: string | Uint8Array): boolean {
257
- if (this._outGio) {
258
- try {
259
- const bytes = typeof data === 'string' ? _encoder.encode(data) : data;
260
- this._outGio.write_all(bytes, null);
261
- return true;
262
- } catch { /* fall through to console fallback */ }
263
- }
264
- // Fallback: console adds a trailing newline which breaks terminal UI,
265
- // but is acceptable when Gio streams are unavailable.
266
- if (this.fd === 2) {
267
- console.error(data);
268
- } else {
269
- console.log(data);
270
- }
271
- return true;
272
- }
273
-
274
- get isTTY(): boolean {
275
- if (nativeTerminal) return nativeTerminal.Terminal.is_tty(this.fd);
276
- try {
277
- const GLib = getGjsGlobal().imports?.gi?.GLib;
278
- if (GLib) return !!(GLib as any).log_writer_supports_color(this.fd);
279
- } catch { /* ignore */ }
280
- return false;
281
- }
282
-
283
- get columns(): number {
284
- if (nativeTerminal) {
285
- const [ok, , cols] = nativeTerminal.Terminal.get_size(this.fd);
286
- if (ok && cols > 0) return cols;
287
- }
288
- try {
289
- const GLib = getGjsGlobal().imports?.gi?.GLib;
290
- if (GLib) {
291
- const c = parseInt((GLib as any).getenv('COLUMNS') ?? '0', 10);
292
- if (c > 0) return c;
293
- }
294
- } catch { /* ignore */ }
295
- return 80;
296
- }
297
-
298
- // stdout/stderr must never be closed — the process owns the fds.
299
- // pipe() calls end() when its source emits 'end' (e.g. MuteStream); no-op here.
300
- end(): void {}
301
- destroy(): void {}
302
-
303
- get rows(): number {
304
- if (nativeTerminal) {
305
- const [ok, rows] = nativeTerminal.Terminal.get_size(this.fd);
306
- if (ok && rows > 0) return rows;
307
- }
308
- try {
309
- const GLib = getGjsGlobal().imports?.gi?.GLib;
310
- if (GLib) {
311
- const r = parseInt((GLib as any).getenv('LINES') ?? '0', 10);
312
- if (r > 0) return r;
313
- }
314
- } catch { /* ignore */ }
315
- return 24;
316
- }
317
- }
318
-
319
- class ProcessReadStream extends EventEmitter {
320
- readonly fd: number;
321
- isRaw = false;
322
- // Do NOT expose `readableFlowing` as an instance property: @inquirer/core uses
323
- // `'readableFlowing' in input` to defer startCycle() via setImmediate. In GJS,
324
- // setImmediate fires as a microtask — the property must stay absent so
325
- // @inquirer/core calls startCycle() synchronously on its own schedule.
326
-
327
- private _gio: any = null;
328
- private _stdinGio: any = null;
329
- private _reading = false;
330
- private _flowing = false;
331
- private _sttyCleanupRegistered = false;
332
- private _mainLoopHeld = false;
333
- // True while a read_bytes_async is in-flight. Prevents a second concurrent
334
- // read from starting when pause()+resume() fires between GLib iterations.
335
- private _pendingRead = false;
336
-
337
- constructor(fd: number) {
338
- super();
339
- this.fd = fd;
340
- // GioUnix.InputStream (GJS ≥ 1.88) supersedes Gio.UnixInputStream; fall back to Gio.
341
- this._gio = getGioNamespace();
342
- }
343
-
344
- get isTTY(): boolean {
345
- if (nativeTerminal) return nativeTerminal.Terminal.is_tty(this.fd);
346
- return false;
347
- }
348
-
349
- setRawMode(mode: boolean): this {
350
- if (nativeTerminal) {
351
- const ok = nativeTerminal.Terminal.set_raw_mode(this.fd, mode);
352
- if (ok) {
353
- this.isRaw = mode;
354
- return this;
355
- }
356
- // set_raw_mode returned false — fd may not be a TTY (e.g. piped stdin).
357
- // Fall through to stty fallback.
358
- }
359
- // Fallback: spawn `stty raw -echo` / `stty sane` with stdin inherited so it
360
- // sees the real terminal and the setting persists in the kernel tty driver.
361
- // Only works when fd 0 is actually a TTY.
362
- this._setRawModeViaStty(mode);
363
- this.isRaw = mode;
364
- return this;
365
- }
366
-
367
- private _setRawModeViaStty(mode: boolean): void {
368
- try {
369
- const _gi: any = (globalThis as any).imports?.gi;
370
- const Gio = _gi?.Gio ?? _gi?.['Gio'];
371
- if (!Gio) return;
372
- // G_SUBPROCESS_FLAGS_STDIN_INHERIT = 1 << 1 = 2
373
- // Makes stty inherit our fd 0 (the real TTY) so tcsetattr targets the same tty.
374
- const STDIN_INHERIT = Gio.SubprocessFlags?.STDIN_INHERIT ?? 2;
375
- // Match the termios settings from GjsifyTerminal's set_raw_mode:
376
- // c_lflag &= ~(ICANON | ECHO) → -icanon -echo
377
- // c_iflag &= ~ICRNL → -icrnl
378
- // VMIN=1, VTIME=0 → min 1 time 0
379
- const argv = mode
380
- ? ['stty', '-icanon', '-echo', '-icrnl', 'min', '1', 'time', '0']
381
- : ['stty', 'icanon', 'echo', 'icrnl'];
382
- const launcher = new Gio.SubprocessLauncher({ flags: STDIN_INHERIT });
383
- const proc = launcher.spawnv(argv);
384
- proc.wait(null);
385
-
386
- // Register a one-time exit handler to restore cooked mode when the process
387
- // exits — without this the shell inherits raw mode and becomes unusable.
388
- if (mode && !this._sttyCleanupRegistered) {
389
- this._sttyCleanupRegistered = true;
390
- const proc_ = (globalThis as any).process;
391
- if (proc_?.once && typeof proc_.once === 'function') {
392
- proc_.once('exit', () => this._setRawModeViaStty(false));
393
- }
394
- }
395
- } catch { /* stty not available or not a TTY */ }
396
- }
397
-
398
- setEncoding(_enc: string): this { return this; }
399
-
400
- resume(): this {
401
- this._flowing = true;
402
- if (this._gio && this.fd === 0 && !this._reading) {
403
- this._startReading();
404
- }
405
- return this;
406
- }
407
-
408
- pause(): this {
409
- this._flowing = false;
410
- this._reading = false;
411
- if (this._mainLoopHeld) {
412
- this._mainLoopHeld = false;
413
- // Defer the quit via GLib idle so microtask continuations run first.
414
- // If the next prompt's Interface calls resume() before this idle fires,
415
- // it sets _mainLoopHeld = true again and the quit is skipped.
416
- // This mirrors Node.js libuv unref: pausing stdin allows the event loop
417
- // to drain when no more prompts follow, but not between sequential prompts.
418
- const _gi: any = (globalThis as any).imports?.gi;
419
- const GLib = _gi?.GLib ?? _gi?.['GLib'];
420
- if (GLib?.idle_add) {
421
- GLib.idle_add(300 /* PRIORITY_LOW */, () => {
422
- if (!this._mainLoopHeld) quitMainLoop();
423
- return false; // SOURCE_REMOVE
424
- });
425
- } else {
426
- quitMainLoop();
427
- }
428
- }
429
- return this;
430
- }
431
-
432
- read(): null { return null; }
433
-
434
- private _startReading(): void {
435
- if (!this._gio || this._reading) return;
436
-
437
- // If a read_bytes_async is already in-flight from the previous loop cycle
438
- // (queued before pause() set _reading = false), don't start a second
439
- // concurrent read on the same fd. Just re-enable _reading so the pending
440
- // callback continues the loop when it fires.
441
- if (this._pendingRead) {
442
- this._reading = true;
443
- if (!this._mainLoopHeld) { this._mainLoopHeld = true; ensureMainLoop(); }
444
- return;
445
- }
446
-
447
- this._reading = true;
448
- // Keep the GLib main loop alive while waiting for stdin — same as
449
- // http.Server.listen() does for network sockets. Without this, gjs -m
450
- // exits as soon as module evaluation completes even though read_bytes_async
451
- // is pending, because GJS only stays alive when runAsync() registered a hook.
452
- if (!this._mainLoopHeld) { this._mainLoopHeld = true; ensureMainLoop(); }
453
-
454
- if (!this._stdinGio) {
455
- // GioUnix: class is `InputStream`. Gio: concrete class is `UnixInputStream`;
456
- // `InputStream` is abstract. The ?? chain picks the right one for each namespace.
457
- const Cls = (this._gio as any).UnixInputStream ?? (this._gio as any).InputStream;
458
- if (!Cls) { this._reading = false; return; }
459
- try {
460
- this._stdinGio = Cls.new(this.fd, false);
461
- } catch {
462
- this._reading = false;
463
- return;
464
- }
465
- }
466
-
467
- const loop = () => {
468
- if (!this._reading) { this._pendingRead = false; return; }
469
- this._pendingRead = true;
470
- (this._stdinGio as any).read_bytes_async(
471
- 4096,
472
- 0,
473
- null,
474
- (src: any, res: any) => {
475
- this._pendingRead = false;
476
- try {
477
- const bytes = (src as any).read_bytes_finish(res);
478
- const data: Uint8Array | null = bytes?.get_data?.() ?? null;
479
- if (data && data.byteLength > 0) {
480
- this.emit('data', Buffer.from(data));
481
- } else if (data !== null && data.byteLength === 0) {
482
- this._reading = false;
483
- this.emit('end');
484
- return;
485
- }
486
- } catch {
487
- this._reading = false;
488
- return;
489
- }
490
- if (this._reading) loop();
491
- },
492
- );
493
- };
494
- loop();
495
- }
496
- }
497
-
498
- function getMonotonicTime(): bigint {
499
- try {
500
- const GLib = getGjsGlobal().imports?.gi?.GLib;
501
- if (GLib?.get_monotonic_time) {
502
- // GLib returns microseconds, convert to nanoseconds
503
- return BigInt(GLib.get_monotonic_time()) * 1000n;
504
- }
505
- } catch { /* ignore */ }
506
- if (typeof performance?.now === 'function') {
507
- return BigInt(Math.round(performance.now() * 1e6));
508
- }
509
- return BigInt(Date.now()) * 1000000n;
510
- }
511
-
512
- const hrtimeBase = getMonotonicTime();
513
-
514
- class Process extends EventEmitter {
515
- readonly platform: ProcessPlatform;
516
- readonly arch: ProcessArch;
517
- readonly env: Record<string, string | undefined>;
518
- readonly argv: string[];
519
- readonly argv0: string;
520
- readonly execPath: string;
521
- readonly pid: number;
522
- readonly ppid: number;
523
- readonly version: string;
524
- readonly versions: Record<string, string>;
525
- title: string;
526
- readonly execArgv: string[];
527
- readonly config: Record<string, unknown>;
528
- exitCode: number | undefined;
529
-
530
- constructor() {
531
- super();
532
-
533
- this.platform = detectPlatform();
534
- this.arch = detectArch();
535
- this.env = getEnvProxy();
536
- this.argv = getArgv();
537
- this.argv0 = this.argv[0] || 'gjs';
538
- this.execPath = getExecPath();
539
- this.execArgv = globalThis.process?.execArgv ?? [];
540
- this.config = (globalThis.process?.config as unknown as Record<string, unknown>) ?? { target_defaults: {}, variables: {} };
541
- this.pid = getPid();
542
- this.ppid = detectPpid();
543
- const versionInfo = detectVersionInfo();
544
- this.version = versionInfo.version;
545
- this.versions = versionInfo.versions;
546
- this.title = versionInfo.title;
547
- }
548
-
549
- cwd(): string {
550
- return getCwd();
551
- }
552
-
553
- chdir(directory: string): void {
554
- try {
555
- const GLib = getGjsGlobal().imports?.gi?.GLib;
556
- if (GLib?.chdir) {
557
- // Check if directory exists first
558
- if (!GLib.file_test(directory, 16 /* G_FILE_TEST_EXISTS */)) {
559
- const err = new Error(`ENOENT: no such file or directory, chdir '${directory}'`) as NodeJS.ErrnoException;
560
- err.code = 'ENOENT';
561
- err.syscall = 'chdir';
562
- err.path = directory;
563
- throw err;
564
- }
565
- GLib.chdir(directory);
566
- return;
567
- }
568
- } catch (e) {
569
- // Re-throw our own ENOENT errors
570
- if (e && typeof e === 'object' && (e as NodeJS.ErrnoException).code === 'ENOENT') throw e;
571
- }
572
-
573
- const nativeProcess = globalThis.process;
574
- if (nativeProcess && nativeProcess !== (this as any) && typeof nativeProcess.chdir === 'function') {
575
- nativeProcess.chdir(directory);
576
- return;
577
- }
578
-
579
- throw new Error('process.chdir() is not supported in this environment');
580
- }
581
-
582
- kill(pid: number, signal?: string | number): boolean {
583
- const nativeProcess = globalThis.process;
584
- if (nativeProcess && nativeProcess !== (this as any) && typeof nativeProcess.kill === 'function') {
585
- return nativeProcess.kill(pid, signal);
586
- }
587
- // On GJS, use GLib.spawn to send signals via /bin/kill
588
- try {
589
- const GLib = getGjsGlobal().imports?.gi?.GLib;
590
- if (GLib) {
591
- const sig = typeof signal === 'number' ? String(signal) : (signal || 'SIGTERM');
592
- const sigArg = sig.startsWith('SIG') ? `-${sig.slice(3)}` : `-${sig}`;
593
- GLib.spawn_command_line_sync(`kill ${sigArg} ${pid}`);
594
- return true;
595
- }
596
- } catch { /* ignore */ }
597
- throw new Error('process.kill() is not supported in this environment');
598
- }
599
-
600
- exit(code?: number): never {
601
- this.exitCode = code ?? this.exitCode ?? 0;
602
- this.emit('exit', this.exitCode);
603
-
604
- const gjsImports = getGjsGlobal().imports;
605
- const GLib = gjsImports?.gi?.GLib as Record<string, unknown> | undefined;
606
- const system = gjsImports?.system;
607
- const idleAdd = GLib?.idle_add as ((priority: number, fn: () => boolean) => number) | undefined;
608
- const sourceRemove = GLib?.SOURCE_REMOVE as boolean | undefined;
609
- const priorityDefault = GLib?.PRIORITY_DEFAULT as number | undefined;
610
-
611
- // GJS path: schedule the exit via GLib.idle_add so the syscall fires
612
- // from a fresh main-loop iteration. Calling system.exit() directly from
613
- // a microtask continuation (e.g. yargs's parseAsync resolution) while a
614
- // GLib.MainLoop is parked deadlocks the process — the loop never returns
615
- // control to top-level, the syscall never runs.
616
- //
617
- // ensureMainLoop() is non-blocking on first call: it registers the loop
618
- // via setMainLoopHook so GJS keeps running it after JS module evaluation
619
- // completes. Without this, our idle source would sit queued in the default
620
- // main context but never dispatch (gjs -m exits as soon as JS eval ends),
621
- // and the process would exit with code 0 regardless of `code`.
622
- //
623
- // The idle source must be added BEFORE quitting the loop. Quit lives in
624
- // the idle callback so the source is guaranteed to dispatch before the
625
- // loop drains.
626
- if (system?.exit && idleAdd && typeof priorityDefault === 'number' && typeof sourceRemove === 'boolean') {
627
- const exitCodeNow = this.exitCode;
628
- ensureMainLoop();
629
- idleAdd(priorityDefault, () => {
630
- quitMainLoop();
631
- system.exit!(exitCodeNow);
632
- return sourceRemove;
633
- });
634
- // Park the JS continuation forever — the idle source will exit the
635
- // process before this Promise can resolve. Cast satisfies the `never`
636
- // return type without taking down the synchronous control flow.
637
- return new Promise<never>(() => { /* never */ }) as unknown as never;
638
- }
639
-
640
- // GJS without GLib (extremely unlikely) — direct syscall, may deadlock
641
- // a parked loop but at least exits when no loop is running.
642
- try {
643
- if (system?.exit) system.exit(this.exitCode);
644
- } catch { /* ignore */ }
645
-
646
- const nativeProcess = globalThis.process;
647
- if (nativeProcess && nativeProcess !== (this as any) && typeof nativeProcess.exit === 'function') {
648
- nativeProcess.exit(this.exitCode);
649
- }
650
-
651
- // Fallback
652
- throw new Error(`process.exit(${this.exitCode})`);
653
- }
654
-
655
- nextTick(callback: Function, ...args: unknown[]): void {
656
- // GTK interleaving is handled at the stream level (@gjsify/utils nextTick → GLib.idle_add).
657
- if (typeof queueMicrotask === 'function') {
658
- queueMicrotask(() => callback(...args));
659
- } else {
660
- Promise.resolve().then(() => callback(...args));
661
- }
662
- }
663
-
664
- hrtime(time?: [number, number]): [number, number] {
665
- const now = getMonotonicTime() - hrtimeBase;
666
- const seconds = Number(now / 1000000000n);
667
- const nanoseconds = Number(now % 1000000000n);
668
-
669
- if (time) {
670
- let diffSec = seconds - time[0];
671
- let diffNano = nanoseconds - time[1];
672
- if (diffNano < 0) {
673
- diffSec--;
674
- diffNano += 1e9;
675
- }
676
- return [diffSec, diffNano];
677
- }
678
-
679
- return [seconds, nanoseconds];
680
- }
681
-
682
- uptime(): number {
683
- return (Date.now() - startTime) / 1000;
684
- }
685
-
686
- memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; external: number; arrayBuffers: number } {
687
- try {
688
- const GLib = getGjsGlobal().imports?.gi?.GLib;
689
- if (GLib) {
690
- const [, contents] = GLib.file_get_contents('/proc/self/status');
691
- if (contents) {
692
- const str = new TextDecoder().decode(contents);
693
- const vmRSS = str.match(/VmRSS:\s+(\d+)/);
694
- const rss = vmRSS ? parseInt(vmRSS[1], 10) * 1024 : 0;
695
- return { rss, heapTotal: rss, heapUsed: rss, external: 0, arrayBuffers: 0 };
696
- }
697
- }
698
- } catch { /* ignore */ }
699
-
700
- // Delegate to native process.memoryUsage on Node.js, but NOT when
701
- // globalThis.process is this same instance (would infinite-recurse on GJS).
702
- const nativeProcess = globalThis.process;
703
- if (nativeProcess && nativeProcess !== (this as any) && typeof nativeProcess.memoryUsage === 'function') {
704
- return nativeProcess.memoryUsage();
705
- }
706
-
707
- return { rss: 0, heapTotal: 0, heapUsed: 0, external: 0, arrayBuffers: 0 };
708
- }
709
-
710
- cpuUsage(previousValue?: { user: number; system: number }): { user: number; system: number } {
711
- // Delegate to native process.cpuUsage on Node.js, but NOT when
712
- // globalThis.process is this same instance (would infinite-recurse on GJS).
713
- const nativeProcess = globalThis.process;
714
- if (nativeProcess && nativeProcess !== (this as any) && typeof nativeProcess.cpuUsage === 'function') {
715
- return nativeProcess.cpuUsage(previousValue);
716
- }
717
- return { user: 0, system: 0 };
718
- }
719
-
720
- // Note: Cannot check globalThis.process.stdout here — on GJS globalThis.process
721
- // IS this instance, so that would cause infinite recursion.
722
- readonly stdout = new ProcessWriteStream(1);
723
- readonly stderr = new ProcessWriteStream(2);
724
- readonly stdin = new ProcessReadStream(0);
725
-
726
- abort(): void {
727
- this.exit(1);
728
- }
729
-
730
- // no-op stubs for compatibility
731
- umask(mask?: number): number { return 0o22; }
732
- emitWarning(warning: string | Error, name?: string): void {
733
- if (typeof warning === 'string') {
734
- console.warn(`(${name || 'Warning'}): ${warning}`);
735
- } else {
736
- console.warn(warning.message);
737
- }
738
- }
739
- }
740
-
741
- (Process.prototype.hrtime as unknown as Record<string, () => bigint>).bigint = function(): bigint {
742
- return getMonotonicTime() - hrtimeBase;
743
- };
744
-
745
- const process = new Process();
746
-
747
- // Wire SIGWINCH → process.stdout.emit('resize') when native terminal is available.
748
- // Runs only on GJS (nativeTerminal is null on Node.js).
749
- if (nativeTerminal) {
750
- try {
751
- const watcher = new nativeTerminal.ResizeWatcher();
752
- watcher.connect('resized', (_obj: any, _rows: number, _cols: number) => {
753
- // Re-emit on both streams; consumers (chalk, inquirer) listen on stdout.
754
- process.stdout.emit('resize');
755
- process.stderr.emit('resize');
756
- });
757
- watcher.start();
758
- } catch { /* ignore if ResizeWatcher instantiation fails */ }
759
- }
760
-
761
- // Re-export everything
762
- export const platform = process.platform;
763
- export const arch = process.arch;
764
- export const env = process.env;
765
- export const argv = process.argv;
766
- export const argv0 = process.argv0;
767
- export const execPath = process.execPath;
768
- export const pid = process.pid;
769
- export const ppid = process.ppid;
770
- export const version = process.version;
771
- export const versions = process.versions;
772
- export const cwd = process.cwd.bind(process);
773
- export const chdir = process.chdir.bind(process);
774
- export const exit = process.exit.bind(process);
775
- export const nextTick = process.nextTick.bind(process);
776
- export const hrtime = process.hrtime.bind(process);
777
- export const uptime = process.uptime.bind(process);
778
- export const memoryUsage = process.memoryUsage.bind(process);
779
- export const cpuUsage = process.cpuUsage.bind(process);
780
- export const kill = process.kill.bind(process);
781
- export const abort = process.abort.bind(process);
782
- export const umask = process.umask.bind(process);
783
- export const emitWarning = process.emitWarning.bind(process);
784
- export const execArgv = process.execArgv;
785
- export const config = process.config;
786
- export const stdout = process.stdout;
787
- export const stderr = process.stderr;
788
- export const stdin = process.stdin;
789
-
790
- export default process;