pinokiod 8.0.1 → 8.0.2
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 +2 -0
- package/kernel/bin/conda.js +9 -96
- package/kernel/bin/index.js +6 -4
- package/kernel/shell.js +8 -0
- package/package.json +1 -1
- package/test/conda-bin.test.js +158 -106
- package/test/shell-run-template.test.js +42 -0
package/kernel/bin/conda-pins.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const semver = require('semver')
|
|
2
2
|
|
|
3
3
|
const CONDA_PIN_VERSION = "26.3.2"
|
|
4
|
+
const CONDA_RUNTIME_MARKER = ".pinokio-runtime-v1"
|
|
4
5
|
const PYTHON_PIN_VERSION = "3.10.20"
|
|
5
6
|
const PYTHON_INSTALL_SPEC = `python=${PYTHON_PIN_VERSION}`
|
|
6
7
|
const WINDOWS_PYTHON_SSL_FIX_SPEC = `${PYTHON_INSTALL_SPEC}=*_1_cpython`
|
|
@@ -28,6 +29,7 @@ const isExpectedPythonPinned = (platform, version, build) => {
|
|
|
28
29
|
|
|
29
30
|
module.exports = {
|
|
30
31
|
CONDA_PIN_VERSION,
|
|
32
|
+
CONDA_RUNTIME_MARKER,
|
|
31
33
|
PYTHON_INSTALL_SPEC,
|
|
32
34
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
33
35
|
isExpectedPythonPinned,
|
package/kernel/bin/conda.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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')
|
|
6
5
|
const {
|
|
7
6
|
CONDA_PIN_VERSION,
|
|
7
|
+
CONDA_RUNTIME_MARKER,
|
|
8
8
|
PYTHON_INSTALL_SPEC,
|
|
9
9
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
10
10
|
isExpectedPythonPinned,
|
|
@@ -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."
|
|
@@ -103,6 +48,9 @@ class Conda {
|
|
|
103
48
|
async hasCondaMeta() {
|
|
104
49
|
return await this.kernel.exists(`bin/${CONDA_ROOT_DIR}/conda-meta`)
|
|
105
50
|
}
|
|
51
|
+
async hasRuntimeMarker() {
|
|
52
|
+
return await this.kernel.exists(`bin/${CONDA_ROOT_DIR}/${CONDA_RUNTIME_MARKER}`)
|
|
53
|
+
}
|
|
106
54
|
env() {
|
|
107
55
|
let base = {
|
|
108
56
|
CONDA_PREFIX: this.kernel.bin.path(CONDA_ROOT_DIR),
|
|
@@ -124,43 +72,6 @@ class Conda {
|
|
|
124
72
|
}
|
|
125
73
|
return base
|
|
126
74
|
}
|
|
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
75
|
async init() {
|
|
165
76
|
if (this.kernel.homedir) {
|
|
166
77
|
console.log("condarc init")
|
|
@@ -182,10 +93,12 @@ report_errors: false`)
|
|
|
182
93
|
await fs.promises.writeFile(this.kernel.path(`bin/${CONDA_ROOT_DIR}/conda-meta/pinned`), this.pinnedPackages())
|
|
183
94
|
await this.ensureCompatibilityAlias()
|
|
184
95
|
}
|
|
185
|
-
await this.ensureWindowsOpenSslHooks()
|
|
186
96
|
}
|
|
187
97
|
}
|
|
188
98
|
async check() {
|
|
99
|
+
if (!(await this.hasRuntimeMarker())) {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
189
102
|
let res = await buildCondaListFromMeta(this.kernel.bin.path(CONDA_ROOT_DIR))
|
|
190
103
|
|
|
191
104
|
let lines = res.response.split(/[\r\n]+/)
|
|
@@ -330,10 +243,10 @@ report_errors: false`)
|
|
|
330
243
|
this.kernel.bin.path(CONDA_ROOT_DIR, "python3.exe"),
|
|
331
244
|
)
|
|
332
245
|
}
|
|
333
|
-
await this.ensureWindowsOpenSslHooks()
|
|
334
246
|
await this.ensureCompatibilityAlias()
|
|
335
|
-
ondata({ raw: `Install finished\r\n` })
|
|
336
247
|
await this.kernel.bin.rm(installer, ondata)
|
|
248
|
+
await fs.promises.writeFile(this.kernel.bin.path(CONDA_ROOT_DIR, CONDA_RUNTIME_MARKER), "")
|
|
249
|
+
ondata({ raw: `Install finished\r\n` })
|
|
337
250
|
}
|
|
338
251
|
async removeInstallPath(target) {
|
|
339
252
|
try {
|
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
|
+
CONDA_RUNTIME_MARKER,
|
|
26
27
|
isExpectedPythonPinned,
|
|
27
28
|
} = require('./conda-pins')
|
|
28
29
|
const { glob } = require('glob')
|
|
@@ -349,6 +350,10 @@ class Bin {
|
|
|
349
350
|
let conda_builds = {}
|
|
350
351
|
this.correct_conda = false
|
|
351
352
|
|
|
353
|
+
const runtimeMarkerExists = await fs.promises.access(
|
|
354
|
+
this.kernel.bin.path("miniforge", CONDA_RUNTIME_MARKER)
|
|
355
|
+
).then(() => true).catch(() => false)
|
|
356
|
+
|
|
352
357
|
//////////////////////////////////////////////////////////////////
|
|
353
358
|
// exception handling
|
|
354
359
|
// if importlib_metadata || uvicorn || fastapi exist in the base environment, this.correct_conda = false
|
|
@@ -375,7 +380,7 @@ class Bin {
|
|
|
375
380
|
|
|
376
381
|
let to_reset_exists = false
|
|
377
382
|
|
|
378
|
-
if (to_reset_exists) {
|
|
383
|
+
if (!runtimeMarkerExists || to_reset_exists) {
|
|
379
384
|
this.correct_conda = false
|
|
380
385
|
} else {
|
|
381
386
|
let res = await buildCondaListFromMeta(this.kernel.bin.path("miniforge"))
|
|
@@ -448,9 +453,6 @@ class Bin {
|
|
|
448
453
|
break
|
|
449
454
|
}
|
|
450
455
|
}
|
|
451
|
-
if (this.mod && this.mod.conda && this.mod.conda.ensureWindowsOpenSslHooks) {
|
|
452
|
-
await this.mod.conda.ensureWindowsOpenSslHooks()
|
|
453
|
-
}
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
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,8 +5,11 @@ 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,
|
|
12
|
+
CONDA_RUNTIME_MARKER,
|
|
10
13
|
PYTHON_INSTALL_SPEC,
|
|
11
14
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
12
15
|
isExpectedPythonPinned,
|
|
@@ -42,11 +45,18 @@ async function writeCondaMeta(root, name, version, build = 'py_0') {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
async function writeHealthyCondaMeta(root, platform = 'win32') {
|
|
45
|
-
await writeCondaMeta(root, 'conda',
|
|
48
|
+
await writeCondaMeta(root, 'conda', CONDA_PIN_VERSION, '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
|
}
|
|
49
52
|
|
|
53
|
+
async function writeRuntimeMarker(root) {
|
|
54
|
+
const marker = path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)
|
|
55
|
+
await fs.mkdir(path.dirname(marker), { recursive: true })
|
|
56
|
+
await fs.writeFile(marker, '')
|
|
57
|
+
return marker
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
async function writeFakeManagedConda(root, platform = 'win32') {
|
|
51
61
|
const condaPath = platform === 'win32'
|
|
52
62
|
? path.join(root, 'bin', 'miniforge', 'Scripts', 'conda.exe')
|
|
@@ -78,86 +88,17 @@ function createConda(kernel) {
|
|
|
78
88
|
return conda
|
|
79
89
|
}
|
|
80
90
|
|
|
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
|
-
}
|
|
91
|
+
function createBin(root, platform = 'win32') {
|
|
92
|
+
const kernel = {
|
|
93
|
+
homedir: root,
|
|
94
|
+
platform,
|
|
95
|
+
path: (...parts) => path.join(root, ...parts),
|
|
160
96
|
}
|
|
97
|
+
const bin = new Bin(kernel)
|
|
98
|
+
bin.platform = platform
|
|
99
|
+
bin.installed = {}
|
|
100
|
+
kernel.bin = bin
|
|
101
|
+
return bin
|
|
161
102
|
}
|
|
162
103
|
|
|
163
104
|
test('Conda uses Miniforge assets and writes conda-forge-only config', async () => {
|
|
@@ -181,29 +122,9 @@ test('Conda uses Miniforge assets and writes conda-forge-only config', async ()
|
|
|
181
122
|
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge')), false)
|
|
182
123
|
})
|
|
183
124
|
|
|
184
|
-
test('Conda
|
|
185
|
-
|
|
186
|
-
|
|
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 () => {
|
|
125
|
+
test('Conda pins the managed Conda and Python versions consistently', async () => {
|
|
126
|
+
assert.equal(CONDA_PIN_VERSION, '26.3.2')
|
|
127
|
+
assert.equal(CONDA_RUNTIME_MARKER, '.pinokio-runtime-v1')
|
|
207
128
|
assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
|
|
208
129
|
assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
|
|
209
130
|
|
|
@@ -215,6 +136,48 @@ test('Conda pins Python 3.10.20 consistently across platforms', async () => {
|
|
|
215
136
|
assert.equal(isExpectedPythonPinned('win32', '3.10.20', 'h4de0772_1_cpython'), true)
|
|
216
137
|
})
|
|
217
138
|
|
|
139
|
+
test('Conda init does not mark an existing runtime as rebuilt', async () => {
|
|
140
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-marker-init-'))
|
|
141
|
+
await createMiniforge(root)
|
|
142
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
143
|
+
|
|
144
|
+
await createConda(createKernel(root, 'darwin')).init()
|
|
145
|
+
|
|
146
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), false)
|
|
147
|
+
assert.equal(
|
|
148
|
+
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
149
|
+
'conda ==26.3.2'
|
|
150
|
+
)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('Conda init leaves Windows OpenSSL activation hooks untouched', async () => {
|
|
154
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-hooks-untouched-'))
|
|
155
|
+
const hookRoot = path.join(root, 'bin', 'miniforge', 'etc', 'conda')
|
|
156
|
+
const hookFiles = [
|
|
157
|
+
path.join('activate.d', 'openssl_activate-win.bat'),
|
|
158
|
+
path.join('activate.d', 'openssl_activate-win.ps1'),
|
|
159
|
+
path.join('activate.d', 'openssl_activate-win.sh'),
|
|
160
|
+
path.join('deactivate.d', 'openssl_deactivate-win.bat'),
|
|
161
|
+
path.join('deactivate.d', 'openssl_deactivate-win.ps1'),
|
|
162
|
+
path.join('deactivate.d', 'openssl_deactivate-win.sh'),
|
|
163
|
+
]
|
|
164
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
165
|
+
for (const relativePath of hookFiles) {
|
|
166
|
+
const hookPath = path.join(hookRoot, relativePath)
|
|
167
|
+
await fs.mkdir(path.dirname(hookPath), { recursive: true })
|
|
168
|
+
await fs.writeFile(hookPath, `upstream ${relativePath}\n`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await createConda(createKernel(root, 'win32')).init()
|
|
172
|
+
|
|
173
|
+
for (const relativePath of hookFiles) {
|
|
174
|
+
assert.equal(
|
|
175
|
+
await fs.readFile(path.join(hookRoot, relativePath), 'utf8'),
|
|
176
|
+
`upstream ${relativePath}\n`
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
218
181
|
test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () => {
|
|
219
182
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-darwin-'))
|
|
220
183
|
const kernel = createKernel(root, 'darwin')
|
|
@@ -239,6 +202,11 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
|
|
|
239
202
|
condaInstall.message[1],
|
|
240
203
|
'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
|
|
241
204
|
)
|
|
205
|
+
assert.equal(
|
|
206
|
+
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
207
|
+
'conda ==26.3.2'
|
|
208
|
+
)
|
|
209
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
|
|
242
210
|
})
|
|
243
211
|
|
|
244
212
|
test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async () => {
|
|
@@ -267,6 +235,40 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
|
|
|
267
235
|
condaInstall.message[1],
|
|
268
236
|
'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
|
|
269
237
|
)
|
|
238
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('Conda install replaces the stale runtime and rebuilds declared Conda modules', async () => {
|
|
242
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-full-reset-'))
|
|
243
|
+
const staleRoot = await createMiniforge(root)
|
|
244
|
+
const staleMarker = path.join(staleRoot, 'stale-runtime.txt')
|
|
245
|
+
await fs.writeFile(staleMarker, 'remove me\n')
|
|
246
|
+
const kernel = createKernel(root, 'darwin')
|
|
247
|
+
const calls = []
|
|
248
|
+
|
|
249
|
+
kernel.bin.mods = [
|
|
250
|
+
{ name: 'uv', mod: { cmd: () => 'uv=0.11.23' } },
|
|
251
|
+
{ name: 'node', mod: { cmd: () => 'nodejs=22.21.1 pnpm' } },
|
|
252
|
+
]
|
|
253
|
+
kernel.bin.download = async () => {}
|
|
254
|
+
kernel.bin.rm = async () => {}
|
|
255
|
+
kernel.bin.exec = async (payload) => {
|
|
256
|
+
calls.push(payload)
|
|
257
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
258
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await createConda(kernel)._install({ dependencies: ['uv', 'node'] }, () => {})
|
|
263
|
+
|
|
264
|
+
assert.equal(await pathExists(staleMarker), false)
|
|
265
|
+
const condaInstall = calls.find((call) => Array.isArray(call.message))
|
|
266
|
+
assert.ok(condaInstall)
|
|
267
|
+
assert.equal(
|
|
268
|
+
condaInstall.message[1],
|
|
269
|
+
'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda-libmamba-solver>=25.4.0" uv=0.11.23 nodejs=22.21.1 pnpm'
|
|
270
|
+
)
|
|
271
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
|
|
270
272
|
})
|
|
271
273
|
|
|
272
274
|
test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
|
|
@@ -327,6 +329,15 @@ test('Conda installed stays false when metadata check already marked Conda inval
|
|
|
327
329
|
test('Conda check rejects metadata-only Windows installs', async () => {
|
|
328
330
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-missing-'))
|
|
329
331
|
await writeHealthyCondaMeta(root, 'win32')
|
|
332
|
+
await writeRuntimeMarker(root)
|
|
333
|
+
|
|
334
|
+
assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test('Conda check rejects an otherwise healthy runtime without the runtime marker', async () => {
|
|
338
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-marker-stale-'))
|
|
339
|
+
await writeHealthyCondaMeta(root, 'win32')
|
|
340
|
+
await writeFakeManagedConda(root, 'win32')
|
|
330
341
|
|
|
331
342
|
assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
|
|
332
343
|
})
|
|
@@ -335,13 +346,31 @@ test('Conda check accepts a runnable managed Windows conda executable', async ()
|
|
|
335
346
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-runnable-'))
|
|
336
347
|
await writeHealthyCondaMeta(root, 'win32')
|
|
337
348
|
await writeFakeManagedConda(root, 'win32')
|
|
349
|
+
await writeRuntimeMarker(root)
|
|
338
350
|
|
|
339
351
|
assert.equal(await createConda(createKernel(root, 'win32')).check(), true)
|
|
340
352
|
})
|
|
341
353
|
|
|
354
|
+
test('Bin readiness rejects a runtime without the marker and accepts it once marked', async () => {
|
|
355
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-conda-marker-'))
|
|
356
|
+
await writeHealthyCondaMeta(root, 'win32')
|
|
357
|
+
await writeFakeManagedConda(root, 'win32')
|
|
358
|
+
const bin = createBin(root, 'win32')
|
|
359
|
+
|
|
360
|
+
await bin.tryList()
|
|
361
|
+
assert.equal(bin.correct_conda, false)
|
|
362
|
+
|
|
363
|
+
await writeRuntimeMarker(root)
|
|
364
|
+
|
|
365
|
+
await bin.tryList()
|
|
366
|
+
assert.equal(bin.correct_conda, true)
|
|
367
|
+
})
|
|
368
|
+
|
|
342
369
|
test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
|
|
343
370
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-download-fails-'))
|
|
344
|
-
const
|
|
371
|
+
const miniforge = await createMiniforge(root)
|
|
372
|
+
const oldRuntimeFile = path.join(miniforge, 'old-runtime.txt')
|
|
373
|
+
await fs.writeFile(oldRuntimeFile, 'keep me\n')
|
|
345
374
|
const kernel = createKernel(root)
|
|
346
375
|
|
|
347
376
|
kernel.bin.download = async () => {
|
|
@@ -352,8 +381,31 @@ test('Conda install keeps the old runtime when the replacement installer downloa
|
|
|
352
381
|
createConda(kernel)._install({ dependencies: [] }, () => {}),
|
|
353
382
|
/download failed/
|
|
354
383
|
)
|
|
355
|
-
assert.equal(await pathExists(
|
|
356
|
-
assert.equal(await pathExists(path.join(
|
|
384
|
+
assert.equal(await pathExists(oldRuntimeFile), true)
|
|
385
|
+
assert.equal(await pathExists(path.join(miniforge, CONDA_RUNTIME_MARKER)), false)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test('Conda install leaves the runtime unmarked when bootstrap fails after replacement', async () => {
|
|
389
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-bootstrap-fails-'))
|
|
390
|
+
await createMiniforge(root)
|
|
391
|
+
const kernel = createKernel(root, 'darwin')
|
|
392
|
+
|
|
393
|
+
kernel.bin.mods = []
|
|
394
|
+
kernel.bin.download = async () => {}
|
|
395
|
+
kernel.bin.rm = async () => {}
|
|
396
|
+
kernel.bin.exec = async (payload) => {
|
|
397
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
398
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
throw new Error('bootstrap failed')
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
await assert.rejects(
|
|
405
|
+
createConda(kernel)._install({ dependencies: [] }, () => {}),
|
|
406
|
+
/bootstrap failed/
|
|
407
|
+
)
|
|
408
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), false)
|
|
357
409
|
})
|
|
358
410
|
|
|
359
411
|
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',
|