pinokiod 8.0.16 → 8.0.17
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.js +2 -0
- package/kernel/bin/index.js +15 -0
- package/package.json +1 -1
- package/server/index.js +24 -15
- package/server/views/install.ejs +2 -0
- package/test/conda-bin.test.js +78 -0
- package/test/path-removal-ui.test.js +59 -0
package/kernel/bin/conda.js
CHANGED
|
@@ -185,7 +185,9 @@ report_errors: false`)
|
|
|
185
185
|
install_path_exists = await this.kernel.exists(`bin/${CONDA_ROOT_DIR}`)
|
|
186
186
|
if (install_path_exists) {
|
|
187
187
|
console.log("Removing existing install path...", install_path)
|
|
188
|
+
ondata({ raw: `removing existing Miniforge installation...\r\n` })
|
|
188
189
|
await this.removeInstallPath(install_path, ondata)
|
|
190
|
+
ondata({ raw: `existing Miniforge installation removed.\r\n` })
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
// 2. run the script
|
package/kernel/bin/index.js
CHANGED
|
@@ -715,7 +715,22 @@ class Bin {
|
|
|
715
715
|
let res = await Util.filepicker(req, ondata, this.kernel)
|
|
716
716
|
return res
|
|
717
717
|
}
|
|
718
|
+
async runInstallExclusive(task) {
|
|
719
|
+
const previous = this.installQueue || Promise.resolve()
|
|
720
|
+
let release
|
|
721
|
+
const current = new Promise((resolve) => { release = resolve })
|
|
722
|
+
this.installQueue = previous.then(() => current)
|
|
723
|
+
await previous
|
|
724
|
+
try {
|
|
725
|
+
return await task()
|
|
726
|
+
} finally {
|
|
727
|
+
release()
|
|
728
|
+
}
|
|
729
|
+
}
|
|
718
730
|
async install(req, ondata) {
|
|
731
|
+
return await this.runInstallExclusive(() => this.installRequirements(req, ondata))
|
|
732
|
+
}
|
|
733
|
+
async installRequirements(req, ondata) {
|
|
719
734
|
/*
|
|
720
735
|
req.params := {
|
|
721
736
|
client: {
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -16303,26 +16303,35 @@ class Server {
|
|
|
16303
16303
|
}
|
|
16304
16304
|
try {
|
|
16305
16305
|
if (req.body.type === 'conda-runtime') {
|
|
16306
|
-
|
|
16307
|
-
const
|
|
16308
|
-
|
|
16306
|
+
await this.kernel.bin.runInstallExclusive(async () => {
|
|
16307
|
+
for (const runtimeName of ["miniconda", "miniforge"]) {
|
|
16308
|
+
const runtimePath = this.kernel.path("bin", runtimeName)
|
|
16309
|
+
await removePath(runtimePath, {
|
|
16310
|
+
recursive: true,
|
|
16311
|
+
force: true,
|
|
16312
|
+
diagnose: req.body.background !== true,
|
|
16313
|
+
operation: "Updating Miniforge",
|
|
16314
|
+
})
|
|
16315
|
+
}
|
|
16316
|
+
})
|
|
16317
|
+
res.json({ success: true })
|
|
16318
|
+
} else if (req.body.type === 'bin') {
|
|
16319
|
+
let folderPath = this.kernel.path("bin")
|
|
16320
|
+
await this.kernel.bin.runInstallExclusive(async () => {
|
|
16321
|
+
const removalStartedAt = Date.now()
|
|
16322
|
+
console.log(`[fresh install] removing bin: start ${folderPath}`)
|
|
16323
|
+
await removePath(folderPath, {
|
|
16309
16324
|
recursive: true,
|
|
16310
16325
|
force: true,
|
|
16311
16326
|
diagnose: req.body.background !== true,
|
|
16312
|
-
operation: "
|
|
16327
|
+
operation: "Resetting managed tools",
|
|
16313
16328
|
})
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
recursive: true,
|
|
16320
|
-
force: true,
|
|
16321
|
-
diagnose: req.body.background !== true,
|
|
16322
|
-
operation: "Resetting managed tools",
|
|
16329
|
+
console.log(`[fresh install] removing bin: complete (${((Date.now() - removalStartedAt) / 1000).toFixed(1)}s)`)
|
|
16330
|
+
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16331
|
+
const refreshStartedAt = Date.now()
|
|
16332
|
+
await this.kernel.bin.refreshInstalled()
|
|
16333
|
+
console.log(`[fresh install] refreshing installed state: complete (${((Date.now() - refreshStartedAt) / 1000).toFixed(1)}s)`)
|
|
16323
16334
|
})
|
|
16324
|
-
await fs.promises.mkdir(folderPath, { recursive: true }).catch((e) => { })
|
|
16325
|
-
await this.kernel.bin.refreshInstalled()
|
|
16326
16335
|
res.json({ success: true })
|
|
16327
16336
|
} else if (req.body.type === 'cache') {
|
|
16328
16337
|
let folderPath = this.kernel.path("cache")
|
package/server/views/install.ejs
CHANGED
|
@@ -820,6 +820,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
820
820
|
}
|
|
821
821
|
}
|
|
822
822
|
if (options && options.fresh === "1") {
|
|
823
|
+
setInstallStatus("<b>Removing existing tools...</b><br>This can take a minute on Windows.")
|
|
823
824
|
try {
|
|
824
825
|
const result = await PinokioPathRemoval.request((background) => fetch("/pinokio/delete", {
|
|
825
826
|
method: "post",
|
|
@@ -839,6 +840,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
839
840
|
})
|
|
840
841
|
return
|
|
841
842
|
}
|
|
843
|
+
setInstallStatus("<b>Installing tools...</b><br>Starting the fresh installation.")
|
|
842
844
|
}
|
|
843
845
|
term.onData((data) => {
|
|
844
846
|
if (shell_id) {
|
package/test/conda-bin.test.js
CHANGED
|
@@ -425,6 +425,84 @@ test('Conda install keeps the old runtime when the replacement installer downloa
|
|
|
425
425
|
assert.equal(await pathExists(oldRuntimeFile), true)
|
|
426
426
|
})
|
|
427
427
|
|
|
428
|
+
test('Conda waits for runtime removal before starting the installer', async () => {
|
|
429
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-removal-order-'))
|
|
430
|
+
await createMiniforge(root)
|
|
431
|
+
const kernel = createKernel(root, 'win32')
|
|
432
|
+
const events = []
|
|
433
|
+
const output = []
|
|
434
|
+
let releaseRemoval
|
|
435
|
+
let removalStarted
|
|
436
|
+
const removalGate = new Promise((resolve) => { releaseRemoval = resolve })
|
|
437
|
+
const removalStartedPromise = new Promise((resolve) => { removalStarted = resolve })
|
|
438
|
+
|
|
439
|
+
kernel.bin.mods = []
|
|
440
|
+
kernel.bin.download = async () => events.push('download')
|
|
441
|
+
kernel.bin.rm = async () => events.push('installer-cleanup')
|
|
442
|
+
kernel.bin.exec = async (payload) => {
|
|
443
|
+
events.push(payload && payload.conda && payload.conda.skip ? 'installer' : 'conda-install')
|
|
444
|
+
if (payload && payload.conda && payload.conda.skip) {
|
|
445
|
+
await fs.mkdir(path.join(root, 'bin', 'miniforge', 'conda-meta'), { recursive: true })
|
|
446
|
+
await fs.writeFile(path.join(root, 'bin', 'miniforge', 'python.exe'), 'fake python\n')
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const conda = createConda(kernel)
|
|
451
|
+
const removeInstallPath = conda.removeInstallPath.bind(conda)
|
|
452
|
+
conda.removeInstallPath = async (...args) => {
|
|
453
|
+
events.push('remove-start')
|
|
454
|
+
removalStarted()
|
|
455
|
+
await removalGate
|
|
456
|
+
await removeInstallPath(...args)
|
|
457
|
+
events.push('remove-complete')
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const installation = conda._install({ dependencies: [] }, (data) => {
|
|
461
|
+
if (data && data.raw) output.push(data.raw)
|
|
462
|
+
})
|
|
463
|
+
await removalStartedPromise
|
|
464
|
+
|
|
465
|
+
assert.deepEqual(events, ['download', 'remove-start'])
|
|
466
|
+
assert.match(output.join(''), /removing existing Miniforge installation/)
|
|
467
|
+
|
|
468
|
+
releaseRemoval()
|
|
469
|
+
await installation
|
|
470
|
+
|
|
471
|
+
assert.ok(events.indexOf('remove-complete') < events.indexOf('installer'))
|
|
472
|
+
assert.match(output.join(''), /existing Miniforge installation removed/)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
test('Bin serializes concurrent installs without dropping either request', async () => {
|
|
476
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-bin-install-single-flight-'))
|
|
477
|
+
const bin = createBin(root)
|
|
478
|
+
const events = []
|
|
479
|
+
let release
|
|
480
|
+
const gate = new Promise((resolve) => { release = resolve })
|
|
481
|
+
|
|
482
|
+
bin.installRequirements = async (req) => {
|
|
483
|
+
events.push(`start:${req.id}`)
|
|
484
|
+
if (req.id === 'first') await gate
|
|
485
|
+
events.push(`finish:${req.id}`)
|
|
486
|
+
return req.id
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const first = bin.install({ id: 'first' }, () => {})
|
|
490
|
+
const second = bin.install({ id: 'second' }, () => {})
|
|
491
|
+
await Promise.resolve()
|
|
492
|
+
assert.deepEqual(events, ['start:first'])
|
|
493
|
+
|
|
494
|
+
release()
|
|
495
|
+
assert.deepEqual(await Promise.all([first, second]), ['first', 'second'])
|
|
496
|
+
assert.deepEqual(events, ['start:first', 'finish:first', 'start:second', 'finish:second'])
|
|
497
|
+
|
|
498
|
+
bin.installRequirements = async (req) => {
|
|
499
|
+
if (req.id === 'failure') throw new Error('install failed')
|
|
500
|
+
return req.id
|
|
501
|
+
}
|
|
502
|
+
await assert.rejects(bin.install({ id: 'failure' }, () => {}), /install failed/)
|
|
503
|
+
assert.equal(await bin.install({ id: 'after-failure' }, () => {}), 'after-failure')
|
|
504
|
+
})
|
|
505
|
+
|
|
428
506
|
test('Conda install propagates bootstrap failure after replacing the runtime', async () => {
|
|
429
507
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-conda-bootstrap-fails-'))
|
|
430
508
|
await createMiniforge(root)
|
|
@@ -457,3 +457,62 @@ test("successful runtime cleanup resubmits the interrupted install", async (t) =
|
|
|
457
457
|
fields: { requirements, callback: "/tools" },
|
|
458
458
|
})
|
|
459
459
|
})
|
|
460
|
+
|
|
461
|
+
test("fresh install shows removal status and waits for cleanup before starting", async (t) => {
|
|
462
|
+
const source = await fs.readFile(installViewPath, "utf8")
|
|
463
|
+
const html = ejs.render(source, {
|
|
464
|
+
agent: "web",
|
|
465
|
+
callback: "/tools",
|
|
466
|
+
requirements: '[{"name":"conda"}]',
|
|
467
|
+
theme: "dark",
|
|
468
|
+
}, { filename: installViewPath })
|
|
469
|
+
|
|
470
|
+
let cleanupStarted = false
|
|
471
|
+
let resolveCleanup
|
|
472
|
+
let socketStarted = false
|
|
473
|
+
const cleanup = new Promise((resolve) => { resolveCleanup = resolve })
|
|
474
|
+
const dom = new JSDOM(html, {
|
|
475
|
+
runScripts: "dangerously",
|
|
476
|
+
url: "http://localhost/pinokio/install?fresh=1",
|
|
477
|
+
beforeParse(window) {
|
|
478
|
+
window.fetch = async () => ({ json: async () => ({ config: {} }) })
|
|
479
|
+
window.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} })
|
|
480
|
+
window.ResizeObserver = class { observe() {} }
|
|
481
|
+
window.N = class { Noty() {} }
|
|
482
|
+
window.Terminal = class {
|
|
483
|
+
constructor() { this.cols = 80; this.rows = 24 }
|
|
484
|
+
attachCustomKeyEventHandler() {}
|
|
485
|
+
focus() {}
|
|
486
|
+
hasSelection() { return false }
|
|
487
|
+
loadAddon() {}
|
|
488
|
+
onData() {}
|
|
489
|
+
open() {}
|
|
490
|
+
write() {}
|
|
491
|
+
}
|
|
492
|
+
window.FitAddon = { FitAddon: class { fit() {} } }
|
|
493
|
+
window.WebLinksAddon = { WebLinksAddon: class {} }
|
|
494
|
+
window.xtermTheme = { FrontEndDelight: {} }
|
|
495
|
+
window.PinokioTouch = { bindTerminalFocus() {} }
|
|
496
|
+
window.PinokioPathRemoval = {
|
|
497
|
+
request: async () => {
|
|
498
|
+
cleanupStarted = true
|
|
499
|
+
return await cleanup
|
|
500
|
+
},
|
|
501
|
+
}
|
|
502
|
+
window.Socket = class {
|
|
503
|
+
emit() {}
|
|
504
|
+
run() { socketStarted = true }
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
})
|
|
508
|
+
t.after(() => dom.window.close())
|
|
509
|
+
|
|
510
|
+
await waitFor(() => cleanupStarted, "fresh cleanup request")
|
|
511
|
+
assert.match(dom.window.document.querySelector("#status-screen").textContent, /Removing existing tools/)
|
|
512
|
+
assert.match(dom.window.document.querySelector("#status-screen").textContent, /take a minute on Windows/)
|
|
513
|
+
assert.equal(socketStarted, false)
|
|
514
|
+
|
|
515
|
+
resolveCleanup({ success: true })
|
|
516
|
+
await waitFor(() => socketStarted, "installer socket")
|
|
517
|
+
assert.match(dom.window.document.querySelector("#status-screen").textContent, /Installing tools/)
|
|
518
|
+
})
|