pinokiod 7.4.3 → 7.5.1
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 +9 -7
- package/kernel/bin/conda.js +117 -204
- package/kernel/bin/cuda.js +7 -7
- package/kernel/bin/index.js +8 -12
- package/kernel/connect/providers/huggingface/index.js +4 -4
- package/kernel/index.js +0 -7
- package/kernel/shell.js +5 -4
- package/kernel/sysinfo.js +1 -1
- package/kernel/util.js +2 -2
- package/package.json +1 -1
- package/server/index.js +2 -2
- package/system/plugin/claude/pinokio.js +2 -2
- package/system/plugin/claude-auto/pinokio.js +2 -2
- package/system/plugin/codex/pinokio.js +1 -1
- package/system/plugin/codex-auto/pinokio.js +1 -1
- package/test/conda-bin.test.js +249 -0
- package/kernel/legacy_cleanup/ffmpeg.js +0 -133
- package/kernel/legacy_cleanup/index.js +0 -47
- package/test/legacy-cleanup.test.js +0 -161
package/kernel/bin/index.js
CHANGED
|
@@ -23,8 +23,8 @@ const Cuda = require("./cuda")
|
|
|
23
23
|
const Torch = require("./torch")
|
|
24
24
|
const { buildCondaListFromMeta } = require('./conda-meta')
|
|
25
25
|
const {
|
|
26
|
+
isExpectedPythonPinned,
|
|
26
27
|
isExpectedSqlitePinned,
|
|
27
|
-
isWindowsPythonSslFixed,
|
|
28
28
|
} = require('./conda-pins')
|
|
29
29
|
const { glob } = require('glob')
|
|
30
30
|
const fakeUa = require('fake-useragent');
|
|
@@ -360,9 +360,9 @@ class Bin {
|
|
|
360
360
|
// if importlib_metadata || uvicorn || fastapi exist in the base environment, this.correct_conda = false
|
|
361
361
|
let site_packages_path
|
|
362
362
|
if (this.platform === "win32") {
|
|
363
|
-
site_packages_path = path.resolve(this.kernel.homedir, "bin/
|
|
363
|
+
site_packages_path = path.resolve(this.kernel.homedir, "bin/miniforge/Lib/site-packages")
|
|
364
364
|
} else {
|
|
365
|
-
site_packages_path = path.resolve(this.kernel.homedir, "bin/
|
|
365
|
+
site_packages_path = path.resolve(this.kernel.homedir, "bin/miniforge/lib/python3.10/site-packages")
|
|
366
366
|
}
|
|
367
367
|
// // check if any of 'uvicorn', 'importlib_metadata', 'fastapi' exists
|
|
368
368
|
// let module_paths = ["fastapi", "uvicorn", "importlib_metadata"].map((name) => {
|
|
@@ -384,7 +384,7 @@ class Bin {
|
|
|
384
384
|
if (to_reset_exists) {
|
|
385
385
|
this.correct_conda = false
|
|
386
386
|
} else {
|
|
387
|
-
let res = await buildCondaListFromMeta(this.kernel.bin.path("
|
|
387
|
+
let res = await buildCondaListFromMeta(this.kernel.bin.path("miniforge"))
|
|
388
388
|
let lines = res.response.split(/[\r\n]+/)
|
|
389
389
|
for(let line of lines) {
|
|
390
390
|
if (start) {
|
|
@@ -400,11 +400,8 @@ class Bin {
|
|
|
400
400
|
conda_check.conda = true
|
|
401
401
|
}
|
|
402
402
|
if (name === "conda-libmamba-solver") {
|
|
403
|
-
//if (String(version) === "24.7.0") {
|
|
404
|
-
let channel = chunks[3]
|
|
405
403
|
let coerced = semver.coerce(version)
|
|
406
404
|
let mamba_requirement = ">=25.4.0"
|
|
407
|
-
//if (semver.satisfies(coerced, mamba_requirement) && channel === "conda-forge") {
|
|
408
405
|
if (semver.satisfies(coerced, mamba_requirement)) {
|
|
409
406
|
conda_check.mamba = true
|
|
410
407
|
}
|
|
@@ -417,7 +414,7 @@ class Bin {
|
|
|
417
414
|
}
|
|
418
415
|
}
|
|
419
416
|
if (name === "python") {
|
|
420
|
-
conda_check.python = this.platform
|
|
417
|
+
conda_check.python = isExpectedPythonPinned(this.platform, version, build)
|
|
421
418
|
}
|
|
422
419
|
}
|
|
423
420
|
} else {
|
|
@@ -427,8 +424,7 @@ class Bin {
|
|
|
427
424
|
}
|
|
428
425
|
}
|
|
429
426
|
|
|
430
|
-
if (conda_check.conda && conda_check.mamba && conda_check.sqlite &&
|
|
431
|
-
//if (conda_check.conda && conda_check.mamba) {
|
|
427
|
+
if (conda_check.conda && conda_check.mamba && conda_check.sqlite && conda_check.python) {
|
|
432
428
|
this.correct_conda = true
|
|
433
429
|
}
|
|
434
430
|
}
|
|
@@ -449,7 +445,7 @@ class Bin {
|
|
|
449
445
|
// 1. conda
|
|
450
446
|
|
|
451
447
|
// check conda location and see if it exists. only run if it exists
|
|
452
|
-
let conda_path = path.resolve(this.kernel.homedir, "bin", "
|
|
448
|
+
let conda_path = path.resolve(this.kernel.homedir, "bin", "miniforge")
|
|
453
449
|
let conda_exists = await this.exists(conda_path)
|
|
454
450
|
|
|
455
451
|
let start
|
|
@@ -513,7 +509,7 @@ class Bin {
|
|
|
513
509
|
}
|
|
514
510
|
|
|
515
511
|
// /// B. base path initialization
|
|
516
|
-
// let conda_meta_path = this.kernel.bin.path("
|
|
512
|
+
// let conda_meta_path = this.kernel.bin.path("miniforge", "conda-meta")
|
|
517
513
|
// const metaFiles = await glob("*.json", {
|
|
518
514
|
// cwd: conda_meta_path
|
|
519
515
|
// })
|
|
@@ -39,11 +39,11 @@ class Huggingface {
|
|
|
39
39
|
}
|
|
40
40
|
hfPath() {
|
|
41
41
|
const candidates = this.kernel.platform === "win32" ? [
|
|
42
|
-
this.kernel.path("bin", "
|
|
43
|
-
this.kernel.path("bin", "
|
|
44
|
-
this.kernel.path("bin", "
|
|
42
|
+
this.kernel.path("bin", "miniforge", "Scripts", "hf.exe"),
|
|
43
|
+
this.kernel.path("bin", "miniforge", "Scripts", "hf"),
|
|
44
|
+
this.kernel.path("bin", "miniforge", "bin", "hf"),
|
|
45
45
|
] : [
|
|
46
|
-
this.kernel.path("bin", "
|
|
46
|
+
this.kernel.path("bin", "miniforge", "bin", "hf"),
|
|
47
47
|
]
|
|
48
48
|
for (const candidate of candidates) {
|
|
49
49
|
if (fs.existsSync(candidate)) {
|
package/kernel/index.js
CHANGED
|
@@ -43,7 +43,6 @@ const AppLauncher = require('./app_launcher')
|
|
|
43
43
|
const ReadyState = require('./ready')
|
|
44
44
|
const Autolaunch = require('./autolaunch')
|
|
45
45
|
const LaunchRequirements = require('./launch_requirements')
|
|
46
|
-
const LegacyCleanup = require('./legacy_cleanup')
|
|
47
46
|
const { DownloaderHelper } = require('node-downloader-helper');
|
|
48
47
|
const { ProxyAgent } = require('proxy-agent');
|
|
49
48
|
const fakeUa = require('fake-useragent');
|
|
@@ -101,7 +100,6 @@ class Kernel {
|
|
|
101
100
|
this.launchRequirements = new LaunchRequirements(this)
|
|
102
101
|
this.autolaunch = new Autolaunch(this)
|
|
103
102
|
this.autolaunch_status = this.autolaunch.startupStatus()
|
|
104
|
-
this.legacyCleanup = new LegacyCleanup(this)
|
|
105
103
|
this.sysReady = new Promise((resolve) => {
|
|
106
104
|
this._resolveSysReady = resolve
|
|
107
105
|
})
|
|
@@ -1194,11 +1192,6 @@ class Kernel {
|
|
|
1194
1192
|
await Environment.ensurePinokioCacheDirs(this, {
|
|
1195
1193
|
throwOnFailure: true
|
|
1196
1194
|
})
|
|
1197
|
-
await this.legacyCleanup.runOnce((event) => {
|
|
1198
|
-
if (event && typeof event.raw === "string") {
|
|
1199
|
-
console.log(event.raw.trimEnd())
|
|
1200
|
-
}
|
|
1201
|
-
})
|
|
1202
1195
|
|
|
1203
1196
|
// if key.json doesn't exist, create an empty json file
|
|
1204
1197
|
let ee = await this.exists(this.homedir, "key.json")
|
package/kernel/shell.js
CHANGED
|
@@ -21,6 +21,7 @@ const ShellParser = require('./shell_parser')
|
|
|
21
21
|
const AnsiStreamTracker = require('./ansi_stream_tracker')
|
|
22
22
|
const ShellStateSync = require('./shell_state_sync')
|
|
23
23
|
const ShellRunTemplate = require('./api/shell_run_template')
|
|
24
|
+
const { PYTHON_INSTALL_SPEC } = require('./bin/conda-pins')
|
|
24
25
|
const home = os.homedir()
|
|
25
26
|
|
|
26
27
|
function normalizeComparablePath(filePath, platform) {
|
|
@@ -959,7 +960,7 @@ class Shell {
|
|
|
959
960
|
conda_hook () {
|
|
960
961
|
if (this.platform === "win32") {
|
|
961
962
|
if (/bash/i.test(this.shell)) {
|
|
962
|
-
const conda_sh = this.kernel.path("bin/
|
|
963
|
+
const conda_sh = this.kernel.path("bin/miniforge/etc/profile.d/conda.sh")
|
|
963
964
|
return `source ${this.quoteArgForShell(Util.p2u(conda_sh))}`
|
|
964
965
|
} else {
|
|
965
966
|
return "conda_hook"
|
|
@@ -986,7 +987,7 @@ class Shell {
|
|
|
986
987
|
// 1. conda
|
|
987
988
|
let conda_path
|
|
988
989
|
let conda_name
|
|
989
|
-
let conda_python =
|
|
990
|
+
let conda_python = PYTHON_INSTALL_SPEC
|
|
990
991
|
let conda_args
|
|
991
992
|
let conda_activate
|
|
992
993
|
|
|
@@ -1106,7 +1107,7 @@ class Shell {
|
|
|
1106
1107
|
// timeout,
|
|
1107
1108
|
]
|
|
1108
1109
|
} else {
|
|
1109
|
-
let envs_path = this.kernel.bin.path("
|
|
1110
|
+
let envs_path = this.kernel.bin.path("miniforge/envs")
|
|
1110
1111
|
let env_path = path.resolve(envs_path, conda_name)
|
|
1111
1112
|
let env_exists = await this.exists(env_path)
|
|
1112
1113
|
if (env_exists) {
|
|
@@ -1185,7 +1186,7 @@ class Shell {
|
|
|
1185
1186
|
console.log(`Unsupported arch: os.arch()=${architecture}, process.arch=${armArchitecture}`)
|
|
1186
1187
|
}
|
|
1187
1188
|
|
|
1188
|
-
const activate_root = this.kernel.bin.path("
|
|
1189
|
+
const activate_root = this.kernel.bin.path("miniforge/etc/conda/activate.d")
|
|
1189
1190
|
const logDir = this.kernel.path("cache", "logs")
|
|
1190
1191
|
await fs.promises.mkdir(logDir, { recursive: true }).catch(() => {})
|
|
1191
1192
|
const logSuffix = this.id || Date.now()
|
package/kernel/sysinfo.js
CHANGED
|
@@ -206,7 +206,7 @@ class Sysinfo {
|
|
|
206
206
|
// console.log(_res)
|
|
207
207
|
// console.log("############### DEBUGGING ENV FINISH")
|
|
208
208
|
|
|
209
|
-
let conda_path = path.resolve(this.kernel.homedir, "bin", "
|
|
209
|
+
let conda_path = path.resolve(this.kernel.homedir, "bin", "miniforge")
|
|
210
210
|
let conda_exists = await this.exists(conda_path)
|
|
211
211
|
|
|
212
212
|
let res
|
package/kernel/util.js
CHANGED
|
@@ -199,9 +199,9 @@ const filepicker = async(req, ondata, kernel) => {
|
|
|
199
199
|
let picker_path = kernel.path("bin/py/picker.py")
|
|
200
200
|
let python
|
|
201
201
|
if (kernel.platform === "win32") {
|
|
202
|
-
python = kernel.path("bin/
|
|
202
|
+
python = kernel.path("bin/miniforge/python")
|
|
203
203
|
} else {
|
|
204
|
-
python = kernel.path("bin/
|
|
204
|
+
python = kernel.path("bin/miniforge/bin/python")
|
|
205
205
|
}
|
|
206
206
|
const proc = spawn(python, [picker_path])
|
|
207
207
|
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -501,7 +501,7 @@ class Server {
|
|
|
501
501
|
if (this.kernel.platform !== "win32") {
|
|
502
502
|
return null
|
|
503
503
|
}
|
|
504
|
-
const bashPath = this.kernel.path("bin/
|
|
504
|
+
const bashPath = this.kernel.path("bin/miniforge/Library/bin/bash.exe")
|
|
505
505
|
return await this.exists(bashPath) ? bashPath : null
|
|
506
506
|
}
|
|
507
507
|
running_dynamic (name, menu, selected_query) {
|
|
@@ -11730,7 +11730,7 @@ class Server {
|
|
|
11730
11730
|
managedLaunchOverrides.conda = { skip: true }
|
|
11731
11731
|
}
|
|
11732
11732
|
if (isWindowsPlatform && (provider.key === "codex" || provider.key === "claude")) {
|
|
11733
|
-
const gitBashPath = this.kernel.path("bin/
|
|
11733
|
+
const gitBashPath = this.kernel.path("bin/miniforge/Library/bin/bash.exe")
|
|
11734
11734
|
const bashExists = await fs.promises.access(gitBashPath, fs.constants.F_OK).then(() => true).catch(() => false)
|
|
11735
11735
|
if (bashExists) {
|
|
11736
11736
|
if (provider.key === "claude") {
|
|
@@ -7,12 +7,12 @@ module.exports = {
|
|
|
7
7
|
id: "run",
|
|
8
8
|
method: "shell.run",
|
|
9
9
|
params: {
|
|
10
|
-
shell: "{{kernel.path('bin/
|
|
10
|
+
shell: "{{kernel.path('bin/miniforge/Library/bin/bash.exe')}}",
|
|
11
11
|
conda: {
|
|
12
12
|
skip: true
|
|
13
13
|
},
|
|
14
14
|
env: {
|
|
15
|
-
CLAUDE_CODE_GIT_BASH_PATH: "{{kernel.path('bin/
|
|
15
|
+
CLAUDE_CODE_GIT_BASH_PATH: "{{kernel.path('bin/miniforge/Library/bin/bash.exe')}}"
|
|
16
16
|
},
|
|
17
17
|
message: {
|
|
18
18
|
_: [
|
|
@@ -8,12 +8,12 @@ module.exports = {
|
|
|
8
8
|
id: "run",
|
|
9
9
|
method: "shell.run",
|
|
10
10
|
params: {
|
|
11
|
-
shell: "{{kernel.path('bin/
|
|
11
|
+
shell: "{{kernel.path('bin/miniforge/Library/bin/bash.exe')}}",
|
|
12
12
|
conda: {
|
|
13
13
|
skip: true
|
|
14
14
|
},
|
|
15
15
|
env: {
|
|
16
|
-
CLAUDE_CODE_GIT_BASH_PATH: "{{kernel.path('bin/
|
|
16
|
+
CLAUDE_CODE_GIT_BASH_PATH: "{{kernel.path('bin/miniforge/Library/bin/bash.exe')}}",
|
|
17
17
|
CLAUBBIT: "true"
|
|
18
18
|
},
|
|
19
19
|
message: {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fsModule = require('node:fs')
|
|
3
|
+
const fs = require('node:fs/promises')
|
|
4
|
+
const os = require('node:os')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const test = require('node:test')
|
|
7
|
+
|
|
8
|
+
const Conda = require('../kernel/bin/conda')
|
|
9
|
+
const {
|
|
10
|
+
PYTHON_INSTALL_SPEC,
|
|
11
|
+
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
12
|
+
isExpectedPythonPinned,
|
|
13
|
+
} = require('../kernel/bin/conda-pins')
|
|
14
|
+
|
|
15
|
+
async function pathExists(target) {
|
|
16
|
+
return fs.access(target).then(() => true).catch(() => false)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createKernel(root, platform = 'win32') {
|
|
20
|
+
return {
|
|
21
|
+
arch: 'x64',
|
|
22
|
+
homedir: root,
|
|
23
|
+
platform,
|
|
24
|
+
path: (...parts) => path.join(root, ...parts),
|
|
25
|
+
exists: async (...parts) => pathExists(path.join(root, ...parts)),
|
|
26
|
+
bin: {
|
|
27
|
+
correct_conda: true,
|
|
28
|
+
path: (...parts) => path.join(root, 'bin', ...parts),
|
|
29
|
+
exists: async (target) => pathExists(path.join(root, 'bin', target)),
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function createCondaRoot(root, name) {
|
|
35
|
+
const condaRoot = path.join(root, 'bin', name)
|
|
36
|
+
await fs.mkdir(path.join(condaRoot, 'Scripts'), { recursive: true })
|
|
37
|
+
await fs.writeFile(path.join(condaRoot, 'Scripts', 'conda.exe'), 'fake conda\n')
|
|
38
|
+
return condaRoot
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function createMiniforge(root) {
|
|
42
|
+
return createCondaRoot(root, 'miniforge')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function createLegacyMiniconda(root) {
|
|
46
|
+
return createCondaRoot(root, 'miniconda')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createConda(kernel) {
|
|
50
|
+
const conda = new Conda()
|
|
51
|
+
conda.kernel = kernel
|
|
52
|
+
return conda
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test('Conda uses Miniforge assets and writes conda-forge-only config', async () => {
|
|
56
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-miniforge-config-'))
|
|
57
|
+
const conda = createConda(createKernel(root))
|
|
58
|
+
|
|
59
|
+
assert.equal(
|
|
60
|
+
conda.urls.win32.x64,
|
|
61
|
+
'https://github.com/conda-forge/miniforge/releases/download/26.3.2-3/Miniforge3-Windows-x86_64.exe'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
await conda.init()
|
|
65
|
+
const condarc = await fs.readFile(path.join(root, 'condarc'), 'utf8')
|
|
66
|
+
|
|
67
|
+
assert.match(condarc, /^channels:\n - conda-forge\n/m)
|
|
68
|
+
assert.match(condarc, / - python=3\.10\.20\n/)
|
|
69
|
+
assert.match(condarc, /bin\/miniforge\/envs/)
|
|
70
|
+
assert.doesNotMatch(condarc, /bin\/miniconda\/envs/)
|
|
71
|
+
assert.doesNotMatch(condarc, /\bdefaults\b/)
|
|
72
|
+
assert.doesNotMatch(condarc, /auto_accept_tos/)
|
|
73
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge')), false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('Conda init writes activation hooks only after Miniforge exists', async () => {
|
|
77
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-init-hooks-'))
|
|
78
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
79
|
+
const conda = createConda(createKernel(root))
|
|
80
|
+
|
|
81
|
+
await conda.init()
|
|
82
|
+
|
|
83
|
+
assert.equal(
|
|
84
|
+
await pathExists(path.join(root, 'bin', 'miniforge', 'etc', 'conda', 'activate.d', 'zz_pinokio_unset_ssl_cert_dir-win.bat')),
|
|
85
|
+
true
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('Conda pins Python 3.10.20 consistently across platforms', async () => {
|
|
90
|
+
assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
|
|
91
|
+
assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
|
|
92
|
+
|
|
93
|
+
assert.equal(isExpectedPythonPinned('darwin', '3.10.20', 'cpython'), true)
|
|
94
|
+
assert.equal(isExpectedPythonPinned('linux', '3.10.20', 'cpython'), true)
|
|
95
|
+
assert.equal(isExpectedPythonPinned('darwin', '3.10.21', 'cpython'), false)
|
|
96
|
+
assert.equal(isExpectedPythonPinned('darwin', '3.11.0', 'cpython'), false)
|
|
97
|
+
assert.equal(isExpectedPythonPinned('win32', '3.10.20', 'h4de0772_0_cpython'), false)
|
|
98
|
+
assert.equal(isExpectedPythonPinned('win32', '3.10.20', 'h4de0772_1_cpython'), true)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () => {
|
|
102
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-darwin-'))
|
|
103
|
+
const kernel = createKernel(root, 'darwin')
|
|
104
|
+
const calls = []
|
|
105
|
+
|
|
106
|
+
kernel.bin.mods = []
|
|
107
|
+
kernel.bin.download = async () => {}
|
|
108
|
+
kernel.bin.rm = async () => {}
|
|
109
|
+
kernel.bin.exec = async (payload) => {
|
|
110
|
+
calls.push(payload)
|
|
111
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
112
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await createConda(kernel)._install({ dependencies: [] }, () => {})
|
|
117
|
+
|
|
118
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniconda')), true)
|
|
119
|
+
const condaInstall = calls.find((call) => Array.isArray(call.message))
|
|
120
|
+
assert.ok(condaInstall)
|
|
121
|
+
assert.equal(
|
|
122
|
+
condaInstall.message[1],
|
|
123
|
+
'conda install -y --override-channels -c conda-forge "python=3.10.20" "sqlite=3.47.2" "conda-libmamba-solver>=25.4.0"'
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async () => {
|
|
128
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-python-pin-win32-'))
|
|
129
|
+
const kernel = createKernel(root, 'win32')
|
|
130
|
+
const calls = []
|
|
131
|
+
|
|
132
|
+
kernel.bin.mods = []
|
|
133
|
+
kernel.bin.download = async () => {}
|
|
134
|
+
kernel.bin.rm = async () => {}
|
|
135
|
+
kernel.bin.exec = async (payload) => {
|
|
136
|
+
calls.push(payload)
|
|
137
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
138
|
+
const miniforge = path.join(root, 'bin', 'miniforge')
|
|
139
|
+
await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
|
|
140
|
+
await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await createConda(kernel)._install({ dependencies: [] }, () => {})
|
|
145
|
+
|
|
146
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniconda')), true)
|
|
147
|
+
const condaInstall = calls.find((call) => Array.isArray(call.message))
|
|
148
|
+
assert.ok(condaInstall)
|
|
149
|
+
assert.equal(
|
|
150
|
+
condaInstall.message[1],
|
|
151
|
+
'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "sqlite=3.53.2" "conda-libmamba-solver>=25.4.0"'
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
|
|
156
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-miniforge-alias-'))
|
|
157
|
+
const legacy = await createLegacyMiniconda(root)
|
|
158
|
+
await fs.writeFile(path.join(legacy, 'legacy.txt'), 'old runtime\n')
|
|
159
|
+
const kernel = createKernel(root, 'darwin')
|
|
160
|
+
|
|
161
|
+
kernel.bin.mods = []
|
|
162
|
+
kernel.bin.download = async () => {}
|
|
163
|
+
kernel.bin.rm = async () => {}
|
|
164
|
+
kernel.bin.exec = async (payload) => {
|
|
165
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
166
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await createConda(kernel)._install({ dependencies: [] }, () => {})
|
|
171
|
+
|
|
172
|
+
const miniforge = path.join(root, 'bin', 'miniforge')
|
|
173
|
+
const miniconda = path.join(root, 'bin', 'miniconda')
|
|
174
|
+
assert.equal(await pathExists(path.join(miniconda, 'legacy.txt')), false)
|
|
175
|
+
assert.equal(await fs.realpath(miniconda), await fs.realpath(miniforge))
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('Conda install removes legacy miniconda symlinks without deleting their target', async () => {
|
|
179
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-legacy-symlink-'))
|
|
180
|
+
const external = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-external-'))
|
|
181
|
+
await fs.writeFile(path.join(external, 'keep.txt'), 'do not delete\n')
|
|
182
|
+
await fs.mkdir(path.join(root, 'bin'), { recursive: true })
|
|
183
|
+
await fs.symlink(external, path.join(root, 'bin', 'miniconda'), 'dir')
|
|
184
|
+
const kernel = createKernel(root, 'darwin')
|
|
185
|
+
|
|
186
|
+
kernel.bin.mods = []
|
|
187
|
+
kernel.bin.download = async () => {}
|
|
188
|
+
kernel.bin.rm = async () => {}
|
|
189
|
+
kernel.bin.exec = async (payload) => {
|
|
190
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
191
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await createConda(kernel)._install({ dependencies: [] }, () => {})
|
|
196
|
+
|
|
197
|
+
assert.equal(await pathExists(path.join(external, 'keep.txt')), true)
|
|
198
|
+
assert.equal(await fs.realpath(path.join(root, 'bin', 'miniconda')), await fs.realpath(path.join(root, 'bin', 'miniforge')))
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
test('Conda installed stays false when metadata check already marked Conda invalid', async () => {
|
|
202
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-metadata-invalid-'))
|
|
203
|
+
await createMiniforge(root)
|
|
204
|
+
const kernel = createKernel(root)
|
|
205
|
+
kernel.bin.correct_conda = false
|
|
206
|
+
|
|
207
|
+
assert.equal(await createConda(kernel).installed(), false)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
|
|
211
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-download-fails-'))
|
|
212
|
+
const miniconda = await createLegacyMiniconda(root)
|
|
213
|
+
const kernel = createKernel(root)
|
|
214
|
+
|
|
215
|
+
kernel.bin.download = async () => {
|
|
216
|
+
throw new Error('download failed')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await assert.rejects(
|
|
220
|
+
createConda(kernel)._install({ dependencies: [] }, () => {}),
|
|
221
|
+
/download failed/
|
|
222
|
+
)
|
|
223
|
+
assert.equal(await pathExists(miniconda), true)
|
|
224
|
+
assert.equal(await pathExists(path.join(root, 'bin', 'miniforge')), false)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test('Conda install reports manual recovery when the old runtime cannot be removed', async () => {
|
|
228
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-runtime-locked-'))
|
|
229
|
+
await createLegacyMiniconda(root)
|
|
230
|
+
const kernel = createKernel(root)
|
|
231
|
+
kernel.bin.download = async () => {}
|
|
232
|
+
const conda = createConda(kernel)
|
|
233
|
+
const originalRm = fsModule.promises.rm
|
|
234
|
+
|
|
235
|
+
fsModule.promises.rm = async () => {
|
|
236
|
+
const error = new Error('resource busy')
|
|
237
|
+
error.code = 'EBUSY'
|
|
238
|
+
throw error
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await assert.rejects(
|
|
243
|
+
conda._install({ dependencies: [] }, () => {}),
|
|
244
|
+
/delete that folder manually/
|
|
245
|
+
)
|
|
246
|
+
} finally {
|
|
247
|
+
fsModule.promises.rm = originalRm
|
|
248
|
+
}
|
|
249
|
+
})
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
|
|
4
|
-
const LEGACY_FFMPEG_HOOK_NAMES = [
|
|
5
|
-
'zz_pinokio_ffmpeg.sh',
|
|
6
|
-
'zz_pinokio_ffmpeg.bat',
|
|
7
|
-
'zz_pinokio_ffmpeg.ps1',
|
|
8
|
-
]
|
|
9
|
-
|
|
10
|
-
const REMOVE_RETRY_COUNT = 5
|
|
11
|
-
const REMOVE_RETRY_DELAY_MS = 100
|
|
12
|
-
|
|
13
|
-
async function pathExists(target) {
|
|
14
|
-
return fs.promises.access(target).then(() => true).catch(() => false)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function legacyFfmpegRuntimePaths(kernel) {
|
|
18
|
-
if (!kernel || !kernel.bin || typeof kernel.bin.path !== 'function') {
|
|
19
|
-
return []
|
|
20
|
-
}
|
|
21
|
-
return [
|
|
22
|
-
kernel.bin.path('ffmpeg-env'),
|
|
23
|
-
kernel.bin.path('ffmpeg-pkgs'),
|
|
24
|
-
kernel.bin.path('ffmpeg'),
|
|
25
|
-
kernel.bin.path('ffmpeg-tmp'),
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function legacyFfmpegHookFiles(kernel) {
|
|
30
|
-
if (!kernel || !kernel.bin || typeof kernel.bin.path !== 'function') {
|
|
31
|
-
return []
|
|
32
|
-
}
|
|
33
|
-
const dirs = [
|
|
34
|
-
kernel.bin.path('miniconda', 'etc', 'conda', 'activate.d'),
|
|
35
|
-
kernel.bin.path('miniconda', 'etc', 'conda', 'deactivate.d'),
|
|
36
|
-
]
|
|
37
|
-
return dirs.flatMap((dir) => LEGACY_FFMPEG_HOOK_NAMES.map((name) => path.resolve(dir, name)))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function manualFfmpegHookCleanupMessage(failures) {
|
|
41
|
-
const files = failures.map((failure) => failure.target)
|
|
42
|
-
return [
|
|
43
|
-
'Pinokio could not remove old FFmpeg Conda activation hooks.',
|
|
44
|
-
'',
|
|
45
|
-
'These old hooks can break the base Conda environment by adding the removed ffmpeg-env runtime to PATH.',
|
|
46
|
-
'',
|
|
47
|
-
'Close all Pinokio windows and any terminals using Pinokio, then delete these files if they exist:',
|
|
48
|
-
...files.map((file) => `- ${file}`),
|
|
49
|
-
'',
|
|
50
|
-
'After deleting them, reopen Pinokio.',
|
|
51
|
-
].join('\n')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class LegacyFfmpegCleanupError extends Error {
|
|
55
|
-
constructor(failures) {
|
|
56
|
-
super(manualFfmpegHookCleanupMessage(failures))
|
|
57
|
-
this.name = 'LegacyFfmpegCleanupError'
|
|
58
|
-
this.failures = failures
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function hasAnyPath(paths) {
|
|
63
|
-
for (const target of paths) {
|
|
64
|
-
if (await pathExists(target)) {
|
|
65
|
-
return true
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return false
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function hasCleanupTargets(kernel) {
|
|
72
|
-
return await hasAnyPath(legacyFfmpegHookFiles(kernel)) ||
|
|
73
|
-
await hasAnyPath(legacyFfmpegRuntimePaths(kernel))
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function removeLegacyPath(target, ondata) {
|
|
77
|
-
const existed = await pathExists(target)
|
|
78
|
-
if (!existed) {
|
|
79
|
-
return { target, found: false, removed: false }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
await fs.promises.rm(target, {
|
|
84
|
-
recursive: true,
|
|
85
|
-
force: true,
|
|
86
|
-
maxRetries: REMOVE_RETRY_COUNT,
|
|
87
|
-
retryDelay: REMOVE_RETRY_DELAY_MS,
|
|
88
|
-
})
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (ondata) {
|
|
91
|
-
const reason = error && (error.code || error.message) ? error.code || error.message : String(error)
|
|
92
|
-
ondata({ raw: `could not remove legacy path: ${target} (${reason})\r\n` })
|
|
93
|
-
}
|
|
94
|
-
return { target, found: true, removed: false, error }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (ondata) {
|
|
98
|
-
ondata({ raw: `removed legacy path: ${target}\r\n` })
|
|
99
|
-
}
|
|
100
|
-
return { target, found: true, removed: true }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function run(kernel, ondata) {
|
|
104
|
-
const results = []
|
|
105
|
-
const hookFailures = []
|
|
106
|
-
|
|
107
|
-
for (const target of legacyFfmpegHookFiles(kernel)) {
|
|
108
|
-
const result = await removeLegacyPath(target, ondata)
|
|
109
|
-
results.push(result)
|
|
110
|
-
if (result.found && !result.removed) {
|
|
111
|
-
hookFailures.push(result)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (hookFailures.length > 0) {
|
|
116
|
-
throw new LegacyFfmpegCleanupError(hookFailures)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for (const target of legacyFfmpegRuntimePaths(kernel)) {
|
|
120
|
-
results.push(await removeLegacyPath(target, ondata))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return results
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
module.exports = {
|
|
127
|
-
LegacyFfmpegCleanupError,
|
|
128
|
-
hasCleanupTargets,
|
|
129
|
-
legacyFfmpegHookFiles,
|
|
130
|
-
legacyFfmpegRuntimePaths,
|
|
131
|
-
name: 'ffmpeg',
|
|
132
|
-
run,
|
|
133
|
-
}
|