@tbrandenburg/node-red-agents 0.1.2 → 0.1.4
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/nodes/agent/agent.js +401 -368
- package/nodes/agent/lib/agents/base.js +31 -31
- package/nodes/agent/lib/agents/opencode.js +127 -120
- package/nodes/agent/lib/agents/pi.js +195 -175
- package/nodes/agent/lib/execution/lifecycle.js +41 -41
- package/nodes/agent/lib/execution/scheduler.js +74 -73
- package/nodes/agent/lib/execution/status.js +10 -10
- package/nodes/agent/lib/mcp/normalize.js +16 -16
- package/nodes/agent/lib/runtimes/base.js +10 -10
- package/nodes/agent/lib/runtimes/direct.js +19 -19
- package/nodes/agent/lib/runtimes/process-exec.js +71 -71
- package/nodes/agent/lib/runtimes/srt.js +40 -40
- package/nodes/agent-server/agent-server.js +495 -458
- package/nodes/agent-server/lib/daemon.js +99 -84
- package/nodes/agent-server/lib/http.js +45 -43
- package/nodes/agent-server/lib/model.js +10 -8
- package/nodes/agent-server/lib/port.js +14 -14
- package/nodes/agent-server/lib/registry.js +54 -54
- package/nodes/agent-server/lib/status.js +6 -6
- package/nodes/gh/README.md +11 -11
- package/nodes/gh/examples/list-pull-requests.json +46 -46
- package/nodes/gh/examples/run-workflow.json +40 -40
- package/nodes/gh/gh.js +220 -211
- package/nodes/gh/lib/parse-args.js +43 -43
- package/package.json +1 -1
- package/shared/srt-settings.js +32 -29
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
2
|
+
{
|
|
3
|
+
"id": "gh-example-inject",
|
|
4
|
+
"type": "inject",
|
|
5
|
+
"name": "",
|
|
6
|
+
"props": [{ "p": "payload" }],
|
|
7
|
+
"repeat": "",
|
|
8
|
+
"crontab": "",
|
|
9
|
+
"once": false,
|
|
10
|
+
"onceDelay": 0.1,
|
|
11
|
+
"topic": "",
|
|
12
|
+
"payload": "",
|
|
13
|
+
"payloadType": "date",
|
|
14
|
+
"x": 150,
|
|
15
|
+
"y": 120,
|
|
16
|
+
"wires": [["gh-example-node"]]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "gh-example-node",
|
|
20
|
+
"type": "gh",
|
|
21
|
+
"name": "Open PRs",
|
|
22
|
+
"command": "pr",
|
|
23
|
+
"commandType": "str",
|
|
24
|
+
"args": "list --state open --json number,title,author,url",
|
|
25
|
+
"argsType": "str",
|
|
26
|
+
"repo": "owner/repository",
|
|
27
|
+
"repoType": "str",
|
|
28
|
+
"host": "",
|
|
29
|
+
"timeoutMs": 60000,
|
|
30
|
+
"x": 350,
|
|
31
|
+
"y": 120,
|
|
32
|
+
"wires": [["gh-example-debug"]]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "gh-example-debug",
|
|
36
|
+
"type": "debug",
|
|
37
|
+
"name": "PRs",
|
|
38
|
+
"active": true,
|
|
39
|
+
"tosidebar": true,
|
|
40
|
+
"console": false,
|
|
41
|
+
"tostatus": false,
|
|
42
|
+
"complete": "payload",
|
|
43
|
+
"targetType": "msg",
|
|
44
|
+
"x": 550,
|
|
45
|
+
"y": 120,
|
|
46
|
+
"wires": []
|
|
47
|
+
}
|
|
48
48
|
]
|
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
2
|
+
{
|
|
3
|
+
"id": "gh-wf-example-function",
|
|
4
|
+
"type": "function",
|
|
5
|
+
"name": "Build gh args",
|
|
6
|
+
"func": "msg.gh = {\n command: 'workflow',\n args: ['run', 'deploy.yml', '--ref', 'main', '-f', 'environment=staging'],\n repo: 'owner/repository'\n};\nreturn msg;",
|
|
7
|
+
"outputs": 1,
|
|
8
|
+
"x": 200,
|
|
9
|
+
"y": 220,
|
|
10
|
+
"wires": [["gh-wf-example-node"]]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "gh-wf-example-node",
|
|
14
|
+
"type": "gh",
|
|
15
|
+
"name": "Trigger workflow",
|
|
16
|
+
"command": "",
|
|
17
|
+
"commandType": "str",
|
|
18
|
+
"args": "",
|
|
19
|
+
"argsType": "str",
|
|
20
|
+
"repo": "",
|
|
21
|
+
"repoType": "str",
|
|
22
|
+
"host": "",
|
|
23
|
+
"timeoutMs": 60000,
|
|
24
|
+
"x": 420,
|
|
25
|
+
"y": 220,
|
|
26
|
+
"wires": [["gh-wf-example-debug"]]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "gh-wf-example-debug",
|
|
30
|
+
"type": "debug",
|
|
31
|
+
"name": "Result",
|
|
32
|
+
"active": true,
|
|
33
|
+
"tosidebar": true,
|
|
34
|
+
"console": false,
|
|
35
|
+
"tostatus": false,
|
|
36
|
+
"complete": "true",
|
|
37
|
+
"targetType": "full",
|
|
38
|
+
"x": 620,
|
|
39
|
+
"y": 220,
|
|
40
|
+
"wires": []
|
|
41
|
+
}
|
|
42
42
|
]
|
package/nodes/gh/gh.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
|
-
const { spawn } = require(
|
|
4
|
-
const { parseArgs } = require(
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const { parseArgs } = require("./lib/parse-args");
|
|
5
5
|
|
|
6
|
-
const GH_BINARY =
|
|
6
|
+
const GH_BINARY = "gh";
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
8
8
|
|
|
9
9
|
// Resolves a typed-input style config field (str/msg/flow/global), honoring
|
|
@@ -11,227 +11,236 @@ const DEFAULT_TIMEOUT_MS = 60000;
|
|
|
11
11
|
// opencode-run.js's prompt/cwd handling). An empty 'str' path is treated as
|
|
12
12
|
// "not configured" rather than an evaluation error.
|
|
13
13
|
function evalField(RED, node, msg, value, type) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
if (type === "str" && (value === undefined || value === null || value === "")) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return RED.util.evaluateNodeProperty(value, type, node, msg);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// A valid gh top-level command is a single token (e.g. "pr", "issue",
|
|
21
21
|
// "api") -- never the literal "gh" itself and never something containing
|
|
22
22
|
// whitespace (which would indicate a whole command line was pasted in).
|
|
23
23
|
function validateCommand(command) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
25
|
+
return "no command configured (set the Command field or msg.gh.command)";
|
|
26
|
+
}
|
|
27
|
+
const trimmed = command.trim();
|
|
28
|
+
if (/\s/.test(trimmed)) {
|
|
29
|
+
return (
|
|
30
|
+
'Command must be a single gh subcommand (e.g. "pr"), not a full command line: "' +
|
|
31
|
+
trimmed +
|
|
32
|
+
'"'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (trimmed.toLowerCase() === "gh") {
|
|
36
|
+
return '"gh" is the executable itself, not a command -- use e.g. "pr" with gh already implied';
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
// Resolves an "args" value (from config or msg.gh.args) into a string[].
|
|
38
42
|
// Strings are tokenized (quote-aware, no shell evaluation); arrays are used
|
|
39
43
|
// as-is (each element coerced to a string); anything else is rejected.
|
|
40
44
|
function resolveArgs(value) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (value === undefined || value === null) return [];
|
|
46
|
+
if (Array.isArray(value)) return value.map(String);
|
|
47
|
+
if (typeof value === "string") return parseArgs(value);
|
|
48
|
+
throw new Error("args must be a string or an array of strings, got " + typeof value);
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
module.exports = function (RED) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
136
|
-
} catch (err) {
|
|
137
|
-
node.status({ fill: 'red', shape: 'ring', text: 'spawn failed' });
|
|
138
|
-
done(err);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Own explicit timeout instead of spawn()'s built-in timeout
|
|
143
|
-
// option: it's simpler to reason about (we control exactly when
|
|
144
|
-
// the timer is armed/cleared) and sidesteps that option's timer
|
|
145
|
-
// not always being cleared promptly when the child never
|
|
146
|
-
// actually starts (e.g. ENOENT).
|
|
147
|
-
let timedOut = false;
|
|
148
|
-
const timer = setTimeout(() => {
|
|
149
|
-
timedOut = true;
|
|
150
|
-
child.kill('SIGTERM');
|
|
151
|
-
}, node.timeoutMs);
|
|
152
|
-
if (typeof timer.unref === 'function') timer.unref();
|
|
153
|
-
|
|
154
|
-
child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
155
|
-
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
156
|
-
|
|
157
|
-
child.on('error', (err) => {
|
|
158
|
-
clearTimeout(timer);
|
|
159
|
-
node.status({ fill: 'red', shape: 'ring', text: 'error' });
|
|
160
|
-
const message = (err && err.code === 'ENOENT')
|
|
161
|
-
? 'gh executable not found on PATH'
|
|
162
|
-
: 'failed to run gh: ' + err.message;
|
|
163
|
-
const wrapped = new Error('gh: ' + message);
|
|
164
|
-
wrapped.command = command;
|
|
165
|
-
wrapped.args = args;
|
|
166
|
-
done(wrapped);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
child.on('close', (code, signal) => {
|
|
170
|
-
clearTimeout(timer);
|
|
171
|
-
if (signal) {
|
|
172
|
-
node.status({ fill: 'red', shape: 'ring', text: timedOut ? 'timeout' : 'killed' });
|
|
173
|
-
const err = new Error(
|
|
174
|
-
timedOut
|
|
175
|
-
? 'gh: timed out after ' + node.timeoutMs + 'ms and was killed'
|
|
176
|
-
: 'gh: process killed by signal ' + signal
|
|
177
|
-
);
|
|
178
|
-
err.command = command;
|
|
179
|
-
err.args = args;
|
|
180
|
-
err.signal = signal;
|
|
181
|
-
err.timedOut = timedOut;
|
|
182
|
-
done(err);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (code !== 0) {
|
|
187
|
-
node.status({ fill: 'red', shape: 'dot', text: 'exit ' + code });
|
|
188
|
-
const err = new Error(
|
|
189
|
-
'gh: command failed (exit ' + code + ')' + (stderr.trim() ? ': ' + stderr.trim() : '')
|
|
190
|
-
);
|
|
191
|
-
err.command = command;
|
|
192
|
-
err.args = args;
|
|
193
|
-
err.exitCode = code;
|
|
194
|
-
err.stderr = stderr.trim();
|
|
195
|
-
done(err);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const text = stdout.replace(/\r?\n$/, '');
|
|
200
|
-
let payload;
|
|
201
|
-
try {
|
|
202
|
-
payload = JSON.parse(text.trim());
|
|
203
|
-
} catch (e) {
|
|
204
|
-
payload = text;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
msg.payload = payload;
|
|
208
|
-
msg.gh = {
|
|
209
|
-
command,
|
|
210
|
-
args,
|
|
211
|
-
repo: repo || '',
|
|
212
|
-
host: host || '',
|
|
213
|
-
exitCode: 0,
|
|
214
|
-
stderr: stderr.trim()
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
node.status({ fill: 'green', shape: 'dot', text: 'done' });
|
|
218
|
-
const clearStatus = setTimeout(() => node.status({}), 2000);
|
|
219
|
-
if (typeof clearStatus.unref === 'function') clearStatus.unref();
|
|
220
|
-
send(msg);
|
|
221
|
-
done();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
node._child = child;
|
|
52
|
+
function GhNode(config) {
|
|
53
|
+
RED.nodes.createNode(this, config);
|
|
54
|
+
const node = this;
|
|
55
|
+
|
|
56
|
+
node.command = config.command || "";
|
|
57
|
+
node.commandType = config.commandType || "str";
|
|
58
|
+
node.args = config.args || "";
|
|
59
|
+
node.argsType = config.argsType || "str";
|
|
60
|
+
node.repo = config.repo || "";
|
|
61
|
+
node.repoType = config.repoType || "str";
|
|
62
|
+
node.host = config.host || "";
|
|
63
|
+
node.timeoutMs = Number(config.timeoutMs) || DEFAULT_TIMEOUT_MS;
|
|
64
|
+
|
|
65
|
+
node.on("input", function (msg, send, done) {
|
|
66
|
+
const ghOverride = msg.gh && typeof msg.gh === "object" ? msg.gh : {};
|
|
67
|
+
|
|
68
|
+
// --- command ---
|
|
69
|
+
let command;
|
|
70
|
+
try {
|
|
71
|
+
command = Object.prototype.hasOwnProperty.call(ghOverride, "command")
|
|
72
|
+
? ghOverride.command
|
|
73
|
+
: evalField(RED, node, msg, node.command, node.commandType);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
node.status({ fill: "red", shape: "ring", text: "bad command config" });
|
|
76
|
+
done(err);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const commandError = validateCommand(command);
|
|
81
|
+
if (commandError) {
|
|
82
|
+
node.status({ fill: "red", shape: "ring", text: "bad command" });
|
|
83
|
+
done(new Error("gh: " + commandError));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
command = command.trim();
|
|
87
|
+
|
|
88
|
+
// --- args ---
|
|
89
|
+
let rawArgs;
|
|
90
|
+
try {
|
|
91
|
+
rawArgs = Object.prototype.hasOwnProperty.call(ghOverride, "args")
|
|
92
|
+
? ghOverride.args
|
|
93
|
+
: evalField(RED, node, msg, node.args, node.argsType);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
node.status({ fill: "red", shape: "ring", text: "bad args config" });
|
|
96
|
+
done(err);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let args;
|
|
101
|
+
try {
|
|
102
|
+
args = resolveArgs(rawArgs);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
node.status({ fill: "red", shape: "ring", text: "bad args" });
|
|
105
|
+
done(new Error("gh: " + err.message));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- repo ---
|
|
110
|
+
let repo;
|
|
111
|
+
try {
|
|
112
|
+
repo = Object.prototype.hasOwnProperty.call(ghOverride, "repo")
|
|
113
|
+
? ghOverride.repo
|
|
114
|
+
: evalField(RED, node, msg, node.repo, node.repoType);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
node.status({ fill: "red", shape: "ring", text: "bad repo config" });
|
|
117
|
+
done(err);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
repo = repo === undefined || repo === null || repo === "" ? undefined : String(repo);
|
|
121
|
+
|
|
122
|
+
// --- host (Advanced; simple str field, no typed input) ---
|
|
123
|
+
const host = ghOverride.host || node.host || undefined;
|
|
124
|
+
|
|
125
|
+
node.status({ fill: "blue", shape: "dot", text: "running " + command });
|
|
126
|
+
|
|
127
|
+
const env = Object.assign({}, process.env);
|
|
128
|
+
if (repo) env.GH_REPO = repo;
|
|
129
|
+
if (host) env.GH_HOST = host;
|
|
130
|
+
|
|
131
|
+
let stdout = "";
|
|
132
|
+
let stderr = "";
|
|
133
|
+
let child;
|
|
134
|
+
try {
|
|
135
|
+
child = spawn(GH_BINARY, [command, ...args], {
|
|
136
|
+
env,
|
|
137
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
138
|
+
shell: false,
|
|
225
139
|
});
|
|
226
|
-
|
|
227
|
-
node.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
140
|
+
} catch (err) {
|
|
141
|
+
node.status({ fill: "red", shape: "ring", text: "spawn failed" });
|
|
142
|
+
done(err);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Own explicit timeout instead of spawn()'s built-in timeout
|
|
147
|
+
// option: it's simpler to reason about (we control exactly when
|
|
148
|
+
// the timer is armed/cleared) and sidesteps that option's timer
|
|
149
|
+
// not always being cleared promptly when the child never
|
|
150
|
+
// actually starts (e.g. ENOENT).
|
|
151
|
+
let timedOut = false;
|
|
152
|
+
const timer = setTimeout(() => {
|
|
153
|
+
timedOut = true;
|
|
154
|
+
child.kill("SIGTERM");
|
|
155
|
+
}, node.timeoutMs);
|
|
156
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
157
|
+
|
|
158
|
+
child.stdout.on("data", (chunk) => {
|
|
159
|
+
stdout += chunk.toString();
|
|
160
|
+
});
|
|
161
|
+
child.stderr.on("data", (chunk) => {
|
|
162
|
+
stderr += chunk.toString();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
child.on("error", (err) => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
node.status({ fill: "red", shape: "ring", text: "error" });
|
|
168
|
+
const message =
|
|
169
|
+
err && err.code === "ENOENT"
|
|
170
|
+
? "gh executable not found on PATH"
|
|
171
|
+
: "failed to run gh: " + err.message;
|
|
172
|
+
const wrapped = new Error("gh: " + message);
|
|
173
|
+
wrapped.command = command;
|
|
174
|
+
wrapped.args = args;
|
|
175
|
+
done(wrapped);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
child.on("close", (code, signal) => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
if (signal) {
|
|
181
|
+
node.status({ fill: "red", shape: "ring", text: timedOut ? "timeout" : "killed" });
|
|
182
|
+
const err = new Error(
|
|
183
|
+
timedOut
|
|
184
|
+
? "gh: timed out after " + node.timeoutMs + "ms and was killed"
|
|
185
|
+
: "gh: process killed by signal " + signal,
|
|
186
|
+
);
|
|
187
|
+
err.command = command;
|
|
188
|
+
err.args = args;
|
|
189
|
+
err.signal = signal;
|
|
190
|
+
err.timedOut = timedOut;
|
|
191
|
+
done(err);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (code !== 0) {
|
|
196
|
+
node.status({ fill: "red", shape: "dot", text: "exit " + code });
|
|
197
|
+
const err = new Error(
|
|
198
|
+
"gh: command failed (exit " + code + ")" + (stderr.trim() ? ": " + stderr.trim() : ""),
|
|
199
|
+
);
|
|
200
|
+
err.command = command;
|
|
201
|
+
err.args = args;
|
|
202
|
+
err.exitCode = code;
|
|
203
|
+
err.stderr = stderr.trim();
|
|
204
|
+
done(err);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const text = stdout.replace(/\r?\n$/, "");
|
|
209
|
+
let payload;
|
|
210
|
+
try {
|
|
211
|
+
payload = JSON.parse(text.trim());
|
|
212
|
+
} catch (e) {
|
|
213
|
+
payload = text;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
msg.payload = payload;
|
|
217
|
+
msg.gh = {
|
|
218
|
+
command,
|
|
219
|
+
args,
|
|
220
|
+
repo: repo || "",
|
|
221
|
+
host: host || "",
|
|
222
|
+
exitCode: 0,
|
|
223
|
+
stderr: stderr.trim(),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
node.status({ fill: "green", shape: "dot", text: "done" });
|
|
227
|
+
const clearStatus = setTimeout(() => node.status({}), 2000);
|
|
228
|
+
if (typeof clearStatus.unref === "function") clearStatus.unref();
|
|
229
|
+
send(msg);
|
|
230
|
+
done();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
node._child = child;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
node.on("close", function (done) {
|
|
237
|
+
if (node._child && !node._child.killed) {
|
|
238
|
+
node._child.kill();
|
|
239
|
+
}
|
|
240
|
+
node.status({});
|
|
241
|
+
done();
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
RED.nodes.registerType("gh", GhNode);
|
|
237
246
|
};
|