pinokiod 7.5.36 → 7.5.37
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 +0 -104
- package/kernel/shell.js +44 -1
- package/package.json +1 -1
- package/test/conda-bin.test.js +0 -48
- package/test/shell-conda-runtime-guard.test.js +31 -0
package/kernel/bin/conda.js
CHANGED
|
@@ -69,98 +69,6 @@ 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_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")
|
|
164
72
|
|
|
165
73
|
class Conda {
|
|
166
74
|
description = "Pinokio uses Conda to install various useful programs in an isolated manner."
|
|
@@ -253,16 +161,6 @@ class Conda {
|
|
|
253
161
|
}
|
|
254
162
|
}
|
|
255
163
|
}
|
|
256
|
-
async ensureWindowsCondaActivateBat() {
|
|
257
|
-
if (this.kernel.platform !== "win32" || !(await this.hasCondaMeta())) {
|
|
258
|
-
return
|
|
259
|
-
}
|
|
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)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
164
|
async init() {
|
|
267
165
|
if (this.kernel.homedir) {
|
|
268
166
|
console.log("condarc init")
|
|
@@ -285,7 +183,6 @@ report_errors: false`)
|
|
|
285
183
|
await this.ensureCompatibilityAlias()
|
|
286
184
|
}
|
|
287
185
|
await this.ensureWindowsOpenSslHooks()
|
|
288
|
-
await this.ensureWindowsCondaActivateBat()
|
|
289
186
|
}
|
|
290
187
|
}
|
|
291
188
|
async check() {
|
|
@@ -434,7 +331,6 @@ report_errors: false`)
|
|
|
434
331
|
)
|
|
435
332
|
}
|
|
436
333
|
await this.ensureWindowsOpenSslHooks()
|
|
437
|
-
await this.ensureWindowsCondaActivateBat()
|
|
438
334
|
await this.ensureCompatibilityAlias()
|
|
439
335
|
ondata({ raw: `Install finished\r\n` })
|
|
440
336
|
await this.kernel.bin.rm(installer, ondata)
|
package/kernel/shell.js
CHANGED
|
@@ -23,6 +23,7 @@ const ShellRunTemplate = require('./api/shell_run_template')
|
|
|
23
23
|
const { PYTHON_INSTALL_SPEC } = require('./bin/conda-pins')
|
|
24
24
|
const CondaRuntimeGuard = require('./shell_conda_runtime_guard')
|
|
25
25
|
const home = os.homedir()
|
|
26
|
+
const WINDOWS_CMD_CONDA_COMMAND_PATTERN = /(?:^|[&|()]\s*)@?\s*(?:call\s+)?(?:"[^"]*[\\/]conda(?:\.bat|\.exe)?"|(?:[a-z]:)?[^&|()\s"]*[\\/]conda(?:\.bat|\.exe)?|conda(?:\.bat|\.exe)?)\s+/i
|
|
26
27
|
|
|
27
28
|
function normalizeComparablePath(filePath, platform) {
|
|
28
29
|
const normalized = path.normalize(filePath)
|
|
@@ -335,6 +336,33 @@ class Shell {
|
|
|
335
336
|
const name = (shellName || '').toLowerCase()
|
|
336
337
|
return name.includes('powershell') || name.includes('pwsh')
|
|
337
338
|
}
|
|
339
|
+
hasCondaCommand(message) {
|
|
340
|
+
if (Array.isArray(message)) {
|
|
341
|
+
return message.some((item) => this.hasCondaCommand(item))
|
|
342
|
+
}
|
|
343
|
+
if (message && message.constructor === Object) {
|
|
344
|
+
return this.hasCondaCommand(this.buildStructuredMessage(message))
|
|
345
|
+
}
|
|
346
|
+
return typeof message === "string" && WINDOWS_CMD_CONDA_COMMAND_PATTERN.test(message)
|
|
347
|
+
}
|
|
348
|
+
shouldSuppressCmdEchoForConda(params) {
|
|
349
|
+
return this.platform === "win32" &&
|
|
350
|
+
this.isCmdShell() &&
|
|
351
|
+
!(params && params.input) &&
|
|
352
|
+
this.hasCondaCommand(params && params.message)
|
|
353
|
+
}
|
|
354
|
+
buildCmdEchoSuppressedCommand(command) {
|
|
355
|
+
return `@echo off\r\n${command}\r\n@echo on`
|
|
356
|
+
}
|
|
357
|
+
prepareCommandExecution(params, command) {
|
|
358
|
+
if (!this.shouldSuppressCmdEchoForConda(params)) {
|
|
359
|
+
return { command }
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
command: this.buildCmdEchoSuppressedCommand(command),
|
|
363
|
+
preview: command,
|
|
364
|
+
}
|
|
365
|
+
}
|
|
338
366
|
isUnresolvedTemplate(value) {
|
|
339
367
|
return typeof value === "string" && /^\{\{[\s\S]*\}\}$/.test(value)
|
|
340
368
|
}
|
|
@@ -413,6 +441,9 @@ class Shell {
|
|
|
413
441
|
this.decsyncBuffer = ''
|
|
414
442
|
this.stateSync.reset()
|
|
415
443
|
this.envArgsPreviewed = false
|
|
444
|
+
this.commandEchoPreview = null
|
|
445
|
+
this.commandEchoPreviewed = false
|
|
446
|
+
this.exec_cmd = null
|
|
416
447
|
|
|
417
448
|
/*
|
|
418
449
|
params := {
|
|
@@ -1399,8 +1430,16 @@ class Shell {
|
|
|
1399
1430
|
async exec(params) {
|
|
1400
1431
|
this.parser = new ShellParser()
|
|
1401
1432
|
this.emitEnvArgsPreview(params)
|
|
1433
|
+
const originalParams = {
|
|
1434
|
+
input: params && params.input,
|
|
1435
|
+
message: params && params.message,
|
|
1436
|
+
}
|
|
1402
1437
|
params = await this.activate(params)
|
|
1403
1438
|
this.cmd = this.build(params)
|
|
1439
|
+
const preparedCommand = this.prepareCommandExecution(originalParams, this.cmd)
|
|
1440
|
+
this.exec_cmd = preparedCommand.command
|
|
1441
|
+
this.commandEchoPreview = preparedCommand.preview || null
|
|
1442
|
+
this.commandEchoPreviewed = false
|
|
1404
1443
|
let res = await new Promise((resolve, reject) => {
|
|
1405
1444
|
this.resolve = resolve
|
|
1406
1445
|
this.reject = reject
|
|
@@ -1853,7 +1892,11 @@ ${cleaned}
|
|
|
1853
1892
|
}
|
|
1854
1893
|
if (this.ptyProcess) {
|
|
1855
1894
|
this.stateSync.noteInput()
|
|
1856
|
-
|
|
1895
|
+
if (this.commandEchoPreview && !this.commandEchoPreviewed) {
|
|
1896
|
+
this.commandEchoPreviewed = true
|
|
1897
|
+
this.queue.push(`${this.commandEchoPreview}${this.EOL}`)
|
|
1898
|
+
}
|
|
1899
|
+
this.ptyProcess.write(`${this.exec_cmd || this.cmd}${this.EOL}`)
|
|
1857
1900
|
// setTimeout(() => {
|
|
1858
1901
|
// this.ptyProcess.write('\x1B[?2004h');
|
|
1859
1902
|
// }, 500)
|
package/package.json
CHANGED
package/test/conda-bin.test.js
CHANGED
|
@@ -125,24 +125,6 @@ const EXPECTED_WINDOWS_OPENSSL_HOOKS = {
|
|
|
125
125
|
].join('\n'),
|
|
126
126
|
}
|
|
127
127
|
|
|
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 = [
|
|
142
|
-
' ) ELSE ENDLOCAL & (',
|
|
143
|
-
' FOR /F "tokens=1,* delims==" %%A IN (%%T) DO (',
|
|
144
|
-
].join('\r\n')
|
|
145
|
-
|
|
146
128
|
async function seedWindowsOpenSslHooks(root, condaRootName) {
|
|
147
129
|
const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
|
|
148
130
|
for (const hookRelativePath of Object.keys(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
|
|
@@ -161,15 +143,6 @@ async function seedWindowsOpenSslHooks(root, condaRootName) {
|
|
|
161
143
|
}
|
|
162
144
|
}
|
|
163
145
|
|
|
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
|
|
171
|
-
}
|
|
172
|
-
|
|
173
146
|
async function assertWindowsOpenSslHooksPatched(root, condaRootName) {
|
|
174
147
|
const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
|
|
175
148
|
for (const [hookRelativePath, expectedContent] of Object.entries(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
|
|
@@ -230,24 +203,6 @@ test('Conda init patches Windows OpenSSL hooks for legacy Miniconda roots', asyn
|
|
|
230
203
|
await assertWindowsOpenSslHooksPatched(root, 'miniconda')
|
|
231
204
|
})
|
|
232
205
|
|
|
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-'))
|
|
235
|
-
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
236
|
-
const activatePaths = await seedWindowsCondaActivateScripts(root)
|
|
237
|
-
const conda = createConda(createKernel(root))
|
|
238
|
-
|
|
239
|
-
await conda.init()
|
|
240
|
-
await conda.init()
|
|
241
|
-
|
|
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/)
|
|
249
|
-
})
|
|
250
|
-
|
|
251
206
|
test('Conda pins Python 3.10.20 consistently across platforms', async () => {
|
|
252
207
|
assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
|
|
253
208
|
assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
|
|
@@ -312,9 +267,6 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
|
|
|
312
267
|
condaInstall.message[1],
|
|
313
268
|
'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
|
|
314
269
|
)
|
|
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
|
-
}
|
|
318
270
|
})
|
|
319
271
|
|
|
320
272
|
test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
|
|
@@ -917,6 +917,37 @@ test('internal shell activation inspects only when shell.run guard option is set
|
|
|
917
917
|
assert.equal(events.some((event) => event.type === 'notify'), true)
|
|
918
918
|
})
|
|
919
919
|
|
|
920
|
+
test('Windows cmd suppresses echo only for explicit conda shell.run commands', () => {
|
|
921
|
+
const shell = new Shell({ bracketedPasteSupport: {} })
|
|
922
|
+
shell.platform = 'win32'
|
|
923
|
+
shell.shell = 'cmd.exe'
|
|
924
|
+
const command = 'conda_hook & conda deactivate & conda activate base & conda install -y -c conda-forge ffmpeg'
|
|
925
|
+
|
|
926
|
+
assert.equal(shell.shouldSuppressCmdEchoForConda({
|
|
927
|
+
message: 'conda install -y -c conda-forge ffmpeg',
|
|
928
|
+
}), true)
|
|
929
|
+
assert.equal(shell.shouldSuppressCmdEchoForConda({
|
|
930
|
+
message: 'python main.py',
|
|
931
|
+
}), false)
|
|
932
|
+
assert.equal(shell.shouldSuppressCmdEchoForConda({
|
|
933
|
+
message: 'conda_hook',
|
|
934
|
+
}), false)
|
|
935
|
+
assert.equal(shell.shouldSuppressCmdEchoForConda({
|
|
936
|
+
input: true,
|
|
937
|
+
message: 'conda install -y -c conda-forge ffmpeg',
|
|
938
|
+
}), false)
|
|
939
|
+
|
|
940
|
+
const prepared = shell.prepareCommandExecution({
|
|
941
|
+
message: 'conda install -y -c conda-forge ffmpeg',
|
|
942
|
+
}, command)
|
|
943
|
+
assert.equal(prepared.preview, command)
|
|
944
|
+
assert.equal(prepared.command, `@echo off\r\n${command}\r\n@echo on`)
|
|
945
|
+
|
|
946
|
+
assert.deepEqual(shell.prepareCommandExecution({
|
|
947
|
+
message: 'python main.py',
|
|
948
|
+
}, command), { command })
|
|
949
|
+
})
|
|
950
|
+
|
|
920
951
|
test('rewrite warning is scoped to each user-visible shell flow', async () => {
|
|
921
952
|
CondaRuntimeGuard.resetNoticeSessionsForTest()
|
|
922
953
|
const { context, events, root } = createContext()
|