pinokiod 8.0.1 → 8.0.3
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-pins.js +4 -1
- package/kernel/bin/conda.js +3 -96
- package/kernel/bin/index.js +2 -4
- package/kernel/shell.js +8 -0
- package/package.json +1 -1
- package/test/conda-bin.test.js +151 -109
- package/test/shell-run-template.test.js +42 -0
package/kernel/bin/conda-pins.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const semver = require('semver')
|
|
2
2
|
|
|
3
|
-
const CONDA_PIN_VERSION = "26.3
|
|
3
|
+
const CONDA_PIN_VERSION = "26.5.3"
|
|
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`
|
|
7
7
|
|
|
8
|
+
const isExpectedCondaPinned = (version) => String(version) === CONDA_PIN_VERSION
|
|
9
|
+
|
|
8
10
|
const condaBuildNumber = (build) => {
|
|
9
11
|
const chunks = String(build || "").split("_").reverse()
|
|
10
12
|
const buildNumber = chunks.find((chunk) => /^\d+$/.test(chunk))
|
|
@@ -30,5 +32,6 @@ module.exports = {
|
|
|
30
32
|
CONDA_PIN_VERSION,
|
|
31
33
|
PYTHON_INSTALL_SPEC,
|
|
32
34
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
35
|
+
isExpectedCondaPinned,
|
|
33
36
|
isExpectedPythonPinned,
|
|
34
37
|
}
|
package/kernel/bin/conda.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
2
|
const { glob } = require('glob')
|
|
4
3
|
const semver = require('semver')
|
|
5
4
|
const { buildCondaListFromMeta, managedCondaRuns } = require('./conda-meta')
|
|
@@ -7,6 +6,7 @@ const {
|
|
|
7
6
|
CONDA_PIN_VERSION,
|
|
8
7
|
PYTHON_INSTALL_SPEC,
|
|
9
8
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
9
|
+
isExpectedCondaPinned,
|
|
10
10
|
isExpectedPythonPinned,
|
|
11
11
|
} = require('./conda-pins')
|
|
12
12
|
|
|
@@ -14,61 +14,6 @@ const MINIFORGE_RELEASE = "26.3.2-3"
|
|
|
14
14
|
const MINIFORGE_BASE_URL = `https://github.com/conda-forge/miniforge/releases/download/${MINIFORGE_RELEASE}`
|
|
15
15
|
const CONDA_ROOT_DIR = "miniforge"
|
|
16
16
|
const LEGACY_CONDA_ROOT_DIR = "miniconda"
|
|
17
|
-
const WINDOWS_OPENSSL_HOOKS = {
|
|
18
|
-
activate: {
|
|
19
|
-
"openssl_activate-win.bat": [
|
|
20
|
-
"@echo off",
|
|
21
|
-
'if "%SSL_CERT_FILE%"=="" (',
|
|
22
|
-
' set "SSL_CERT_FILE=%CONDA_PREFIX%\\Library\\ssl\\cacert.pem"',
|
|
23
|
-
' set "__CONDA_OPENSSL_CERT_FILE_SET=1"',
|
|
24
|
-
")",
|
|
25
|
-
"",
|
|
26
|
-
].join("\r\n"),
|
|
27
|
-
"openssl_activate-win.ps1": [
|
|
28
|
-
"if (-not $Env:SSL_CERT_FILE) {",
|
|
29
|
-
' $Env:SSL_CERT_FILE = "$Env:CONDA_PREFIX\\Library\\ssl\\cacert.pem"',
|
|
30
|
-
' $Env:__CONDA_OPENSSL_CERT_FILE_SET = "1"',
|
|
31
|
-
"}",
|
|
32
|
-
"",
|
|
33
|
-
].join("\r\n"),
|
|
34
|
-
"openssl_activate-win.sh": [
|
|
35
|
-
'if [[ "${SSL_CERT_FILE:-}" == "" ]]; then',
|
|
36
|
-
' export SSL_CERT_FILE="${CONDA_PREFIX}\\\\Library\\\\ssl\\\\cacert.pem"',
|
|
37
|
-
' export __CONDA_OPENSSL_CERT_FILE_SET="1"',
|
|
38
|
-
"fi",
|
|
39
|
-
"",
|
|
40
|
-
].join("\n"),
|
|
41
|
-
},
|
|
42
|
-
deactivate: {
|
|
43
|
-
"openssl_deactivate-win.bat": [
|
|
44
|
-
"@echo off",
|
|
45
|
-
'if "%__CONDA_OPENSSL_CERT_FILE_SET%" == "1" (',
|
|
46
|
-
" set SSL_CERT_FILE=",
|
|
47
|
-
" set __CONDA_OPENSSL_CERT_FILE_SET=",
|
|
48
|
-
")",
|
|
49
|
-
"",
|
|
50
|
-
].join("\r\n"),
|
|
51
|
-
"openssl_deactivate-win.ps1": [
|
|
52
|
-
'if ($Env:__CONDA_OPENSSL_CERT_FILE_SET -eq "1") {',
|
|
53
|
-
" Remove-Item -Path Env:\\SSL_CERT_FILE",
|
|
54
|
-
" Remove-Item -Path Env:\\__CONDA_OPENSSL_CERT_FILE_SET",
|
|
55
|
-
"}",
|
|
56
|
-
"",
|
|
57
|
-
].join("\r\n"),
|
|
58
|
-
"openssl_deactivate-win.sh": [
|
|
59
|
-
'if [[ "${__CONDA_OPENSSL_CERT_FILE_SET:-}" == "1" ]]; then',
|
|
60
|
-
" unset SSL_CERT_FILE",
|
|
61
|
-
" unset __CONDA_OPENSSL_CERT_FILE_SET",
|
|
62
|
-
"fi",
|
|
63
|
-
"",
|
|
64
|
-
].join("\n"),
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
const LEGACY_SSL_CERT_DIR_HOOK_FILES = [
|
|
68
|
-
"zz_pinokio_unset_ssl_cert_dir-win.bat",
|
|
69
|
-
"zz_pinokio_unset_ssl_cert_dir-win.ps1",
|
|
70
|
-
"zz_pinokio_unset_ssl_cert_dir-win.sh",
|
|
71
|
-
]
|
|
72
17
|
|
|
73
18
|
class Conda {
|
|
74
19
|
description = "Pinokio uses Conda to install various useful programs in an isolated manner."
|
|
@@ -124,43 +69,6 @@ class Conda {
|
|
|
124
69
|
}
|
|
125
70
|
return base
|
|
126
71
|
}
|
|
127
|
-
async ensureWindowsOpenSslHooks() {
|
|
128
|
-
if (this.kernel.platform !== "win32") {
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
const condaRootDirs = []
|
|
132
|
-
for (const rootDir of [CONDA_ROOT_DIR, LEGACY_CONDA_ROOT_DIR]) {
|
|
133
|
-
if (await this.kernel.exists(`bin/${rootDir}/conda-meta`)) {
|
|
134
|
-
condaRootDirs.push(rootDir)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (condaRootDirs.length === 0) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
for (const rootDir of condaRootDirs) {
|
|
141
|
-
const activateDir = this.kernel.bin.path(`${rootDir}/etc/conda/activate.d`)
|
|
142
|
-
const deactivateDir = this.kernel.bin.path(`${rootDir}/etc/conda/deactivate.d`)
|
|
143
|
-
for (const hookDir of [activateDir, deactivateDir]) {
|
|
144
|
-
for (const filename of LEGACY_SSL_CERT_DIR_HOOK_FILES) {
|
|
145
|
-
await fs.promises.rm(path.resolve(hookDir, filename), { force: true }).catch(() => {})
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
for (const [filename, content] of Object.entries(WINDOWS_OPENSSL_HOOKS.activate)) {
|
|
149
|
-
const hookPath = path.resolve(activateDir, filename)
|
|
150
|
-
const exists = await fs.promises.access(hookPath).then(() => true).catch(() => false)
|
|
151
|
-
if (exists) {
|
|
152
|
-
await fs.promises.writeFile(hookPath, content)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
for (const [filename, content] of Object.entries(WINDOWS_OPENSSL_HOOKS.deactivate)) {
|
|
156
|
-
const hookPath = path.resolve(deactivateDir, filename)
|
|
157
|
-
const exists = await fs.promises.access(hookPath).then(() => true).catch(() => false)
|
|
158
|
-
if (exists) {
|
|
159
|
-
await fs.promises.writeFile(hookPath, content)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
72
|
async init() {
|
|
165
73
|
if (this.kernel.homedir) {
|
|
166
74
|
console.log("condarc init")
|
|
@@ -182,7 +90,6 @@ report_errors: false`)
|
|
|
182
90
|
await fs.promises.writeFile(this.kernel.path(`bin/${CONDA_ROOT_DIR}/conda-meta/pinned`), this.pinnedPackages())
|
|
183
91
|
await this.ensureCompatibilityAlias()
|
|
184
92
|
}
|
|
185
|
-
await this.ensureWindowsOpenSslHooks()
|
|
186
93
|
}
|
|
187
94
|
}
|
|
188
95
|
async check() {
|
|
@@ -205,7 +112,7 @@ report_errors: false`)
|
|
|
205
112
|
conda_versions[name] = version
|
|
206
113
|
conda_builds[name] = build
|
|
207
114
|
if (name === "conda") {
|
|
208
|
-
conda_check.conda =
|
|
115
|
+
conda_check.conda = isExpectedCondaPinned(version)
|
|
209
116
|
}
|
|
210
117
|
// check conda-libmamba-solver is up to date
|
|
211
118
|
// sometimes it just fails silently so need to check
|
|
@@ -307,6 +214,7 @@ report_errors: false`)
|
|
|
307
214
|
console.log("Conda dependencies to install", { mods })
|
|
308
215
|
|
|
309
216
|
let condaPackages = [
|
|
217
|
+
`"conda=${CONDA_PIN_VERSION}"`,
|
|
310
218
|
this.kernel.platform === "win32" ? `"${WINDOWS_PYTHON_SSL_FIX_SPEC}"` : `"${PYTHON_INSTALL_SPEC}"`,
|
|
311
219
|
`"conda-libmamba-solver>=25.4.0"`,
|
|
312
220
|
]
|
|
@@ -330,7 +238,6 @@ report_errors: false`)
|
|
|
330
238
|
this.kernel.bin.path(CONDA_ROOT_DIR, "python3.exe"),
|
|
331
239
|
)
|
|
332
240
|
}
|
|
333
|
-
await this.ensureWindowsOpenSslHooks()
|
|
334
241
|
await this.ensureCompatibilityAlias()
|
|
335
242
|
ondata({ raw: `Install finished\r\n` })
|
|
336
243
|
await this.kernel.bin.rm(installer, ondata)
|
package/kernel/bin/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const Cuda = require("./cuda")
|
|
|
23
23
|
const Torch = require("./torch")
|
|
24
24
|
const { buildCondaListFromMeta, managedCondaRuns } = require('./conda-meta')
|
|
25
25
|
const {
|
|
26
|
+
isExpectedCondaPinned,
|
|
26
27
|
isExpectedPythonPinned,
|
|
27
28
|
} = require('./conda-pins')
|
|
28
29
|
const { glob } = require('glob')
|
|
@@ -391,7 +392,7 @@ class Bin {
|
|
|
391
392
|
conda_versions[name] = version
|
|
392
393
|
conda_builds[name] = build
|
|
393
394
|
if (name === "conda") {
|
|
394
|
-
conda_check.conda =
|
|
395
|
+
conda_check.conda = isExpectedCondaPinned(version)
|
|
395
396
|
}
|
|
396
397
|
if (name === "conda-libmamba-solver") {
|
|
397
398
|
let coerced = semver.coerce(version)
|
|
@@ -448,9 +449,6 @@ class Bin {
|
|
|
448
449
|
break
|
|
449
450
|
}
|
|
450
451
|
}
|
|
451
|
-
if (this.mod && this.mod.conda && this.mod.conda.ensureWindowsOpenSslHooks) {
|
|
452
|
-
await this.mod.conda.ensureWindowsOpenSslHooks()
|
|
453
|
-
}
|
|
454
452
|
}
|
|
455
453
|
|
|
456
454
|
if (this.platform === "darwin") {
|
package/kernel/shell.js
CHANGED
|
@@ -294,6 +294,14 @@ class Shell {
|
|
|
294
294
|
setDefaultEnvValue(this.env, "HF_TOKEN_PATH", path.resolve(this.kernel.homedir, "cache", "HF_AUTH", "token"))
|
|
295
295
|
|
|
296
296
|
if (this.platform === "win32") {
|
|
297
|
+
const managedSslDir = path.resolve(this.kernel.homedir, "bin", "miniforge", "Library", "ssl")
|
|
298
|
+
const managedCertFile = path.resolve(managedSslDir, "cacert.pem")
|
|
299
|
+
const managedCertFileExists = await fs.promises.access(managedCertFile).then(() => true).catch(() => false)
|
|
300
|
+
if (managedCertFileExists) {
|
|
301
|
+
setDefaultEnvValue(this.env, "SSL_CERT_FILE", managedCertFile)
|
|
302
|
+
setDefaultEnvValue(this.env, "SSL_CERT_DIR", managedSslDir)
|
|
303
|
+
}
|
|
304
|
+
|
|
297
305
|
// Hugging Face file symlinks regularly fail on non-admin Windows setups.
|
|
298
306
|
// Default to no-symlink cache mode unless the user/app explicitly overrides it.
|
|
299
307
|
setDefaultEnvValue(this.env, "HF_HUB_DISABLE_SYMLINKS", "1")
|
package/package.json
CHANGED
package/test/conda-bin.test.js
CHANGED
|
@@ -5,10 +5,13 @@ const os = require('node:os')
|
|
|
5
5
|
const path = require('node:path')
|
|
6
6
|
const test = require('node:test')
|
|
7
7
|
|
|
8
|
+
const Bin = require('../kernel/bin')
|
|
8
9
|
const Conda = require('../kernel/bin/conda')
|
|
9
10
|
const {
|
|
11
|
+
CONDA_PIN_VERSION,
|
|
10
12
|
PYTHON_INSTALL_SPEC,
|
|
11
13
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
14
|
+
isExpectedCondaPinned,
|
|
12
15
|
isExpectedPythonPinned,
|
|
13
16
|
} = require('../kernel/bin/conda-pins')
|
|
14
17
|
|
|
@@ -41,8 +44,8 @@ async function writeCondaMeta(root, name, version, build = 'py_0') {
|
|
|
41
44
|
)
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
async function writeHealthyCondaMeta(root, platform = 'win32') {
|
|
45
|
-
await writeCondaMeta(root, 'conda',
|
|
47
|
+
async function writeHealthyCondaMeta(root, platform = 'win32', condaVersion = CONDA_PIN_VERSION) {
|
|
48
|
+
await writeCondaMeta(root, 'conda', condaVersion, 'py310')
|
|
46
49
|
await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
|
|
47
50
|
await writeCondaMeta(root, 'python', '3.10.20', platform === 'win32' ? 'h4de0772_1_cpython' : 'cpython')
|
|
48
51
|
}
|
|
@@ -78,86 +81,17 @@ function createConda(kernel) {
|
|
|
78
81
|
return conda
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
' set "__CONDA_OPENSSL_CERT_FILE_SET=1"',
|
|
87
|
-
')',
|
|
88
|
-
'',
|
|
89
|
-
].join('\r\n'),
|
|
90
|
-
[path.join('activate.d', 'openssl_activate-win.ps1')]: [
|
|
91
|
-
'if (-not $Env:SSL_CERT_FILE) {',
|
|
92
|
-
' $Env:SSL_CERT_FILE = "$Env:CONDA_PREFIX\\Library\\ssl\\cacert.pem"',
|
|
93
|
-
' $Env:__CONDA_OPENSSL_CERT_FILE_SET = "1"',
|
|
94
|
-
'}',
|
|
95
|
-
'',
|
|
96
|
-
].join('\r\n'),
|
|
97
|
-
[path.join('activate.d', 'openssl_activate-win.sh')]: [
|
|
98
|
-
'if [[ "${SSL_CERT_FILE:-}" == "" ]]; then',
|
|
99
|
-
' export SSL_CERT_FILE="${CONDA_PREFIX}\\\\Library\\\\ssl\\\\cacert.pem"',
|
|
100
|
-
' export __CONDA_OPENSSL_CERT_FILE_SET="1"',
|
|
101
|
-
'fi',
|
|
102
|
-
'',
|
|
103
|
-
].join('\n'),
|
|
104
|
-
[path.join('deactivate.d', 'openssl_deactivate-win.bat')]: [
|
|
105
|
-
'@echo off',
|
|
106
|
-
'if "%__CONDA_OPENSSL_CERT_FILE_SET%" == "1" (',
|
|
107
|
-
' set SSL_CERT_FILE=',
|
|
108
|
-
' set __CONDA_OPENSSL_CERT_FILE_SET=',
|
|
109
|
-
')',
|
|
110
|
-
'',
|
|
111
|
-
].join('\r\n'),
|
|
112
|
-
[path.join('deactivate.d', 'openssl_deactivate-win.ps1')]: [
|
|
113
|
-
'if ($Env:__CONDA_OPENSSL_CERT_FILE_SET -eq "1") {',
|
|
114
|
-
' Remove-Item -Path Env:\\SSL_CERT_FILE',
|
|
115
|
-
' Remove-Item -Path Env:\\__CONDA_OPENSSL_CERT_FILE_SET',
|
|
116
|
-
'}',
|
|
117
|
-
'',
|
|
118
|
-
].join('\r\n'),
|
|
119
|
-
[path.join('deactivate.d', 'openssl_deactivate-win.sh')]: [
|
|
120
|
-
'if [[ "${__CONDA_OPENSSL_CERT_FILE_SET:-}" == "1" ]]; then',
|
|
121
|
-
' unset SSL_CERT_FILE',
|
|
122
|
-
' unset __CONDA_OPENSSL_CERT_FILE_SET',
|
|
123
|
-
'fi',
|
|
124
|
-
'',
|
|
125
|
-
].join('\n'),
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function seedWindowsOpenSslHooks(root, condaRootName) {
|
|
129
|
-
const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
|
|
130
|
-
for (const hookRelativePath of Object.keys(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
|
|
131
|
-
const hookPath = path.join(condaHookRoot, hookRelativePath)
|
|
132
|
-
await fs.mkdir(path.dirname(hookPath), { recursive: true })
|
|
133
|
-
await fs.writeFile(hookPath, 'upstream hook with SSL_CERT_DIR\n')
|
|
134
|
-
}
|
|
135
|
-
for (const hookDirName of ['activate.d', 'deactivate.d']) {
|
|
136
|
-
for (const filename of [
|
|
137
|
-
'zz_pinokio_unset_ssl_cert_dir-win.bat',
|
|
138
|
-
'zz_pinokio_unset_ssl_cert_dir-win.ps1',
|
|
139
|
-
'zz_pinokio_unset_ssl_cert_dir-win.sh',
|
|
140
|
-
]) {
|
|
141
|
-
await fs.writeFile(path.join(condaHookRoot, hookDirName, filename), 'legacy hook\n')
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function assertWindowsOpenSslHooksPatched(root, condaRootName) {
|
|
147
|
-
const condaHookRoot = path.join(root, 'bin', condaRootName, 'etc', 'conda')
|
|
148
|
-
for (const [hookRelativePath, expectedContent] of Object.entries(EXPECTED_WINDOWS_OPENSSL_HOOKS)) {
|
|
149
|
-
const hookPath = path.join(condaHookRoot, hookRelativePath)
|
|
150
|
-
assert.equal(await fs.readFile(hookPath, 'utf8'), expectedContent)
|
|
151
|
-
}
|
|
152
|
-
for (const hookDirName of ['activate.d', 'deactivate.d']) {
|
|
153
|
-
for (const filename of [
|
|
154
|
-
'zz_pinokio_unset_ssl_cert_dir-win.bat',
|
|
155
|
-
'zz_pinokio_unset_ssl_cert_dir-win.ps1',
|
|
156
|
-
'zz_pinokio_unset_ssl_cert_dir-win.sh',
|
|
157
|
-
]) {
|
|
158
|
-
assert.equal(await pathExists(path.join(condaHookRoot, hookDirName, filename)), false)
|
|
159
|
-
}
|
|
84
|
+
function createBin(root, platform = 'win32') {
|
|
85
|
+
const kernel = {
|
|
86
|
+
homedir: root,
|
|
87
|
+
platform,
|
|
88
|
+
path: (...parts) => path.join(root, ...parts),
|
|
160
89
|
}
|
|
90
|
+
const bin = new Bin(kernel)
|
|
91
|
+
bin.platform = platform
|
|
92
|
+
bin.installed = {}
|
|
93
|
+
kernel.bin = bin
|
|
94
|
+
return bin
|
|
161
95
|
}
|
|
162
96
|
|
|
163
97
|
test('Conda uses Miniforge assets and writes conda-forge-only config', async () => {
|
|
@@ -181,32 +115,14 @@ test('Conda uses Miniforge assets and writes conda-forge-only config', async ()
|
|
|
181
115
|
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge')), false)
|
|
182
116
|
})
|
|
183
117
|
|
|
184
|
-
test('Conda
|
|
185
|
-
|
|
186
|
-
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
187
|
-
await seedWindowsOpenSslHooks(root, 'miniforge')
|
|
188
|
-
const conda = createConda(createKernel(root))
|
|
189
|
-
|
|
190
|
-
await conda.init()
|
|
191
|
-
|
|
192
|
-
await assertWindowsOpenSslHooksPatched(root, 'miniforge')
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
test('Conda init patches Windows OpenSSL hooks for legacy Miniconda roots', async () => {
|
|
196
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-legacy-openssl-hooks-'))
|
|
197
|
-
await fs.mkdir(path.join(root, 'bin', 'miniconda', 'conda-meta'), { recursive: true })
|
|
198
|
-
await seedWindowsOpenSslHooks(root, 'miniconda')
|
|
199
|
-
const conda = createConda(createKernel(root))
|
|
200
|
-
|
|
201
|
-
await conda.init()
|
|
202
|
-
|
|
203
|
-
await assertWindowsOpenSslHooksPatched(root, 'miniconda')
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
test('Conda pins Python 3.10.20 consistently across platforms', async () => {
|
|
118
|
+
test('Conda pins the managed Conda and Python versions consistently', async () => {
|
|
119
|
+
assert.equal(CONDA_PIN_VERSION, '26.5.3')
|
|
207
120
|
assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
|
|
208
121
|
assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
|
|
209
122
|
|
|
123
|
+
assert.equal(isExpectedCondaPinned('26.5.3'), true)
|
|
124
|
+
assert.equal(isExpectedCondaPinned('26.3.2'), false)
|
|
125
|
+
|
|
210
126
|
assert.equal(isExpectedPythonPinned('darwin', '3.10.20', 'cpython'), true)
|
|
211
127
|
assert.equal(isExpectedPythonPinned('linux', '3.10.20', 'cpython'), true)
|
|
212
128
|
assert.equal(isExpectedPythonPinned('darwin', '3.10.21', 'cpython'), false)
|
|
@@ -215,6 +131,47 @@ test('Conda pins Python 3.10.20 consistently across platforms', async () => {
|
|
|
215
131
|
assert.equal(isExpectedPythonPinned('win32', '3.10.20', 'h4de0772_1_cpython'), true)
|
|
216
132
|
})
|
|
217
133
|
|
|
134
|
+
test('Conda init writes the current Conda pin into an existing runtime', async () => {
|
|
135
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-pin-init-'))
|
|
136
|
+
await createMiniforge(root)
|
|
137
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
138
|
+
|
|
139
|
+
await createConda(createKernel(root, 'darwin')).init()
|
|
140
|
+
|
|
141
|
+
assert.equal(
|
|
142
|
+
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
143
|
+
'conda ==26.5.3'
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('Conda init leaves Windows OpenSSL activation hooks untouched', async () => {
|
|
148
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-hooks-untouched-'))
|
|
149
|
+
const hookRoot = path.join(root, 'bin', 'miniforge', 'etc', 'conda')
|
|
150
|
+
const hookFiles = [
|
|
151
|
+
path.join('activate.d', 'openssl_activate-win.bat'),
|
|
152
|
+
path.join('activate.d', 'openssl_activate-win.ps1'),
|
|
153
|
+
path.join('activate.d', 'openssl_activate-win.sh'),
|
|
154
|
+
path.join('deactivate.d', 'openssl_deactivate-win.bat'),
|
|
155
|
+
path.join('deactivate.d', 'openssl_deactivate-win.ps1'),
|
|
156
|
+
path.join('deactivate.d', 'openssl_deactivate-win.sh'),
|
|
157
|
+
]
|
|
158
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
159
|
+
for (const relativePath of hookFiles) {
|
|
160
|
+
const hookPath = path.join(hookRoot, relativePath)
|
|
161
|
+
await fs.mkdir(path.dirname(hookPath), { recursive: true })
|
|
162
|
+
await fs.writeFile(hookPath, `upstream ${relativePath}\n`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await createConda(createKernel(root, 'win32')).init()
|
|
166
|
+
|
|
167
|
+
for (const relativePath of hookFiles) {
|
|
168
|
+
assert.equal(
|
|
169
|
+
await fs.readFile(path.join(hookRoot, relativePath), 'utf8'),
|
|
170
|
+
`upstream ${relativePath}\n`
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
218
175
|
test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () => {
|
|
219
176
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-darwin-'))
|
|
220
177
|
const kernel = createKernel(root, 'darwin')
|
|
@@ -237,7 +194,11 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
|
|
|
237
194
|
assert.ok(condaInstall)
|
|
238
195
|
assert.equal(
|
|
239
196
|
condaInstall.message[1],
|
|
240
|
-
'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
|
|
197
|
+
'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
|
|
198
|
+
)
|
|
199
|
+
assert.equal(
|
|
200
|
+
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
201
|
+
'conda ==26.5.3'
|
|
241
202
|
)
|
|
242
203
|
})
|
|
243
204
|
|
|
@@ -265,7 +226,39 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
|
|
|
265
226
|
assert.ok(condaInstall)
|
|
266
227
|
assert.equal(
|
|
267
228
|
condaInstall.message[1],
|
|
268
|
-
'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
|
|
229
|
+
'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
|
|
230
|
+
)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('Conda install replaces the stale runtime and rebuilds declared Conda modules', async () => {
|
|
234
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-full-reset-'))
|
|
235
|
+
const staleRoot = await createMiniforge(root)
|
|
236
|
+
const staleMarker = path.join(staleRoot, 'stale-runtime.txt')
|
|
237
|
+
await fs.writeFile(staleMarker, 'remove me\n')
|
|
238
|
+
const kernel = createKernel(root, 'darwin')
|
|
239
|
+
const calls = []
|
|
240
|
+
|
|
241
|
+
kernel.bin.mods = [
|
|
242
|
+
{ name: 'uv', mod: { cmd: () => 'uv=0.11.23' } },
|
|
243
|
+
{ name: 'node', mod: { cmd: () => 'nodejs=22.21.1 pnpm' } },
|
|
244
|
+
]
|
|
245
|
+
kernel.bin.download = async () => {}
|
|
246
|
+
kernel.bin.rm = async () => {}
|
|
247
|
+
kernel.bin.exec = async (payload) => {
|
|
248
|
+
calls.push(payload)
|
|
249
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
250
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await createConda(kernel)._install({ dependencies: ['uv', 'node'] }, () => {})
|
|
255
|
+
|
|
256
|
+
assert.equal(await pathExists(staleMarker), false)
|
|
257
|
+
const condaInstall = calls.find((call) => Array.isArray(call.message))
|
|
258
|
+
assert.ok(condaInstall)
|
|
259
|
+
assert.equal(
|
|
260
|
+
condaInstall.message[1],
|
|
261
|
+
'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20" "conda-libmamba-solver>=25.4.0" uv=0.11.23 nodejs=22.21.1 pnpm'
|
|
269
262
|
)
|
|
270
263
|
})
|
|
271
264
|
|
|
@@ -331,6 +324,14 @@ test('Conda check rejects metadata-only Windows installs', async () => {
|
|
|
331
324
|
assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
|
|
332
325
|
})
|
|
333
326
|
|
|
327
|
+
test('Conda check rejects a runnable runtime with the previous Conda version', async () => {
|
|
328
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-version-stale-'))
|
|
329
|
+
await writeHealthyCondaMeta(root, 'win32', '26.3.2')
|
|
330
|
+
await writeFakeManagedConda(root, 'win32')
|
|
331
|
+
|
|
332
|
+
assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
|
|
333
|
+
})
|
|
334
|
+
|
|
334
335
|
test('Conda check accepts a runnable managed Windows conda executable', async () => {
|
|
335
336
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-runnable-'))
|
|
336
337
|
await writeHealthyCondaMeta(root, 'win32')
|
|
@@ -339,9 +340,29 @@ test('Conda check accepts a runnable managed Windows conda executable', async ()
|
|
|
339
340
|
assert.equal(await createConda(createKernel(root, 'win32')).check(), true)
|
|
340
341
|
})
|
|
341
342
|
|
|
343
|
+
test('Bin readiness rejects the previous Conda version and accepts the pinned version', async () => {
|
|
344
|
+
const staleRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-conda-version-stale-'))
|
|
345
|
+
await writeHealthyCondaMeta(staleRoot, 'win32', '26.3.2')
|
|
346
|
+
await writeFakeManagedConda(staleRoot, 'win32')
|
|
347
|
+
const staleBin = createBin(staleRoot, 'win32')
|
|
348
|
+
|
|
349
|
+
await staleBin.tryList()
|
|
350
|
+
assert.equal(staleBin.correct_conda, false)
|
|
351
|
+
|
|
352
|
+
const currentRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-conda-version-current-'))
|
|
353
|
+
await writeHealthyCondaMeta(currentRoot, 'win32')
|
|
354
|
+
await writeFakeManagedConda(currentRoot, 'win32')
|
|
355
|
+
const currentBin = createBin(currentRoot, 'win32')
|
|
356
|
+
|
|
357
|
+
await currentBin.tryList()
|
|
358
|
+
assert.equal(currentBin.correct_conda, true)
|
|
359
|
+
})
|
|
360
|
+
|
|
342
361
|
test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
|
|
343
362
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-download-fails-'))
|
|
344
|
-
const
|
|
363
|
+
const miniforge = await createMiniforge(root)
|
|
364
|
+
const oldRuntimeFile = path.join(miniforge, 'old-runtime.txt')
|
|
365
|
+
await fs.writeFile(oldRuntimeFile, 'keep me\n')
|
|
345
366
|
const kernel = createKernel(root)
|
|
346
367
|
|
|
347
368
|
kernel.bin.download = async () => {
|
|
@@ -352,8 +373,29 @@ test('Conda install keeps the old runtime when the replacement installer downloa
|
|
|
352
373
|
createConda(kernel)._install({ dependencies: [] }, () => {}),
|
|
353
374
|
/download failed/
|
|
354
375
|
)
|
|
355
|
-
assert.equal(await pathExists(
|
|
356
|
-
|
|
376
|
+
assert.equal(await pathExists(oldRuntimeFile), true)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test('Conda install propagates bootstrap failure after replacing the runtime', async () => {
|
|
380
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-bootstrap-fails-'))
|
|
381
|
+
await createMiniforge(root)
|
|
382
|
+
const kernel = createKernel(root, 'darwin')
|
|
383
|
+
|
|
384
|
+
kernel.bin.mods = []
|
|
385
|
+
kernel.bin.download = async () => {}
|
|
386
|
+
kernel.bin.rm = async () => {}
|
|
387
|
+
kernel.bin.exec = async (payload) => {
|
|
388
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
389
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
throw new Error('bootstrap failed')
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await assert.rejects(
|
|
396
|
+
createConda(kernel)._install({ dependencies: [] }, () => {}),
|
|
397
|
+
/bootstrap failed/
|
|
398
|
+
)
|
|
357
399
|
})
|
|
358
400
|
|
|
359
401
|
test('Conda install reports manual recovery when the old runtime cannot be removed', async () => {
|
|
@@ -197,6 +197,48 @@ test('Shell.init_env keeps Windows Hugging Face symlink defaults scoped to win32
|
|
|
197
197
|
assert.equal(winShell.env.HF_HUB_DISABLE_SYMLINKS_WARNING, '1')
|
|
198
198
|
})
|
|
199
199
|
|
|
200
|
+
test('Shell.init_env gives Windows uv a valid managed certificate directory without overriding apps', async () => {
|
|
201
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-shell-ssl-win-'))
|
|
202
|
+
const managedSslDir = path.join(root, 'bin', 'miniforge', 'Library', 'ssl')
|
|
203
|
+
const managedCertFile = path.join(managedSslDir, 'cacert.pem')
|
|
204
|
+
await fs.mkdir(path.join(root, 'api'), { recursive: true })
|
|
205
|
+
await fs.mkdir(managedSslDir, { recursive: true })
|
|
206
|
+
await fs.writeFile(path.join(root, 'ENVIRONMENT'), 'PINOKIO_TEST_ENV=1\n')
|
|
207
|
+
await fs.writeFile(managedCertFile, 'test certificate bundle\n')
|
|
208
|
+
|
|
209
|
+
const defaultShell = createShell(createKernel(root))
|
|
210
|
+
defaultShell.platform = 'win32'
|
|
211
|
+
await defaultShell.init_env({
|
|
212
|
+
path: process.cwd(),
|
|
213
|
+
env: {}
|
|
214
|
+
})
|
|
215
|
+
assert.equal(defaultShell.env.SSL_CERT_FILE, managedCertFile)
|
|
216
|
+
assert.equal(defaultShell.env.SSL_CERT_DIR, managedSslDir)
|
|
217
|
+
|
|
218
|
+
const customCertFile = path.join(root, 'custom', 'certs.pem')
|
|
219
|
+
const customCertDir = path.join(root, 'custom', 'certs')
|
|
220
|
+
const overrideShell = createShell(createKernel(root))
|
|
221
|
+
overrideShell.platform = 'win32'
|
|
222
|
+
await overrideShell.init_env({
|
|
223
|
+
path: process.cwd(),
|
|
224
|
+
env: {
|
|
225
|
+
SSL_CERT_FILE: customCertFile,
|
|
226
|
+
SSL_CERT_DIR: customCertDir
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
assert.equal(overrideShell.env.SSL_CERT_FILE, customCertFile)
|
|
230
|
+
assert.equal(overrideShell.env.SSL_CERT_DIR, customCertDir)
|
|
231
|
+
|
|
232
|
+
const darwinShell = createShell(createKernel(root))
|
|
233
|
+
darwinShell.platform = 'darwin'
|
|
234
|
+
await darwinShell.init_env({
|
|
235
|
+
path: process.cwd(),
|
|
236
|
+
env: {}
|
|
237
|
+
})
|
|
238
|
+
assert.equal(darwinShell.env.SSL_CERT_FILE, undefined)
|
|
239
|
+
assert.equal(darwinShell.env.SSL_CERT_DIR, undefined)
|
|
240
|
+
})
|
|
241
|
+
|
|
200
242
|
test('redactEnvArgs summarizes protected argv env values', () => {
|
|
201
243
|
const redacted = ShellRunTemplate.redactEnvArgs({
|
|
202
244
|
PINOKIO_ARG_0: 'line one\nline two',
|