pinokiod 7.5.7 → 7.5.9

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.
@@ -1639,6 +1639,8 @@ document.addEventListener("DOMContentLoaded", async () => {
1639
1639
  this.currentId = baseScriptId
1640
1640
  this.latestLogFallbackActive = false
1641
1641
  this.pendingInput = []
1642
+ this.pendingTerminalWrite = ""
1643
+ this.pendingTerminalWriteFrame = null
1642
1644
  this.inputBuffer = ""
1643
1645
  this.inputTracker = window.TerminalInputTracker ? new window.TerminalInputTracker({
1644
1646
  getFrameName: () => window.name || null,
@@ -1723,6 +1725,32 @@ document.addEventListener("DOMContentLoaded", async () => {
1723
1725
  }
1724
1726
  this.dirty = true
1725
1727
  }
1728
+ flushPendingTerminalWrite() {
1729
+ if (!this.pendingTerminalWrite) {
1730
+ this.pendingTerminalWriteFrame = null
1731
+ return
1732
+ }
1733
+ const text = this.pendingTerminalWrite
1734
+ this.pendingTerminalWrite = ""
1735
+ this.pendingTerminalWriteFrame = null
1736
+ this.write(text)
1737
+ }
1738
+ writeBatched(text) {
1739
+ if (text === "\u0007" || typeof text !== "string" || text.length === 0) {
1740
+ return
1741
+ }
1742
+ this.pendingTerminalWrite += text
1743
+ this.dirty = true
1744
+ if (this.pendingTerminalWriteFrame !== null) {
1745
+ return
1746
+ }
1747
+ const flush = () => this.flushPendingTerminalWrite()
1748
+ if (typeof window.requestAnimationFrame === "function") {
1749
+ this.pendingTerminalWriteFrame = window.requestAnimationFrame(flush)
1750
+ } else {
1751
+ this.pendingTerminalWriteFrame = window.setTimeout(flush, 16)
1752
+ }
1753
+ }
1726
1754
  finished() {
1727
1755
  /*
1728
1756
  n.Noty({
@@ -1921,13 +1949,13 @@ document.addEventListener("DOMContentLoaded", async () => {
1921
1949
  this.resizeSync.sendInitial()
1922
1950
  }
1923
1951
  if (packet.data.raw) {
1924
- this.write(packet.data.raw)
1952
+ this.writeBatched(packet.data.raw)
1925
1953
  } else if (packet.data.json) {
1926
- this.write(JSON.stringify(packet.data.json).replace(/\n/g, "\r\n"))
1927
- this.write("\r\n")
1954
+ this.writeBatched(JSON.stringify(packet.data.json).replace(/\n/g, "\r\n"))
1955
+ this.writeBatched("\r\n")
1928
1956
  } else if (packet.data.json2) {
1929
- this.write(JSON.stringify(packet.data.json2, null, 2).replace(/\n/g, "\r\n"))
1930
- this.write("\r\n")
1957
+ this.writeBatched(JSON.stringify(packet.data.json2, null, 2).replace(/\n/g, "\r\n"))
1958
+ this.writeBatched("\r\n")
1931
1959
  }
1932
1960
  if (packet.data.type === "emit2") {
1933
1961
  // long text => still posting to the shell => show progress
@@ -1945,6 +1973,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1945
1973
  refreshParent(packet)
1946
1974
  pluginTerminalDiscoveryRefresher.clear()
1947
1975
  reloadMemory()
1976
+ this.flushPendingTerminalWrite()
1948
1977
  this.term.write("\r\nDisconnected...\r\n")
1949
1978
  document.querySelector("#status-window").innerHTML = "<b>Ready</b>"
1950
1979
  this.socket.close()
@@ -74,6 +74,26 @@ test("launch settings visible labels use autolaunch wording", async () => {
74
74
  assert.doesNotMatch(sidebar, />Launch settings</)
75
75
  })
76
76
 
77
+ test("autolaunch page presents launch script choices before requirements", async () => {
78
+ const globalView = await fs.readFile(path.resolve(root, "server/views/autolaunch.ejs"), "utf8")
79
+
80
+ const currentIndex = globalView.indexOf('<h3 class="autolaunch-section-title">Current</h3>')
81
+ const menuIndex = globalView.indexOf('${renderScriptSection("Menu scripts"')
82
+ const otherIndex = globalView.indexOf('${renderScriptSection("Other local scripts"')
83
+ const manualIndex = globalView.indexOf('<div class="autolaunch-manual">')
84
+ const requirementsIndex = globalView.indexOf('${renderDependencySection(app)}')
85
+
86
+ assert.notEqual(currentIndex, -1)
87
+ assert.notEqual(menuIndex, -1)
88
+ assert.notEqual(otherIndex, -1)
89
+ assert.notEqual(manualIndex, -1)
90
+ assert.notEqual(requirementsIndex, -1)
91
+ assert.ok(currentIndex < menuIndex)
92
+ assert.ok(menuIndex < otherIndex)
93
+ assert.ok(otherIndex < manualIndex)
94
+ assert.ok(manualIndex < requirementsIndex)
95
+ })
96
+
77
97
  test("startup progress uses canonical ready-state progress instead of autolaunch mirrors", async () => {
78
98
  const kernelIndex = await fs.readFile(path.resolve(root, "kernel/index.js"), "utf8")
79
99
  const autolaunch = await fs.readFile(path.resolve(root, "kernel/autolaunch.js"), "utf8")
@@ -0,0 +1,39 @@
1
+ const assert = require('node:assert/strict')
2
+ const test = require('node:test')
3
+
4
+ const Node = require('../kernel/bin/node')
5
+
6
+ function createNode({ nodeVersion = '24.18.0', pnpmVersion = '11.9.0', hasNode = true, hasPnpm = true } = {}) {
7
+ const node = new Node()
8
+ const conda = new Set()
9
+ if (hasNode) conda.add('nodejs')
10
+ if (hasPnpm) conda.add('pnpm')
11
+ node.kernel = {
12
+ bin: {
13
+ installed: {
14
+ conda,
15
+ conda_versions: {
16
+ nodejs: nodeVersion,
17
+ pnpm: pnpmVersion,
18
+ },
19
+ },
20
+ },
21
+ }
22
+ return node
23
+ }
24
+
25
+ test('Node bin pins Node.js 24 LTS and matching pnpm', () => {
26
+ assert.equal(createNode().cmd(), 'nodejs=24.18.0 pnpm=11.9.0')
27
+ })
28
+
29
+ test('Node.installed accepts exactly the pinned Node.js and pnpm versions', async () => {
30
+ assert.equal(await createNode().installed(), true)
31
+ assert.equal(await createNode({ nodeVersion: '24.18.0-h654b19f_0' }).installed(), true)
32
+ assert.equal(await createNode({ pnpmVersion: '11.9.0-h7c87c79_0' }).installed(), true)
33
+ assert.equal(await createNode({ nodeVersion: '22.21.1' }).installed(), false)
34
+ assert.equal(await createNode({ nodeVersion: '26.4.0' }).installed(), false)
35
+ assert.equal(await createNode({ pnpmVersion: '10.29.3' }).installed(), false)
36
+ assert.equal(await createNode({ pnpmVersion: '11.8.0' }).installed(), false)
37
+ assert.equal(await createNode({ hasNode: false }).installed(), false)
38
+ assert.equal(await createNode({ hasPnpm: false }).installed(), false)
39
+ })
@@ -2,6 +2,9 @@ const test = require('node:test')
2
2
  const assert = require('node:assert/strict')
3
3
  const path = require('node:path')
4
4
  const ShellAPI = require('../kernel/api/shell')
5
+ const Shell = require('../kernel/shell')
6
+ const Shells = require('../kernel/shells')
7
+ const CondaRuntimeGuard = require('../kernel/shell_conda_runtime_guard')
5
8
 
6
9
  function createKernel () {
7
10
  const calls = []
@@ -10,23 +13,23 @@ function createKernel () {
10
13
  homedir: '/tmp/pinokio-home',
11
14
  shell: {
12
15
  start: async (params, options) => {
13
- calls.push({ method: 'start', params: structuredClone(params), options: structuredClone(options) })
16
+ calls.push({ method: 'start', guard: params[CondaRuntimeGuard.SHELL_RUN_GUARD] === true, params: structuredClone(params), options: structuredClone(options) })
14
17
  return 'shell-id'
15
18
  },
16
19
  enter: async (params) => {
17
- calls.push({ method: 'enter', params: structuredClone(params) })
20
+ calls.push({ method: 'enter', guard: params[CondaRuntimeGuard.SHELL_RUN_GUARD] === true, params: structuredClone(params) })
18
21
  return 'entered'
19
22
  },
20
23
  write: async (params) => {
21
- calls.push({ method: 'write', params: structuredClone(params) })
24
+ calls.push({ method: 'write', guard: params[CondaRuntimeGuard.SHELL_RUN_GUARD] === true, params: structuredClone(params) })
22
25
  return 'written'
23
26
  },
24
27
  run: async (params, options) => {
25
- calls.push({ method: 'run', params: structuredClone(params), options: structuredClone(options) })
28
+ calls.push({ method: 'run', guard: params[CondaRuntimeGuard.SHELL_RUN_GUARD] === true, params: structuredClone(params), options: structuredClone(options) })
26
29
  return { stdout: 'ok' }
27
30
  },
28
31
  kill: async (params) => {
29
- calls.push({ method: 'kill', params: structuredClone(params) })
32
+ calls.push({ method: 'kill', guard: params[CondaRuntimeGuard.SHELL_RUN_GUARD] === true, params: structuredClone(params) })
30
33
  }
31
34
  }
32
35
  }
@@ -51,6 +54,7 @@ test('shell.start forwards defaults, client size, parent, and shell options', as
51
54
  assert.equal(result, 'shell-id')
52
55
  assert.deepEqual(kernel.calls, [{
53
56
  method: 'start',
57
+ guard: false,
54
58
  params: {
55
59
  id: cwd,
56
60
  path: cwd,
@@ -76,25 +80,28 @@ test('shell.enter, shell.write, and shell.stop target current cwd by default', a
76
80
  const cwd = path.join('/tmp', 'pinokio-shell-api')
77
81
  const parent = { path: path.join(cwd, 'script.js') }
78
82
 
79
- assert.equal(await api.enter({ cwd, parent, params: { message: 'echo hi' } }, () => {}, kernel), 'entered')
80
- assert.equal(await api.write({ cwd, params: { message: 'typed' } }, () => {}, kernel), 'written')
83
+ assert.equal(await api.enter({ cwd, parent, params: { message: 'conda install python=3.12 -y' } }, () => {}, kernel), 'entered')
84
+ assert.equal(await api.write({ cwd, params: { message: 'conda update --all' } }, () => {}, kernel), 'written')
81
85
  await api.stop({ cwd, params: {} }, () => {}, kernel)
82
86
 
83
87
  assert.deepEqual(kernel.calls, [{
84
88
  method: 'enter',
89
+ guard: false,
85
90
  params: {
86
- message: 'echo hi',
91
+ message: 'conda install python=3.12 -y',
87
92
  id: cwd,
88
93
  $parent: parent
89
94
  }
90
95
  }, {
91
96
  method: 'write',
97
+ guard: false,
92
98
  params: {
93
- message: 'typed',
99
+ message: 'conda update --all',
94
100
  id: cwd
95
101
  }
96
102
  }, {
97
103
  method: 'kill',
104
+ guard: false,
98
105
  params: {
99
106
  id: cwd
100
107
  }
@@ -118,6 +125,7 @@ test('shell.run applies deterministic defaults and forwards execution options',
118
125
  assert.deepEqual(result, { stdout: 'ok' })
119
126
  assert.deepEqual(kernel.calls, [{
120
127
  method: 'run',
128
+ guard: true,
121
129
  params: {
122
130
  message: 'echo ok',
123
131
  path: cwd,
@@ -132,3 +140,85 @@ test('shell.run applies deterministic defaults and forwards execution options',
132
140
  }
133
141
  }])
134
142
  })
143
+
144
+ test('shell.run forwards typed stream events', async () => {
145
+ const api = new ShellAPI()
146
+ const events = []
147
+ const kernel = createKernel()
148
+ kernel.shell.run = async (params, options, ondata) => {
149
+ ondata({ html: 'notice', type: 'warning' }, 'notify')
150
+ return { stdout: 'ok' }
151
+ }
152
+
153
+ const result = await api.run({
154
+ cwd: path.join('/tmp', 'pinokio-shell-api'),
155
+ params: {
156
+ message: 'echo ok'
157
+ }
158
+ }, (stream, type) => {
159
+ events.push({ stream, type })
160
+ }, kernel)
161
+
162
+ assert.deepEqual(result, { stdout: 'ok' })
163
+ assert.deepEqual(events, [{
164
+ stream: {
165
+ html: 'notice',
166
+ type: 'warning'
167
+ },
168
+ type: 'notify'
169
+ }])
170
+ })
171
+
172
+ test('Shells.launch forwards typed stream events without live-output parsing', async () => {
173
+ const events = []
174
+ const originalStart = Shell.prototype.start
175
+ Shell.prototype.start = async function (_params, ondata) {
176
+ this.id = 'stub-shell'
177
+ await ondata({ html: 'notice', type: 'warning' }, 'notify')
178
+ return ''
179
+ }
180
+
181
+ try {
182
+ const root = path.join('/tmp', 'pinokio-shells-api')
183
+ const shells = new Shells({
184
+ homedir: root,
185
+ platform: 'darwin',
186
+ bracketedPasteSupport: { bash: true },
187
+ which: () => null,
188
+ path: (...parts) => path.join(root, ...parts),
189
+ bin: {
190
+ envs: (env = {}) => env
191
+ },
192
+ api: {
193
+ resolvePath: (cwd, target) => path.resolve(cwd, target),
194
+ running: {}
195
+ },
196
+ git: {
197
+ repos: async () => [],
198
+ restoreNewReposForActiveSnapshot: async () => {}
199
+ },
200
+ template: {
201
+ render: (value) => value
202
+ }
203
+ })
204
+
205
+ await shells.launch({
206
+ path: path.join(root, 'api', 'demo'),
207
+ message: 'echo ok'
208
+ }, {
209
+ cwd: root
210
+ }, (stream, type) => {
211
+ events.push({ stream, type })
212
+ })
213
+
214
+ assert.deepEqual(events, [{
215
+ stream: {
216
+ html: 'notice',
217
+ type: 'warning'
218
+ },
219
+ type: 'notify'
220
+ }])
221
+ } finally {
222
+ Shell.prototype.start = originalStart
223
+ }
224
+ })