pinokiod 8.2.5 → 8.2.6
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/anywhere/index.js +1 -9
- package/kernel/bin/conda-pins.js +1 -3
- package/kernel/bin/conda.js +4 -7
- package/kernel/bin/tailscaled.js +0 -1
- package/kernel/shell.js +3 -10
- package/package.json +1 -1
- package/test/anywhere.test.js +0 -15
- package/test/conda-bin.test.js +14 -11
- package/test/shell-run-template.test.js +41 -42
package/kernel/anywhere/index.js
CHANGED
|
@@ -187,11 +187,8 @@ class Anywhere {
|
|
|
187
187
|
async autostart() {
|
|
188
188
|
// a pid file left behind by a crash or reboot must never point at an unrelated process
|
|
189
189
|
if (!(await this.daemonAlive())) fs.rmSync(this.pidPath(), { force: true })
|
|
190
|
-
if (!(await this.installed())) {
|
|
191
|
-
await this.stop()
|
|
192
|
-
return false
|
|
193
|
-
}
|
|
194
190
|
if (!(await this.enabled())) return false
|
|
191
|
+
if (!(await this.installed())) return false
|
|
195
192
|
await this.start()
|
|
196
193
|
return true
|
|
197
194
|
}
|
|
@@ -202,11 +199,6 @@ class Anywhere {
|
|
|
202
199
|
}
|
|
203
200
|
async _start() {
|
|
204
201
|
this.error = null
|
|
205
|
-
if (!(await this.installed())) {
|
|
206
|
-
await this.stop()
|
|
207
|
-
this.error = "Update the tailscaled tool before starting Pinokio Anywhere."
|
|
208
|
-
throw new Error(this.error)
|
|
209
|
-
}
|
|
210
202
|
await this.loadConfig()
|
|
211
203
|
await fs.promises.mkdir(this.stateDir(), { recursive: true })
|
|
212
204
|
const bin = this.binPath()
|
package/kernel/bin/conda-pins.js
CHANGED
|
@@ -11,7 +11,7 @@ const TLS_PACKAGE_MINIMUMS = {
|
|
|
11
11
|
// Bootstrap URL and hash: https://discuss.python.org/t/109576
|
|
12
12
|
const PIP_MIN_VERSION = '26.2.2'
|
|
13
13
|
const PIP_INSTALL_SPEC = 'pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70'
|
|
14
|
-
const PIP_INSTALL_COMMAND = `python -I -m pip --isolated
|
|
14
|
+
const PIP_INSTALL_COMMAND = `python -I -m pip --isolated install --no-index "${PIP_INSTALL_SPEC}"`
|
|
15
15
|
const TLS_PACKAGE_SPECS = Object.entries(TLS_PACKAGE_MINIMUMS).map(([name, version]) => `${name}>=${version}`)
|
|
16
16
|
const hasSecureTlsPackages = (versions = {}) => Object.entries(TLS_PACKAGE_MINIMUMS).every(([name, minimum]) => {
|
|
17
17
|
const version = semver.coerce(versions[name])
|
|
@@ -46,9 +46,7 @@ const isExpectedPythonPinned = (platform, version, build) => {
|
|
|
46
46
|
|
|
47
47
|
module.exports = {
|
|
48
48
|
CONDA_PIN_VERSION,
|
|
49
|
-
TLS_PACKAGE_MINIMUMS,
|
|
50
49
|
PIP_MIN_VERSION,
|
|
51
|
-
PIP_INSTALL_SPEC,
|
|
52
50
|
PIP_INSTALL_COMMAND,
|
|
53
51
|
TLS_PACKAGE_SPECS,
|
|
54
52
|
hasSecureTlsPackages,
|
package/kernel/bin/conda.js
CHANGED
|
@@ -9,7 +9,6 @@ const { buildCondaListFromMeta, managedCondaRuns, hasSecureManagedPip } = requir
|
|
|
9
9
|
const {
|
|
10
10
|
CONDA_PIN_VERSION,
|
|
11
11
|
PIP_INSTALL_COMMAND,
|
|
12
|
-
TLS_PACKAGE_MINIMUMS,
|
|
13
12
|
TLS_PACKAGE_SPECS,
|
|
14
13
|
hasSecureTlsPackages,
|
|
15
14
|
PYTHON_INSTALL_SPEC,
|
|
@@ -183,16 +182,14 @@ report_errors: false`)
|
|
|
183
182
|
// Refresh a working runtime in place, preserving named app environments.
|
|
184
183
|
await this.init()
|
|
185
184
|
const bootstrap = await this.kernel.bin.exec({
|
|
186
|
-
message:
|
|
185
|
+
message: PIP_INSTALL_COMMAND,
|
|
187
186
|
}, ondata)
|
|
188
|
-
if (bootstrap?.error?.length) throw new Error("Conda certificate bootstrap failed; existing environments were preserved.")
|
|
187
|
+
if (bootstrap?.error?.length || !(await hasSecureManagedPip(this.kernel.bin.path(CONDA_ROOT_DIR), this.kernel.platform))) throw new Error("Conda certificate bootstrap failed; existing environments were preserved.")
|
|
189
188
|
const python = this.kernel.bin.path(CONDA_ROOT_DIR, this.kernel.platform === 'win32' ? 'python.exe' : 'bin/python')
|
|
190
|
-
const
|
|
191
|
-
['-I', '-c', '
|
|
189
|
+
const caFile = await new Promise((resolve, reject) => execFile(python,
|
|
190
|
+
['-I', '-c', 'from pip._vendor import certifi; print(certifi.where())'],
|
|
192
191
|
{ env: managedPythonEnv(), windowsHide: true, timeout: 15000 },
|
|
193
192
|
(error, stdout) => error ? reject(error) : resolve(stdout.trim())))
|
|
194
|
-
const [caFile, certifiVersion] = output.split(/[\r\n]+/)
|
|
195
|
-
if (!semver.valid(certifiVersion) || !semver.gte(certifiVersion, TLS_PACKAGE_MINIMUMS.certifi)) throw new Error("The certificate bootstrap did not complete.")
|
|
196
193
|
const result = await this.kernel.bin.exec({
|
|
197
194
|
message: [`conda install -y --override-channels -c conda-forge "${this.kernel.platform === "win32" ? WINDOWS_PYTHON_SSL_FIX_SPEC : PYTHON_INSTALL_SPEC}" "conda-libmamba-solver>=25.4.0" pip ${TLS_PACKAGE_SPECS.map(spec => `"${spec}"`).join(" ")}`, PIP_INSTALL_COMMAND],
|
|
198
195
|
env: { CONDA_SSL_VERIFY: caFile },
|
package/kernel/bin/tailscaled.js
CHANGED
|
@@ -18,7 +18,6 @@ class Tailscaled {
|
|
|
18
18
|
return this.kernel.bin.path("miniforge", "bin", "tailscaled")
|
|
19
19
|
}
|
|
20
20
|
async install(req, ondata) {
|
|
21
|
-
if (this.kernel.anywhere) await this.kernel.anywhere.stop()
|
|
22
21
|
await this.kernel.bin.exec({
|
|
23
22
|
message: [
|
|
24
23
|
// a stale libmamba index cache reports the package as missing even
|
package/kernel/shell.js
CHANGED
|
@@ -21,7 +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
|
|
24
|
+
const { PYTHON_INSTALL_SPEC } = require('./bin/conda-pins')
|
|
25
25
|
const CondaRuntimeGuard = require('./shell_conda_runtime_guard')
|
|
26
26
|
const home = os.homedir()
|
|
27
27
|
const WINDOWS_CMD_CONDA_COMMAND_PATTERN = /(?:^|[&|()]\s*)@?\s*(?:call\s+)?(?:"[^"]*[\\/]conda(?:\.bat|\.exe)?"|(?:[a-z]:)?[^&|()\s"]*[\\/]conda(?:\.bat|\.exe)?|conda(?:\.bat|\.exe)?)\s+/i
|
|
@@ -1391,25 +1391,18 @@ class Shell {
|
|
|
1391
1391
|
// timeout,
|
|
1392
1392
|
]
|
|
1393
1393
|
} else {
|
|
1394
|
-
// when python version is not specified,
|
|
1394
|
+
// when python version is not specified, seed pip using the activated Python
|
|
1395
1395
|
venv_activation = [
|
|
1396
|
-
`python -
|
|
1396
|
+
`uv venv --seed --python-preference only-system --python python ${shellEnvPath}`,
|
|
1397
1397
|
activate_command,
|
|
1398
1398
|
// `python -m pip install --upgrade pip setuptools wheel`,
|
|
1399
1399
|
deactivate_path,
|
|
1400
1400
|
// timeout,
|
|
1401
1401
|
activate_command,
|
|
1402
|
-
this.isPowerShell()
|
|
1403
|
-
? `${PIP_INSTALL_COMMAND}; if ($LASTEXITCODE -ne 0) { exit 1 }`
|
|
1404
|
-
: `${PIP_INSTALL_COMMAND} || exit 1`,
|
|
1405
1402
|
// timeout,
|
|
1406
1403
|
]
|
|
1407
1404
|
}
|
|
1408
1405
|
}
|
|
1409
|
-
if (!use_uv) {
|
|
1410
|
-
const checkPip = `python -I -c "from importlib.metadata import distributions; assert all(tuple(map(int, d.version.split('.')[:3])) >= (${PIP_MIN_VERSION.split(".").join(", ")}) for d in distributions(name='pip')), 'Update pip in this app environment before running it.'"`
|
|
1411
|
-
venv_activation.push(this.isPowerShell() ? `${checkPip}; if ($LASTEXITCODE -ne 0) { exit 1 }` : `${checkPip} || exit 1`)
|
|
1412
|
-
}
|
|
1413
1406
|
} else {
|
|
1414
1407
|
venv_activation = []
|
|
1415
1408
|
}
|
package/package.json
CHANGED
package/test/anywhere.test.js
CHANGED
|
@@ -441,18 +441,3 @@ test('sign-out resets the login state, and a link the server rejected is replace
|
|
|
441
441
|
fs.rmSync(tmp, { recursive: true, force: true })
|
|
442
442
|
}
|
|
443
443
|
})
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
test('old Tailscale daemons are stopped and cannot be adopted through direct start', async t => {
|
|
447
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-anywhere-version-'))
|
|
448
|
-
t.after(() => fs.rmSync(root, { recursive: true, force: true }))
|
|
449
|
-
const anywhere = new Anywhere(fakeKernel(root, { installed: false }))
|
|
450
|
-
let stopped = 0
|
|
451
|
-
anywhere.daemonAlive = async () => true
|
|
452
|
-
anywhere.enabled = async () => true
|
|
453
|
-
anywhere.stop = async () => { stopped++ }
|
|
454
|
-
assert.equal(await anywhere.autostart(), false)
|
|
455
|
-
assert.equal(stopped, 1)
|
|
456
|
-
await assert.rejects(anywhere.start(), /Update the tailscaled tool/)
|
|
457
|
-
assert.equal(stopped, 2)
|
|
458
|
-
})
|
package/test/conda-bin.test.js
CHANGED
|
@@ -14,9 +14,7 @@ const {
|
|
|
14
14
|
WINDOWS_PYTHON_SSL_FIX_SPEC,
|
|
15
15
|
isExpectedCondaPinned,
|
|
16
16
|
isExpectedPythonPinned,
|
|
17
|
-
TLS_PACKAGE_MINIMUMS,
|
|
18
17
|
TLS_PACKAGE_SPECS,
|
|
19
|
-
PIP_INSTALL_SPEC,
|
|
20
18
|
PIP_MIN_VERSION,
|
|
21
19
|
} = require('../kernel/bin/conda-pins')
|
|
22
20
|
|
|
@@ -26,7 +24,8 @@ test.beforeEach(t => {
|
|
|
26
24
|
if (args[0] !== '-I' || args[1] !== '-c') return realExecFile(binary, args, options, done)
|
|
27
25
|
const prefix = path.basename(binary) === 'python.exe' ? path.dirname(binary) : path.dirname(path.dirname(binary))
|
|
28
26
|
if (args[2].includes('certifi.where()')) {
|
|
29
|
-
|
|
27
|
+
assert.match(args[2], /^from pip\._vendor import certifi;/)
|
|
28
|
+
done(null, path.join(prefix, 'certificates.pem') + '\n')
|
|
30
29
|
return
|
|
31
30
|
}
|
|
32
31
|
fs.readFile(path.join(prefix, 'test-pip-version'), 'utf8').then(
|
|
@@ -73,8 +72,8 @@ async function writeHealthyCondaMeta(root, platform = 'win32', condaVersion = CO
|
|
|
73
72
|
await writeCondaMeta(root, 'conda', condaVersion, 'py310')
|
|
74
73
|
await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
|
|
75
74
|
await writeCondaMeta(root, 'python', '3.10.20', platform === 'win32' ? 'h4de0772_1_cpython' : 'cpython')
|
|
76
|
-
for (const [
|
|
77
|
-
await writeCondaMeta(root, name,
|
|
75
|
+
for (const name of ['ca-certificates', 'certifi']) {
|
|
76
|
+
await writeCondaMeta(root, name, '2026.10.19')
|
|
78
77
|
}
|
|
79
78
|
await fs.writeFile(path.join(root, 'bin/miniforge/test-pip-version'), PIP_MIN_VERSION)
|
|
80
79
|
}
|
|
@@ -282,7 +281,7 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
|
|
|
282
281
|
assert.equal(await pathExists(path.join(root, 'bin', 'miniconda')), true)
|
|
283
282
|
const condaInstall = calls.find((call) => Array.isArray(call.message))
|
|
284
283
|
assert.ok(condaInstall)
|
|
285
|
-
assert.
|
|
284
|
+
assert.match(condaInstall.message[2], /^python -I -m pip --isolated install --no-index "pip @ https:\/\/files\.pythonhosted\.org\/packages\/[a-f0-9/]+\/pip-26\.2\.2-py3-none-any\.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"$/)
|
|
286
285
|
assert.equal(
|
|
287
286
|
condaInstall.message[1],
|
|
288
287
|
'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20" "conda-libmamba-solver>=25.4.0" pip "ca-certificates>=2026.10.19" "certifi>=2026.10.19"'
|
|
@@ -620,8 +619,8 @@ test('stale certificate packages trigger the existing Conda setup checks', async
|
|
|
620
619
|
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
621
620
|
await writeHealthyCondaMeta(root, 'darwin')
|
|
622
621
|
await writeFakeManagedConda(root, 'darwin')
|
|
623
|
-
for (const [
|
|
624
|
-
await fs.rm(path.join(root, 'bin/miniforge/conda-meta', `${name}
|
|
622
|
+
for (const name of ['ca-certificates', 'certifi']) {
|
|
623
|
+
await fs.rm(path.join(root, 'bin/miniforge/conda-meta', `${name}-2026.10.19.json`))
|
|
625
624
|
await writeCondaMeta(root, name, '2026.4.22')
|
|
626
625
|
}
|
|
627
626
|
assert.equal(await createConda(createKernel(root, 'darwin')).check(), false)
|
|
@@ -655,7 +654,7 @@ test('old pip cannot pass Conda readiness even with current CA packages', async
|
|
|
655
654
|
assert.equal(bin.correct_conda, false)
|
|
656
655
|
})
|
|
657
656
|
|
|
658
|
-
test(
|
|
657
|
+
test("certificate refresh preserves named environments and uses pip's bundled CA", async t => {
|
|
659
658
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-ca-refresh-'))
|
|
660
659
|
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
661
660
|
await writeHealthyCondaMeta(root, 'darwin')
|
|
@@ -670,6 +669,7 @@ test('certificate refresh preserves named environments and uses the new CA bundl
|
|
|
670
669
|
await createConda(kernel).install({}, () => {})
|
|
671
670
|
assert.equal(await fs.readFile(marker, 'utf8'), 'existing app')
|
|
672
671
|
assert.equal(calls.length, 2)
|
|
672
|
+
assert.match(calls[0].message, /^python -I -m pip --isolated install --no-index /)
|
|
673
673
|
assert.equal(calls[1].env.CONDA_SSL_VERIFY, path.join(root, 'bin/miniforge/certificates.pem'))
|
|
674
674
|
})
|
|
675
675
|
|
|
@@ -679,9 +679,12 @@ test('failed pip bootstrap stops certificate refresh without replacing environme
|
|
|
679
679
|
await writeHealthyCondaMeta(root, 'darwin')
|
|
680
680
|
await writeFakeManagedConda(root, 'darwin')
|
|
681
681
|
const kernel = createKernel(root, 'darwin')
|
|
682
|
-
kernel.bin.exec = async () => ({ error: ['ERROR: hash verification failed'] })
|
|
683
682
|
kernel.bin.download = async () => assert.fail('must preserve the runtime')
|
|
684
|
-
await
|
|
683
|
+
await fs.writeFile(path.join(root, 'bin/miniforge/test-pip-version'), '26.2.1')
|
|
684
|
+
for (const response of [{ error: ['ERROR: hash verification failed'] }, {}]) {
|
|
685
|
+
kernel.bin.exec = async () => response
|
|
686
|
+
await assert.rejects(createConda(kernel).install({}, () => {}), /bootstrap failed/)
|
|
687
|
+
}
|
|
685
688
|
assert.equal(await pathExists(path.join(root, 'bin/miniforge/conda-meta')), true)
|
|
686
689
|
})
|
|
687
690
|
|
|
@@ -379,47 +379,46 @@ test('redactEnvArgs summarizes protected argv env values', () => {
|
|
|
379
379
|
})
|
|
380
380
|
|
|
381
381
|
|
|
382
|
-
test('
|
|
383
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-venv-
|
|
382
|
+
test('venv creation preserves interpreter selection and existing environments across shells', async t => {
|
|
383
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-venv-selection-'))
|
|
384
384
|
t.after(() => fs.rm(root, { recursive: true, force: true }))
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
assert.equal(await fs.access(path.join(root, 'app-ran')).then(() => true, () => false), false)
|
|
385
|
+
const cases = [
|
|
386
|
+
{ venv: 'default-env', seeded: true },
|
|
387
|
+
{ venv: 'explicit-env', venv_python: '3.10', version: '3.10' },
|
|
388
|
+
{ venv: { path: 'object-env', python: '3.11' }, version: '3.11' },
|
|
389
|
+
{ venv: 'existing-env', existing: true },
|
|
390
|
+
]
|
|
391
|
+
await fs.mkdir(path.join(root, 'existing-env'))
|
|
392
|
+
for (const [platform, shellName] of [
|
|
393
|
+
['linux', 'bash'],
|
|
394
|
+
['darwin', 'bash'],
|
|
395
|
+
['win32', 'cmd.exe'],
|
|
396
|
+
['win32', 'powershell'],
|
|
397
|
+
['win32', 'bash'],
|
|
398
|
+
]) {
|
|
399
|
+
for (const scenario of cases) {
|
|
400
|
+
const shell = createShell(createKernel(root))
|
|
401
|
+
shell.platform = platform
|
|
402
|
+
shell.shell = shellName
|
|
403
|
+
const params = await shell.activate({
|
|
404
|
+
path: root,
|
|
405
|
+
conda: { skip: true },
|
|
406
|
+
venv: scenario.venv,
|
|
407
|
+
venv_python: scenario.venv_python,
|
|
408
|
+
message: 'echo PINOKIO_REQUIREMENTS_RAN',
|
|
409
|
+
})
|
|
410
|
+
const command = shell.build(params)
|
|
411
|
+
assert.doesNotMatch(command, /python -m venv|python -I|files\.pythonhosted\.org|\|\| exit 1/)
|
|
412
|
+
assert.match(command, /echo PINOKIO_REQUIREMENTS_RAN$/)
|
|
413
|
+
if (scenario.existing) {
|
|
414
|
+
assert.doesNotMatch(command, /uv venv|--seed/)
|
|
415
|
+
} else if (scenario.seeded) {
|
|
416
|
+
assert.match(command, /uv venv --seed --python-preference only-system --python python /)
|
|
417
|
+
} else {
|
|
418
|
+
assert.ok(command.includes(` --python ${scenario.version}`), command)
|
|
419
|
+
assert.doesNotMatch(command, /--seed|--python-preference only-system/)
|
|
420
|
+
}
|
|
421
|
+
assert.equal(shell.env.UV_PYTHON_PREFERENCE, 'only-managed')
|
|
422
|
+
}
|
|
423
|
+
}
|
|
425
424
|
})
|