pinokiod 7.3.11 → 7.3.13
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/kernel/shells.js +59 -50
- package/package.json +1 -1
- package/test/shells-live-error.test.js +143 -0
package/kernel/shells.js
CHANGED
|
@@ -415,7 +415,6 @@ class Shells {
|
|
|
415
415
|
.replaceAll(/\r\n/g, "\n")
|
|
416
416
|
.replaceAll(/\r/g, "\n")
|
|
417
417
|
}
|
|
418
|
-
|
|
419
418
|
// if error doesn't exist, add default "error:" event
|
|
420
419
|
if (!params.on) {
|
|
421
420
|
params.on = []
|
|
@@ -436,6 +435,56 @@ class Shells {
|
|
|
436
435
|
}]
|
|
437
436
|
params.on = params.on.concat(defaultHandlers)
|
|
438
437
|
|
|
438
|
+
const collectBreakErrors = (text) => {
|
|
439
|
+
const errors = new Set()
|
|
440
|
+
if (!text || !params.on || !Array.isArray(params.on)) {
|
|
441
|
+
return errors
|
|
442
|
+
}
|
|
443
|
+
let line = text.replaceAll(/[\r\n]/g, "")
|
|
444
|
+
// 1. find all break event handlers
|
|
445
|
+
let breakPoints = params.on.filter((x) => {
|
|
446
|
+
return x.event && x.hasOwnProperty("break")
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
// 2. first find the `break: false` handlers and replace the patterns with blank => so they won't be matched in the next step
|
|
450
|
+
//let line = response.replaceAll(/[\r\n]/g, "")
|
|
451
|
+
for(let handler of breakPoints) {
|
|
452
|
+
if (handler.event) {
|
|
453
|
+
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
454
|
+
if (!/g/.test(matches[2])) {
|
|
455
|
+
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
456
|
+
}
|
|
457
|
+
let re = new RegExp(matches[1], matches[2])
|
|
458
|
+
|
|
459
|
+
// only the "break: false" ones => these need to be ignored
|
|
460
|
+
if (!handler.break) {
|
|
461
|
+
line = line.replaceAll(re, "")
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// 3. Now with all the `break: false` (ignored patterns) gone, look for the `break: true` patterns
|
|
466
|
+
for(let handler of breakPoints) {
|
|
467
|
+
if (handler.event) {
|
|
468
|
+
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
469
|
+
if (!/g/.test(matches[2])) {
|
|
470
|
+
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
471
|
+
}
|
|
472
|
+
let re = new RegExp(matches[1], matches[2])
|
|
473
|
+
|
|
474
|
+
// only the "break: true" ones
|
|
475
|
+
if (handler.break) {
|
|
476
|
+
let match;
|
|
477
|
+
// Keep executing the regex on the text until no more matches are found
|
|
478
|
+
while ((match = re.exec(line)) !== null) {
|
|
479
|
+
errors.add(match[0])
|
|
480
|
+
// errors.add(match.slice(1));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return errors
|
|
486
|
+
}
|
|
487
|
+
|
|
439
488
|
if (!isParentRequestActive()) {
|
|
440
489
|
return ""
|
|
441
490
|
}
|
|
@@ -519,8 +568,10 @@ class Shells {
|
|
|
519
568
|
const lastMatch = rendered_event[rendered_event.length - 1]
|
|
520
569
|
handlerLastMatchEnd.set(i, liveEventBufferStart + lastMatch.index + lastMatch[0].length)
|
|
521
570
|
const triggerAction = typeof handler.trigger === "string" ? handler.trigger.trim() : ""
|
|
571
|
+
const isBreakHandler = handler.break === true
|
|
572
|
+
const liveErrors = isBreakHandler ? collectBreakErrors(liveEventBuffer) : null
|
|
522
573
|
const shouldCaptureEvent =
|
|
523
|
-
handler.break !== false
|
|
574
|
+
(handler.break !== false && (!isBreakHandler || liveErrors.size > 0))
|
|
524
575
|
|| handler.done
|
|
525
576
|
|| handler.kill
|
|
526
577
|
|| !!triggerAction
|
|
@@ -542,6 +593,11 @@ class Shells {
|
|
|
542
593
|
liveEventHandlersClosed = true
|
|
543
594
|
break
|
|
544
595
|
}
|
|
596
|
+
if (isBreakHandler && liveErrors.size > 0) {
|
|
597
|
+
sh.continue(liveEventBuffer)
|
|
598
|
+
liveEventHandlersClosed = true
|
|
599
|
+
break
|
|
600
|
+
}
|
|
545
601
|
if (handler.done) {
|
|
546
602
|
sh.continue()
|
|
547
603
|
liveEventHandlersClosed = true
|
|
@@ -589,54 +645,7 @@ class Shells {
|
|
|
589
645
|
- TEMPLATE: parse the template
|
|
590
646
|
*/
|
|
591
647
|
|
|
592
|
-
let errors =
|
|
593
|
-
if (response) {
|
|
594
|
-
if (params.on && Array.isArray(params.on)) {
|
|
595
|
-
|
|
596
|
-
let line = response.replaceAll(/[\r\n]/g, "")
|
|
597
|
-
// 1. find all break event handlers
|
|
598
|
-
let breakPoints = params.on.filter((x) => {
|
|
599
|
-
return x.event && x.hasOwnProperty("break")
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
// 2. first find the `break: false` handlers and replace the patterns with blank => so they won't be matched in the next step
|
|
603
|
-
//let line = response.replaceAll(/[\r\n]/g, "")
|
|
604
|
-
for(let handler of breakPoints) {
|
|
605
|
-
if (handler.event) {
|
|
606
|
-
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
607
|
-
if (!/g/.test(matches[2])) {
|
|
608
|
-
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
609
|
-
}
|
|
610
|
-
let re = new RegExp(matches[1], matches[2])
|
|
611
|
-
|
|
612
|
-
// only the "break: false" ones => these need to be ignored
|
|
613
|
-
if (!handler.break) {
|
|
614
|
-
line = line.replaceAll(re, "")
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
// 3. Now with all the `break: false` (ignored patterns) gone, look for the `break: true` patterns
|
|
619
|
-
for(let handler of breakPoints) {
|
|
620
|
-
if (handler.event) {
|
|
621
|
-
let matches = /^\/(.+)\/([dgimsuy]*)$/gs.exec(handler.event)
|
|
622
|
-
if (!/g/.test(matches[2])) {
|
|
623
|
-
matches[2] += "g" // if g option is not included, include it (need it for matchAll)
|
|
624
|
-
}
|
|
625
|
-
let re = new RegExp(matches[1], matches[2])
|
|
626
|
-
|
|
627
|
-
// only the "break: true" ones
|
|
628
|
-
if (handler.break) {
|
|
629
|
-
let match;
|
|
630
|
-
// Keep executing the regex on the text until no more matches are found
|
|
631
|
-
while ((match = re.exec(line)) !== null) {
|
|
632
|
-
errors.add(match[0])
|
|
633
|
-
// errors.add(match.slice(1));
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
648
|
+
let errors = collectBreakErrors(response)
|
|
640
649
|
|
|
641
650
|
|
|
642
651
|
if (ondata) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const test = require('node:test')
|
|
2
|
+
const assert = require('node:assert/strict')
|
|
3
|
+
const Module = require('node:module')
|
|
4
|
+
|
|
5
|
+
async function runWithFakeShell ({ platform = 'win32', chunks, response, waitForBreak = false }) {
|
|
6
|
+
class FakeShell {
|
|
7
|
+
constructor () {
|
|
8
|
+
this.id = 'fake-shell'
|
|
9
|
+
this.monitor = ''
|
|
10
|
+
this.breakResolver = null
|
|
11
|
+
this.resolved = undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
stripAnsi (value) {
|
|
15
|
+
return value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
kill (message) {
|
|
19
|
+
this.resolved = message || response
|
|
20
|
+
if (this.breakResolver) {
|
|
21
|
+
this.breakResolver(this.resolved)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
continue (message) {
|
|
26
|
+
this.resolved = message || response
|
|
27
|
+
if (this.breakResolver) {
|
|
28
|
+
this.breakResolver(this.resolved)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async start (_params, onstream) {
|
|
33
|
+
let breakPromise = null
|
|
34
|
+
if (waitForBreak) {
|
|
35
|
+
breakPromise = new Promise((resolve) => {
|
|
36
|
+
this.breakResolver = resolve
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
for (const chunk of chunks) {
|
|
40
|
+
await onstream({ raw: chunk })
|
|
41
|
+
if (this.resolved !== undefined) {
|
|
42
|
+
return this.resolved
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (waitForBreak) {
|
|
46
|
+
return await breakPromise
|
|
47
|
+
}
|
|
48
|
+
return response
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const shellsModulePath = require.resolve('../kernel/shells')
|
|
53
|
+
delete require.cache[shellsModulePath]
|
|
54
|
+
|
|
55
|
+
const originalLoad = Module._load
|
|
56
|
+
Module._load = function (request, parent, isMain) {
|
|
57
|
+
if (request === './shell' && parent && /kernel[\\/]shells\.js$/.test(parent.filename)) {
|
|
58
|
+
return FakeShell
|
|
59
|
+
}
|
|
60
|
+
return originalLoad.apply(this, arguments)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const Shells = require('../kernel/shells')
|
|
65
|
+
const kernel = {
|
|
66
|
+
platform,
|
|
67
|
+
homedir: '/tmp/pinokio-home',
|
|
68
|
+
bracketedPasteSupport: { 'cmd.exe': true },
|
|
69
|
+
bin: { envs: (env) => env || {} },
|
|
70
|
+
api: {
|
|
71
|
+
resolvePath: (_cwd, execPath) => execPath,
|
|
72
|
+
running: {}
|
|
73
|
+
},
|
|
74
|
+
which: () => null
|
|
75
|
+
}
|
|
76
|
+
const output = []
|
|
77
|
+
const shells = new Shells(kernel)
|
|
78
|
+
const result = await shells.run({ message: 'python wgp.py' }, { cwd: '/tmp/app' }, (stream) => {
|
|
79
|
+
output.push(stream.raw || '')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return { output, result }
|
|
83
|
+
} finally {
|
|
84
|
+
Module._load = originalLoad
|
|
85
|
+
delete require.cache[shellsModulePath]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test('shell.run promotes a live break match to an error when the final response loses it', async () => {
|
|
90
|
+
const { output, result } = await runWithFakeShell({
|
|
91
|
+
chunks: [
|
|
92
|
+
"Traceback (most recent call last):\r\n",
|
|
93
|
+
"ModuleNotFoundError: No module named 'torch'\r\n"
|
|
94
|
+
],
|
|
95
|
+
response: "(env) (base) C:\\pinokio\\api\\wan2gp-amd.git\\app>"
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
assert.equal(output.some((chunk) => chunk.includes('# input.event')), true)
|
|
99
|
+
assert.deepEqual(result.event[0], 'Error:')
|
|
100
|
+
assert.deepEqual(result.error, ['Error:'])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('shell.run promotes a live argparse error to an error when the final response loses it', async () => {
|
|
104
|
+
const { output, result } = await runWithFakeShell({
|
|
105
|
+
platform: 'darwin',
|
|
106
|
+
chunks: [
|
|
107
|
+
"usage: x-voice_infer-gradio [-h] [--port PORT] [--host HOST]\n",
|
|
108
|
+
"x-voice_infer-gradio: error: argument --port: invalid int value: 'aaaa'\n"
|
|
109
|
+
],
|
|
110
|
+
response: "(/Users/x/pinokio/api/X-Voice.git2/app/conda_env) <<PINOKIO_SHELL>>"
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
assert.equal(output.some((chunk) => chunk.includes('# input.event')), true)
|
|
114
|
+
assert.deepEqual(result.event[0], 'error:')
|
|
115
|
+
assert.deepEqual(result.error, ['error:'])
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('shell.run resolves an interactive run on a real live break match', async () => {
|
|
119
|
+
const { output, result } = await runWithFakeShell({
|
|
120
|
+
platform: 'darwin',
|
|
121
|
+
chunks: [
|
|
122
|
+
"x-voice_infer-gradio: error: argument --port: invalid int value: 'aaaa'\n"
|
|
123
|
+
],
|
|
124
|
+
response: "(/Users/x/pinokio/api/X-Voice.git2/app/conda_env) <<PINOKIO_SHELL>>",
|
|
125
|
+
waitForBreak: true
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
assert.equal(output.some((chunk) => chunk.includes('# input.event')), true)
|
|
129
|
+
assert.deepEqual(result.event[0], 'error:')
|
|
130
|
+
assert.deepEqual(result.error, ['error:'])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('shell.run does not return an error for a live match covered by break:false', async () => {
|
|
134
|
+
const { result } = await runWithFakeShell({
|
|
135
|
+
platform: 'darwin',
|
|
136
|
+
chunks: [
|
|
137
|
+
"error: triton is not available, continuing without it\n"
|
|
138
|
+
],
|
|
139
|
+
response: "error: triton is not available, continuing without it\nready"
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
assert.equal(result.error, undefined)
|
|
143
|
+
})
|