pinokiod 7.2.7 → 7.2.8

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.
@@ -11,6 +11,7 @@ const fastq = require('fastq')
11
11
  const Loader = require("../loader")
12
12
  const Environment = require("../environment")
13
13
  const Util = require('../util')
14
+ const ShellRunTemplate = require('./shell_run_template')
14
15
 
15
16
  class Api {
16
17
  constructor(kernel) {
@@ -1010,6 +1011,7 @@ class Api {
1010
1011
  }
1011
1012
  // replace {{{ }}} with {{ }}
1012
1013
  rpc = this.kernel.template.flatten(rpc)
1014
+ rpc = ShellRunTemplate.renderEnvArgs(this.kernel, rpc, memory)
1013
1015
 
1014
1016
  // 6. rpc must have method names
1015
1017
  if (rpc.method) {
@@ -0,0 +1,273 @@
1
+ function isCmdShellName(shellName) {
2
+ const name = (shellName || '').toLowerCase()
3
+ return name.includes('cmd.exe') || name === 'cmd'
4
+ }
5
+
6
+ function isPowerShellName(shellName) {
7
+ const name = (shellName || '').toLowerCase()
8
+ return name.includes('powershell') || name.includes('pwsh')
9
+ }
10
+
11
+ const ENV_ARG_MARKER_RE = /__PINOKIO_ENVARG_(\d+)__/g
12
+
13
+ function envArgMarker(index) {
14
+ return `__PINOKIO_ENVARG_${index}__`
15
+ }
16
+
17
+ function isPinokioEnvArgKey(key) {
18
+ return /^PINOKIO_ARG_\d+$/.test(key || "")
19
+ }
20
+
21
+ function hasEnvArgMarker(value) {
22
+ ENV_ARG_MARKER_RE.lastIndex = 0
23
+ return ENV_ARG_MARKER_RE.test(String(value))
24
+ }
25
+
26
+ function quotePosixLiteral(value) {
27
+ const input = value == null ? "" : String(value)
28
+ return `'${input.split("'").join("'\"'\"'")}'`
29
+ }
30
+
31
+ function quotePowerShellComposite(value) {
32
+ const input = value == null ? "" : String(value)
33
+ ENV_ARG_MARKER_RE.lastIndex = 0
34
+ let output = '"'
35
+ let lastIndex = 0
36
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
37
+ const literal = input.slice(lastIndex, match.index)
38
+ output += literal.replace(/[`"$]/g, (char) => "`" + char)
39
+ output += "${env:PINOKIO_ARG_" + match[1] + "}"
40
+ lastIndex = match.index + match[0].length
41
+ }
42
+ output += input.slice(lastIndex).replace(/[`"$]/g, (char) => "`" + char)
43
+ output += '"'
44
+ return output
45
+ }
46
+
47
+ function quoteCmdComposite(value) {
48
+ const input = value == null ? "" : String(value)
49
+ ENV_ARG_MARKER_RE.lastIndex = 0
50
+ let output = '"'
51
+ let lastIndex = 0
52
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
53
+ const literal = input.slice(lastIndex, match.index)
54
+ output += literal.replace(/([()%!^"<>&|])/g, '^$1')
55
+ output += "!PINOKIO_ARG_" + match[1] + "!"
56
+ lastIndex = match.index + match[0].length
57
+ }
58
+ output += input.slice(lastIndex).replace(/([()%!^"<>&|])/g, '^$1')
59
+ output += '"'
60
+ return output
61
+ }
62
+
63
+ function quoteEnvArgComposite(value, shellName) {
64
+ const input = value == null ? "" : String(value)
65
+ if (isCmdShellName(shellName)) {
66
+ return quoteCmdComposite(input)
67
+ }
68
+ if (isPowerShellName(shellName)) {
69
+ return quotePowerShellComposite(input)
70
+ }
71
+
72
+ ENV_ARG_MARKER_RE.lastIndex = 0
73
+ const parts = []
74
+ let lastIndex = 0
75
+ for (const match of input.matchAll(ENV_ARG_MARKER_RE)) {
76
+ const literal = input.slice(lastIndex, match.index)
77
+ if (literal) {
78
+ parts.push(quotePosixLiteral(literal))
79
+ }
80
+ parts.push('"$PINOKIO_ARG_' + match[1] + '"')
81
+ lastIndex = match.index + match[0].length
82
+ }
83
+ const tail = input.slice(lastIndex)
84
+ if (tail) {
85
+ parts.push(quotePosixLiteral(tail))
86
+ }
87
+ return parts.length > 0 ? parts.join("") : "''"
88
+ }
89
+
90
+ function shellNameFor(kernel, params) {
91
+ let shellName = kernel && kernel.platform === "win32" ? "cmd.exe" : "bash"
92
+ if (params && typeof params.shell === "string" && params.shell.trim()) {
93
+ shellName = params.shell
94
+ }
95
+ return shellName
96
+ }
97
+
98
+ function isPlainObject(value) {
99
+ return value && value.constructor === Object
100
+ }
101
+
102
+ function hasMultiline(value) {
103
+ return typeof value === "string" && /[\r\n]/.test(value)
104
+ }
105
+
106
+ function isStructuredArgvMessage(value) {
107
+ return isPlainObject(value) && Array.isArray(value._)
108
+ }
109
+
110
+ function hasStructuredArgvMessage(value) {
111
+ if (isStructuredArgvMessage(value)) {
112
+ return true
113
+ }
114
+ if (Array.isArray(value)) {
115
+ return value.some((item) => isStructuredArgvMessage(item))
116
+ }
117
+ return false
118
+ }
119
+
120
+ function protectStructuredString(value, state) {
121
+ if (!hasMultiline(value)) {
122
+ return value
123
+ }
124
+ const name = `PINOKIO_ARG_${state.args.length}`
125
+ state.args.push({
126
+ name,
127
+ value: value == null ? "" : String(value)
128
+ })
129
+ return envArgMarker(state.args.length - 1)
130
+ }
131
+
132
+ function protectStructuredValue(value, state) {
133
+ if (typeof value === "string") {
134
+ return protectStructuredString(value, state)
135
+ }
136
+ if (Array.isArray(value)) {
137
+ return value.map((item) => protectStructuredValue(item, state))
138
+ }
139
+ if (isPlainObject(value)) {
140
+ const rendered = {}
141
+ for (const [key, item] of Object.entries(value)) {
142
+ rendered[key] = protectStructuredValue(item, state)
143
+ }
144
+ return rendered
145
+ }
146
+ return value
147
+ }
148
+
149
+ function protectStructuredMessage(value, state) {
150
+ if (isStructuredArgvMessage(value)) {
151
+ return protectStructuredValue(value, state)
152
+ }
153
+ if (Array.isArray(value)) {
154
+ return value.map((item) => isStructuredArgvMessage(item) ? protectStructuredValue(item, state) : item)
155
+ }
156
+ return value
157
+ }
158
+
159
+ function renderEnvArgs(kernel, rpc, memory) {
160
+ if (!rpc || rpc.method !== "shell.run" || !rpc.params || !hasStructuredArgvMessage(rpc.params.message)) {
161
+ return rpc
162
+ }
163
+
164
+ const shellName = shellNameFor(kernel, rpc.params)
165
+ const state = { args: [] }
166
+ const message = protectStructuredMessage(rpc.params.message, state)
167
+
168
+ if (state.args.length === 0) {
169
+ return rpc
170
+ }
171
+
172
+ const env = Object.assign({}, rpc.params.env || {})
173
+ for (const arg of state.args) {
174
+ env[arg.name] = arg.value
175
+ }
176
+
177
+ return {
178
+ ...rpc,
179
+ params: {
180
+ ...rpc.params,
181
+ message,
182
+ env,
183
+ _pinokio_env_args: state.args,
184
+ _pinokio_cmd_delayed_expansion: isCmdShellName(shellName)
185
+ }
186
+ }
187
+ }
188
+
189
+ function envArgDetails(value) {
190
+ const normalized = String(value == null ? "" : value)
191
+ .replace(/\r\n/g, "\n")
192
+ .replace(/\r/g, "\n")
193
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "?")
194
+ const lines = normalized.split("\n")
195
+ const previewLines = []
196
+ const maxLines = 8
197
+ const maxChars = 800
198
+ let used = 0
199
+
200
+ for (const line of lines.slice(0, maxLines)) {
201
+ const remaining = maxChars - used
202
+ if (remaining <= 0) {
203
+ break
204
+ }
205
+ if (line.length > remaining) {
206
+ previewLines.push(line.slice(0, remaining) + "...")
207
+ used = maxChars
208
+ break
209
+ }
210
+ previewLines.push(line)
211
+ used += line.length + 1
212
+ }
213
+
214
+ const preview = previewLines.join("\n")
215
+ const truncated = lines.length > previewLines.length || normalized.length > preview.length
216
+ if (truncated && previewLines[previewLines.length - 1] !== "...") {
217
+ previewLines.push("...")
218
+ }
219
+
220
+ return {
221
+ lineCount: normalized.length === 0 ? 0 : lines.length,
222
+ previewLines,
223
+ truncated
224
+ }
225
+ }
226
+
227
+ function formatEnvArgsPreview(args) {
228
+ if (!Array.isArray(args) || args.length === 0) {
229
+ return ""
230
+ }
231
+ const lines = ["\r\nPinokio shell args", ""]
232
+ for (const arg of args) {
233
+ const details = envArgDetails(arg.value)
234
+ lines.push(`${arg.name} ${details.lineCount} lines`)
235
+ for (const previewLine of details.previewLines) {
236
+ lines.push(` ${previewLine}`)
237
+ }
238
+ lines.push("")
239
+ }
240
+ return lines.join("\r\n") + "\r\n"
241
+ }
242
+
243
+ function envArgSummary(value) {
244
+ const details = envArgDetails(value)
245
+ return {
246
+ type: "pinokio env arg",
247
+ lines: details.lineCount,
248
+ preview: details.previewLines.join("\n"),
249
+ truncated: details.truncated
250
+ }
251
+ }
252
+
253
+ function redactEnvArgs(env) {
254
+ if (!env || typeof env !== "object") {
255
+ return env
256
+ }
257
+ const redacted = { ...env }
258
+ for (const key of Object.keys(redacted)) {
259
+ if (isPinokioEnvArgKey(key)) {
260
+ redacted[key] = envArgSummary(redacted[key])
261
+ }
262
+ }
263
+ return redacted
264
+ }
265
+
266
+ module.exports = {
267
+ renderEnvArgs,
268
+ quoteEnvArgComposite,
269
+ hasEnvArgMarker,
270
+ isPinokioEnvArgKey,
271
+ formatEnvArgsPreview,
272
+ redactEnvArgs
273
+ }
package/kernel/shell.js CHANGED
@@ -20,6 +20,7 @@ const { applyWindowsNodePackageManagerEnv } = require('./windows_node_package_ma
20
20
  const ShellParser = require('./shell_parser')
21
21
  const AnsiStreamTracker = require('./ansi_stream_tracker')
22
22
  const ShellStateSync = require('./shell_state_sync')
23
+ const ShellRunTemplate = require('./api/shell_run_template')
23
24
  const home = os.homedir()
24
25
 
25
26
  function normalizeComparablePath(filePath, platform) {
@@ -308,7 +309,7 @@ class Shell {
308
309
  delete this.env[key]
309
310
  }
310
311
  let val = this.env[key]
311
- if (/[\r\n]/.test(val)) {
312
+ if (!ShellRunTemplate.isPinokioEnvArgKey(key) && /[\r\n]/.test(val)) {
312
313
  const replaced = val.replaceAll(/[\r\n]+/g, ' ');
313
314
  this.env[key] = replaced
314
315
  // delete this.env[key]
@@ -362,6 +363,9 @@ class Shell {
362
363
  }
363
364
  quoteArgForShell(value, shellName=this.shell) {
364
365
  const input = value == null ? "" : String(value)
366
+ if (ShellRunTemplate.hasEnvArgMarker(input)) {
367
+ return ShellRunTemplate.quoteEnvArgComposite(input, shellName)
368
+ }
365
369
  if (this.isCmdShell(shellName)) {
366
370
  return `"${input.replace(/([()%!^"<>&|])/g, '^$1')}"`
367
371
  }
@@ -409,6 +413,7 @@ class Shell {
409
413
  this.userActive = false
410
414
  this.decsyncBuffer = ''
411
415
  this.stateSync.reset()
416
+ this.envArgsPreviewed = false
412
417
 
413
418
  /*
414
419
  params := {
@@ -540,6 +545,9 @@ class Shell {
540
545
  //}
541
546
  }
542
547
  }
548
+ if (params._pinokio_cmd_delayed_expansion && this.isCmdShell(this.shell) && !this.args.includes("/V:ON")) {
549
+ this.args.push("/V:ON")
550
+ }
543
551
 
544
552
  // 3. path => path can be http, relative, absolute
545
553
  this.path = params.path
@@ -757,6 +765,16 @@ class Shell {
757
765
  }
758
766
  this.stateSync.invalidate({ clearTail: true })
759
767
  }
768
+ emitEnvArgsPreview(params) {
769
+ if (!params || !Array.isArray(params._pinokio_env_args) || params._pinokio_env_args.length === 0 || this.envArgsPreviewed) {
770
+ return
771
+ }
772
+ this.envArgsPreviewed = true
773
+ const raw = ShellRunTemplate.formatEnvArgsPreview(params._pinokio_env_args)
774
+ if (raw && this.ondata) {
775
+ this.ondata({ raw })
776
+ }
777
+ }
760
778
  async run(params, cb) {
761
779
  let r = await this.request(params, cb)
762
780
  return r
@@ -1362,6 +1380,7 @@ class Shell {
1362
1380
  }
1363
1381
  async exec(params) {
1364
1382
  this.parser = new ShellParser()
1383
+ this.emitEnvArgsPreview(params)
1365
1384
  params = await this.activate(params)
1366
1385
  this.cmd = this.build(params)
1367
1386
  let res = await new Promise((resolve, reject) => {
@@ -1729,7 +1748,7 @@ class Shell {
1729
1748
  cmd: this.cmd,
1730
1749
  index: this.index,
1731
1750
  group: this.group,
1732
- env: this.env,
1751
+ env: ShellRunTemplate.redactEnvArgs(this.env),
1733
1752
  done: this.done,
1734
1753
  ready: this.ready,
1735
1754
  id: this.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.2.7",
3
+ "version": "7.2.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -28,6 +28,8 @@ const system = require('systeminformation')
28
28
  const serveIndex = require('./serveIndex')
29
29
  const registerFileRoutes = require('./routes/files')
30
30
  const registerAppRoutes = require('./routes/apps')
31
+ const registerWorkspacesRoutes = require('./routes/workspaces')
32
+ const registerDraftImportRoutes = require('./routes/draft_import')
31
33
  const Git = require("../kernel/git")
32
34
  const TerminalApi = require('../kernel/api/terminal')
33
35
 
@@ -78,6 +80,9 @@ const { createDesktopEventRouter } = require("./lib/desktop_event_router")
78
80
  const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
79
81
  const { createTaskPackageService } = require("./lib/task_packages")
80
82
  const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
83
+ const { createDraftService } = require("./lib/drafts")
84
+ const { createWorkspaceRuntimeService } = require("./lib/workspace_runtime")
85
+ const { createWorkspaceCatalogService } = require("./lib/workspace_catalog")
81
86
  const { createContentValidationService } = require("./lib/content_validation")
82
87
  const { buildSecureRouterDebugSnapshot, createSecureRouterDebugStore } = require("./lib/secure_router_debug")
83
88
  const AppRegistryService = require("./lib/app_registry")
@@ -8403,6 +8408,35 @@ class Server {
8403
8408
  const taskWorkspaceLinks = createTaskWorkspaceLinkService({
8404
8409
  kernel: this.kernel
8405
8410
  })
8411
+ const workspaceRuntime = createWorkspaceRuntimeService({
8412
+ kernel: this.kernel
8413
+ })
8414
+ this.workspaceRuntime = workspaceRuntime
8415
+ const drafts = createDraftService({
8416
+ kernel: this.kernel,
8417
+ taskWorkspaceLinks
8418
+ })
8419
+ this.drafts = drafts
8420
+ const workspaceCatalog = createWorkspaceCatalogService({
8421
+ kernel: this.kernel,
8422
+ workspaceRuntime,
8423
+ drafts
8424
+ })
8425
+ drafts.start().catch((error) => {
8426
+ console.warn("[drafts] failed to start", error && error.message ? error.message : error)
8427
+ })
8428
+ registerDraftImportRoutes(this.app, {
8429
+ drafts,
8430
+ defaultRegistryUrl: DEFAULT_REGISTRY_URL
8431
+ })
8432
+ registerWorkspacesRoutes(this.app, {
8433
+ workspaceCatalog,
8434
+ composePeerAccessPayload: () => this.composePeerAccessPayload(),
8435
+ getTheme: () => this.theme,
8436
+ getPeers: () => this.getPeers(),
8437
+ getCurrentHost: () => this.kernel.peer.host,
8438
+ getPortal: () => this.portal
8439
+ })
8406
8440
  const TASK_INPUT_NAME_PATTERN = /^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/
8407
8441
  const suggestTaskFolderName = async (rootDir, preferredName) => {
8408
8442
  const normalizedRoot = path.resolve(rootDir)
@@ -9836,6 +9870,9 @@ class Server {
9836
9870
  }
9837
9871
  try {
9838
9872
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
9873
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
9874
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
9875
+ })
9839
9876
  } catch (error) {
9840
9877
  await taskPackages.deleteTaskPackage(task.id).catch(() => {})
9841
9878
  throw error
@@ -10179,6 +10216,9 @@ class Server {
10179
10216
  }
10180
10217
  try {
10181
10218
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
10219
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
10220
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
10221
+ })
10182
10222
  } catch (error) {
10183
10223
  await renderTaskLaunchPage(req, res, task, {
10184
10224
  selectedTool,
@@ -10198,6 +10238,20 @@ class Server {
10198
10238
  })
10199
10239
  this.app.post("/task/start", handleTaskStartRequest)
10200
10240
 
10241
+ this.app.get("/drafts", ex(async (req, res) => {
10242
+ const cwd = typeof req.query.cwd === "string" ? req.query.cwd : ""
10243
+ const items = await drafts.listPending({ cwd })
10244
+ res.json({
10245
+ ok: true,
10246
+ items
10247
+ })
10248
+ }))
10249
+
10250
+ this.app.post("/drafts/:id/dismiss", ex(async (req, res) => {
10251
+ const ok = await drafts.dismiss(req.params.id)
10252
+ res.json({ ok })
10253
+ }))
10254
+
10201
10255
  this.app.get("/home", renderHomePage)
10202
10256
 
10203
10257
  const normalizePathForComparison = (value) => {
@@ -10594,6 +10648,9 @@ class Server {
10594
10648
  }
10595
10649
 
10596
10650
  await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
10651
+ drafts.trackWorkspace({ taskId: task.id, ref: workspaceRef }).catch((error) => {
10652
+ console.warn("[drafts] failed to track workspace", error && error.message ? error.message : error)
10653
+ })
10597
10654
 
10598
10655
  const params = new URLSearchParams()
10599
10656
  params.set("cwd", targetPath)
@@ -11851,11 +11908,14 @@ class Server {
11851
11908
  // but allow it when the request originates from the local machine
11852
11909
  if (payload.audience === 'device' && typeof payload.device_id === 'string' && payload.device_id) {
11853
11910
  try {
11854
- if (this.socket && typeof this.socket.isLocalDevice === 'function') {
11855
- payload.host = !!this.socket.isLocalDevice(payload.device_id)
11856
- } else {
11857
- payload.host = false
11858
- }
11911
+ const remoteAddress = req.socket && req.socket.remoteAddress ? req.socket.remoteAddress : req.ip
11912
+ const isLocalRequest = this.socket && typeof this.socket.isLocalAddress === 'function'
11913
+ ? this.socket.isLocalAddress(remoteAddress)
11914
+ : false
11915
+ const isLocalDevice = this.socket && typeof this.socket.isLocalDevice === 'function'
11916
+ ? this.socket.isLocalDevice(payload.device_id)
11917
+ : false
11918
+ payload.host = !!(isLocalRequest || isLocalDevice)
11859
11919
  } catch (_) {
11860
11920
  payload.host = false
11861
11921
  }