@gjsify/diagnostics_channel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/lib/esm/index.js +263 -0
- package/lib/types/index.d.ts +116 -0
- package/package.json +38 -0
- package/src/index.spec.ts +927 -0
- package/src/index.ts +339 -0
- package/src/test.mts +6 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @gjsify/diagnostics_channel
|
|
2
|
+
|
|
3
|
+
GJS implementation of the Node.js `diagnostics_channel` module. Provides Channel and TracingChannel.
|
|
4
|
+
|
|
5
|
+
Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @gjsify/diagnostics_channel
|
|
11
|
+
# or
|
|
12
|
+
yarn add @gjsify/diagnostics_channel
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { channel } from '@gjsify/diagnostics_channel';
|
|
19
|
+
|
|
20
|
+
const ch = channel('my-channel');
|
|
21
|
+
ch.subscribe((message) => {
|
|
22
|
+
console.log('Received:', message);
|
|
23
|
+
});
|
|
24
|
+
ch.publish({ data: 'hello' });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## License
|
|
28
|
+
|
|
29
|
+
MIT
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const channels = /* @__PURE__ */ new Map();
|
|
2
|
+
class Channel {
|
|
3
|
+
name;
|
|
4
|
+
_subscribers = [];
|
|
5
|
+
constructor(name) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
}
|
|
8
|
+
/** Whether this channel has active subscribers. */
|
|
9
|
+
get hasSubscribers() {
|
|
10
|
+
return this._subscribers.length > 0;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Publish a message to all subscribers.
|
|
14
|
+
* Subscriber errors are caught and re-thrown asynchronously so that
|
|
15
|
+
* remaining subscribers still run (matches Node.js behavior).
|
|
16
|
+
*/
|
|
17
|
+
publish(message) {
|
|
18
|
+
if (this._subscribers.length === 0) return;
|
|
19
|
+
const subscribers = this._subscribers;
|
|
20
|
+
for (let i = 0; i < subscribers.length; i++) {
|
|
21
|
+
try {
|
|
22
|
+
subscribers[i](message, this.name);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
Promise.resolve().then(() => {
|
|
25
|
+
throw err;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Subscribe to this channel. */
|
|
31
|
+
subscribe(onMessage) {
|
|
32
|
+
if (typeof onMessage !== "function") {
|
|
33
|
+
throw new TypeError('The "subscription" argument must be of type function');
|
|
34
|
+
}
|
|
35
|
+
this._subscribers = [...this._subscribers, onMessage];
|
|
36
|
+
}
|
|
37
|
+
/** Unsubscribe from this channel. Returns true if the subscriber was found. */
|
|
38
|
+
unsubscribe(onMessage) {
|
|
39
|
+
const index = this._subscribers.indexOf(onMessage);
|
|
40
|
+
if (index === -1) return false;
|
|
41
|
+
this._subscribers = [
|
|
42
|
+
...this._subscribers.slice(0, index),
|
|
43
|
+
...this._subscribers.slice(index + 1)
|
|
44
|
+
];
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Run stores and publish (simplified — no AsyncLocalStorage store support yet).
|
|
49
|
+
* Publishes the data and calls fn with the given thisArg and args.
|
|
50
|
+
*/
|
|
51
|
+
runStores(data, fn, thisArg, ...args) {
|
|
52
|
+
this.publish(data);
|
|
53
|
+
return fn.apply(thisArg, args);
|
|
54
|
+
}
|
|
55
|
+
/** Bind a store to this channel (stub — AsyncLocalStorage integration). */
|
|
56
|
+
bindStore(_store, _transform) {
|
|
57
|
+
}
|
|
58
|
+
/** Unbind a store from this channel (stub). */
|
|
59
|
+
unbindStore(_store) {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function channel(name) {
|
|
63
|
+
if (typeof name !== "string" && typeof name !== "symbol") {
|
|
64
|
+
throw new TypeError('The "channel" argument must be of type string or symbol');
|
|
65
|
+
}
|
|
66
|
+
let ch = channels.get(name);
|
|
67
|
+
if (!ch) {
|
|
68
|
+
ch = new Channel(name);
|
|
69
|
+
channels.set(name, ch);
|
|
70
|
+
}
|
|
71
|
+
return ch;
|
|
72
|
+
}
|
|
73
|
+
function hasSubscribers(name) {
|
|
74
|
+
const ch = channels.get(name);
|
|
75
|
+
return ch ? ch.hasSubscribers : false;
|
|
76
|
+
}
|
|
77
|
+
function subscribe(name, onMessage) {
|
|
78
|
+
channel(name).subscribe(onMessage);
|
|
79
|
+
}
|
|
80
|
+
function unsubscribe(name, onMessage) {
|
|
81
|
+
return channel(name).unsubscribe(onMessage);
|
|
82
|
+
}
|
|
83
|
+
class TracingChannel {
|
|
84
|
+
start;
|
|
85
|
+
end;
|
|
86
|
+
asyncStart;
|
|
87
|
+
asyncEnd;
|
|
88
|
+
error;
|
|
89
|
+
constructor(nameOrChannels) {
|
|
90
|
+
if (typeof nameOrChannels === "string") {
|
|
91
|
+
const name = nameOrChannels;
|
|
92
|
+
this.start = channel(`tracing:${name}:start`);
|
|
93
|
+
this.end = channel(`tracing:${name}:end`);
|
|
94
|
+
this.asyncStart = channel(`tracing:${name}:asyncStart`);
|
|
95
|
+
this.asyncEnd = channel(`tracing:${name}:asyncEnd`);
|
|
96
|
+
this.error = channel(`tracing:${name}:error`);
|
|
97
|
+
} else if (typeof nameOrChannels === "object" && nameOrChannels !== null) {
|
|
98
|
+
const traceEvents = ["start", "end", "asyncStart", "asyncEnd", "error"];
|
|
99
|
+
for (const eventName of traceEvents) {
|
|
100
|
+
const ch = nameOrChannels[eventName];
|
|
101
|
+
if (!(ch instanceof Channel)) {
|
|
102
|
+
throw new TypeError(
|
|
103
|
+
`The "nameOrChannels.${eventName}" property must be an instance of Channel`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.start = nameOrChannels.start;
|
|
108
|
+
this.end = nameOrChannels.end;
|
|
109
|
+
this.asyncStart = nameOrChannels.asyncStart;
|
|
110
|
+
this.asyncEnd = nameOrChannels.asyncEnd;
|
|
111
|
+
this.error = nameOrChannels.error;
|
|
112
|
+
} else {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
'The "nameOrChannels" argument must be of type string or an instance of TracingChannel or Object'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Whether any of the tracing sub-channels has subscribers. */
|
|
119
|
+
get hasSubscribers() {
|
|
120
|
+
return this.start.hasSubscribers || this.end.hasSubscribers || this.asyncStart.hasSubscribers || this.asyncEnd.hasSubscribers || this.error.hasSubscribers;
|
|
121
|
+
}
|
|
122
|
+
/** Subscribe to all tracing channels. */
|
|
123
|
+
subscribe(handlers) {
|
|
124
|
+
if (handlers.start) this.start.subscribe(handlers.start);
|
|
125
|
+
if (handlers.end) this.end.subscribe(handlers.end);
|
|
126
|
+
if (handlers.asyncStart) this.asyncStart.subscribe(handlers.asyncStart);
|
|
127
|
+
if (handlers.asyncEnd) this.asyncEnd.subscribe(handlers.asyncEnd);
|
|
128
|
+
if (handlers.error) this.error.subscribe(handlers.error);
|
|
129
|
+
}
|
|
130
|
+
/** Unsubscribe from all tracing channels. Returns true if all were found. */
|
|
131
|
+
unsubscribe(handlers) {
|
|
132
|
+
let done = true;
|
|
133
|
+
if (handlers.start && !this.start.unsubscribe(handlers.start)) done = false;
|
|
134
|
+
if (handlers.end && !this.end.unsubscribe(handlers.end)) done = false;
|
|
135
|
+
if (handlers.asyncStart && !this.asyncStart.unsubscribe(handlers.asyncStart)) done = false;
|
|
136
|
+
if (handlers.asyncEnd && !this.asyncEnd.unsubscribe(handlers.asyncEnd)) done = false;
|
|
137
|
+
if (handlers.error && !this.error.unsubscribe(handlers.error)) done = false;
|
|
138
|
+
return done;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Execute a function within a tracing context.
|
|
142
|
+
* Matches Node.js signature: traceSync(fn, context, thisArg, ...args)
|
|
143
|
+
*/
|
|
144
|
+
traceSync(fn, context = {}, thisArg, ...args) {
|
|
145
|
+
if (!this.hasSubscribers) {
|
|
146
|
+
return fn.apply(thisArg, args);
|
|
147
|
+
}
|
|
148
|
+
const { start, end, error } = this;
|
|
149
|
+
start.publish(context);
|
|
150
|
+
try {
|
|
151
|
+
const result = fn.apply(thisArg, args);
|
|
152
|
+
context.result = result;
|
|
153
|
+
return result;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
context.error = err;
|
|
156
|
+
error.publish(context);
|
|
157
|
+
throw err;
|
|
158
|
+
} finally {
|
|
159
|
+
end.publish(context);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Execute an async function within a tracing context.
|
|
164
|
+
* Matches Node.js signature: tracePromise(fn, context, thisArg, ...args)
|
|
165
|
+
*/
|
|
166
|
+
tracePromise(fn, context = {}, thisArg, ...args) {
|
|
167
|
+
if (!this.hasSubscribers) {
|
|
168
|
+
return fn.apply(thisArg, args);
|
|
169
|
+
}
|
|
170
|
+
const { start, end, asyncStart, asyncEnd, error } = this;
|
|
171
|
+
function resolve(result) {
|
|
172
|
+
context.result = result;
|
|
173
|
+
asyncStart.publish(context);
|
|
174
|
+
asyncEnd.publish(context);
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
function reject(err) {
|
|
178
|
+
context.error = err;
|
|
179
|
+
error.publish(context);
|
|
180
|
+
asyncStart.publish(context);
|
|
181
|
+
asyncEnd.publish(context);
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
start.publish(context);
|
|
185
|
+
try {
|
|
186
|
+
const result = fn.apply(thisArg, args);
|
|
187
|
+
if (typeof result?.then !== "function") {
|
|
188
|
+
context.result = result;
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
return result.then(resolve, reject);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
context.error = err;
|
|
194
|
+
error.publish(context);
|
|
195
|
+
throw err;
|
|
196
|
+
} finally {
|
|
197
|
+
end.publish(context);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Execute a callback-style function within a tracing context.
|
|
202
|
+
* Matches Node.js signature: traceCallback(fn, position, context, thisArg, ...args)
|
|
203
|
+
*/
|
|
204
|
+
traceCallback(fn, position = -1, context = {}, thisArg, ...args) {
|
|
205
|
+
if (!this.hasSubscribers) {
|
|
206
|
+
return fn.apply(thisArg, args);
|
|
207
|
+
}
|
|
208
|
+
const { start, end, asyncStart, asyncEnd, error } = this;
|
|
209
|
+
const actualPos = position < 0 ? args.length + position : position;
|
|
210
|
+
const callback = args[actualPos];
|
|
211
|
+
if (typeof callback !== "function") {
|
|
212
|
+
throw new TypeError('The "callback" argument must be of type function');
|
|
213
|
+
}
|
|
214
|
+
function wrappedCallback(err, res) {
|
|
215
|
+
if (err) {
|
|
216
|
+
context.error = err;
|
|
217
|
+
error.publish(context);
|
|
218
|
+
} else {
|
|
219
|
+
context.result = res;
|
|
220
|
+
}
|
|
221
|
+
asyncStart.publish(context);
|
|
222
|
+
try {
|
|
223
|
+
return callback.apply(this, arguments);
|
|
224
|
+
} finally {
|
|
225
|
+
asyncEnd.publish(context);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const wrappedArgs = [...args];
|
|
229
|
+
wrappedArgs[actualPos] = wrappedCallback;
|
|
230
|
+
start.publish(context);
|
|
231
|
+
try {
|
|
232
|
+
return fn.apply(thisArg, wrappedArgs);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
context.error = err;
|
|
235
|
+
error.publish(context);
|
|
236
|
+
throw err;
|
|
237
|
+
} finally {
|
|
238
|
+
end.publish(context);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function tracingChannel(nameOrChannels) {
|
|
243
|
+
return new TracingChannel(nameOrChannels);
|
|
244
|
+
}
|
|
245
|
+
var index_default = {
|
|
246
|
+
Channel,
|
|
247
|
+
channel,
|
|
248
|
+
hasSubscribers,
|
|
249
|
+
subscribe,
|
|
250
|
+
unsubscribe,
|
|
251
|
+
TracingChannel,
|
|
252
|
+
tracingChannel
|
|
253
|
+
};
|
|
254
|
+
export {
|
|
255
|
+
Channel,
|
|
256
|
+
TracingChannel,
|
|
257
|
+
channel,
|
|
258
|
+
index_default as default,
|
|
259
|
+
hasSubscribers,
|
|
260
|
+
subscribe,
|
|
261
|
+
tracingChannel,
|
|
262
|
+
unsubscribe
|
|
263
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
type MessageHandler = (message: unknown, name: string | symbol) => void;
|
|
2
|
+
/**
|
|
3
|
+
* A named channel for publishing diagnostic messages.
|
|
4
|
+
*/
|
|
5
|
+
export declare class Channel {
|
|
6
|
+
readonly name: string | symbol;
|
|
7
|
+
private _subscribers;
|
|
8
|
+
constructor(name: string | symbol);
|
|
9
|
+
/** Whether this channel has active subscribers. */
|
|
10
|
+
get hasSubscribers(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Publish a message to all subscribers.
|
|
13
|
+
* Subscriber errors are caught and re-thrown asynchronously so that
|
|
14
|
+
* remaining subscribers still run (matches Node.js behavior).
|
|
15
|
+
*/
|
|
16
|
+
publish(message: unknown): void;
|
|
17
|
+
/** Subscribe to this channel. */
|
|
18
|
+
subscribe(onMessage: MessageHandler): void;
|
|
19
|
+
/** Unsubscribe from this channel. Returns true if the subscriber was found. */
|
|
20
|
+
unsubscribe(onMessage: MessageHandler): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Run stores and publish (simplified — no AsyncLocalStorage store support yet).
|
|
23
|
+
* Publishes the data and calls fn with the given thisArg and args.
|
|
24
|
+
*/
|
|
25
|
+
runStores(data: unknown, fn: (...args: unknown[]) => unknown, thisArg?: unknown, ...args: unknown[]): unknown;
|
|
26
|
+
/** Bind a store to this channel (stub — AsyncLocalStorage integration). */
|
|
27
|
+
bindStore(_store: unknown, _transform?: (message: unknown) => unknown): void;
|
|
28
|
+
/** Unbind a store from this channel (stub). */
|
|
29
|
+
unbindStore(_store: unknown): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get or create a named channel.
|
|
33
|
+
*/
|
|
34
|
+
export declare function channel(name: string | symbol): Channel;
|
|
35
|
+
/**
|
|
36
|
+
* Check if the named channel has subscribers.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasSubscribers(name: string | symbol): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to a named channel.
|
|
41
|
+
*/
|
|
42
|
+
export declare function subscribe(name: string | symbol, onMessage: MessageHandler): void;
|
|
43
|
+
/**
|
|
44
|
+
* Unsubscribe from a named channel.
|
|
45
|
+
*/
|
|
46
|
+
export declare function unsubscribe(name: string | symbol, onMessage: MessageHandler): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* TracingChannel — groups related channels for start/end/asyncStart/asyncEnd/error.
|
|
49
|
+
*/
|
|
50
|
+
export declare class TracingChannel {
|
|
51
|
+
readonly start: Channel;
|
|
52
|
+
readonly end: Channel;
|
|
53
|
+
readonly asyncStart: Channel;
|
|
54
|
+
readonly asyncEnd: Channel;
|
|
55
|
+
readonly error: Channel;
|
|
56
|
+
constructor(nameOrChannels: string | {
|
|
57
|
+
start: Channel;
|
|
58
|
+
end: Channel;
|
|
59
|
+
asyncStart: Channel;
|
|
60
|
+
asyncEnd: Channel;
|
|
61
|
+
error: Channel;
|
|
62
|
+
});
|
|
63
|
+
/** Whether any of the tracing sub-channels has subscribers. */
|
|
64
|
+
get hasSubscribers(): boolean;
|
|
65
|
+
/** Subscribe to all tracing channels. */
|
|
66
|
+
subscribe(handlers: {
|
|
67
|
+
start?: MessageHandler;
|
|
68
|
+
end?: MessageHandler;
|
|
69
|
+
asyncStart?: MessageHandler;
|
|
70
|
+
asyncEnd?: MessageHandler;
|
|
71
|
+
error?: MessageHandler;
|
|
72
|
+
}): void;
|
|
73
|
+
/** Unsubscribe from all tracing channels. Returns true if all were found. */
|
|
74
|
+
unsubscribe(handlers: {
|
|
75
|
+
start?: MessageHandler;
|
|
76
|
+
end?: MessageHandler;
|
|
77
|
+
asyncStart?: MessageHandler;
|
|
78
|
+
asyncEnd?: MessageHandler;
|
|
79
|
+
error?: MessageHandler;
|
|
80
|
+
}): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Execute a function within a tracing context.
|
|
83
|
+
* Matches Node.js signature: traceSync(fn, context, thisArg, ...args)
|
|
84
|
+
*/
|
|
85
|
+
traceSync(fn: (...args: unknown[]) => unknown, context?: Record<string, unknown>, thisArg?: unknown, ...args: unknown[]): unknown;
|
|
86
|
+
/**
|
|
87
|
+
* Execute an async function within a tracing context.
|
|
88
|
+
* Matches Node.js signature: tracePromise(fn, context, thisArg, ...args)
|
|
89
|
+
*/
|
|
90
|
+
tracePromise(fn: (...args: unknown[]) => unknown, context?: Record<string, unknown>, thisArg?: unknown, ...args: unknown[]): unknown;
|
|
91
|
+
/**
|
|
92
|
+
* Execute a callback-style function within a tracing context.
|
|
93
|
+
* Matches Node.js signature: traceCallback(fn, position, context, thisArg, ...args)
|
|
94
|
+
*/
|
|
95
|
+
traceCallback(fn: (...args: unknown[]) => unknown, position?: number, context?: Record<string, unknown>, thisArg?: unknown, ...args: unknown[]): unknown;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a TracingChannel instance.
|
|
99
|
+
*/
|
|
100
|
+
export declare function tracingChannel(nameOrChannels: string | {
|
|
101
|
+
start: Channel;
|
|
102
|
+
end: Channel;
|
|
103
|
+
asyncStart: Channel;
|
|
104
|
+
asyncEnd: Channel;
|
|
105
|
+
error: Channel;
|
|
106
|
+
}): TracingChannel;
|
|
107
|
+
declare const _default: {
|
|
108
|
+
Channel: typeof Channel;
|
|
109
|
+
channel: typeof channel;
|
|
110
|
+
hasSubscribers: typeof hasSubscribers;
|
|
111
|
+
subscribe: typeof subscribe;
|
|
112
|
+
unsubscribe: typeof unsubscribe;
|
|
113
|
+
TracingChannel: typeof TracingChannel;
|
|
114
|
+
tracingChannel: typeof tracingChannel;
|
|
115
|
+
};
|
|
116
|
+
export default _default;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/diagnostics_channel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js diagnostics_channel module for Gjs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "lib/esm/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/esm/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
16
|
+
"check": "tsc --noEmit",
|
|
17
|
+
"build": "yarn build:gjsify && yarn build:types",
|
|
18
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
|
|
19
|
+
"build:types": "tsc",
|
|
20
|
+
"build:test": "yarn build:test:gjs && yarn build:test:node",
|
|
21
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
|
|
22
|
+
"build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
|
|
23
|
+
"test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
|
|
24
|
+
"test:gjs": "gjs -m test.gjs.mjs",
|
|
25
|
+
"test:node": "node test.node.mjs"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"gjs",
|
|
29
|
+
"node",
|
|
30
|
+
"diagnostics_channel"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@gjsify/cli": "^0.1.0",
|
|
34
|
+
"@gjsify/unit": "^0.1.0",
|
|
35
|
+
"@types/node": "^25.5.0",
|
|
36
|
+
"typescript": "^6.0.2"
|
|
37
|
+
}
|
|
38
|
+
}
|