pinokiod 7.5.33 → 7.5.34

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.
@@ -69,6 +69,76 @@ const LEGACY_SSL_CERT_DIR_HOOK_FILES = [
69
69
  "zz_pinokio_unset_ssl_cert_dir-win.ps1",
70
70
  "zz_pinokio_unset_ssl_cert_dir-win.sh",
71
71
  ]
72
+ const WINDOWS_CONDA_ACTIVATE_RELATIVE_PATH = path.join(
73
+ "Lib",
74
+ "site-packages",
75
+ "conda",
76
+ "shell",
77
+ "condabin",
78
+ "_conda_activate.bat"
79
+ )
80
+ const WINDOWS_CONDA_ACTIVATE_PINOKIO_MARKER = ":PINOKIO_APPLY_CONDA_ENV"
81
+
82
+ function windowsCondaActivateOriginalBlock(eol) {
83
+ return [
84
+ " ) ELSE ENDLOCAL & (",
85
+ ' FOR /F "tokens=1,* delims==" %%A IN (%%T) DO (',
86
+ ' :: A, B = "key", "value"',
87
+ ' IF "%CONDA_DEBUG%" == "1" ECHO DEBUG: "%%A=%%B">&2',
88
+ ' IF "%%A"=="_CONDA_SCRIPT" (',
89
+ " :: Script execution, fast exit if activation scripts fail",
90
+ ' CALL "%%B" || (',
91
+ ' ECHO ERROR: Activation script "%%B" failed with code %ERRORLEVEL%.>&2',
92
+ ' CALL :DELETE "%%T"',
93
+ ' CALL :DELETE "%__conda_tmp%"',
94
+ " EXIT /B 4",
95
+ " )",
96
+ ' ) ELSE IF "%%B"=="" (',
97
+ " :: Unset variable",
98
+ ' SET "%%A="',
99
+ " ) ELSE (",
100
+ " :: Set variable",
101
+ ' SET "%%A=%%B"',
102
+ " )",
103
+ " )",
104
+ " :: Clean up",
105
+ ' CALL :DELETE "%%T"',
106
+ ' CALL :DELETE "%__conda_tmp%"',
107
+ " EXIT /B 0",
108
+ " )",
109
+ ].join(eol)
110
+ }
111
+
112
+ function windowsCondaActivatePatchedBlock(eol) {
113
+ return [
114
+ ' ) ELSE ENDLOCAL & @CALL :PINOKIO_APPLY_CONDA_ENV "%%T" "%__conda_tmp%" & @GOTO :EOF',
115
+ ].join(eol)
116
+ }
117
+
118
+ function windowsCondaActivatePatchRoutines(eol) {
119
+ return [
120
+ ":PINOKIO_APPLY_CONDA_ENV",
121
+ '@FOR /F "tokens=1,* delims==" %%A IN (%~1) DO (',
122
+ '@IF "%CONDA_DEBUG%" == "1" ECHO DEBUG: "%%A=%%B">&2',
123
+ '@IF "%%A"=="_CONDA_SCRIPT" (',
124
+ '@CALL "%%B" || (',
125
+ '@ECHO ERROR: Activation script "%%B" failed with code %ERRORLEVEL%.>&2',
126
+ '@CALL :DELETE "%~1"',
127
+ '@CALL :DELETE "%~2"',
128
+ "@EXIT /B 4",
129
+ ")",
130
+ ') ELSE IF "%%B"=="" (',
131
+ '@SET "%%A="',
132
+ ") ELSE (",
133
+ '@SET "%%A=%%B"',
134
+ ")",
135
+ ")",
136
+ '@CALL :DELETE "%~1"',
137
+ '@CALL :DELETE "%~2"',
138
+ "@EXIT /B 0",
139
+ "",
140
+ ].join(eol)
141
+ }
72
142
 
73
143
  class Conda {
74
144
  description = "Pinokio uses Conda to install various useful programs in an isolated manner."
@@ -161,6 +231,42 @@ class Conda {
161
231
  }
162
232
  }
163
233
  }
234
+ async ensureWindowsCondaActivateEchoPatch() {
235
+ if (this.kernel.platform !== "win32" || !(await this.hasCondaMeta())) {
236
+ return
237
+ }
238
+ const activatePath = this.kernel.bin.path(CONDA_ROOT_DIR, WINDOWS_CONDA_ACTIVATE_RELATIVE_PATH)
239
+ let content
240
+ try {
241
+ content = await fs.promises.readFile(activatePath, "utf8")
242
+ } catch (error) {
243
+ if (error && error.code === "ENOENT") {
244
+ return
245
+ }
246
+ throw error
247
+ }
248
+ if (content.includes(WINDOWS_CONDA_ACTIVATE_PINOKIO_MARKER)) {
249
+ return
250
+ }
251
+ const eol = content.includes("\r\n") ? "\r\n" : "\n"
252
+ const originalBlock = windowsCondaActivateOriginalBlock(eol)
253
+ if (!content.includes(originalBlock)) {
254
+ console.warn("Could not patch Conda CMD activation echo leak; unexpected _conda_activate.bat")
255
+ return
256
+ }
257
+ const routineAnchor = `${eol}:: Routine to delete a temp file if CONDA_DEBUG is not set`
258
+ if (!content.includes(routineAnchor)) {
259
+ console.warn("Could not patch Conda CMD activation echo leak; missing _conda_activate.bat routine anchor")
260
+ return
261
+ }
262
+ const patched = content
263
+ .replace(originalBlock, windowsCondaActivatePatchedBlock(eol))
264
+ .replace(
265
+ routineAnchor,
266
+ `${eol}${windowsCondaActivatePatchRoutines(eol)}:: Routine to delete a temp file if CONDA_DEBUG is not set`
267
+ )
268
+ await fs.promises.writeFile(activatePath, patched)
269
+ }
164
270
  async init() {
165
271
  if (this.kernel.homedir) {
166
272
  console.log("condarc init")
@@ -183,6 +289,7 @@ report_errors: false`)
183
289
  await this.ensureCompatibilityAlias()
184
290
  }
185
291
  await this.ensureWindowsOpenSslHooks()
292
+ await this.ensureWindowsCondaActivateEchoPatch()
186
293
  }
187
294
  }
188
295
  async check() {
@@ -331,6 +438,7 @@ report_errors: false`)
331
438
  )
332
439
  }
333
440
  await this.ensureWindowsOpenSslHooks()
441
+ await this.ensureWindowsCondaActivateEchoPatch()
334
442
  await this.ensureCompatibilityAlias()
335
443
  ondata({ raw: `Install finished\r\n` })
336
444
  await this.kernel.bin.rm(installer, ondata)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.33",
3
+ "version": "7.5.34",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11040,6 +11040,9 @@ const rerenderMenuSection = (container, html) => {
11040
11040
  try {
11041
11041
  setLogsNewBadge(window.sessionStorage.getItem(logsNotificationKey) === "1")
11042
11042
  } catch (_) {}
11043
+ if (document.querySelector(".appcanvas") && document.querySelector(".appcanvas").dataset.pageError === "1") {
11044
+ setLogsNewBadge(true)
11045
+ }
11043
11046
  const pluginInstallPreflightCache = new Map()
11044
11047
  const normalizePluginPathFromLaunchHref = (rawHref) => {
11045
11048
  if (!rawHref || typeof rawHref !== "string") {
@@ -12348,7 +12351,7 @@ const rerenderMenuSection = (container, html) => {
12348
12351
  }
12349
12352
  return
12350
12353
  }
12351
- if (event.data.e === "pinokio:logs-new" || event.data.type === "pinokio:logs-new") {
12354
+ 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") {
12352
12355
  setLogsNewBadge(true)
12353
12356
  return
12354
12357
  }
@@ -125,6 +125,58 @@ const EXPECTED_WINDOWS_OPENSSL_HOOKS = {
125
125
  ].join('\n'),
126
126
  }
127
127
 
128
+ const WINDOWS_CONDA_ACTIVATE_PATH = path.join(
129
+ 'bin',
130
+ 'miniforge',
131
+ 'Lib',
132
+ 'site-packages',
133
+ 'conda',
134
+ 'shell',
135
+ 'condabin',
136
+ '_conda_activate.bat'
137
+ )
138
+ const WINDOWS_CONDA_ACTIVATE_ORIGINAL_BLOCK = [
139
+ ' ) ELSE ENDLOCAL & (',
140
+ ' FOR /F "tokens=1,* delims==" %%A IN (%%T) DO (',
141
+ ' :: A, B = "key", "value"',
142
+ ' IF "%CONDA_DEBUG%" == "1" ECHO DEBUG: "%%A=%%B">&2',
143
+ ' IF "%%A"=="_CONDA_SCRIPT" (',
144
+ ' :: Script execution, fast exit if activation scripts fail',
145
+ ' CALL "%%B" || (',
146
+ ' ECHO ERROR: Activation script "%%B" failed with code %ERRORLEVEL%.>&2',
147
+ ' CALL :DELETE "%%T"',
148
+ ' CALL :DELETE "%__conda_tmp%"',
149
+ ' EXIT /B 4',
150
+ ' )',
151
+ ' ) ELSE IF "%%B"=="" (',
152
+ ' :: Unset variable',
153
+ ' SET "%%A="',
154
+ ' ) ELSE (',
155
+ ' :: Set variable',
156
+ ' SET "%%A=%%B"',
157
+ ' )',
158
+ ' )',
159
+ ' :: Clean up',
160
+ ' CALL :DELETE "%%T"',
161
+ ' CALL :DELETE "%__conda_tmp%"',
162
+ ' EXIT /B 0',
163
+ ' )',
164
+ ].join('\r\n')
165
+ const WINDOWS_CONDA_ACTIVATE_SCRIPT = [
166
+ '@ECHO OFF',
167
+ 'SETLOCAL',
168
+ 'FOR /F "delims=" %%T IN (%__conda_tmp%) DO (',
169
+ ' IF NOT EXIST "%%T" (',
170
+ ' ENDLOCAL & EXIT /B 3',
171
+ WINDOWS_CONDA_ACTIVATE_ORIGINAL_BLOCK,
172
+ ')',
173
+ '',
174
+ ':: Routine to delete a temp file if CONDA_DEBUG is not set',
175
+ ':DELETE',
176
+ 'GOTO :EOF',
177
+ '',
178
+ ].join('\r\n')
179
+
128
180
  async function seedWindowsOpenSslHooks(root, condaRootName) {
129
181
  const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
130
182
  for (const hookRelativePath of Object.keys(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
@@ -143,6 +195,13 @@ async function seedWindowsOpenSslHooks(root, condaRootName) {
143
195
  }
144
196
  }
145
197
 
198
+ async function seedWindowsCondaActivateScript(root, content = WINDOWS_CONDA_ACTIVATE_SCRIPT) {
199
+ const activatePath = path.join(root, WINDOWS_CONDA_ACTIVATE_PATH)
200
+ await fs.mkdir(path.dirname(activatePath), { recursive: true })
201
+ await fs.writeFile(activatePath, content)
202
+ return activatePath
203
+ }
204
+
146
205
  async function assertWindowsOpenSslHooksPatched(root, condaRootName) {
147
206
  const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
148
207
  for (const [hookRelativePath, expectedContent] of Object.entries(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
@@ -203,6 +262,51 @@ test('Conda init patches Windows OpenSSL hooks for legacy Miniconda roots', asyn
203
262
  await assertWindowsOpenSslHooksPatched(root, 'miniconda')
204
263
  })
205
264
 
265
+ test('Conda init patches Windows CMD activation echo leak idempotently', async () => {
266
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-patch-'))
267
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
268
+ const activatePath = await seedWindowsCondaActivateScript(root)
269
+ const conda = createConda(createKernel(root))
270
+
271
+ await conda.init()
272
+ const patched = await fs.readFile(activatePath, 'utf8')
273
+ await conda.init()
274
+
275
+ assert.equal(await fs.readFile(activatePath, 'utf8'), patched)
276
+ assert.equal(patched.includes(WINDOWS_CONDA_ACTIVATE_ORIGINAL_BLOCK), false)
277
+ assert.match(patched, /ENDLOCAL & @CALL :PINOKIO_APPLY_CONDA_ENV "%%T" "%__conda_tmp%" & @GOTO :EOF/)
278
+ assert.match(patched, /:PINOKIO_APPLY_CONDA_ENV/)
279
+ assert.match(patched, /@CALL "%%B" \|\| \(/)
280
+ assert.match(patched, /:: Routine to delete a temp file if CONDA_DEBUG is not set/)
281
+ })
282
+
283
+ test('Conda init leaves unexpected Windows CMD activation script unchanged', async () => {
284
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-unexpected-'))
285
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
286
+ const unexpected = '@ECHO OFF\r\nREM unexpected upstream script\r\n'
287
+ const activatePath = await seedWindowsCondaActivateScript(root, unexpected)
288
+ const conda = createConda(createKernel(root))
289
+
290
+ await conda.init()
291
+
292
+ assert.equal(await fs.readFile(activatePath, 'utf8'), unexpected)
293
+ })
294
+
295
+ test('Conda init leaves Windows CMD activation script unchanged when patch anchor is missing', async () => {
296
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-missing-anchor-'))
297
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
298
+ const missingAnchor = WINDOWS_CONDA_ACTIVATE_SCRIPT.replace(
299
+ '\r\n:: Routine to delete a temp file if CONDA_DEBUG is not set',
300
+ '\r\n:: unexpected routine header'
301
+ )
302
+ const activatePath = await seedWindowsCondaActivateScript(root, missingAnchor)
303
+ const conda = createConda(createKernel(root))
304
+
305
+ await conda.init()
306
+
307
+ assert.equal(await fs.readFile(activatePath, 'utf8'), missingAnchor)
308
+ })
309
+
206
310
  test('Conda pins Python 3.10.20 consistently across platforms', async () => {
207
311
  assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
208
312
  assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
@@ -245,6 +349,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
245
349
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-win32-'))
246
350
  const kernel = createKernel(root, 'win32')
247
351
  const calls = []
352
+ let activatePath
248
353
 
249
354
  kernel.bin.mods = []
250
355
  kernel.bin.download = async () => {}
@@ -255,6 +360,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
255
360
  const miniforge = path.join(root, 'bin', 'miniforge')
256
361
  await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
257
362
  await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
363
+ activatePath = await seedWindowsCondaActivateScript(root)
258
364
  }
259
365
  }
260
366
 
@@ -267,6 +373,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
267
373
  condaInstall.message[1],
268
374
  'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
269
375
  )
376
+ assert.match(await fs.readFile(activatePath, 'utf8'), /:PINOKIO_APPLY_CONDA_ENV/)
270
377
  })
271
378
 
272
379
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {