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.
- package/kernel/bin/conda.js +108 -0
- package/package.json +1 -1
- package/server/views/app.ejs +4 -1
- package/test/conda-bin.test.js +107 -0
package/kernel/bin/conda.js
CHANGED
|
@@ -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
package/server/views/app.ejs
CHANGED
|
@@ -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
|
}
|
package/test/conda-bin.test.js
CHANGED
|
@@ -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 () => {
|