pinokiod 8.0.13 → 8.0.14
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/package.json +1 -1
- package/server/index.js +5 -1
- package/server/public/task-launcher.css +0 -10
- package/server/views/install.ejs +25 -0
- package/server/views/setup.ejs +6 -30
- package/server/views/tools.ejs +2 -42
- package/test/path-removal-ui.test.js +63 -0
- package/test/tools-reinstall-ui.test.js +34 -54
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -16619,6 +16619,7 @@ class Server {
|
|
|
16619
16619
|
this.app.post("/pinokio/install", ex((req, res) => {
|
|
16620
16620
|
req.session.requirements = req.body.requirements
|
|
16621
16621
|
req.session.callback = req.body.callback
|
|
16622
|
+
req.session.fresh = req.body.fresh === "1"
|
|
16622
16623
|
res.redirect("/pinokio/install")
|
|
16623
16624
|
}))
|
|
16624
16625
|
this.app.post("/pinokio/install/validate", ex(async (req, res) => {
|
|
@@ -16630,8 +16631,10 @@ class Server {
|
|
|
16630
16631
|
res.set("Cache-Control", "no-store")
|
|
16631
16632
|
let requirements = req.session.requirements
|
|
16632
16633
|
let callback = req.session.callback
|
|
16634
|
+
let fresh = req.session.fresh === true
|
|
16633
16635
|
req.session.requirements = null
|
|
16634
16636
|
req.session.callback = null
|
|
16637
|
+
req.session.fresh = null
|
|
16635
16638
|
res.render("install", {
|
|
16636
16639
|
logo: this.logo,
|
|
16637
16640
|
theme: this.theme,
|
|
@@ -16640,7 +16643,8 @@ class Server {
|
|
|
16640
16643
|
display: ["form"],
|
|
16641
16644
|
// query: req.query,
|
|
16642
16645
|
requirements,
|
|
16643
|
-
callback
|
|
16646
|
+
callback,
|
|
16647
|
+
fresh,
|
|
16644
16648
|
})
|
|
16645
16649
|
}))
|
|
16646
16650
|
this.app.get("/pinokio", ex((req, res) => {
|
|
@@ -1902,16 +1902,6 @@ body.dark .setup-requirement-row.needs-update .setup-requirement-state {
|
|
|
1902
1902
|
color: var(--task-muted);
|
|
1903
1903
|
}
|
|
1904
1904
|
|
|
1905
|
-
.setup-reset-loading {
|
|
1906
|
-
display: flex;
|
|
1907
|
-
align-items: center;
|
|
1908
|
-
gap: 8px;
|
|
1909
|
-
margin-top: 12px;
|
|
1910
|
-
color: var(--task-muted);
|
|
1911
|
-
font-size: 12px;
|
|
1912
|
-
line-height: 1.45;
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
1905
|
.setup-state-root {
|
|
1916
1906
|
min-height: 260px;
|
|
1917
1907
|
display: flex;
|
package/server/views/install.ejs
CHANGED
|
@@ -540,6 +540,7 @@ body.install-page #terminal .xterm {
|
|
|
540
540
|
</div>
|
|
541
541
|
<input id='requirements' type='hidden' value="<%=requirements%>">
|
|
542
542
|
<input id='callback' type='hidden' value="<%=callback%>">
|
|
543
|
+
<input id='fresh' type='hidden' value="<%=fresh ? '1' : '0'%>">
|
|
543
544
|
<script>
|
|
544
545
|
//
|
|
545
546
|
// /install_bin?type=bin&uri=conda
|
|
@@ -731,6 +732,29 @@ const navigateToCallback = () => {
|
|
|
731
732
|
}
|
|
732
733
|
location.href = href
|
|
733
734
|
}
|
|
735
|
+
const prepareFreshInstall = async () => {
|
|
736
|
+
if (document.querySelector("#fresh").value !== "1") return true
|
|
737
|
+
try {
|
|
738
|
+
const result = await PinokioPathRemoval.request((background) => fetch("/pinokio/delete", {
|
|
739
|
+
method: "post",
|
|
740
|
+
headers: { "Content-Type": "application/json" },
|
|
741
|
+
body: JSON.stringify({ type: "bin", background })
|
|
742
|
+
}))
|
|
743
|
+
if (result.cancelled) {
|
|
744
|
+
navigateToCallback()
|
|
745
|
+
return false
|
|
746
|
+
}
|
|
747
|
+
if (result.error) throw new Error(result.error)
|
|
748
|
+
return true
|
|
749
|
+
} catch (error) {
|
|
750
|
+
ModalInput({
|
|
751
|
+
title: "Error",
|
|
752
|
+
type: "modal",
|
|
753
|
+
description: error && error.message ? error.message : "Unable to reset managed tools."
|
|
754
|
+
})
|
|
755
|
+
return false
|
|
756
|
+
}
|
|
757
|
+
}
|
|
734
758
|
const resumeInstall = () => {
|
|
735
759
|
const form = document.createElement("form")
|
|
736
760
|
form.method = "post"
|
|
@@ -803,6 +827,7 @@ const createTerm = async (_theme) => {
|
|
|
803
827
|
}
|
|
804
828
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
805
829
|
applyInstallSpinnerVariant(randomInstallSpinnerVariant())
|
|
830
|
+
if (!await prepareFreshInstall()) return
|
|
806
831
|
const n = new N()
|
|
807
832
|
let socket = new Socket()
|
|
808
833
|
<% if (theme === "dark") { %>
|
package/server/views/setup.ejs
CHANGED
|
@@ -177,18 +177,18 @@
|
|
|
177
177
|
<%= install_required ? 'This uses the same install flow as before.' : 'Pinokio can reopen the same destination without reinstalling anything.' %>
|
|
178
178
|
</p>
|
|
179
179
|
|
|
180
|
+
<form class="task-hidden" id="fresh-install-form" method="post" action="/pinokio/install">
|
|
181
|
+
<input type="hidden" name="requirements" value="<%= JSON.stringify(requirementList) %>">
|
|
182
|
+
<input type="hidden" name="callback" value="<%= targetPath %>">
|
|
183
|
+
<input type="hidden" name="fresh" value="1">
|
|
184
|
+
</form>
|
|
180
185
|
<div class="setup-secondary-actions">
|
|
181
186
|
<span class="task-inline-help">Not working?</span>
|
|
182
|
-
<button class="task-button" type="
|
|
187
|
+
<button class="task-button" type="submit" form="fresh-install-form">
|
|
183
188
|
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
|
184
189
|
<span>Try a fresh install</span>
|
|
185
190
|
</button>
|
|
186
191
|
</div>
|
|
187
|
-
|
|
188
|
-
<div class="reset-bin-loading hidden setup-reset-loading">
|
|
189
|
-
<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i>
|
|
190
|
-
<span>Stand by. Do not close this window.</span>
|
|
191
|
-
</div>
|
|
192
192
|
</section>
|
|
193
193
|
</div>
|
|
194
194
|
</div>
|
|
@@ -383,30 +383,6 @@
|
|
|
383
383
|
showWaitError(error && error.message ? error.message : "Failed to restart Pinokio")
|
|
384
384
|
}
|
|
385
385
|
<% } %>
|
|
386
|
-
if (document.querySelector("#del-bin")) {
|
|
387
|
-
document.querySelector("#del-bin").addEventListener("click", async () => {
|
|
388
|
-
let proceed = confirm("Are you sure you wish to delete the bin folder?")
|
|
389
|
-
if (proceed) {
|
|
390
|
-
document.querySelector(".reset-bin-loading").classList.remove("hidden")
|
|
391
|
-
document.querySelector("#del-bin").classList.add("hidden")
|
|
392
|
-
let res = await PinokioPathRemoval.request((background) => fetch("/pinokio/delete", {
|
|
393
|
-
method: "post",
|
|
394
|
-
headers: {
|
|
395
|
-
"Content-Type": "application/json"
|
|
396
|
-
},
|
|
397
|
-
body: JSON.stringify({ type: "bin", background })
|
|
398
|
-
}))
|
|
399
|
-
document.querySelector(".reset-bin-loading").classList.add("hidden")
|
|
400
|
-
if (res.cancelled) {
|
|
401
|
-
document.querySelector("#del-bin").classList.remove("hidden")
|
|
402
|
-
} else if (res.error) {
|
|
403
|
-
alert(res.error)
|
|
404
|
-
} else {
|
|
405
|
-
document.querySelector("#install-form").submit()
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
})
|
|
409
|
-
}
|
|
410
386
|
})
|
|
411
387
|
</script>
|
|
412
388
|
<%- include('partials/app_common_scripts') %>
|
package/server/views/tools.ejs
CHANGED
|
@@ -501,7 +501,8 @@
|
|
|
501
501
|
<form class="tools-reinstall-form" method="post" action="/pinokio/install">
|
|
502
502
|
<input type="hidden" name="requirements" value="<%= JSON.stringify(bundle.requirements) %>">
|
|
503
503
|
<input type="hidden" name="callback" value="/tools">
|
|
504
|
-
<
|
|
504
|
+
<input type="hidden" name="fresh" value="1">
|
|
505
|
+
<button class="task-link-button" type="submit">
|
|
505
506
|
<i class="fa-solid fa-rotate" aria-hidden="true"></i>
|
|
506
507
|
<span>Reinstall</span>
|
|
507
508
|
</button>
|
|
@@ -840,46 +841,6 @@
|
|
|
840
841
|
});
|
|
841
842
|
};
|
|
842
843
|
|
|
843
|
-
const registerToolsReinstall = () => {
|
|
844
|
-
document.querySelectorAll("[data-tools-reinstall]").forEach((button) => {
|
|
845
|
-
button.addEventListener("click", async () => {
|
|
846
|
-
const bundleTitle = button.dataset.bundleTitle || "this bundle";
|
|
847
|
-
const proceed = confirm(`Reinstall ${bundleTitle} from scratch? This removes the entire managed tools folder before reinstalling the selected bundle.`);
|
|
848
|
-
if (!proceed) return;
|
|
849
|
-
|
|
850
|
-
const form = button.closest("form");
|
|
851
|
-
const icon = button.querySelector("i");
|
|
852
|
-
const restoreButton = () => {
|
|
853
|
-
button.disabled = false;
|
|
854
|
-
button.removeAttribute("aria-busy");
|
|
855
|
-
if (icon) icon.classList.remove("fa-spin");
|
|
856
|
-
};
|
|
857
|
-
button.disabled = true;
|
|
858
|
-
button.setAttribute("aria-busy", "true");
|
|
859
|
-
if (icon) icon.classList.add("fa-spin");
|
|
860
|
-
let result;
|
|
861
|
-
try {
|
|
862
|
-
result = await PinokioPathRemoval.request((background) => fetch("/pinokio/delete", {
|
|
863
|
-
method: "post",
|
|
864
|
-
headers: { "Content-Type": "application/json" },
|
|
865
|
-
body: JSON.stringify({ type: "bin", background })
|
|
866
|
-
}));
|
|
867
|
-
} catch (error) {
|
|
868
|
-
restoreButton();
|
|
869
|
-
alert(error && error.message ? error.message : "Unable to reset managed tools.");
|
|
870
|
-
return;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
if (result.cancelled || result.error) {
|
|
874
|
-
restoreButton();
|
|
875
|
-
if (result.error) alert(result.error);
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
form.submit();
|
|
879
|
-
});
|
|
880
|
-
});
|
|
881
|
-
};
|
|
882
|
-
|
|
883
844
|
document.addEventListener("DOMContentLoaded", function() {
|
|
884
845
|
const isPending = <%= pending ? 'true' : 'false' %>;
|
|
885
846
|
|
|
@@ -892,7 +853,6 @@
|
|
|
892
853
|
|
|
893
854
|
registerPackageInstallSheets();
|
|
894
855
|
registerPackageInstallForms();
|
|
895
|
-
registerToolsReinstall();
|
|
896
856
|
|
|
897
857
|
const tabs = document.querySelectorAll(".tools-tab-button");
|
|
898
858
|
const panels = document.querySelectorAll(".tools-package-panel");
|
|
@@ -393,6 +393,7 @@ test("successful runtime cleanup resubmits the interrupted install", async (t) =
|
|
|
393
393
|
const html = ejs.render(source, {
|
|
394
394
|
agent: "web",
|
|
395
395
|
callback: "/tools",
|
|
396
|
+
fresh: false,
|
|
396
397
|
requirements,
|
|
397
398
|
theme: "dark",
|
|
398
399
|
}, { filename: installViewPath })
|
|
@@ -457,3 +458,65 @@ test("successful runtime cleanup resubmits the interrupted install", async (t) =
|
|
|
457
458
|
fields: { requirements, callback: "/tools" },
|
|
458
459
|
})
|
|
459
460
|
})
|
|
461
|
+
|
|
462
|
+
test("fresh install removes bin on the install page before starting the installer", async (t) => {
|
|
463
|
+
const source = await fs.readFile(installViewPath, "utf8")
|
|
464
|
+
const requirements = '[{"name":"conda"}]'
|
|
465
|
+
const html = ejs.render(source, {
|
|
466
|
+
agent: "web",
|
|
467
|
+
callback: "/tools",
|
|
468
|
+
fresh: true,
|
|
469
|
+
requirements,
|
|
470
|
+
theme: "dark",
|
|
471
|
+
}, { filename: installViewPath })
|
|
472
|
+
|
|
473
|
+
let removal
|
|
474
|
+
let started
|
|
475
|
+
const startedPromise = new Promise((resolve) => { started = resolve })
|
|
476
|
+
const dom = new JSDOM(html, {
|
|
477
|
+
runScripts: "dangerously",
|
|
478
|
+
url: "http://localhost/pinokio/install",
|
|
479
|
+
beforeParse(window) {
|
|
480
|
+
window.fetch = async (url, options) => {
|
|
481
|
+
if (url === "/pinokio/delete") {
|
|
482
|
+
removal = { url, options }
|
|
483
|
+
return { ok: true, status: 200, json: async () => ({ success: true }) }
|
|
484
|
+
}
|
|
485
|
+
return { ok: true, status: 200, json: async () => ({ config: {} }) }
|
|
486
|
+
}
|
|
487
|
+
window.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} })
|
|
488
|
+
window.ResizeObserver = class { observe() {} }
|
|
489
|
+
window.N = class { Noty() {} }
|
|
490
|
+
window.Terminal = class {
|
|
491
|
+
constructor() { this.cols = 80; this.rows = 24 }
|
|
492
|
+
attachCustomKeyEventHandler() {}
|
|
493
|
+
focus() {}
|
|
494
|
+
hasSelection() { return false }
|
|
495
|
+
loadAddon() {}
|
|
496
|
+
onData() {}
|
|
497
|
+
open() {}
|
|
498
|
+
write() {}
|
|
499
|
+
}
|
|
500
|
+
window.FitAddon = { FitAddon: class { fit() {} } }
|
|
501
|
+
window.WebLinksAddon = { WebLinksAddon: class {} }
|
|
502
|
+
window.xtermTheme = { FrontEndDelight: {} }
|
|
503
|
+
window.PinokioTouch = { bindTerminalFocus() {} }
|
|
504
|
+
window.PinokioPathRemoval = {
|
|
505
|
+
request: async (remove) => (await remove(false)).json(),
|
|
506
|
+
}
|
|
507
|
+
window.Socket = class {
|
|
508
|
+
emit() {}
|
|
509
|
+
run(request) {
|
|
510
|
+
assert.ok(removal, "fresh removal must finish before the installer starts")
|
|
511
|
+
assert.equal(request.params, requirements)
|
|
512
|
+
started()
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
})
|
|
517
|
+
t.after(() => dom.window.close())
|
|
518
|
+
|
|
519
|
+
await startedPromise
|
|
520
|
+
assert.equal(removal.url, "/pinokio/delete")
|
|
521
|
+
assert.deepEqual(JSON.parse(removal.options.body), { type: "bin", background: false })
|
|
522
|
+
})
|
|
@@ -6,6 +6,7 @@ const ejs = require("ejs")
|
|
|
6
6
|
const { JSDOM } = require("jsdom")
|
|
7
7
|
|
|
8
8
|
const viewPath = path.resolve(__dirname, "../server/views/tools.ejs")
|
|
9
|
+
const setupViewPath = path.resolve(__dirname, "../server/views/setup.ejs")
|
|
9
10
|
|
|
10
11
|
async function renderTools() {
|
|
11
12
|
const source = await fs.readFile(viewPath, "utf8")
|
|
@@ -27,78 +28,57 @@ async function renderTools() {
|
|
|
27
28
|
})
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
test("Tools reinstall
|
|
31
|
+
test("Tools reinstall immediately submits a fresh install request", async (t) => {
|
|
31
32
|
const html = await renderTools()
|
|
32
|
-
let request
|
|
33
33
|
let submission
|
|
34
34
|
const dom = new JSDOM(html, {
|
|
35
|
-
runScripts: "dangerously",
|
|
36
35
|
url: "http://localhost/tools",
|
|
37
|
-
beforeParse(window) {
|
|
38
|
-
window.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} })
|
|
39
|
-
window.requestAnimationFrame = (callback) => window.setTimeout(callback, 0)
|
|
40
|
-
window.confirm = () => true
|
|
41
|
-
window.PinokioPathRemoval = {
|
|
42
|
-
request: async (remove) => {
|
|
43
|
-
request = await remove(false)
|
|
44
|
-
return { success: true }
|
|
45
|
-
},
|
|
46
|
-
}
|
|
47
|
-
window.fetch = async (url, options) => ({ url, options })
|
|
48
|
-
window.HTMLFormElement.prototype.submit = function () {
|
|
49
|
-
submission = {
|
|
50
|
-
action: new URL(this.action).pathname,
|
|
51
|
-
fields: Object.fromEntries(new window.FormData(this)),
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
36
|
})
|
|
56
37
|
t.after(() => dom.window.close())
|
|
57
38
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
const form = dom.window.document.querySelector(".tools-reinstall-form")
|
|
40
|
+
form.addEventListener("submit", (event) => {
|
|
41
|
+
event.preventDefault()
|
|
42
|
+
submission = {
|
|
43
|
+
action: new URL(form.action).pathname,
|
|
44
|
+
fields: Object.fromEntries(new dom.window.FormData(form)),
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
const button = form.querySelector("button[type=submit]")
|
|
61
48
|
button.click()
|
|
62
|
-
await new Promise((resolve) => dom.window.setTimeout(resolve, 0))
|
|
63
49
|
|
|
64
|
-
assert.equal(request.url, "/pinokio/delete")
|
|
65
|
-
assert.equal(request.options.method, "post")
|
|
66
|
-
assert.deepEqual(JSON.parse(request.options.body), { type: "bin", background: false })
|
|
67
50
|
assert.deepEqual(submission, {
|
|
68
51
|
action: "/pinokio/install",
|
|
69
52
|
fields: {
|
|
70
53
|
requirements: '[{"name":"conda","type":"bin","installed":true}]',
|
|
71
54
|
callback: "/tools",
|
|
55
|
+
fresh: "1",
|
|
72
56
|
},
|
|
73
57
|
})
|
|
74
58
|
})
|
|
75
59
|
|
|
76
|
-
test("
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
beforeParse(window) {
|
|
84
|
-
window.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} })
|
|
85
|
-
window.requestAnimationFrame = (callback) => window.setTimeout(callback, 0)
|
|
86
|
-
window.fetch = async () => ({ ok: true, json: async () => ({ status: "off" }) })
|
|
87
|
-
window.confirm = () => false
|
|
88
|
-
window.PinokioPathRemoval = {
|
|
89
|
-
request: async () => {
|
|
90
|
-
removed = true
|
|
91
|
-
return { success: true }
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
window.HTMLFormElement.prototype.submit = () => { submitted = true }
|
|
95
|
-
},
|
|
60
|
+
test("setup fresh install is a direct form submission", async () => {
|
|
61
|
+
const source = await fs.readFile(setupViewPath, "utf8")
|
|
62
|
+
const start = source.indexOf('<form class="task-hidden" id="fresh-install-form"')
|
|
63
|
+
const end = source.indexOf("</div>", source.indexOf("</button>", start)) + 6
|
|
64
|
+
const fragment = ejs.render(source.slice(start, end), {
|
|
65
|
+
requirementList: [{ name: "conda", installed: true }],
|
|
66
|
+
targetPath: "/tools",
|
|
96
67
|
})
|
|
97
|
-
|
|
68
|
+
const dom = new JSDOM(fragment, { url: "http://localhost/setup/dev" })
|
|
69
|
+
const form = dom.window.document.querySelector("#fresh-install-form")
|
|
70
|
+
let submission
|
|
71
|
+
form.addEventListener("submit", (event) => {
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
submission = Object.fromEntries(new dom.window.FormData(form))
|
|
74
|
+
})
|
|
75
|
+
dom.window.document.querySelector('[form="fresh-install-form"]').click()
|
|
98
76
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
77
|
+
assert.deepEqual(submission, {
|
|
78
|
+
requirements: '[{"name":"conda","installed":true}]',
|
|
79
|
+
callback: "/tools",
|
|
80
|
+
fresh: "1",
|
|
81
|
+
})
|
|
82
|
+
dom.window.close()
|
|
83
|
+
assert.doesNotMatch(source, /id="del-bin"|setup-reset-loading/)
|
|
104
84
|
})
|