pinokiod 7.5.38 → 7.5.39

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": "7.5.38",
3
+ "version": "7.5.39",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -865,7 +865,7 @@ body.dark .menu-btns .toggle-star.is-starred {
865
865
  padding: 0;
866
866
  border: 1px solid rgba(15, 23, 42, 0.14);
867
867
  border-radius: 8px;
868
- background: rgba(255, 255, 255, 0.98);
868
+ background: var(--home-page-nav-bg, #ffffff);
869
869
  color: rgba(15, 23, 42, 0.94);
870
870
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
871
871
  }
@@ -886,7 +886,7 @@ body.dark .menu-btns .toggle-star.is-starred {
886
886
  }
887
887
  body.dark .home-actions-dialog {
888
888
  border-color: rgba(255, 255, 255, 0.12);
889
- background: rgba(18, 20, 25, 0.98);
889
+ background: var(--home-page-nav-bg, #1b1c1d);
890
890
  color: rgba(248, 250, 252, 0.94);
891
891
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55);
892
892
  }
@@ -1125,6 +1125,35 @@ body.dark .home-mode-command {
1125
1125
  font-size: 10px;
1126
1126
  opacity: 0.6;
1127
1127
  }
1128
+ .home-run-command.has-block-label,
1129
+ .home-run-menu-summary.has-block-label {
1130
+ min-height: 52px;
1131
+ padding-top: 8px;
1132
+ padding-bottom: 8px;
1133
+ }
1134
+ .home-run-menu-label {
1135
+ display: flex;
1136
+ align-items: center;
1137
+ gap: 9px;
1138
+ }
1139
+ .home-run-menu-label.has-block-content {
1140
+ align-items: flex-start;
1141
+ white-space: normal;
1142
+ line-height: 1.18;
1143
+ }
1144
+ .home-run-menu-label.has-block-content > i:first-child {
1145
+ margin-top: 1px;
1146
+ }
1147
+ .home-run-menu-label.has-block-content > div {
1148
+ min-width: 0;
1149
+ display: flex;
1150
+ flex-direction: column;
1151
+ gap: 2px;
1152
+ }
1153
+ .home-run-menu-label.has-block-content > div > strong,
1154
+ .home-run-menu-label.has-block-content > div > div {
1155
+ line-height: 1.18;
1156
+ }
1128
1157
  .home-run-stop {
1129
1158
  width: 38px;
1130
1159
  min-height: 38px;
@@ -1,3 +1,15 @@
1
+ <%
2
+ const homeActionsAppId = item && (item.uri || item.path || item.name) ? String(item.uri || item.path || item.name) : ""
3
+ const homeActionsSettingsHref = homeActionsAppId
4
+ ? `/v/${encodeURIComponent(homeActionsAppId)}?${new URLSearchParams({
5
+ pinokio_home_select: JSON.stringify({
6
+ target: "env-settings",
7
+ hrefAttr: `/env/api/${homeActionsAppId}?host=app`,
8
+ textValue: "Settings"
9
+ })
10
+ }).toString()}`
11
+ : ""
12
+ %>
1
13
  <dialog id='<%= id %>' class='home-actions-dialog' aria-labelledby='<%= id %>-title'>
2
14
  <div class='home-actions-shell'>
3
15
  <div class='home-actions-header'>
@@ -49,6 +61,12 @@
49
61
  <span class='home-actions-count'><%= item.terminal_online_count %> online</span>
50
62
  <% } %>
51
63
  </button>
64
+ <% if (homeActionsSettingsHref) { %>
65
+ <a class='home-mode-command' href='<%= homeActionsSettingsHref %>'>
66
+ <i class='fa-solid fa-gear' aria-hidden='true'></i>
67
+ <span>Settings</span>
68
+ </a>
69
+ <% } %>
52
70
  <button type='button' class='home-mode-command' data-filepath='<%= item.filepath %>'>
53
71
  <i class='fa-solid fa-folder-open' aria-hidden='true'></i>
54
72
  <span>Open in file explorer</span>
@@ -9,6 +9,7 @@ const stripHtml = (value) => String(value ?? "")
9
9
  .replace(/<[^>]*>/g, " ")
10
10
  .replace(/\s+/g, " ")
11
11
  .trim()
12
+ const hasBlockContent = (value) => /<\s*(div|p|section|article|ul|ol|li|h[1-6])(?:\s|>)/i.test(String(value ?? ""))
12
13
  const buildHomeSelectionPayload = (menuItem, index) => {
13
14
  const dataFilepath = (menuItem.fs || menuItem.command) ? (menuItem.href || "") : ""
14
15
  const payload = {
@@ -49,9 +50,10 @@ const buildHomeSelectionUrl = (menuItem, index) => {
49
50
  %>
50
51
  <% menu.forEach((item, index) => { %>
51
52
  <% if (item && item.menu) { %>
53
+ <% const itemBtnHasBlock = hasBlockContent(item.btn) %>
52
54
  <details class='home-run-menu-group' open>
53
- <summary class='home-run-menu-summary'>
54
- <span class='home-run-menu-label'><%- item.btn %></span>
55
+ <summary class='home-run-menu-summary <%= itemBtnHasBlock ? "has-block-label" : "" %>'>
56
+ <div class='home-run-menu-label <%= itemBtnHasBlock ? "has-block-content" : "" %>'><%- item.btn %></div>
55
57
  <i class='fa-solid fa-chevron-down' aria-hidden='true'></i>
56
58
  </summary>
57
59
  <div class='home-run-menu-children'>
@@ -60,13 +62,14 @@ const buildHomeSelectionUrl = (menuItem, index) => {
60
62
  </details>
61
63
  <% } else if (item && item.btn) { %>
62
64
  <% const openUrl = buildHomeSelectionUrl(item, index) %>
65
+ <% const itemBtnHasBlock = hasBlockContent(item.btn) %>
63
66
  <div class='home-run-menu-row <%= item.running ? "is-running" : "" %>'>
64
67
  <a
65
- class='home-run-command'
68
+ class='home-run-command <%= itemBtnHasBlock ? "has-block-label" : "" %>'
66
69
  href='<%= openUrl %>'
67
70
  <% if (item.confirm) { %>data-confirm="<%- escapeAttr(item.confirm) %>"<% } %>
68
71
  >
69
- <span class='home-run-menu-label'><%- item.btn %></span>
72
+ <div class='home-run-menu-label <%= itemBtnHasBlock ? "has-block-content" : "" %>'><%- item.btn %></div>
70
73
  <% if (item.running) { %><span class='home-run-menu-status'>Running</span><% } %>
71
74
  <i class='fa-solid fa-chevron-right' aria-hidden='true'></i>
72
75
  </a>
@@ -2,6 +2,7 @@ const assert = require("node:assert/strict")
2
2
  const fs = require("node:fs/promises")
3
3
  const path = require("node:path")
4
4
  const test = require("node:test")
5
+ const ejs = require("ejs")
5
6
 
6
7
  const root = path.resolve(__dirname, "..")
7
8
 
@@ -370,6 +371,11 @@ test("static guard: open without launching is not wired to launch requirement st
370
371
  assert.match(homeActionModal, /Open without launching/)
371
372
  assert.match(homeActionModal, /class='home-mode-command home-browse' data-src='<%= item\.view_url %>'/)
372
373
  assert.match(homeActionModal, /Open dev mode/)
374
+ assert.match(homeActionModal, /const homeActionsAppId = item && \(item\.uri \|\| item\.path \|\| item\.name\)/)
375
+ assert.match(homeActionModal, /pinokio_home_select: JSON\.stringify/)
376
+ assert.match(homeActionModal, /target: "env-settings"/)
377
+ assert.match(homeActionModal, /href='<%= homeActionsSettingsHref %>'/)
378
+ assert.match(homeActionModal, /Settings/)
373
379
  assert.doesNotMatch(homeActionModal, /home-actions-tabs|home-actions-tab|role='tab'|role='tabpanel'/)
374
380
  assert.doesNotMatch(homeActionModal, /Open files mode/)
375
381
  assert.doesNotMatch(homeActionModal, /launch.requirements|data-launch-requirements|frame-link/)
@@ -378,6 +384,66 @@ test("static guard: open without launching is not wired to launch requirement st
378
384
  assert.doesNotMatch(statusClient, /saved requirement/)
379
385
  })
380
386
 
387
+ test("home actions modal renders an app settings link", async () => {
388
+ const html = await ejs.renderFile(path.resolve(root, "server/views/partials/home_action_modal.ejs"), {
389
+ id: "home-actions-test",
390
+ item: {
391
+ icon: "",
392
+ name: "Test App",
393
+ filepath: "/tmp/pinokio/api/test app.git",
394
+ uri: "test app.git",
395
+ menu: [],
396
+ view_url: "/v/test%20app.git",
397
+ dev_url: "/p/test%20app.git/dev",
398
+ url: "/p/test%20app.git",
399
+ terminal_online_count: 0,
400
+ running: false
401
+ }
402
+ })
403
+
404
+ const hrefMatch = html.match(/href='([^']+)'/)
405
+ assert.ok(hrefMatch)
406
+ const href = new URL(hrefMatch[1], "http://127.0.0.1")
407
+ assert.equal(href.pathname, "/v/test%20app.git")
408
+ const payload = JSON.parse(href.searchParams.get("pinokio_home_select"))
409
+ assert.deepEqual(payload, {
410
+ target: "env-settings",
411
+ hrefAttr: "/env/api/test app.git?host=app",
412
+ textValue: "Settings"
413
+ })
414
+ assert.match(html, />\s*Settings\s*</)
415
+ })
416
+
417
+ test("home actions drawer background follows the home page surface", async () => {
418
+ const homeView = await fs.readFile(path.resolve(root, "server/views/index.ejs"), "utf8")
419
+
420
+ assert.match(homeView, /body\.is-home \{[\s\S]*--home-page-nav-bg: #ffffff;[\s\S]*background: #ffffff;/)
421
+ assert.match(homeView, /body\.dark\.is-home \{[\s\S]*--home-page-nav-bg: #1b1c1d;[\s\S]*background: #1b1c1d;/)
422
+ assert.match(homeView, /\.home-actions-dialog \{[\s\S]*background: var\(--home-page-nav-bg, #ffffff\);/)
423
+ assert.match(homeView, /body\.dark \.home-actions-dialog \{[\s\S]*background: var\(--home-page-nav-bg, #1b1c1d\);/)
424
+ assert.doesNotMatch(homeView, /body\.dark \.home-actions-dialog \{[\s\S]*background: rgba\(18, 20, 25, 0\.98\);/)
425
+ })
426
+
427
+ test("home run menu gives block labels valid padded layout", async () => {
428
+ const homeView = await fs.readFile(path.resolve(root, "server/views/index.ejs"), "utf8")
429
+ const html = await ejs.renderFile(path.resolve(root, "server/views/partials/home_run_menu.ejs"), {
430
+ app: {
431
+ view_url: "/v/test-app"
432
+ },
433
+ menu: [{
434
+ icon: "fa-regular fa-circle-xmark",
435
+ btn: '<i class="fa-regular fa-circle-xmark"></i><div><strong>Reset</strong><div>Revert to pre-install state</div></div>',
436
+ href: "reset.js"
437
+ }]
438
+ })
439
+
440
+ assert.match(html, /class='home-run-command has-block-label'/)
441
+ assert.match(html, /<div class='home-run-menu-label has-block-content'>/)
442
+ assert.doesNotMatch(html, /<span class='home-run-menu-label/)
443
+ assert.match(homeView, /\.home-run-command\.has-block-label,[\s\S]*\.home-run-menu-summary\.has-block-label \{[\s\S]*padding-top: 8px;[\s\S]*padding-bottom: 8px;/)
444
+ assert.match(homeView, /\.home-run-menu-label\.has-block-content \{[\s\S]*white-space: normal;/)
445
+ })
446
+
381
447
  test("static guard: open without launching disables page-load script frame selection", async () => {
382
448
  const appView = await fs.readFile(path.resolve(root, "server/views/app.ejs"), "utf8")
383
449
 
@@ -949,6 +949,81 @@ test('Windows cmd suppresses echo only for explicit conda shell.run commands', (
949
949
  }, command), { command })
950
950
  })
951
951
 
952
+ test('Windows cmd array shell.run marks only the conda launch quiet', async () => {
953
+ const { context, root } = createContext('win32')
954
+ const records = []
955
+ const originalPrompt = Shell.prototype.prompt
956
+ const originalExec = Shell.prototype.exec
957
+ Shell.prototype.prompt = async () => 'PINOKIO_PROMPT'
958
+ Shell.prototype.exec = async function (params) {
959
+ this.platform = 'win32'
960
+ this.shell = 'cmd.exe'
961
+ const originalParams = {
962
+ input: params && params.input,
963
+ message: params && params.message,
964
+ }
965
+ const activatedParams = await this.activate(params)
966
+ const command = this.build(activatedParams)
967
+ records.push({
968
+ message: originalParams.message,
969
+ prepared: this.prepareCommandExecution(originalParams, command),
970
+ })
971
+ return command
972
+ }
973
+
974
+ try {
975
+ const kernel = {
976
+ homedir: root,
977
+ platform: 'win32',
978
+ bracketedPasteSupport: { 'cmd.exe': false },
979
+ envs: {},
980
+ exists: async () => false,
981
+ which: () => null,
982
+ path: (...parts) => path.join(root, ...parts),
983
+ bin: {
984
+ envs: (env = {}) => env,
985
+ path: (...parts) => path.join(root, 'bin', ...parts),
986
+ activationCommands: () => [],
987
+ },
988
+ api: {
989
+ resolvePath: (_cwd, target) => target,
990
+ running: {},
991
+ },
992
+ git: {
993
+ repos: async () => [],
994
+ restoreNewReposForActiveSnapshot: async () => {},
995
+ },
996
+ template: {
997
+ render: (value) => value,
998
+ },
999
+ }
1000
+ kernel.shell = new Shells(kernel)
1001
+
1002
+ await kernel.shell.run({
1003
+ path: context.appPath,
1004
+ message: [
1005
+ 'echo before',
1006
+ 'conda install -y -c conda-forge ffmpeg',
1007
+ 'python main.py',
1008
+ ],
1009
+ }, {}, () => {})
1010
+
1011
+ assert.deepEqual(records.map((record) => record.message), [
1012
+ 'echo before',
1013
+ 'conda install -y -c conda-forge ffmpeg',
1014
+ 'python main.py',
1015
+ ])
1016
+ assert.deepEqual(records.map((record) => !!record.prepared.quietCmd), [
1017
+ false,
1018
+ true,
1019
+ false,
1020
+ ])
1021
+ } finally {
1022
+ Shell.prototype.prompt = originalPrompt
1023
+ Shell.prototype.exec = originalExec
1024
+ }
1025
+ })
1026
+
952
1027
  test('rewrite warning is scoped to each user-visible shell flow', async () => {
953
1028
  CondaRuntimeGuard.resetNoticeSessionsForTest()
954
1029
  const { context, events, root } = createContext()