pinokiod 8.2.19 → 8.2.21
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 +1 -1
- package/kernel/bin/conda.js +5 -0
- package/package.json +1 -1
- package/test/conda-bin.test.js +130 -7
package/kernel/bin/conda-pins.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path')
|
|
|
3
3
|
const { resolveAsarPath } = require('../util')
|
|
4
4
|
const PIP_WHEEL = require('../../system/pip_seed.json')
|
|
5
5
|
|
|
6
|
-
const CONDA_PIN_VERSION = "26.
|
|
6
|
+
const CONDA_PIN_VERSION = "26.7.0"
|
|
7
7
|
// Conda 26.9.4 has no Python 3.10 builds. Apply its CA-bundle requirements
|
|
8
8
|
// explicitly while retaining the managed Python version required by apps.
|
|
9
9
|
const TLS_PACKAGE_MINIMUMS = {
|
package/kernel/bin/conda.js
CHANGED
|
@@ -273,6 +273,11 @@ report_errors: false`)
|
|
|
273
273
|
await caddy.stop()
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
if (this.kernel.anywhere) {
|
|
277
|
+
ondata({ raw: `stopping Pinokio Anywhere before replacing Miniforge...\r\n` })
|
|
278
|
+
await this.kernel.anywhere.stop()
|
|
279
|
+
}
|
|
280
|
+
|
|
276
281
|
legacy_path_exists = await this.kernel.exists(`bin/${LEGACY_CONDA_ROOT_DIR}`)
|
|
277
282
|
if (legacy_path_exists) {
|
|
278
283
|
console.log("Removing legacy install path...", legacy_path)
|
package/package.json
CHANGED
package/test/conda-bin.test.js
CHANGED
|
@@ -211,11 +211,12 @@ test('Conda uses Miniforge assets and writes conda-forge-only config', async ()
|
|
|
211
211
|
})
|
|
212
212
|
|
|
213
213
|
test('Conda pins the managed Conda and Python versions consistently', async () => {
|
|
214
|
-
assert.equal(CONDA_PIN_VERSION, '26.
|
|
214
|
+
assert.equal(CONDA_PIN_VERSION, '26.7.0')
|
|
215
215
|
assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
|
|
216
216
|
assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
|
|
217
217
|
|
|
218
|
-
assert.equal(isExpectedCondaPinned('26.
|
|
218
|
+
assert.equal(isExpectedCondaPinned('26.7.0'), true)
|
|
219
|
+
assert.equal(isExpectedCondaPinned('26.5.3'), false)
|
|
219
220
|
assert.equal(isExpectedCondaPinned('26.3.2'), false)
|
|
220
221
|
|
|
221
222
|
assert.equal(isExpectedPythonPinned('darwin', '3.10.20', 'cpython'), true)
|
|
@@ -235,7 +236,7 @@ test('Conda init writes the current Conda pin into an existing runtime', async (
|
|
|
235
236
|
|
|
236
237
|
assert.equal(
|
|
237
238
|
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
238
|
-
['conda ==26.
|
|
239
|
+
['conda ==26.7.0', ...TLS_PACKAGE_SPECS].join('\n')
|
|
239
240
|
)
|
|
240
241
|
})
|
|
241
242
|
|
|
@@ -290,11 +291,11 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
|
|
|
290
291
|
assert.deepEqual(condaInstall.message[2], { _: ['python', '-I', '-m', 'pip', '--isolated', 'install', '--no-index', PIP_WHEEL.filename] })
|
|
291
292
|
assert.equal(
|
|
292
293
|
condaInstall.message[1],
|
|
293
|
-
'conda install -y --override-channels -c conda-forge "conda=26.
|
|
294
|
+
'conda install -y --override-channels -c conda-forge "conda=26.7.0" "python=3.10.20" "conda-libmamba-solver>=25.4.0" "ca-certificates>=2026.10.19" "certifi>=2026.10.19"'
|
|
294
295
|
)
|
|
295
296
|
assert.equal(
|
|
296
297
|
await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
|
|
297
|
-
['conda ==26.
|
|
298
|
+
['conda ==26.7.0', ...TLS_PACKAGE_SPECS].join('\n')
|
|
298
299
|
)
|
|
299
300
|
})
|
|
300
301
|
|
|
@@ -322,7 +323,7 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
|
|
|
322
323
|
assert.ok(condaInstall)
|
|
323
324
|
assert.equal(
|
|
324
325
|
condaInstall.message[1],
|
|
325
|
-
'conda install -y --override-channels -c conda-forge "conda=26.
|
|
326
|
+
'conda install -y --override-channels -c conda-forge "conda=26.7.0" "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0" "ca-certificates>=2026.10.19" "certifi>=2026.10.19"'
|
|
326
327
|
)
|
|
327
328
|
})
|
|
328
329
|
|
|
@@ -354,10 +355,73 @@ test('Conda install replaces the stale runtime and rebuilds declared Conda modul
|
|
|
354
355
|
assert.ok(condaInstall)
|
|
355
356
|
assert.equal(
|
|
356
357
|
condaInstall.message[1],
|
|
357
|
-
'conda install -y --override-channels -c conda-forge "conda=26.
|
|
358
|
+
'conda install -y --override-channels -c conda-forge "conda=26.7.0" "python=3.10.20" "conda-libmamba-solver>=25.4.0" "ca-certificates>=2026.10.19" "certifi>=2026.10.19" uv=0.11.23 nodejs=22.21.1 pnpm'
|
|
358
359
|
)
|
|
359
360
|
})
|
|
360
361
|
|
|
362
|
+
test('Conda 26.5.3 triggers replacement even when certificates and pip are current', async t => {
|
|
363
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-version-update-'))
|
|
364
|
+
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
365
|
+
for (const platform of ['darwin', 'linux', 'win32']) {
|
|
366
|
+
for (const securityReady of [false, true]) {
|
|
367
|
+
const dir = path.join(root, platform, String(securityReady))
|
|
368
|
+
await writeHealthyCondaMeta(dir, platform, '26.5.3')
|
|
369
|
+
await writeFakeManagedConda(dir, platform)
|
|
370
|
+
if (!securityReady) {
|
|
371
|
+
for (const name of ['ca-certificates', 'certifi']) {
|
|
372
|
+
await fs.rm(path.join(dir, 'bin/miniforge/conda-meta', `${name}-2026.10.19.json`))
|
|
373
|
+
await writeCondaMeta(dir, name, '2026.7.22')
|
|
374
|
+
}
|
|
375
|
+
await fs.writeFile(path.join(dir, 'bin/miniforge/test-pip-version'), '26.2.1')
|
|
376
|
+
await fs.rm(path.join(dir, 'bin/miniforge/test-pip-seed'))
|
|
377
|
+
}
|
|
378
|
+
const trash = path.join(dir, 'bin/miniforge/Library/bin/libcrypto-3-x64.dll.c~.conda_trash')
|
|
379
|
+
await fs.mkdir(path.dirname(trash), { recursive: true })
|
|
380
|
+
await fs.writeFile(trash, 'stale trash')
|
|
381
|
+
const appFile = path.join(dir, 'api/demo/env/keep.txt')
|
|
382
|
+
await fs.mkdir(path.dirname(appFile), { recursive: true })
|
|
383
|
+
await fs.writeFile(appFile, 'existing app venv')
|
|
384
|
+
const bin = createBin(dir, platform)
|
|
385
|
+
await bin.tryList()
|
|
386
|
+
assert.equal(bin.correct_conda, false)
|
|
387
|
+
|
|
388
|
+
const kernel = createKernel(dir, platform)
|
|
389
|
+
kernel.bin.mods = []
|
|
390
|
+
let downloads = 0
|
|
391
|
+
kernel.bin.download = async () => { downloads++ }
|
|
392
|
+
kernel.bin.rm = async () => {}
|
|
393
|
+
const calls = []
|
|
394
|
+
kernel.bin.exec = async payload => {
|
|
395
|
+
calls.push(payload)
|
|
396
|
+
if (payload.conda?.skip) {
|
|
397
|
+
assert.equal(await pathExists(trash), false, 'replacement removes old runtime and trash')
|
|
398
|
+
assert.equal(await pathExists(path.join(dir, 'bin/miniforge/conda-meta')), false)
|
|
399
|
+
await fs.mkdir(path.join(dir, 'bin/miniforge/conda-meta'), { recursive: true })
|
|
400
|
+
await writeFakeManagedConda(dir, platform)
|
|
401
|
+
if (platform === 'win32') await fs.writeFile(path.join(dir, 'bin/miniforge/python.exe'), 'fake python\n')
|
|
402
|
+
} else {
|
|
403
|
+
assert.match(payload.message[1], /"conda=26\.7\.0"/)
|
|
404
|
+
assert.deepEqual(payload.message[2], { _: ['python', '-I', '-m', 'pip', '--isolated', 'install', '--no-index', PIP_WHEEL.filename] })
|
|
405
|
+
await writeHealthyCondaMeta(dir, platform)
|
|
406
|
+
}
|
|
407
|
+
return {}
|
|
408
|
+
}
|
|
409
|
+
const conda = createConda(kernel)
|
|
410
|
+
await conda.install({ dependencies: [] }, () => {})
|
|
411
|
+
assert.equal(await conda.check(), true)
|
|
412
|
+
await bin.tryList()
|
|
413
|
+
assert.equal(bin.correct_conda, true)
|
|
414
|
+
assert.equal(await fs.readFile(appFile, 'utf8'), 'existing app venv')
|
|
415
|
+
assert.equal(await pathExists(trash), false)
|
|
416
|
+
assert.equal(downloads, 1)
|
|
417
|
+
assert.equal(calls.length, 2, 'installer and bootstrap run once')
|
|
418
|
+
await conda.install({ dependencies: [] }, () => {})
|
|
419
|
+
assert.equal(downloads, 1, 'the updated runtime is not reinstalled on a retry')
|
|
420
|
+
assert.equal(calls.length, 2)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
|
|
361
425
|
test('Conda replacement stops a running managed Caddy without restoring it', async () => {
|
|
362
426
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-caddy-lifecycle-'))
|
|
363
427
|
await createMiniforge(root)
|
|
@@ -407,6 +471,65 @@ test('Conda replacement stops a running managed Caddy without restoring it', asy
|
|
|
407
471
|
assert.equal(caddyRunning, false)
|
|
408
472
|
})
|
|
409
473
|
|
|
474
|
+
test('Conda replacement waits for Anywhere shutdown before deleting either runtime', async t => {
|
|
475
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-anywhere-stop-'))
|
|
476
|
+
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
477
|
+
const miniforge = await createMiniforge(root)
|
|
478
|
+
const miniconda = await createLegacyMiniconda(root)
|
|
479
|
+
const kernel = createKernel(root, 'win32')
|
|
480
|
+
let finishStop
|
|
481
|
+
let enteredStop
|
|
482
|
+
const stopping = new Promise(resolve => { enteredStop = resolve })
|
|
483
|
+
const stopped = new Promise(resolve => { finishStop = resolve })
|
|
484
|
+
kernel.anywhere = { stop: async () => { enteredStop(); await stopped } }
|
|
485
|
+
kernel.bin.mods = []
|
|
486
|
+
kernel.bin.download = async () => {}
|
|
487
|
+
kernel.bin.rm = async () => {}
|
|
488
|
+
let bootstrapped = false
|
|
489
|
+
kernel.bin.exec = async payload => {
|
|
490
|
+
if (payload.conda?.skip) {
|
|
491
|
+
bootstrapped = true
|
|
492
|
+
assert.equal(await pathExists(miniforge), false)
|
|
493
|
+
assert.equal(await pathExists(miniconda), false)
|
|
494
|
+
await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
|
|
495
|
+
await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const conda = createConda(kernel)
|
|
500
|
+
const installing = conda._install({ dependencies: [] }, () => {})
|
|
501
|
+
try {
|
|
502
|
+
await stopping
|
|
503
|
+
await new Promise(setImmediate)
|
|
504
|
+
assert.equal(await pathExists(path.join(miniforge, 'Scripts/conda.exe')), true)
|
|
505
|
+
assert.equal(await pathExists(path.join(miniconda, 'Scripts/conda.exe')), true)
|
|
506
|
+
assert.equal(bootstrapped, false)
|
|
507
|
+
} finally {
|
|
508
|
+
finishStop()
|
|
509
|
+
await installing
|
|
510
|
+
}
|
|
511
|
+
assert.equal(bootstrapped, true)
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
test('Conda replacement preserves both runtimes when Anywhere shutdown fails', async t => {
|
|
515
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-anywhere-failure-'))
|
|
516
|
+
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
517
|
+
const miniforge = await createMiniforge(root)
|
|
518
|
+
const miniconda = await createLegacyMiniconda(root)
|
|
519
|
+
const kernel = createKernel(root, 'win32')
|
|
520
|
+
const failure = new Error('tailscaled did not stop')
|
|
521
|
+
kernel.anywhere = { stop: async () => { throw failure } }
|
|
522
|
+
kernel.bin.download = async () => {}
|
|
523
|
+
kernel.bin.exec = async () => assert.fail('installer must not run after shutdown failure')
|
|
524
|
+
const conda = createConda(kernel)
|
|
525
|
+
conda.removeInstallPath = async () => assert.fail('runtime deletion must not start after shutdown failure')
|
|
526
|
+
|
|
527
|
+
await assert.rejects(conda._install({ dependencies: [] }, () => {}), error => error === failure)
|
|
528
|
+
|
|
529
|
+
assert.equal(await fs.readFile(path.join(miniforge, 'Scripts/conda.exe'), 'utf8'), 'fake conda\n')
|
|
530
|
+
assert.equal(await fs.readFile(path.join(miniconda, 'Scripts/conda.exe'), 'utf8'), 'fake conda\n')
|
|
531
|
+
})
|
|
532
|
+
|
|
410
533
|
test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
|
|
411
534
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-miniforge-alias-'))
|
|
412
535
|
const legacy = await createLegacyMiniconda(root)
|