pinokiod 7.5.33 → 7.5.35

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,79 @@ 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
+ "@ECHO OFF",
122
+ '@FOR /F "tokens=1,* delims==" %%A IN (%~1) DO (',
123
+ '@IF "%CONDA_DEBUG%" == "1" ECHO DEBUG: "%%A=%%B">&2',
124
+ '@IF "%%A"=="_CONDA_SCRIPT" (',
125
+ '@CALL "%%B" || (',
126
+ '@ECHO ERROR: Activation script "%%B" failed with code %ERRORLEVEL%.>&2',
127
+ '@CALL :DELETE "%~1"',
128
+ '@CALL :DELETE "%~2"',
129
+ "@ECHO ON",
130
+ "@EXIT /B 4",
131
+ ")",
132
+ ') ELSE IF "%%B"=="" (',
133
+ '@SET "%%A="',
134
+ ") ELSE (",
135
+ '@SET "%%A=%%B"',
136
+ ")",
137
+ ")",
138
+ '@CALL :DELETE "%~1"',
139
+ '@CALL :DELETE "%~2"',
140
+ "@ECHO ON",
141
+ "@EXIT /B 0",
142
+ "",
143
+ ].join(eol)
144
+ }
72
145
 
73
146
  class Conda {
74
147
  description = "Pinokio uses Conda to install various useful programs in an isolated manner."
@@ -161,6 +234,57 @@ class Conda {
161
234
  }
162
235
  }
163
236
  }
237
+ async ensureWindowsCondaActivateEchoPatch() {
238
+ if (this.kernel.platform !== "win32" || !(await this.hasCondaMeta())) {
239
+ return
240
+ }
241
+ const activatePath = this.kernel.bin.path(CONDA_ROOT_DIR, WINDOWS_CONDA_ACTIVATE_RELATIVE_PATH)
242
+ let content
243
+ try {
244
+ content = await fs.promises.readFile(activatePath, "utf8")
245
+ } catch (error) {
246
+ if (error && error.code === "ENOENT") {
247
+ return
248
+ }
249
+ throw error
250
+ }
251
+ const eol = content.includes("\r\n") ? "\r\n" : "\n"
252
+ const routineAnchor = `${eol}:: Routine to delete a temp file if CONDA_DEBUG is not set`
253
+ if (!content.includes(routineAnchor)) {
254
+ console.warn("Could not patch Conda CMD activation echo leak; missing _conda_activate.bat routine anchor")
255
+ return
256
+ }
257
+ if (content.includes(WINDOWS_CONDA_ACTIVATE_PINOKIO_MARKER)) {
258
+ if (content.includes(`${WINDOWS_CONDA_ACTIVATE_PINOKIO_MARKER}${eol}@ECHO OFF`)) {
259
+ return
260
+ }
261
+ const routineStart = content.indexOf(WINDOWS_CONDA_ACTIVATE_PINOKIO_MARKER)
262
+ const routineAnchorIndex = content.indexOf(routineAnchor, routineStart)
263
+ if (routineAnchorIndex < 0) {
264
+ console.warn("Could not update Conda CMD activation echo patch; missing _conda_activate.bat routine anchor")
265
+ return
266
+ }
267
+ const patched = [
268
+ content.slice(0, routineStart),
269
+ windowsCondaActivatePatchRoutines(eol),
270
+ content.slice(routineAnchorIndex + eol.length),
271
+ ].join("")
272
+ await fs.promises.writeFile(activatePath, patched)
273
+ return
274
+ }
275
+ const originalBlock = windowsCondaActivateOriginalBlock(eol)
276
+ if (!content.includes(originalBlock)) {
277
+ console.warn("Could not patch Conda CMD activation echo leak; unexpected _conda_activate.bat")
278
+ return
279
+ }
280
+ const patched = content
281
+ .replace(originalBlock, windowsCondaActivatePatchedBlock(eol))
282
+ .replace(
283
+ routineAnchor,
284
+ `${eol}${windowsCondaActivatePatchRoutines(eol)}:: Routine to delete a temp file if CONDA_DEBUG is not set`
285
+ )
286
+ await fs.promises.writeFile(activatePath, patched)
287
+ }
164
288
  async init() {
165
289
  if (this.kernel.homedir) {
166
290
  console.log("condarc init")
@@ -183,6 +307,7 @@ report_errors: false`)
183
307
  await this.ensureCompatibilityAlias()
184
308
  }
185
309
  await this.ensureWindowsOpenSslHooks()
310
+ await this.ensureWindowsCondaActivateEchoPatch()
186
311
  }
187
312
  }
188
313
  async check() {
@@ -331,6 +456,7 @@ report_errors: false`)
331
456
  )
332
457
  }
333
458
  await this.ensureWindowsOpenSslHooks()
459
+ await this.ensureWindowsCondaActivateEchoPatch()
334
460
  await this.ensureCompatibilityAlias()
335
461
  ondata({ raw: `Install finished\r\n` })
336
462
  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.35",
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,83 @@ 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\r\n@ECHO OFF/)
279
+ assert.match(patched, /@CALL "%%B" \|\| \(/)
280
+ assert.match(patched, /@ECHO ON\r\n@EXIT \/B 0/)
281
+ assert.match(patched, /:: Routine to delete a temp file if CONDA_DEBUG is not set/)
282
+ })
283
+
284
+ test('Conda init updates the earlier Windows CMD activation echo patch', async () => {
285
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-update-patch-'))
286
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
287
+ const oldPatch = WINDOWS_CONDA_ACTIVATE_SCRIPT
288
+ .replace(
289
+ WINDOWS_CONDA_ACTIVATE_ORIGINAL_BLOCK,
290
+ ' ) ELSE ENDLOCAL & @CALL :PINOKIO_APPLY_CONDA_ENV "%%T" "%__conda_tmp%" & @GOTO :EOF'
291
+ )
292
+ .replace(
293
+ '\r\n:: Routine to delete a temp file if CONDA_DEBUG is not set',
294
+ [
295
+ '\r\n:PINOKIO_APPLY_CONDA_ENV',
296
+ '@FOR /F "tokens=1,* delims==" %%A IN (%~1) DO (',
297
+ '@IF "%CONDA_DEBUG%" == "1" ECHO DEBUG: "%%A=%%B">&2',
298
+ ')',
299
+ '@CALL :DELETE "%~1"',
300
+ '@CALL :DELETE "%~2"',
301
+ '@EXIT /B 0',
302
+ ':: Routine to delete a temp file if CONDA_DEBUG is not set',
303
+ ].join('\r\n')
304
+ )
305
+ const activatePath = await seedWindowsCondaActivateScript(root, oldPatch)
306
+ const conda = createConda(createKernel(root))
307
+
308
+ await conda.init()
309
+
310
+ const patched = await fs.readFile(activatePath, 'utf8')
311
+ assert.match(patched, /:PINOKIO_APPLY_CONDA_ENV\r\n@ECHO OFF/)
312
+ assert.match(patched, /@ECHO ON\r\n@EXIT \/B 0/)
313
+ })
314
+
315
+ test('Conda init leaves unexpected Windows CMD activation script unchanged', async () => {
316
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-unexpected-'))
317
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
318
+ const unexpected = '@ECHO OFF\r\nREM unexpected upstream script\r\n'
319
+ const activatePath = await seedWindowsCondaActivateScript(root, unexpected)
320
+ const conda = createConda(createKernel(root))
321
+
322
+ await conda.init()
323
+
324
+ assert.equal(await fs.readFile(activatePath, 'utf8'), unexpected)
325
+ })
326
+
327
+ test('Conda init leaves Windows CMD activation script unchanged when patch anchor is missing', async () => {
328
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-missing-anchor-'))
329
+ await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
330
+ const missingAnchor = WINDOWS_CONDA_ACTIVATE_SCRIPT.replace(
331
+ '\r\n:: Routine to delete a temp file if CONDA_DEBUG is not set',
332
+ '\r\n:: unexpected routine header'
333
+ )
334
+ const activatePath = await seedWindowsCondaActivateScript(root, missingAnchor)
335
+ const conda = createConda(createKernel(root))
336
+
337
+ await conda.init()
338
+
339
+ assert.equal(await fs.readFile(activatePath, 'utf8'), missingAnchor)
340
+ })
341
+
206
342
  test('Conda pins Python 3.10.20 consistently across platforms', async () => {
207
343
  assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
208
344
  assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
@@ -245,6 +381,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
245
381
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-win32-'))
246
382
  const kernel = createKernel(root, 'win32')
247
383
  const calls = []
384
+ let activatePath
248
385
 
249
386
  kernel.bin.mods = []
250
387
  kernel.bin.download = async () => {}
@@ -255,6 +392,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
255
392
  const miniforge = path.join(root, 'bin', 'miniforge')
256
393
  await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
257
394
  await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
395
+ activatePath = await seedWindowsCondaActivateScript(root)
258
396
  }
259
397
  }
260
398
 
@@ -267,6 +405,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
267
405
  condaInstall.message[1],
268
406
  'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
269
407
  )
408
+ assert.match(await fs.readFile(activatePath, 'utf8'), /:PINOKIO_APPLY_CONDA_ENV/)
270
409
  })
271
410
 
272
411
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {