pinokiod 7.5.37 → 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/kernel/shell.js CHANGED
@@ -351,16 +351,14 @@ class Shell {
351
351
  !(params && params.input) &&
352
352
  this.hasCondaCommand(params && params.message)
353
353
  }
354
- buildCmdEchoSuppressedCommand(command) {
355
- return `@echo off\r\n${command}\r\n@echo on`
356
- }
357
354
  prepareCommandExecution(params, command) {
358
355
  if (!this.shouldSuppressCmdEchoForConda(params)) {
359
356
  return { command }
360
357
  }
361
358
  return {
362
- command: this.buildCmdEchoSuppressedCommand(command),
359
+ command,
363
360
  preview: command,
361
+ quietCmd: true,
364
362
  }
365
363
  }
366
364
  isUnresolvedTemplate(value) {
@@ -444,6 +442,7 @@ class Shell {
444
442
  this.commandEchoPreview = null
445
443
  this.commandEchoPreviewed = false
446
444
  this.exec_cmd = null
445
+ this.quietCmdExecution = false
447
446
 
448
447
  /*
449
448
  params := {
@@ -1440,6 +1439,7 @@ class Shell {
1440
1439
  this.exec_cmd = preparedCommand.command
1441
1440
  this.commandEchoPreview = preparedCommand.preview || null
1442
1441
  this.commandEchoPreviewed = false
1442
+ this.quietCmdExecution = !!preparedCommand.quietCmd
1443
1443
  let res = await new Promise((resolve, reject) => {
1444
1444
  this.resolve = resolve
1445
1445
  this.reject = reject
@@ -1459,7 +1459,10 @@ class Shell {
1459
1459
  if (!this.ptyProcess) {
1460
1460
  // ptyProcess doesn't exist => create
1461
1461
  this.done = false
1462
- this.ptyProcess = pty.spawn(this.shell, this.args, config)
1462
+ const shellArgs = this.quietCmdExecution && this.isCmdShell()
1463
+ ? this.args.concat(this.args.some((arg) => /^\/q$/i.test(arg)) ? [] : ["/Q"])
1464
+ : this.args
1465
+ this.ptyProcess = pty.spawn(this.shell, shellArgs, config)
1463
1466
  this.ptyProcess.onData((data) => {
1464
1467
  if (!this.monitor) {
1465
1468
  this.monitor = ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.5.37",
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
 
@@ -941,13 +941,89 @@ test('Windows cmd suppresses echo only for explicit conda shell.run commands', (
941
941
  message: 'conda install -y -c conda-forge ffmpeg',
942
942
  }, command)
943
943
  assert.equal(prepared.preview, command)
944
- assert.equal(prepared.command, `@echo off\r\n${command}\r\n@echo on`)
944
+ assert.equal(prepared.command, command)
945
+ assert.equal(prepared.quietCmd, true)
945
946
 
946
947
  assert.deepEqual(shell.prepareCommandExecution({
947
948
  message: 'python main.py',
948
949
  }, command), { command })
949
950
  })
950
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
+
951
1027
  test('rewrite warning is scoped to each user-visible shell flow', async () => {
952
1028
  CondaRuntimeGuard.resetNoticeSessionsForTest()
953
1029
  const { context, events, root } = createContext()