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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.13",
3
+ "version": "8.0.14",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
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;
@@ -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") { %>
@@ -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="button" id="del-bin">
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') %>
@@ -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
- <button class="task-link-button" type="button" data-tools-reinstall data-bundle-title="<%= bundle.title %>">
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 removes bin and submits the selected bundle", async (t) => {
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
- await new Promise((resolve) => dom.window.setTimeout(resolve, 0))
59
- const button = dom.window.document.querySelector("[data-tools-reinstall]")
60
- assert.ok(button)
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("Tools reinstall keeps the bundle intact when the user cancels", async (t) => {
77
- const html = await renderTools()
78
- let removed = false
79
- let submitted = false
80
- const dom = new JSDOM(html, {
81
- runScripts: "dangerously",
82
- url: "http://localhost/tools",
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
- t.after(() => dom.window.close())
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
- await new Promise((resolve) => dom.window.setTimeout(resolve, 0))
100
- dom.window.document.querySelector("[data-tools-reinstall]").click()
101
- await new Promise((resolve) => dom.window.setTimeout(resolve, 0))
102
- assert.equal(removed, false)
103
- assert.equal(submitted, false)
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
  })