pinokiod 7.5.29 → 7.5.31

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.
@@ -69,15 +69,27 @@ async function runCondaList(minicondaPath) {
69
69
  })
70
70
  }
71
71
 
72
- function resolveCondaBinary(minicondaPath) {
73
- if (process.platform === 'win32') {
72
+ async function managedCondaRuns(minicondaPath, platform = process.platform) {
73
+ const condaBinary = resolveCondaBinary(minicondaPath, platform, false)
74
+ if (!condaBinary) {
75
+ return false
76
+ }
77
+ return await new Promise((resolve) => {
78
+ execFile(condaBinary, ['--version'], { windowsHide: true, timeout: 15000 }, (err) => {
79
+ resolve(!err)
80
+ })
81
+ })
82
+ }
83
+
84
+ function resolveCondaBinary(minicondaPath, platform = process.platform, allowFallback = true) {
85
+ if (platform === 'win32') {
74
86
  if (minicondaPath) {
75
87
  const scriptPath = path.join(minicondaPath, 'Scripts', 'conda.exe')
76
88
  if (fs.existsSync(scriptPath)) {
77
89
  return scriptPath
78
90
  }
79
91
  }
80
- return 'conda'
92
+ return allowFallback ? 'conda' : null
81
93
  }
82
94
  if (minicondaPath) {
83
95
  const binPath = path.join(minicondaPath, 'bin', 'conda')
@@ -85,9 +97,10 @@ function resolveCondaBinary(minicondaPath) {
85
97
  return binPath
86
98
  }
87
99
  }
88
- return 'conda'
100
+ return allowFallback ? 'conda' : null
89
101
  }
90
102
 
91
103
  module.exports = {
92
- buildCondaListFromMeta
104
+ buildCondaListFromMeta,
105
+ managedCondaRuns,
93
106
  }
@@ -2,7 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const { glob } = require('glob')
4
4
  const semver = require('semver')
5
- const { buildCondaListFromMeta } = require('./conda-meta')
5
+ const { buildCondaListFromMeta, managedCondaRuns } = require('./conda-meta')
6
6
  const {
7
7
  CONDA_PIN_VERSION,
8
8
  PYTHON_INSTALL_SPEC,
@@ -14,6 +14,61 @@ 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
+ ]
17
72
 
18
73
  class Conda {
19
74
  description = "Pinokio uses Conda to install various useful programs in an isolated manner."
@@ -69,37 +124,42 @@ class Conda {
69
124
  }
70
125
  return base
71
126
  }
72
- async ensureSslCertDirOverride() {
127
+ async ensureWindowsOpenSslHooks() {
73
128
  if (this.kernel.platform !== "win32") {
74
129
  return
75
130
  }
76
- if (!(await this.hasCondaMeta())) {
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) {
77
138
  return
78
139
  }
79
- const activateDir = this.kernel.bin.path(`${CONDA_ROOT_DIR}/etc/conda/activate.d`)
80
- await fs.promises.mkdir(activateDir, { recursive: true }).catch(() => {})
81
- await fs.promises.writeFile(
82
- path.resolve(activateDir, "zz_pinokio_unset_ssl_cert_dir-win.bat"),
83
- `@echo off
84
- if "%__CONDA_OPENSSL_CERT_DIR_SET%"=="1" (
85
- set "SSL_CERT_DIR="
86
- )
87
- `
88
- )
89
- await fs.promises.writeFile(
90
- path.resolve(activateDir, "zz_pinokio_unset_ssl_cert_dir-win.ps1"),
91
- `if ($Env:__CONDA_OPENSSL_CERT_DIR_SET -eq "1") {
92
- Remove-Item -Path Env:\\SSL_CERT_DIR -ErrorAction SilentlyContinue
93
- }
94
- `
95
- )
96
- await fs.promises.writeFile(
97
- path.resolve(activateDir, "zz_pinokio_unset_ssl_cert_dir-win.sh"),
98
- `if [[ "\${__CONDA_OPENSSL_CERT_DIR_SET:-}" == "1" ]]; then
99
- unset SSL_CERT_DIR
100
- fi
101
- `
102
- )
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
+ }
103
163
  }
104
164
  async init() {
105
165
  if (this.kernel.homedir) {
@@ -120,9 +180,9 @@ report_errors: false`)
120
180
  let pinned_exists = await this.hasCondaMeta()
121
181
  if (pinned_exists) {
122
182
  await fs.promises.writeFile(this.kernel.path(`bin/${CONDA_ROOT_DIR}/conda-meta/pinned`), this.pinnedPackages())
123
- await this.ensureSslCertDirOverride()
124
183
  await this.ensureCompatibilityAlias()
125
184
  }
185
+ await this.ensureWindowsOpenSslHooks()
126
186
  }
127
187
  }
128
188
  async check() {
@@ -170,7 +230,10 @@ report_errors: false`)
170
230
  this.kernel.bin.installed.conda = conda
171
231
  this.kernel.bin.installed.conda_versions = conda_versions
172
232
  this.kernel.bin.installed.conda_builds = conda_builds
173
- return conda_check.conda && conda_check.mamba && conda_check.python
233
+ if (!(conda_check.conda && conda_check.mamba && conda_check.python)) {
234
+ return false
235
+ }
236
+ return await managedCondaRuns(this.kernel.bin.path(CONDA_ROOT_DIR), this.kernel.platform)
174
237
  }
175
238
  async install(req, ondata) {
176
239
  for(let i=0; i<5; i++) {
@@ -267,7 +330,7 @@ report_errors: false`)
267
330
  this.kernel.bin.path(CONDA_ROOT_DIR, "python3.exe"),
268
331
  )
269
332
  }
270
- await this.ensureSslCertDirOverride()
333
+ await this.ensureWindowsOpenSslHooks()
271
334
  await this.ensureCompatibilityAlias()
272
335
  ondata({ raw: `Install finished\r\n` })
273
336
  await this.kernel.bin.rm(installer, ondata)
@@ -21,7 +21,7 @@ const LLVM = require('./llvm')
21
21
  const VS = require("./vs")
22
22
  const Cuda = require("./cuda")
23
23
  const Torch = require("./torch")
24
- const { buildCondaListFromMeta } = require('./conda-meta')
24
+ const { buildCondaListFromMeta, managedCondaRuns } = require('./conda-meta')
25
25
  const {
26
26
  isExpectedPythonPinned,
27
27
  } = require('./conda-pins')
@@ -412,7 +412,7 @@ class Bin {
412
412
  }
413
413
 
414
414
  if (conda_check.conda && conda_check.mamba && conda_check.python) {
415
- this.correct_conda = true
415
+ this.correct_conda = await managedCondaRuns(this.kernel.bin.path("miniforge"), this.platform)
416
416
  }
417
417
  }
418
418
  this.installed.conda = conda
@@ -448,6 +448,9 @@ class Bin {
448
448
  break
449
449
  }
450
450
  }
451
+ if (this.mod && this.mod.conda && this.mod.conda.ensureWindowsOpenSslHooks) {
452
+ await this.mod.conda.ensureWindowsOpenSslHooks()
453
+ }
451
454
  }
452
455
 
453
456
  if (this.platform === "darwin") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.29",
3
+ "version": "7.5.31",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -6041,6 +6041,13 @@ class Server {
6041
6041
  if (version === this.version.pinokiod) {
6042
6042
  console.log("version up to date")
6043
6043
  } else if (!(await fse.pathExists(path.resolve(home, "bin/miniforge/conda-meta")))) {
6044
+ // if miniconda => miniforge migration hasn't happened yet
6045
+ if (!(await fse.pathExists(path.resolve(home, "bin/miniconda/conda-meta")))) {
6046
+ // if miniconda folder does NOT exist (new user), set version
6047
+ // if miniconda folder exists (existing user), don't set version yet
6048
+ // => version only set after miniconda => miniforge migration has happened
6049
+ this.kernel.store.set("version", this.version.pinokiod)
6050
+ }
6044
6051
  console.warn("[WARN] Managed home refresh deferred until Miniforge is installed")
6045
6052
  } else {
6046
6053
  // For every update, this gets triggered exactly once.
@@ -1284,6 +1284,10 @@ body.main-sidebar-page .navheader {
1284
1284
  box-shadow: none;
1285
1285
  isolation: isolate;
1286
1286
  }
1287
+ body.main-sidebar-page > header.navheader h1,
1288
+ body.app-page > header.navheader h1 {
1289
+ gap: 6px;
1290
+ }
1287
1291
  body.main-sidebar-page .navheader .sidebar-toggle {
1288
1292
  color: #0f172a;
1289
1293
  position: relative;
@@ -25,12 +25,38 @@ function createKernel(root, platform = 'win32') {
25
25
  exists: async (...parts) => pathExists(path.join(root, ...parts)),
26
26
  bin: {
27
27
  correct_conda: true,
28
+ installed: {},
28
29
  path: (...parts) => path.join(root, 'bin', ...parts),
29
30
  exists: async (target) => pathExists(path.join(root, 'bin', target)),
30
31
  },
31
32
  }
32
33
  }
33
34
 
35
+ async function writeCondaMeta(root, name, version, build = 'py_0') {
36
+ const metaDir = path.join(root, 'bin', 'miniforge', 'conda-meta')
37
+ await fs.mkdir(metaDir, { recursive: true })
38
+ await fs.writeFile(
39
+ path.join(metaDir, `${name}-${version}.json`),
40
+ JSON.stringify({ name, version, build_string: build, channel: 'conda-forge' })
41
+ )
42
+ }
43
+
44
+ async function writeHealthyCondaMeta(root, platform = 'win32') {
45
+ await writeCondaMeta(root, 'conda', '26.3.2', 'py310')
46
+ await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
47
+ await writeCondaMeta(root, 'python', '3.10.20', platform === 'win32' ? 'h4de0772_1_cpython' : 'cpython')
48
+ }
49
+
50
+ async function writeFakeManagedConda(root, platform = 'win32') {
51
+ const condaPath = platform === 'win32'
52
+ ? path.join(root, 'bin', 'miniforge', 'Scripts', 'conda.exe')
53
+ : path.join(root, 'bin', 'miniforge', 'bin', 'conda')
54
+ await fs.mkdir(path.dirname(condaPath), { recursive: true })
55
+ await fs.copyFile(process.execPath, condaPath)
56
+ await fs.chmod(condaPath, 0o755)
57
+ return condaPath
58
+ }
59
+
34
60
  async function createCondaRoot(root, name) {
35
61
  const condaRoot = path.join(root, 'bin', name)
36
62
  await fs.mkdir(path.join(condaRoot, 'Scripts'), { recursive: true })
@@ -52,6 +78,88 @@ function createConda(kernel) {
52
78
  return conda
53
79
  }
54
80
 
81
+ const EXPECTED_WINDOWS_OPENSSL_HOOKS = {
82
+ [path.join('activate.d', 'openssl_activate-win.bat')]: [
83
+ '@echo off',
84
+ 'if "%SSL_CERT_FILE%"=="" (',
85
+ ' set "SSL_CERT_FILE=%CONDA_PREFIX%\\Library\\ssl\\cacert.pem"',
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
+ }
160
+ }
161
+ }
162
+
55
163
  test('Conda uses Miniforge assets and writes conda-forge-only config', async () => {
56
164
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-miniforge-config-'))
57
165
  const conda = createConda(createKernel(root))
@@ -73,17 +181,26 @@ test('Conda uses Miniforge assets and writes conda-forge-only config', async ()
73
181
  assert.equal(await pathExists(path.join(root, 'bin', 'miniforge')), false)
74
182
  })
75
183
 
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-'))
184
+ test('Conda init patches Windows OpenSSL hooks and removes legacy SSL_CERT_DIR hooks', async () => {
185
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-openssl-hooks-'))
78
186
  await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
187
+ await seedWindowsOpenSslHooks(root, 'miniforge')
79
188
  const conda = createConda(createKernel(root))
80
189
 
81
190
  await conda.init()
82
191
 
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
- )
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')
87
204
  })
88
205
 
89
206
  test('Conda pins Python 3.10.20 consistently across platforms', async () => {
@@ -207,6 +324,21 @@ test('Conda installed stays false when metadata check already marked Conda inval
207
324
  assert.equal(await createConda(kernel).installed(), false)
208
325
  })
209
326
 
327
+ test('Conda check rejects metadata-only Windows installs', async () => {
328
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-missing-'))
329
+ await writeHealthyCondaMeta(root, 'win32')
330
+
331
+ assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
332
+ })
333
+
334
+ test('Conda check accepts a runnable managed Windows conda executable', async () => {
335
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-runnable-'))
336
+ await writeHealthyCondaMeta(root, 'win32')
337
+ await writeFakeManagedConda(root, 'win32')
338
+
339
+ assert.equal(await createConda(createKernel(root, 'win32')).check(), true)
340
+ })
341
+
210
342
  test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
211
343
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-download-fails-'))
212
344
  const miniconda = await createLegacyMiniconda(root)