pinokiod 8.0.2 → 8.0.4

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.
@@ -8,6 +8,7 @@ const Util = require('../util')
8
8
  const ADMIN_READY_TIMEOUT_MS = 30000
9
9
  const ADMIN_START_TIMEOUT_MS = 5000
10
10
  const ADMIN_POLL_INTERVAL_MS = 250
11
+ const ADMIN_STOP_TIMEOUT_MS = 5000
11
12
 
12
13
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
13
14
 
@@ -29,6 +30,35 @@ class Caddy {
29
30
  return false
30
31
  }
31
32
  }
33
+ async waitForStopped(timeout = ADMIN_STOP_TIMEOUT_MS, interval = ADMIN_POLL_INTERVAL_MS) {
34
+ const started = Date.now()
35
+ while ((Date.now() - started) < timeout) {
36
+ if (!(await this.running())) {
37
+ return true
38
+ }
39
+ await sleep(interval)
40
+ }
41
+ return !(await this.running())
42
+ }
43
+ async stop() {
44
+ if (!(await this.running())) {
45
+ return false
46
+ }
47
+ try {
48
+ await axios.post('http://127.0.0.1:2019/stop', null, {
49
+ timeout: ADMIN_STOP_TIMEOUT_MS
50
+ })
51
+ } catch (error) {
52
+ if (await this.running()) {
53
+ throw error
54
+ }
55
+ }
56
+ const stopped = await this.waitForStopped()
57
+ if (!stopped) {
58
+ throw new Error("Pinokio could not stop Caddy before replacing the managed Conda runtime.")
59
+ }
60
+ return true
61
+ }
32
62
  async start() {
33
63
  // if peer.https_active is true,
34
64
  // 1. kill existing caddy
@@ -1,11 +1,12 @@
1
1
  const semver = require('semver')
2
2
 
3
- const CONDA_PIN_VERSION = "26.3.2"
4
- const CONDA_RUNTIME_MARKER = ".pinokio-runtime-v1"
3
+ const CONDA_PIN_VERSION = "26.5.3"
5
4
  const PYTHON_PIN_VERSION = "3.10.20"
6
5
  const PYTHON_INSTALL_SPEC = `python=${PYTHON_PIN_VERSION}`
7
6
  const WINDOWS_PYTHON_SSL_FIX_SPEC = `${PYTHON_INSTALL_SPEC}=*_1_cpython`
8
7
 
8
+ const isExpectedCondaPinned = (version) => String(version) === CONDA_PIN_VERSION
9
+
9
10
  const condaBuildNumber = (build) => {
10
11
  const chunks = String(build || "").split("_").reverse()
11
12
  const buildNumber = chunks.find((chunk) => /^\d+$/.test(chunk))
@@ -29,8 +30,8 @@ const isExpectedPythonPinned = (platform, version, build) => {
29
30
 
30
31
  module.exports = {
31
32
  CONDA_PIN_VERSION,
32
- CONDA_RUNTIME_MARKER,
33
33
  PYTHON_INSTALL_SPEC,
34
34
  WINDOWS_PYTHON_SSL_FIX_SPEC,
35
+ isExpectedCondaPinned,
35
36
  isExpectedPythonPinned,
36
37
  }
@@ -4,9 +4,9 @@ const semver = require('semver')
4
4
  const { buildCondaListFromMeta, managedCondaRuns } = require('./conda-meta')
5
5
  const {
6
6
  CONDA_PIN_VERSION,
7
- CONDA_RUNTIME_MARKER,
8
7
  PYTHON_INSTALL_SPEC,
9
8
  WINDOWS_PYTHON_SSL_FIX_SPEC,
9
+ isExpectedCondaPinned,
10
10
  isExpectedPythonPinned,
11
11
  } = require('./conda-pins')
12
12
 
@@ -48,9 +48,6 @@ class Conda {
48
48
  async hasCondaMeta() {
49
49
  return await this.kernel.exists(`bin/${CONDA_ROOT_DIR}/conda-meta`)
50
50
  }
51
- async hasRuntimeMarker() {
52
- return await this.kernel.exists(`bin/${CONDA_ROOT_DIR}/${CONDA_RUNTIME_MARKER}`)
53
- }
54
51
  env() {
55
52
  let base = {
56
53
  CONDA_PREFIX: this.kernel.bin.path(CONDA_ROOT_DIR),
@@ -96,9 +93,6 @@ report_errors: false`)
96
93
  }
97
94
  }
98
95
  async check() {
99
- if (!(await this.hasRuntimeMarker())) {
100
- return false
101
- }
102
96
  let res = await buildCondaListFromMeta(this.kernel.bin.path(CONDA_ROOT_DIR))
103
97
 
104
98
  let lines = res.response.split(/[\r\n]+/)
@@ -118,7 +112,7 @@ report_errors: false`)
118
112
  conda_versions[name] = version
119
113
  conda_builds[name] = build
120
114
  if (name === "conda") {
121
- conda_check.conda = true
115
+ conda_check.conda = isExpectedCondaPinned(version)
122
116
  }
123
117
  // check conda-libmamba-solver is up to date
124
118
  // sometimes it just fails silently so need to check
@@ -176,6 +170,16 @@ report_errors: false`)
176
170
  ondata({ raw: `downloading installer: ${installer_url}...\r\n` })
177
171
  await this.kernel.bin.download(installer_url, installer, ondata)
178
172
 
173
+ const caddy = this.kernel.bin.mod && this.kernel.bin.mod.caddy
174
+ let caddyWasRunning = false
175
+ if (caddy && typeof caddy.running === "function") {
176
+ caddyWasRunning = await caddy.running()
177
+ }
178
+ if (caddyWasRunning) {
179
+ ondata({ raw: `stopping Home Server before replacing Miniforge...\r\n` })
180
+ await caddy.stop()
181
+ }
182
+
179
183
  legacy_path_exists = await this.kernel.exists(`bin/${LEGACY_CONDA_ROOT_DIR}`)
180
184
  if (legacy_path_exists) {
181
185
  console.log("Removing legacy install path...", legacy_path)
@@ -208,8 +212,12 @@ report_errors: false`)
208
212
  await fs.promises.writeFile(this.kernel.path(`bin/${CONDA_ROOT_DIR}/conda-meta/pinned`), this.pinnedPackages())
209
213
  }
210
214
 
215
+ const dependencies = new Set(Array.isArray(req.dependencies) ? req.dependencies : [])
216
+ if (caddyWasRunning) {
217
+ dependencies.add("caddy")
218
+ }
211
219
  let mods = this.kernel.bin.mods.filter((m) => {
212
- return req.dependencies.includes(m.name)
220
+ return dependencies.has(m.name)
213
221
  }).map((m) => {
214
222
  if (m.mod.cmd) {
215
223
  return m.mod.cmd()
@@ -220,6 +228,7 @@ report_errors: false`)
220
228
  console.log("Conda dependencies to install", { mods })
221
229
 
222
230
  let condaPackages = [
231
+ `"conda=${CONDA_PIN_VERSION}"`,
223
232
  this.kernel.platform === "win32" ? `"${WINDOWS_PYTHON_SSL_FIX_SPEC}"` : `"${PYTHON_INSTALL_SPEC}"`,
224
233
  `"conda-libmamba-solver>=25.4.0"`,
225
234
  ]
@@ -244,9 +253,12 @@ report_errors: false`)
244
253
  )
245
254
  }
246
255
  await this.ensureCompatibilityAlias()
247
- await this.kernel.bin.rm(installer, ondata)
248
- await fs.promises.writeFile(this.kernel.bin.path(CONDA_ROOT_DIR, CONDA_RUNTIME_MARKER), "")
249
256
  ondata({ raw: `Install finished\r\n` })
257
+ await this.kernel.bin.rm(installer, ondata)
258
+ if (caddyWasRunning) {
259
+ ondata({ raw: `restarting Home Server after replacing Miniforge...\r\n` })
260
+ await caddy.start()
261
+ }
250
262
  }
251
263
  async removeInstallPath(target) {
252
264
  try {
@@ -23,7 +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
+ isExpectedCondaPinned,
27
27
  isExpectedPythonPinned,
28
28
  } = require('./conda-pins')
29
29
  const { glob } = require('glob')
@@ -350,10 +350,6 @@ class Bin {
350
350
  let conda_builds = {}
351
351
  this.correct_conda = false
352
352
 
353
- const runtimeMarkerExists = await fs.promises.access(
354
- this.kernel.bin.path("miniforge", CONDA_RUNTIME_MARKER)
355
- ).then(() => true).catch(() => false)
356
-
357
353
  //////////////////////////////////////////////////////////////////
358
354
  // exception handling
359
355
  // if importlib_metadata || uvicorn || fastapi exist in the base environment, this.correct_conda = false
@@ -380,7 +376,7 @@ class Bin {
380
376
 
381
377
  let to_reset_exists = false
382
378
 
383
- if (!runtimeMarkerExists || to_reset_exists) {
379
+ if (to_reset_exists) {
384
380
  this.correct_conda = false
385
381
  } else {
386
382
  let res = await buildCondaListFromMeta(this.kernel.bin.path("miniforge"))
@@ -396,7 +392,7 @@ class Bin {
396
392
  conda_versions[name] = version
397
393
  conda_builds[name] = build
398
394
  if (name === "conda") {
399
- conda_check.conda = true
395
+ conda_check.conda = isExpectedCondaPinned(version)
400
396
  }
401
397
  if (name === "conda-libmamba-solver") {
402
398
  let coerced = semver.coerce(version)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.2",
3
+ "version": "8.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -51,3 +51,68 @@ test('caddy admin wait returns true when admin responds', async () => {
51
51
  assert.equal(ready, true)
52
52
  assert.equal(checks, 2)
53
53
  })
54
+
55
+ test('caddy start uses the normal shell execution contract', async () => {
56
+ const caddy = new Caddy()
57
+ let running = false
58
+ let launch
59
+ caddy.kernel = {
60
+ homedir: '/tmp/pinokio',
61
+ peer: {
62
+ https_active: true,
63
+ announce() {}
64
+ },
65
+ processes: {},
66
+ exec(params, ondata) {
67
+ launch = params
68
+ running = true
69
+ ondata({ raw: '', cleaned: 'admin endpoint started' })
70
+ }
71
+ }
72
+ caddy.running = async () => running
73
+ caddy.installed = async () => true
74
+
75
+ await caddy.start()
76
+
77
+ assert.equal(launch.message, 'caddy run --watch')
78
+ assert.deepEqual(Object.keys(launch).sort(), ['message', 'path'])
79
+ })
80
+
81
+ test('caddy stop uses the native admin API only when Caddy is running', async () => {
82
+ const caddy = new Caddy()
83
+ let running = true
84
+ caddy.running = async () => running
85
+ const originalPost = require('axios').post
86
+ const calls = []
87
+ require('axios').post = async (...args) => {
88
+ calls.push(args)
89
+ running = false
90
+ }
91
+
92
+ try {
93
+ assert.equal(await caddy.stop(), true)
94
+ assert.equal(await caddy.stop(), false)
95
+ } finally {
96
+ require('axios').post = originalPost
97
+ }
98
+
99
+ assert.equal(calls.length, 1)
100
+ assert.equal(calls[0][0], 'http://127.0.0.1:2019/stop')
101
+ })
102
+
103
+ test('caddy stop refuses to continue when native shutdown is not confirmed', async () => {
104
+ const caddy = new Caddy()
105
+ caddy.running = async () => true
106
+ caddy.waitForStopped = async () => false
107
+ const originalPost = require('axios').post
108
+ require('axios').post = async () => {}
109
+
110
+ try {
111
+ await assert.rejects(
112
+ caddy.stop(),
113
+ /could not stop Caddy/
114
+ )
115
+ } finally {
116
+ require('axios').post = originalPost
117
+ }
118
+ })
@@ -9,9 +9,9 @@ const Bin = require('../kernel/bin')
9
9
  const Conda = require('../kernel/bin/conda')
10
10
  const {
11
11
  CONDA_PIN_VERSION,
12
- CONDA_RUNTIME_MARKER,
13
12
  PYTHON_INSTALL_SPEC,
14
13
  WINDOWS_PYTHON_SSL_FIX_SPEC,
14
+ isExpectedCondaPinned,
15
15
  isExpectedPythonPinned,
16
16
  } = require('../kernel/bin/conda-pins')
17
17
 
@@ -44,19 +44,12 @@ async function writeCondaMeta(root, name, version, build = 'py_0') {
44
44
  )
45
45
  }
46
46
 
47
- async function writeHealthyCondaMeta(root, platform = 'win32') {
48
- await writeCondaMeta(root, 'conda', CONDA_PIN_VERSION, 'py310')
47
+ async function writeHealthyCondaMeta(root, platform = 'win32', condaVersion = CONDA_PIN_VERSION) {
48
+ await writeCondaMeta(root, 'conda', condaVersion, 'py310')
49
49
  await writeCondaMeta(root, 'conda-libmamba-solver', '25.4.0', 'pyhd3eb1b0_0')
50
50
  await writeCondaMeta(root, 'python', '3.10.20', platform === 'win32' ? 'h4de0772_1_cpython' : 'cpython')
51
51
  }
52
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
-
60
53
  async function writeFakeManagedConda(root, platform = 'win32') {
61
54
  const condaPath = platform === 'win32'
62
55
  ? path.join(root, 'bin', 'miniforge', 'Scripts', 'conda.exe')
@@ -123,11 +116,13 @@ test('Conda uses Miniforge assets and writes conda-forge-only config', async ()
123
116
  })
124
117
 
125
118
  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')
119
+ assert.equal(CONDA_PIN_VERSION, '26.5.3')
128
120
  assert.equal(PYTHON_INSTALL_SPEC, 'python=3.10.20')
129
121
  assert.equal(WINDOWS_PYTHON_SSL_FIX_SPEC, 'python=3.10.20=*_1_cpython')
130
122
 
123
+ assert.equal(isExpectedCondaPinned('26.5.3'), true)
124
+ assert.equal(isExpectedCondaPinned('26.3.2'), false)
125
+
131
126
  assert.equal(isExpectedPythonPinned('darwin', '3.10.20', 'cpython'), true)
132
127
  assert.equal(isExpectedPythonPinned('linux', '3.10.20', 'cpython'), true)
133
128
  assert.equal(isExpectedPythonPinned('darwin', '3.10.21', 'cpython'), false)
@@ -136,17 +131,16 @@ test('Conda pins the managed Conda and Python versions consistently', async () =
136
131
  assert.equal(isExpectedPythonPinned('win32', '3.10.20', 'h4de0772_1_cpython'), true)
137
132
  })
138
133
 
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-'))
134
+ test('Conda init writes the current Conda pin into an existing runtime', async () => {
135
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-pin-init-'))
141
136
  await createMiniforge(root)
142
137
  await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
143
138
 
144
139
  await createConda(createKernel(root, 'darwin')).init()
145
140
 
146
- assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), false)
147
141
  assert.equal(
148
142
  await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
149
- 'conda ==26.3.2'
143
+ 'conda ==26.5.3'
150
144
  )
151
145
  })
152
146
 
@@ -200,13 +194,12 @@ test('Conda install requests the Python 3.10.20 pin on macOS/Linux', async () =>
200
194
  assert.ok(condaInstall)
201
195
  assert.equal(
202
196
  condaInstall.message[1],
203
- 'conda install -y --override-channels -c conda-forge "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
197
+ 'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20" "conda-libmamba-solver>=25.4.0"'
204
198
  )
205
199
  assert.equal(
206
200
  await fs.readFile(path.join(root, 'bin', 'miniforge', 'conda-meta', 'pinned'), 'utf8'),
207
- 'conda ==26.3.2'
201
+ 'conda ==26.5.3'
208
202
  )
209
- assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
210
203
  })
211
204
 
212
205
  test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async () => {
@@ -233,9 +226,8 @@ test('Conda install keeps the Windows Python 3.10.20 SSL-fixed build pin', async
233
226
  assert.ok(condaInstall)
234
227
  assert.equal(
235
228
  condaInstall.message[1],
236
- 'conda install -y --override-channels -c conda-forge "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
229
+ 'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20=*_1_cpython" "conda-libmamba-solver>=25.4.0"'
237
230
  )
238
- assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
239
231
  })
240
232
 
241
233
  test('Conda install replaces the stale runtime and rebuilds declared Conda modules', async () => {
@@ -266,9 +258,62 @@ test('Conda install replaces the stale runtime and rebuilds declared Conda modul
266
258
  assert.ok(condaInstall)
267
259
  assert.equal(
268
260
  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'
261
+ 'conda install -y --override-channels -c conda-forge "conda=26.5.3" "python=3.10.20" "conda-libmamba-solver>=25.4.0" uv=0.11.23 nodejs=22.21.1 pnpm'
270
262
  )
271
- assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), true)
263
+ })
264
+
265
+ test('Conda replacement stops, preserves, and restarts a running managed Caddy', async () => {
266
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-caddy-lifecycle-'))
267
+ await createMiniforge(root)
268
+ const kernel = createKernel(root, 'win32')
269
+ const events = []
270
+ let caddyRunning = true
271
+ let condaInstall
272
+
273
+ const caddy = {
274
+ cmd: () => 'caddy=2.9.1',
275
+ running: async () => caddyRunning,
276
+ stop: async () => {
277
+ events.push('caddy-stop')
278
+ caddyRunning = false
279
+ },
280
+ start: async () => {
281
+ events.push('caddy-start')
282
+ caddyRunning = true
283
+ },
284
+ }
285
+ kernel.bin.installed.conda = new Set(['caddy'])
286
+ kernel.bin.mod = { caddy }
287
+ kernel.bin.mods = [{ name: 'caddy', mod: caddy }]
288
+ kernel.bin.download = async () => events.push('download')
289
+ kernel.bin.rm = async () => events.push('installer-cleanup')
290
+ kernel.bin.exec = async (payload) => {
291
+ if (payload && payload.conda && payload.conda.skip) {
292
+ events.push('bootstrap')
293
+ const miniforge = path.join(root, 'bin', 'miniforge')
294
+ await fs.mkdir(path.join(miniforge, 'conda-meta'), { recursive: true })
295
+ await fs.writeFile(path.join(miniforge, 'python.exe'), 'fake python\n')
296
+ return
297
+ }
298
+ events.push('conda-install')
299
+ condaInstall = payload
300
+ }
301
+
302
+ const conda = createConda(kernel)
303
+ const removeInstallPath = conda.removeInstallPath.bind(conda)
304
+ conda.removeInstallPath = async (target) => {
305
+ assert.equal(caddyRunning, false)
306
+ events.push('remove-miniforge')
307
+ await removeInstallPath(target)
308
+ }
309
+
310
+ await conda._install({ dependencies: [] }, () => {})
311
+
312
+ assert.ok(events.indexOf('caddy-stop') < events.indexOf('remove-miniforge'))
313
+ assert.ok(events.indexOf('remove-miniforge') < events.indexOf('bootstrap'))
314
+ assert.ok(events.indexOf('conda-install') < events.indexOf('caddy-start'))
315
+ assert.match(condaInstall.message[1], / caddy=2\.9\.1$/)
316
+ assert.equal(caddyRunning, true)
272
317
  })
273
318
 
274
319
  test('Conda install replaces legacy miniconda with a compatibility alias to miniforge', async () => {
@@ -329,14 +374,13 @@ test('Conda installed stays false when metadata check already marked Conda inval
329
374
  test('Conda check rejects metadata-only Windows installs', async () => {
330
375
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-missing-'))
331
376
  await writeHealthyCondaMeta(root, 'win32')
332
- await writeRuntimeMarker(root)
333
377
 
334
378
  assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
335
379
  })
336
380
 
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')
381
+ test('Conda check rejects a runnable runtime with the previous Conda version', async () => {
382
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-version-stale-'))
383
+ await writeHealthyCondaMeta(root, 'win32', '26.3.2')
340
384
  await writeFakeManagedConda(root, 'win32')
341
385
 
342
386
  assert.equal(await createConda(createKernel(root, 'win32')).check(), false)
@@ -346,24 +390,26 @@ test('Conda check accepts a runnable managed Windows conda executable', async ()
346
390
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-smoke-runnable-'))
347
391
  await writeHealthyCondaMeta(root, 'win32')
348
392
  await writeFakeManagedConda(root, 'win32')
349
- await writeRuntimeMarker(root)
350
393
 
351
394
  assert.equal(await createConda(createKernel(root, 'win32')).check(), true)
352
395
  })
353
396
 
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')
397
+ test('Bin readiness rejects the previous Conda version and accepts the pinned version', async () => {
398
+ const staleRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-conda-version-stale-'))
399
+ await writeHealthyCondaMeta(staleRoot, 'win32', '26.3.2')
400
+ await writeFakeManagedConda(staleRoot, 'win32')
401
+ const staleBin = createBin(staleRoot, 'win32')
359
402
 
360
- await bin.tryList()
361
- assert.equal(bin.correct_conda, false)
403
+ await staleBin.tryList()
404
+ assert.equal(staleBin.correct_conda, false)
362
405
 
363
- await writeRuntimeMarker(root)
406
+ const currentRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-conda-version-current-'))
407
+ await writeHealthyCondaMeta(currentRoot, 'win32')
408
+ await writeFakeManagedConda(currentRoot, 'win32')
409
+ const currentBin = createBin(currentRoot, 'win32')
364
410
 
365
- await bin.tryList()
366
- assert.equal(bin.correct_conda, true)
411
+ await currentBin.tryList()
412
+ assert.equal(currentBin.correct_conda, true)
367
413
  })
368
414
 
369
415
  test('Conda install keeps the old runtime when the replacement installer download fails', async () => {
@@ -382,10 +428,9 @@ test('Conda install keeps the old runtime when the replacement installer downloa
382
428
  /download failed/
383
429
  )
384
430
  assert.equal(await pathExists(oldRuntimeFile), true)
385
- assert.equal(await pathExists(path.join(miniforge, CONDA_RUNTIME_MARKER)), false)
386
431
  })
387
432
 
388
- test('Conda install leaves the runtime unmarked when bootstrap fails after replacement', async () => {
433
+ test('Conda install propagates bootstrap failure after replacing the runtime', async () => {
389
434
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-bootstrap-fails-'))
390
435
  await createMiniforge(root)
391
436
  const kernel = createKernel(root, 'darwin')
@@ -405,7 +450,6 @@ test('Conda install leaves the runtime unmarked when bootstrap fails after repla
405
450
  createConda(kernel)._install({ dependencies: [] }, () => {}),
406
451
  /bootstrap failed/
407
452
  )
408
- assert.equal(await pathExists(path.join(root, 'bin', 'miniforge', CONDA_RUNTIME_MARKER)), false)
409
453
  })
410
454
 
411
455
  test('Conda install reports manual recovery when the old runtime cannot be removed', async () => {