pinokiod 7.5.32 → 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.
@@ -1,6 +1,6 @@
1
1
  const semver = require('semver')
2
2
 
3
- const CONDA_PIN_VERSION = "26.5.2"
3
+ const CONDA_PIN_VERSION = "26.3.2"
4
4
  const PYTHON_PIN_VERSION = "3.10.20"
5
5
  const PYTHON_INSTALL_SPEC = `python=${PYTHON_PIN_VERSION}`
6
6
  const WINDOWS_PYTHON_SSL_FIX_SPEC = `${PYTHON_INSTALL_SPEC}=*_1_cpython`
@@ -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() {
@@ -205,8 +312,7 @@ report_errors: false`)
205
312
  conda_versions[name] = version
206
313
  conda_builds[name] = build
207
314
  if (name === "conda") {
208
- const coerced = semver.coerce(version)
209
- conda_check.conda = coerced && semver.eq(coerced, CONDA_PIN_VERSION)
315
+ conda_check.conda = true
210
316
  }
211
317
  // check conda-libmamba-solver is up to date
212
318
  // sometimes it just fails silently so need to check
@@ -309,7 +415,6 @@ report_errors: false`)
309
415
 
310
416
  let condaPackages = [
311
417
  this.kernel.platform === "win32" ? `"${WINDOWS_PYTHON_SSL_FIX_SPEC}"` : `"${PYTHON_INSTALL_SPEC}"`,
312
- `"conda=${CONDA_PIN_VERSION}"`,
313
418
  `"conda-libmamba-solver>=25.4.0"`,
314
419
  ]
315
420
 
@@ -333,6 +438,7 @@ report_errors: false`)
333
438
  )
334
439
  }
335
440
  await this.ensureWindowsOpenSslHooks()
441
+ await this.ensureWindowsCondaActivateEchoPatch()
336
442
  await this.ensureCompatibilityAlias()
337
443
  ondata({ raw: `Install finished\r\n` })
338
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.32",
3
+ "version": "7.5.34",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,7 +7,6 @@ const test = require('node:test')
7
7
 
8
8
  const Conda = require('../kernel/bin/conda')
9
9
  const {
10
- CONDA_PIN_VERSION,
11
10
  PYTHON_INSTALL_SPEC,
12
11
  WINDOWS_PYTHON_SSL_FIX_SPEC,
13
12
  isExpectedPythonPinned,
@@ -43,7 +42,7 @@ async function writeCondaMeta(root, name, version, build = 'py_0') {
43
42
  }
44
43
 
45
44
  async function writeHealthyCondaMeta(root, platform = 'win32') {
46
- await writeCondaMeta(root, 'conda', CONDA_PIN_VERSION, 'py310')
45
+ await writeCondaMeta(root, 'conda', '26.3.2', 'py310')
47
46
  await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
48
47
  await writeCondaMeta(root, 'python', '3.10.20', platform === 'win32' ? 'h4de0772_1_cpython' : 'cpython')
49
48
  }
@@ -126,6 +125,58 @@ const EXPECTED_WINDOWS_OPENSSL_HOOKS = {
126
125
  ].join('\n'),
127
126
  }
128
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
+
129
180
  async function seedWindowsOpenSslHooks(root, condaRootName) {
130
181
  const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
131
182
  for (const hookRelativePath of Object.keys(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
@@ -144,6 +195,13 @@ async function seedWindowsOpenSslHooks(root, condaRootName) {
144
195
  }
145
196
  }
146
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
+
147
205
  async function assertWindowsOpenSslHooksPatched(root, condaRootName) {
148
206
  const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
149
207
  for (const [hookRelativePath, expectedContent] of Object.entries(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
@@ -204,8 +262,52 @@ test('Conda init patches Windows OpenSSL hooks for legacy Miniconda roots', asyn
204
262
  await assertWindowsOpenSslHooksPatched(root, 'miniconda')
205
263
  })
206
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
+
207
310
  test('Conda pins Python 3.10.20 consistently across platforms', async () => {
208
- assert.equal(CONDA_PIN_VERSION, '26.5.2')
209
311
  assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
210
312
  assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
211
313
 
@@ -239,7 +341,7 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
239
341
  assert.ok(condaInstall)
240
342
  assert.equal(
241
343
  condaInstall.message[1],
242
- 'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda=26.5.2" "conda-libmamba-solver>=25.4.0"'
344
+ 'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
243
345
  )
244
346
  })
245
347
 
@@ -247,6 +349,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
247
349
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-win32-'))
248
350
  const kernel = createKernel(root, 'win32')
249
351
  const calls = []
352
+ let activatePath
250
353
 
251
354
  kernel.bin.mods = []
252
355
  kernel.bin.download = async () => {}
@@ -257,6 +360,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
257
360
  const miniforge = path.join(root, 'bin', 'miniforge')
258
361
  await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
259
362
  await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
363
+ activatePath = await seedWindowsCondaActivateScript(root)
260
364
  }
261
365
  }
262
366
 
@@ -267,8 +371,9 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
267
371
  assert.ok(condaInstall)
268
372
  assert.equal(
269
373
  condaInstall.message[1],
270
- 'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda=26.5.2" "conda-libmamba-solver>=25.4.0"'
374
+ 'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
271
375
  )
376
+ assert.match(await fs.readFile(activatePath, 'utf8'), /:PINOKIO_APPLY_CONDA_ENV/)
272
377
  })
273
378
 
274
379
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
@@ -341,16 +446,6 @@ test('Conda check accepts a runnable managed Windows conda executable', async ()
341
446
  assert.equal(await createConda(createKernel(root, 'win32')).check(), true)
342
447
  })
343
448
 
344
- test('Conda check rejects runnable installs with the wrong Conda version', async () => {
345
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-wrong-version-'))
346
- await writeCondaMeta(root, 'conda', '26.3.2', 'py310')
347
- await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
348
- await writeCondaMeta(root, 'python', '3.10.20', 'h4de0772_1_cpython')
349
- await writeFakeManagedConda(root, 'win32')
350
-
351
- assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
352
- })
353
-
354
449
  test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
355
450
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-download-fails-'))
356
451
  const miniconda = await createLegacyMiniconda(root)