pinokiod 7.5.34 → 7.5.36

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,76 +69,98 @@ 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
+ const WINDOWS_CONDA_ACTIVATE_RELATIVE_PATHS = [
73
+ path.join("condabin", "_conda_activate.bat"),
74
+ path.join("Lib", "site-packages", "conda", "shell", "condabin", "_conda_activate.bat"),
75
+ ]
76
+ const WINDOWS_CONDA_ACTIVATE_BAT = [
77
+ ":: Copyright (C) 2012 Anaconda, Inc",
78
+ ":: SPDX-License-Identifier: BSD-3-Clause",
79
+ ":: Helper routine for activation, deactivation, and reactivation.",
80
+ "@ECHO OFF",
81
+ "SETLOCAL",
82
+ "",
83
+ ":: Generate a unique temporary filename using a GUID to support parallel workflows",
84
+ ":: (e.g., `start /b conda run ...`). %RANDOM% is insufficient because processes",
85
+ ":: started simultaneously get identical time-based seeds, producing collisions.",
86
+ ":: See: https://devblogs.microsoft.com/oldnewthing/20100617-00/?p=13673",
87
+ "FOR /F \"delims=\" %%G IN ('powershell -NoProfile -Command \"[guid]::NewGuid()\"') DO SET \"__conda_guid=%%G\"",
88
+ "SET \"__conda_tmp=%TEMP%\\__conda_tmp_%__conda_guid%.txt\"",
89
+ "",
90
+ ":: Run conda command and get its output",
91
+ ":: WARNING: This cannot be simplified into a FOR /F parsing loop because of the way",
92
+ ":: MSDOS associates the outer quotes for usebackq. E.g., the following:",
93
+ ":: `\"%CONDA_EXE%\" shell.cmd.exe activate \"name\"`",
94
+ ":: Which results in the following gibberish being run:",
95
+ ":: %CONDA_EXE%\" shell.cmd.exe activate \"environment",
96
+ ":: Producing an error like:",
97
+ ":: The filename, directory name, or volume label syntax is incorrect.",
98
+ ":: Instead we run the command and store the output for subsequent processing.",
99
+ "\"%CONDA_EXE%\" %_CE_M% %_CE_CONDA% shell.cmd.exe %* > \"%__conda_tmp%\"",
100
+ "IF NOT EXIST \"%__conda_tmp%\" (",
101
+ " ECHO ERROR: Failed to create temp file for 'conda %*'.>&2",
102
+ " ECHO.>&2",
103
+ " ECHO This is likely a TEMP directory issue>&2",
104
+ " ECHO ^(permissions, disk space, or invalid path^).>&2",
105
+ " ECHO.>&2",
106
+ " ECHO Ensure TEMP or TMP environment variables point to a writable location.>&2",
107
+ " ECHO See: https://docs.conda.io/projects/conda/en/stable/user-guide/troubleshooting.html#temp-file-errors>&2",
108
+ " ENDLOCAL & EXIT /B 1",
109
+ ") ELSE IF %ERRORLEVEL% NEQ 0 (",
110
+ " ECHO ERROR: 'conda %*' exited with code %ERRORLEVEL%.>&2",
111
+ " CALL :DELETE \"%__conda_tmp%\"",
112
+ " ENDLOCAL & EXIT /B 2",
113
+ ")",
114
+ "",
115
+ ":: Check if conda produced output",
116
+ "FOR /F \"delims=\" %%T IN (%__conda_tmp%) DO (",
117
+ " :: T = \"%TEMP%\\<uuid>.env\"",
118
+ " IF NOT EXIST \"%%T\" (",
119
+ " ECHO ERROR: Activation file missing for 'conda %*'.>&2",
120
+ " CALL :DELETE \"%__conda_tmp%\"",
121
+ " ENDLOCAL & EXIT /B 3",
122
+ " ) ELSE ENDLOCAL & @CALL :PINOKIO_APPLY_CONDA_ENV \"%%T\" \"%__conda_tmp%\" & @GOTO :EOF",
123
+ ")",
124
+ "",
125
+ ":: If we get here, the FOR loop never ran which means no output",
126
+ "ECHO ERROR: No output from 'conda %*'.>&2",
127
+ "CALL :DELETE \"%__conda_tmp%\"",
128
+ "ENDLOCAL & EXIT /B 5",
129
+ "",
130
+ ":PINOKIO_APPLY_CONDA_ENV",
131
+ "@ECHO OFF",
132
+ "@FOR /F \"tokens=1,* delims==\" %%A IN (%~1) DO (",
133
+ "@IF \"%CONDA_DEBUG%\" == \"1\" ECHO DEBUG: \"%%A=%%B\">&2",
134
+ "@IF \"%%A\"==\"_CONDA_SCRIPT\" (",
135
+ "@CALL \"%%B\" || (",
136
+ "@ECHO ERROR: Activation script \"%%B\" failed with code %ERRORLEVEL%.>&2",
137
+ "@CALL :DELETE \"%~1\"",
138
+ "@CALL :DELETE \"%~2\"",
139
+ "@ECHO ON",
140
+ "@EXIT /B 4",
141
+ ")",
142
+ ") ELSE IF \"%%B\"==\"\" (",
143
+ "@SET \"%%A=\"",
144
+ ") ELSE (",
145
+ "@SET \"%%A=%%B\"",
146
+ ")",
147
+ ")",
148
+ "@CALL :DELETE \"%~1\"",
149
+ "@CALL :DELETE \"%~2\"",
150
+ "@ECHO ON",
151
+ "@EXIT /B 0",
152
+ "",
153
+ ":: Routine to delete a temp file if CONDA_DEBUG is not set",
154
+ ":: usage: call :DELETE \"path/to/file\"",
155
+ ":DELETE",
156
+ "IF \"%CONDA_DEBUG%\" == \"1\" (",
157
+ " ECHO DEBUG: Retaining temporary file \"%~1\".>&2",
158
+ ") ELSE IF EXIST \"%~1\" (",
159
+ " DEL /F /Q \"%~1\" 2>NUL",
160
+ ")",
161
+ "GOTO :EOF",
162
+ "",
163
+ ].join("\r\n")
142
164
 
143
165
  class Conda {
144
166
  description = "Pinokio uses Conda to install various useful programs in an isolated manner."
@@ -231,41 +253,15 @@ class Conda {
231
253
  }
232
254
  }
233
255
  }
234
- async ensureWindowsCondaActivateEchoPatch() {
256
+ async ensureWindowsCondaActivateBat() {
235
257
  if (this.kernel.platform !== "win32" || !(await this.hasCondaMeta())) {
236
258
  return
237
259
  }
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
260
+ for (const relativePath of WINDOWS_CONDA_ACTIVATE_RELATIVE_PATHS) {
261
+ const activatePath = this.kernel.bin.path(CONDA_ROOT_DIR, relativePath)
262
+ await fs.promises.mkdir(path.dirname(activatePath), { recursive: true })
263
+ await fs.promises.writeFile(activatePath, WINDOWS_CONDA_ACTIVATE_BAT)
261
264
  }
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
265
  }
270
266
  async init() {
271
267
  if (this.kernel.homedir) {
@@ -289,7 +285,7 @@ report_errors: false`)
289
285
  await this.ensureCompatibilityAlias()
290
286
  }
291
287
  await this.ensureWindowsOpenSslHooks()
292
- await this.ensureWindowsCondaActivateEchoPatch()
288
+ await this.ensureWindowsCondaActivateBat()
293
289
  }
294
290
  }
295
291
  async check() {
@@ -438,7 +434,7 @@ report_errors: false`)
438
434
  )
439
435
  }
440
436
  await this.ensureWindowsOpenSslHooks()
441
- await this.ensureWindowsCondaActivateEchoPatch()
437
+ await this.ensureWindowsCondaActivateBat()
442
438
  await this.ensureCompatibilityAlias()
443
439
  ondata({ raw: `Install finished\r\n` })
444
440
  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.34",
3
+ "version": "7.5.36",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -125,56 +125,22 @@ 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 = [
128
+ const WINDOWS_CONDA_ACTIVATE_PATHS = [
129
+ path.join('bin', 'miniforge', 'condabin', '_conda_activate.bat'),
130
+ path.join(
131
+ 'bin',
132
+ 'miniforge',
133
+ 'Lib',
134
+ 'site-packages',
135
+ 'conda',
136
+ 'shell',
137
+ 'condabin',
138
+ '_conda_activate.bat'
139
+ ),
140
+ ]
141
+ const WINDOWS_CONDA_ACTIVATE_LEAK_BLOCK = [
139
142
  ' ) ELSE ENDLOCAL & (',
140
143
  ' 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
144
  ].join('\r\n')
179
145
 
180
146
  async function seedWindowsOpenSslHooks(root, condaRootName) {
@@ -195,11 +161,13 @@ async function seedWindowsOpenSslHooks(root, condaRootName) {
195
161
  }
196
162
  }
197
163
 
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
164
+ async function seedWindowsCondaActivateScripts(root, content = 'corrupted helper\r\n') {
165
+ const activatePaths = WINDOWS_CONDA_ACTIVATE_PATHS.map((activatePath) => path.join(root, activatePath))
166
+ for (const activatePath of activatePaths) {
167
+ await fs.mkdir(path.dirname(activatePath), { recursive: true })
168
+ await fs.writeFile(activatePath, content)
169
+ }
170
+ return activatePaths
203
171
  }
204
172
 
205
173
  async function assertWindowsOpenSslHooksPatched(root, condaRootName) {
@@ -262,49 +230,22 @@ test('Conda init patches Windows OpenSSL hooks for legacy Miniconda roots', asyn
262
230
  await assertWindowsOpenSslHooksPatched(root, 'miniconda')
263
231
  })
264
232
 
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-'))
233
+ test('Conda init replaces Windows CMD activation helper files', async () => {
234
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-cmd-activate-replace-'))
285
235
  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)
236
+ const activatePaths = await seedWindowsCondaActivateScripts(root)
288
237
  const conda = createConda(createKernel(root))
289
238
 
290
239
  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
240
  await conda.init()
306
241
 
307
- assert.equal(await fs.readFile(activatePath, 'utf8'), missingAnchor)
242
+ const contents = await Promise.all(activatePaths.map((activatePath) => fs.readFile(activatePath, 'utf8')))
243
+ assert.equal(contents[0], contents[1])
244
+ assert.equal(contents[0].includes(WINDOWS_CONDA_ACTIVATE_LEAK_BLOCK), false)
245
+ assert.match(contents[0], /ENDLOCAL & @CALL :PINOKIO_APPLY_CONDA_ENV "%%T" "%__conda_tmp%" & @GOTO :EOF/)
246
+ assert.match(contents[0], /:PINOKIO_APPLY_CONDA_ENV\r\n@ECHO OFF/)
247
+ assert.match(contents[0], /@CALL "%%B" \|\| \(/)
248
+ assert.match(contents[0], /@ECHO ON\r\n@EXIT \/B 0/)
308
249
  })
309
250
 
310
251
  test('Conda pins Python 3.10.20 consistently across platforms', async () => {
@@ -349,7 +290,6 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
349
290
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-win32-'))
350
291
  const kernel = createKernel(root, 'win32')
351
292
  const calls = []
352
- let activatePath
353
293
 
354
294
  kernel.bin.mods = []
355
295
  kernel.bin.download = async () => {}
@@ -360,7 +300,6 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
360
300
  const miniforge = path.join(root, 'bin', 'miniforge')
361
301
  await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
362
302
  await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
363
- activatePath = await seedWindowsCondaActivateScript(root)
364
303
  }
365
304
  }
366
305
 
@@ -373,7 +312,9 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
373
312
  condaInstall.message[1],
374
313
  'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
375
314
  )
376
- assert.match(await fs.readFile(activatePath, 'utf8'), /:PINOKIO_APPLY_CONDA_ENV/)
315
+ for (const activatePath of WINDOWS_CONDA_ACTIVATE_PATHS.map((activatePath) => path.join(root, activatePath))) {
316
+ assert.match(await fs.readFile(activatePath, 'utf8'), /:PINOKIO_APPLY_CONDA_ENV/)
317
+ }
377
318
  })
378
319
 
379
320
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {