@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/package.json +44 -41
- package/src/extended.spec.ts +0 -214
- package/src/index.spec.ts +0 -454
- package/src/index.ts +0 -790
- package/src/test.mts +0 -5
- package/tsconfig.json +0 -31
- package/tsconfig.tsbuildinfo +0 -1
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;
|