pinokiod 7.1.73 → 7.1.75
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/api/script/index.js +10 -8
- package/kernel/api/shell/index.js +32 -7
- package/kernel/bin/bluefairy.js +72 -11
- package/kernel/shell.js +55 -0
- package/kernel/shells.js +18 -6
- package/package.json +1 -1
- package/server/index.js +4 -4
- package/server/public/task-launcher.css +249 -6
- package/server/public/universal-launcher.js +23 -28
- package/server/socket.js +5 -1
- package/server/views/app.ejs +217 -3
- package/server/views/checkpoints.ejs +1 -1
- package/server/views/d.ejs +1 -1
- package/server/views/env_editor.ejs +11 -1
- package/server/views/index.ejs +28 -1
- package/server/views/launch.ejs +1 -1
- package/server/views/net.ejs +1 -1
- package/server/views/network.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/screenshots.ejs +1 -1
- package/server/views/setup.ejs +376 -402
- package/server/views/shell.ejs +1 -1
- package/server/views/task_list.ejs +1 -1
- package/server/views/terminal.ejs +6 -6
- package/test-results/after-conda-launch.png +0 -0
- package/test-results/dev-page.png +0 -0
|
@@ -13,13 +13,13 @@ class Script {
|
|
|
13
13
|
return null
|
|
14
14
|
}
|
|
15
15
|
currentSessionId(req) {
|
|
16
|
-
if (req && typeof req.id === "string" && req.id.trim()) {
|
|
17
|
-
return req.id
|
|
18
|
-
}
|
|
19
16
|
const parent = this.currentParent(req)
|
|
20
17
|
if (parent && typeof parent.id === "string" && parent.id.trim()) {
|
|
21
18
|
return parent.id
|
|
22
19
|
}
|
|
20
|
+
if (req && typeof req.id === "string" && req.id.trim()) {
|
|
21
|
+
return req.id
|
|
22
|
+
}
|
|
23
23
|
return undefined
|
|
24
24
|
}
|
|
25
25
|
currentCaller(req) {
|
|
@@ -192,14 +192,16 @@ class Script {
|
|
|
192
192
|
input: req.params.params,
|
|
193
193
|
client: this.currentClient(req),
|
|
194
194
|
}
|
|
195
|
-
if (req.id) {
|
|
196
|
-
request.id = req.id
|
|
197
|
-
}
|
|
198
195
|
const caller = this.currentCaller(req)
|
|
199
196
|
if (caller) {
|
|
200
197
|
request.caller = caller
|
|
201
|
-
} else
|
|
202
|
-
|
|
198
|
+
} else {
|
|
199
|
+
const parent = this.currentParent(req)
|
|
200
|
+
if (parent && typeof parent.id === "string" && parent.id.trim()) {
|
|
201
|
+
request.caller = parent.id
|
|
202
|
+
} else if (req.parent && req.parent.path) {
|
|
203
|
+
request.caller = req.parent.path
|
|
204
|
+
}
|
|
203
205
|
}
|
|
204
206
|
const origin = this.currentOrigin(req)
|
|
205
207
|
if (origin) {
|
|
@@ -2,15 +2,35 @@ const path = require('path')
|
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
const os = require('os')
|
|
4
4
|
class Shell {
|
|
5
|
-
applyBluefairyDefault(req = {}) {
|
|
6
|
-
if (!req.params
|
|
5
|
+
async applyBluefairyDefault(req = {}, kernel) {
|
|
6
|
+
if (!req.params) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
if (Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const preferences = kernel && kernel.appPreferences
|
|
13
|
+
if (
|
|
14
|
+
!preferences
|
|
15
|
+
|| !kernel
|
|
16
|
+
|| !kernel.api
|
|
17
|
+
|| typeof kernel.api.resolvePath !== "function"
|
|
18
|
+
|| typeof preferences.resolveAppIdFromPath !== "function"
|
|
19
|
+
|| typeof preferences.getPreference !== "function"
|
|
20
|
+
) {
|
|
21
|
+
req.params.bluefairy = "off"
|
|
7
22
|
return
|
|
8
23
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
const cwd = req.cwd || kernel.homedir
|
|
25
|
+
const requestedPath = req.params.path || "."
|
|
26
|
+
const resolvedPath = kernel.api.resolvePath(cwd, requestedPath)
|
|
27
|
+
const appId = preferences.resolveAppIdFromPath(resolvedPath)
|
|
28
|
+
if (!appId) {
|
|
12
29
|
req.params.bluefairy = "off"
|
|
30
|
+
return
|
|
13
31
|
}
|
|
32
|
+
const preference = await preferences.getPreference(appId)
|
|
33
|
+
req.params.bluefairy = preference && preference.protection_enabled === true ? "on" : "off"
|
|
14
34
|
}
|
|
15
35
|
async start(req, ondata, kernel) {
|
|
16
36
|
/*
|
|
@@ -50,7 +70,9 @@ class Shell {
|
|
|
50
70
|
if (req.params) {
|
|
51
71
|
req.params.$parent = req.parent
|
|
52
72
|
}
|
|
53
|
-
|
|
73
|
+
if (!Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
|
|
74
|
+
req.params.bluefairy = "off"
|
|
75
|
+
}
|
|
54
76
|
|
|
55
77
|
// // create a persistent session
|
|
56
78
|
// req.params.persistent = true
|
|
@@ -136,10 +158,13 @@ class Shell {
|
|
|
136
158
|
if (!req.params) {
|
|
137
159
|
req.params = {}
|
|
138
160
|
}
|
|
161
|
+
if (!req.params.path) {
|
|
162
|
+
req.params.path = req.cwd
|
|
163
|
+
}
|
|
139
164
|
if (req.params) {
|
|
140
165
|
req.params.$parent = req.parent
|
|
141
166
|
}
|
|
142
|
-
this.applyBluefairyDefault(req)
|
|
167
|
+
await this.applyBluefairyDefault(req, kernel)
|
|
143
168
|
let options = {}
|
|
144
169
|
if (req.cwd) options.cwd = req.cwd
|
|
145
170
|
if (req.parent && req.parent.id) {
|
package/kernel/bin/bluefairy.js
CHANGED
|
@@ -4,7 +4,7 @@ const semver = require('semver')
|
|
|
4
4
|
|
|
5
5
|
class Bluefairy {
|
|
6
6
|
description = "Installs Bluefairy, a standalone package freshness guard."
|
|
7
|
-
version = ">=0.0.
|
|
7
|
+
version = ">=0.0.31"
|
|
8
8
|
|
|
9
9
|
packageName() {
|
|
10
10
|
return "bluefairy"
|
|
@@ -37,6 +37,10 @@ class Bluefairy {
|
|
|
37
37
|
return this.kernel.bin.path("bluefairy")
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
packageInstallPath() {
|
|
41
|
+
return path.resolve(this.moduleRoot(), this.packageName())
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
env() {
|
|
41
45
|
return {
|
|
42
46
|
BLUEFAIRY_HOME: this.runtimeHome()
|
|
@@ -63,6 +67,20 @@ class Bluefairy {
|
|
|
63
67
|
]
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
binLauncherArtifacts() {
|
|
71
|
+
const binDir = this.npmBinDir()
|
|
72
|
+
if (this.kernel.platform === "win32") {
|
|
73
|
+
return [
|
|
74
|
+
path.resolve(binDir, "bluefairy"),
|
|
75
|
+
path.resolve(binDir, "bluefairy.cmd"),
|
|
76
|
+
path.resolve(binDir, "bluefairy.ps1"),
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
path.resolve(binDir, "bluefairy"),
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
runtimeArtifacts() {
|
|
67
85
|
const runtimeHome = this.runtimeHome()
|
|
68
86
|
if (this.kernel.platform === "win32") {
|
|
@@ -97,18 +115,61 @@ class Bluefairy {
|
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
|
|
100
|
-
async
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
async removePath(target, ondata, label) {
|
|
119
|
+
if (!fs.existsSync(target)) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
await fs.promises.rm(target, { recursive: true, force: true })
|
|
124
|
+
ondata({ raw: `Removed ${label}: ${target}\r\n` })
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const message = error && error.message ? error.message : String(error)
|
|
127
|
+
ondata({ raw: `Warning: failed to remove ${label} ${target}: ${message}\r\n` })
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async removeRetiredEntries(parentDir, ondata) {
|
|
132
|
+
let entries
|
|
133
|
+
try {
|
|
134
|
+
entries = await fs.promises.readdir(parentDir)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = error && error.message ? error.message : String(error)
|
|
137
|
+
ondata({ raw: `Warning: failed to list ${parentDir}: ${message}\r\n` })
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const retiredPrefix = `.${this.packageName()}-`
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.startsWith(retiredPrefix)) {
|
|
144
|
+
continue
|
|
110
145
|
}
|
|
146
|
+
await this.removePath(
|
|
147
|
+
path.resolve(parentDir, entry),
|
|
148
|
+
ondata,
|
|
149
|
+
"stale Bluefairy install temp"
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async cleanupInstallState(ondata) {
|
|
155
|
+
const cleanupTargets = new Set([
|
|
156
|
+
this.runtimeHome(),
|
|
157
|
+
this.packageInstallPath(),
|
|
158
|
+
...this.installedArtifacts(),
|
|
159
|
+
...this.binLauncherArtifacts(),
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
for (const target of cleanupTargets) {
|
|
163
|
+
await this.removePath(target, ondata, "existing Bluefairy install state")
|
|
111
164
|
}
|
|
165
|
+
|
|
166
|
+
await this.removeRetiredEntries(this.moduleRoot(), ondata)
|
|
167
|
+
await this.removeRetiredEntries(this.npmBinDir(), ondata)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async install(req, ondata) {
|
|
171
|
+
const spec = this.packageSpec().replaceAll('"', '\\"')
|
|
172
|
+
await this.cleanupInstallState(ondata)
|
|
112
173
|
await this.kernel.exec({
|
|
113
174
|
env: this.env(),
|
|
114
175
|
message: `npm install -g "${spec}" --force`,
|
package/kernel/shell.js
CHANGED
|
@@ -21,6 +21,51 @@ const AnsiStreamTracker = require('./ansi_stream_tracker')
|
|
|
21
21
|
const ShellStateSync = require('./shell_state_sync')
|
|
22
22
|
const home = os.homedir()
|
|
23
23
|
|
|
24
|
+
function normalizeComparablePath(filePath, platform) {
|
|
25
|
+
const normalized = path.normalize(filePath)
|
|
26
|
+
return platform === 'win32' ? normalized.toLowerCase() : normalized
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isBluefairyShimPath(filePath, platform) {
|
|
30
|
+
if (!filePath || typeof filePath !== "string") {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
const normalized = normalizeComparablePath(filePath.trim(), platform)
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
const shimDirName = path.basename(normalized)
|
|
38
|
+
if (shimDirName !== "shims") {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
const parentDirName = path.basename(path.dirname(normalized))
|
|
42
|
+
return parentDirName === "bluefairy" || parentDirName === ".bluefairy"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripBluefairyShimPaths(pathValue, platform) {
|
|
46
|
+
if (!pathValue || typeof pathValue !== "string") {
|
|
47
|
+
return pathValue
|
|
48
|
+
}
|
|
49
|
+
return pathValue
|
|
50
|
+
.split(path.delimiter)
|
|
51
|
+
.filter((entry) => entry && !isBluefairyShimPath(entry, platform))
|
|
52
|
+
.join(path.delimiter)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const CONDA_ACTIVATION_STATE_PATTERN = /^(?:_CE_(?:M|CONDA)|CONDA_(?:EXE|PYTHON_EXE|PREFIX(?:_\d+)?|DEFAULT_ENV|PROMPT_MODIFIER|SHLVL|PS1_BACKUP))$/
|
|
56
|
+
|
|
57
|
+
function isCondaActivationStateKey(key) {
|
|
58
|
+
return typeof key === "string" && CONDA_ACTIVATION_STATE_PATTERN.test(key)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripInheritedCondaActivationState(env) {
|
|
62
|
+
for (const key of Object.keys(env)) {
|
|
63
|
+
if (isCondaActivationStateKey(key)) {
|
|
64
|
+
delete env[key]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
// xterm.js currently ignores DECSYNCTERM (CSI ? 2026 h/l) and renders it as text on Windows.
|
|
25
70
|
// filterDecsync() removes these sequences so they do not pollute the terminal output.
|
|
26
71
|
class Shell {
|
|
@@ -91,6 +136,12 @@ class Shell {
|
|
|
91
136
|
}
|
|
92
137
|
async init_env(params) {
|
|
93
138
|
this.env = Object.assign({}, process.env)
|
|
139
|
+
for (const key of Object.keys(this.env)) {
|
|
140
|
+
if (key.toUpperCase().startsWith("BLUEFAIRY_")) {
|
|
141
|
+
delete this.env[key]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
stripInheritedCondaActivationState(this.env)
|
|
94
145
|
// If the user has set PYTHONPATH, unset it.
|
|
95
146
|
if (this.env.PYTHONPATH) {
|
|
96
147
|
delete this.env.PYTHONPATH
|
|
@@ -166,6 +217,7 @@ class Shell {
|
|
|
166
217
|
let app_env = await Environment.get(api_path, this.kernel)
|
|
167
218
|
this.env = Object.assign(this.env, app_env)
|
|
168
219
|
}
|
|
220
|
+
stripInheritedCondaActivationState(this.env)
|
|
169
221
|
let PATH_KEY = Object.keys(this.env).find((key) => key.toLowerCase() === "path") || "PATH";
|
|
170
222
|
if (!this.env[PATH_KEY]) {
|
|
171
223
|
// fall back to whichever casing exists so we don't end up writing to an undefined key
|
|
@@ -232,6 +284,9 @@ class Shell {
|
|
|
232
284
|
}
|
|
233
285
|
}
|
|
234
286
|
|
|
287
|
+
stripInheritedCondaActivationState(this.env)
|
|
288
|
+
this.env[PATH_KEY] = stripBluefairyShimPaths(this.env[PATH_KEY], this.platform)
|
|
289
|
+
|
|
235
290
|
for(let key in this.env) {
|
|
236
291
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) && key !== "ProgramFiles(x86)") {
|
|
237
292
|
delete this.env[key]
|
package/kernel/shells.js
CHANGED
|
@@ -301,6 +301,7 @@ class Shells {
|
|
|
301
301
|
let liveEventBuffer = ""
|
|
302
302
|
let liveEventOffset = 0
|
|
303
303
|
let liveEventAnsiCarry = ""
|
|
304
|
+
let liveEventHandlersClosed = false
|
|
304
305
|
|
|
305
306
|
// Keep cross-chunk event matching, but normalize terminal styling away first.
|
|
306
307
|
// Preserve line boundaries so later shell banners cannot fuse onto prior matches.
|
|
@@ -428,7 +429,7 @@ class Shells {
|
|
|
428
429
|
liveEventOffset += normalizedChunk.length
|
|
429
430
|
}
|
|
430
431
|
const liveEventBufferStart = Math.max(0, liveEventOffset - liveEventBuffer.length)
|
|
431
|
-
if (params.on && Array.isArray(params.on)) {
|
|
432
|
+
if (!liveEventHandlersClosed && params.on && Array.isArray(params.on)) {
|
|
432
433
|
for(let i=0; i<params.on.length; i++) {
|
|
433
434
|
let handler = params.on[i]
|
|
434
435
|
if (handler.once && onceHandlers.has(i)) {
|
|
@@ -476,11 +477,18 @@ class Shells {
|
|
|
476
477
|
}
|
|
477
478
|
const lastMatch = rendered_event[rendered_event.length - 1]
|
|
478
479
|
handlerLastMatchEnd.set(i, liveEventBufferStart + lastMatch.index + lastMatch[0].length)
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
480
|
+
const triggerAction = typeof handler.trigger === "string" ? handler.trigger.trim() : ""
|
|
481
|
+
const shouldCaptureEvent =
|
|
482
|
+
handler.break !== false
|
|
483
|
+
|| handler.done
|
|
484
|
+
|| handler.kill
|
|
485
|
+
|| !!triggerAction
|
|
486
|
+
if (shouldCaptureEvent) {
|
|
487
|
+
stream.matches = rendered_event
|
|
488
|
+
m = rendered_event[0]
|
|
489
|
+
matched_index = i
|
|
490
|
+
}
|
|
491
|
+
if (triggerAction) {
|
|
484
492
|
queueTriggerAction(triggerAction, rendered_event[0]).catch((e) => {
|
|
485
493
|
console.log("Trigger error", e)
|
|
486
494
|
if (ondata) {
|
|
@@ -490,9 +498,13 @@ class Shells {
|
|
|
490
498
|
}
|
|
491
499
|
if (handler.kill) {
|
|
492
500
|
sh.kill()
|
|
501
|
+
liveEventHandlersClosed = true
|
|
502
|
+
break
|
|
493
503
|
}
|
|
494
504
|
if (handler.done) {
|
|
495
505
|
sh.continue()
|
|
506
|
+
liveEventHandlersClosed = true
|
|
507
|
+
break
|
|
496
508
|
}
|
|
497
509
|
}
|
|
498
510
|
}
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -213,6 +213,7 @@ class Server {
|
|
|
213
213
|
})
|
|
214
214
|
this.appRegistry = new AppRegistryService({ kernel: this.kernel })
|
|
215
215
|
this.appPreferences = new AppPreferencesService({ kernel: this.kernel })
|
|
216
|
+
this.kernel.appPreferences = this.appPreferences
|
|
216
217
|
this.appLogs = new AppLogService({ registry: this.appRegistry })
|
|
217
218
|
this.appSearch = new AppSearchService({
|
|
218
219
|
kernel: this.kernel,
|
|
@@ -1251,7 +1252,7 @@ class Server {
|
|
|
1251
1252
|
path: 'remote.origin.url'
|
|
1252
1253
|
})
|
|
1253
1254
|
if (gitRemote && this.portal) {
|
|
1254
|
-
community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}`
|
|
1255
|
+
community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}&pinokio_checkin_bridge=v1`
|
|
1255
1256
|
}
|
|
1256
1257
|
} catch (_) {
|
|
1257
1258
|
community_url = ""
|
|
@@ -6417,8 +6418,6 @@ class Server {
|
|
|
6417
6418
|
} else {
|
|
6418
6419
|
registryOverride = null
|
|
6419
6420
|
}
|
|
6420
|
-
console.log("[registry/checkin] repo=%s return=%s registryRaw=%s registryOverride=%s", repoUrl || "", returnUrl || "", registryRaw || "", registryOverride || "")
|
|
6421
|
-
|
|
6422
6421
|
let candidates = []
|
|
6423
6422
|
if (repoUrl && this.kernel && this.kernel.git) {
|
|
6424
6423
|
const apiRoot = this.kernel.path('api')
|
|
@@ -6538,7 +6537,6 @@ class Server {
|
|
|
6538
6537
|
if (wantPublish) {
|
|
6539
6538
|
const registry = await this.getRegistryConfig()
|
|
6540
6539
|
const baseUrl = registryOverride || (registry && registry.url ? String(registry.url).replace(/\/$/, '') : null)
|
|
6541
|
-
console.log("[checkpoints/snapshot] publish=1 registryOverride=%s baseUrl=%s hasToken=%s", registryOverride || "", baseUrl || "", registryToken ? "yes" : "no")
|
|
6542
6540
|
if (!baseUrl || !registryToken) {
|
|
6543
6541
|
await this.kernel.git.setCheckpointSync(created.remoteKey, created.id, { status: "needs_token", at: Date.now() }).catch(() => {})
|
|
6544
6542
|
res.json({ ok: true, created: created || null, publish: { ok: false, code: "missing_token" } })
|
|
@@ -12714,12 +12712,14 @@ class Server {
|
|
|
12714
12712
|
// }
|
|
12715
12713
|
//
|
|
12716
12714
|
res.render("setup", {
|
|
12715
|
+
mode: req.params.mode,
|
|
12717
12716
|
wait,
|
|
12718
12717
|
error,
|
|
12719
12718
|
current,
|
|
12720
12719
|
install_required,
|
|
12721
12720
|
requirements,
|
|
12722
12721
|
requirements_pending,
|
|
12722
|
+
portal: this.portal,
|
|
12723
12723
|
logo: this.logo,
|
|
12724
12724
|
theme: this.theme,
|
|
12725
12725
|
agent: req.agent,
|