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 +8 -5
- package/package.json +1 -1
- package/server/views/index.ejs +31 -2
- package/server/views/partials/home_action_modal.ejs +18 -0
- package/server/views/partials/home_run_menu.ejs +7 -4
- package/test/launch-settings-ui.test.js +66 -0
- package/test/shell-conda-runtime-guard.test.js +77 -1
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
|
|
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
|
-
|
|
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
package/server/views/index.ejs
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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,
|
|
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()
|