pinokiod 7.3.12 → 7.3.14

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 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 = new Set()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.3.12",
3
+ "version": "7.3.14",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -12099,24 +12099,24 @@ const rerenderMenuSection = (container, html) => {
12099
12099
  }, 0)
12100
12100
  <% } %>
12101
12101
  window.addEventListener('message', async (event) => {
12102
- if (event.data) {
12103
- if (event.data.e === "pinokio:close-logs" || event.data.type === "pinokio:close-logs") {
12104
- if (window.PinokioCloseLogsPage && typeof window.PinokioCloseLogsPage === "function") {
12105
- await window.PinokioCloseLogsPage()
12106
- }
12107
- return
12108
- }
12109
- if (event.data.e === "pinokio:logs-new" || event.data.type === "pinokio:logs-new" || event.data.e === "pinokio:open-logs" || event.data.type === "pinokio:open-logs") {
12110
- setLogsNewBadge(true)
12111
- return
12112
- }
12113
- }
12114
12102
  // only process the event it's coming from pinokio
12115
12103
  let origin = event.origin
12116
12104
  if (origin) {
12117
12105
  let port = new URL(origin).port || 80
12118
12106
  if (String(port) === String(location.port) || /https:\/\/.*pinokio\..*(localhost|co)/.test(origin)) {
12119
- if (event.data) {
12107
+ //if (String(port) === "<%=port%>" || /https:\/\/pinokio\..*localhost/.test(origin)) {
12108
+ //if (String(port) === "<%=port%>") {
12109
+ if (event.data) {
12110
+ if (event.data.e === "pinokio:close-logs" || event.data.type === "pinokio:close-logs") {
12111
+ if (window.PinokioCloseLogsPage && typeof window.PinokioCloseLogsPage === "function") {
12112
+ await window.PinokioCloseLogsPage()
12113
+ }
12114
+ return
12115
+ }
12116
+ if (event.data.e === "pinokio:logs-new" || event.data.type === "pinokio:logs-new" || event.data.e === "pinokio:open-logs" || event.data.type === "pinokio:open-logs") {
12117
+ setLogsNewBadge(true)
12118
+ return
12119
+ }
12120
12120
  if (event.data.action) {
12121
12121
  if (event.data.action.type === "newtab") {
12122
12122
  addTab(event.data.action.url)
@@ -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, on }) {
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', on }, { 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
+ })