pinokiod 7.5.24 → 7.5.26
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/index.js +29 -1
- package/kernel/app_log_sessions.js +424 -0
- package/kernel/index.js +0 -3
- package/kernel/procs.js +4 -2
- package/kernel/router/localhost_static_router.js +5 -3
- package/package.json +1 -1
- package/server/index.js +183 -32
- package/server/lib/app_log_report.js +126 -61
- package/server/lib/log_redaction.js +273 -0
- package/server/public/logs-top-redaction.css +154 -0
- package/server/public/logs-top-redaction.js +711 -0
- package/server/public/logs.js +169 -42
- package/server/public/style.css +62 -23
- package/server/routes/apps.js +2 -1
- package/server/socket.js +18 -1
- package/server/views/logs.ejs +17 -7
- package/server/views/net.ejs +85 -6
- package/server/views/partials/logs_top_redaction_actions.ejs +2 -0
- package/server/views/partials/logs_top_redaction_pane.ejs +25 -0
- package/server/views/partials/net_route_list.ejs +278 -0
- package/server/views/partials/net_summary.ejs +10 -0
- package/test/app-log-report.test.js +224 -17
- package/test/app-log-sessions.test.js +518 -0
- package/test/dns-static-routes.test.js +146 -0
- package/test/log-redaction-overrides.test.js +157 -0
- package/test/logs-ask-ai.test.js +194 -3
- package/test/procs.test.js +3 -3
- package/test/terminal-log-paths.test.js +47 -0
package/kernel/api/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const Environment = require("../environment")
|
|
|
13
13
|
const Util = require('../util')
|
|
14
14
|
const ShellRunTemplate = require('./shell_run_template')
|
|
15
15
|
const PluginSources = require('../plugin_sources')
|
|
16
|
+
const AppLogSessions = require('../app_log_sessions')
|
|
16
17
|
|
|
17
18
|
const escapeHtml = (value) => String(value || "")
|
|
18
19
|
.replace(/&/g, "&")
|
|
@@ -36,6 +37,7 @@ class Api {
|
|
|
36
37
|
this.mods = {}
|
|
37
38
|
this.child_procs = {}
|
|
38
39
|
this.resolved_actions = {}
|
|
40
|
+
this.logSessions = new AppLogSessions({ kernel })
|
|
39
41
|
this.lproxy = new Lproxy()
|
|
40
42
|
}
|
|
41
43
|
async launcher_path(name) {
|
|
@@ -640,6 +642,14 @@ class Api {
|
|
|
640
642
|
internal_completion: !!(options && options.internal_completion)
|
|
641
643
|
})
|
|
642
644
|
}
|
|
645
|
+
if (this.logSessions) {
|
|
646
|
+
await this.logSessions.finishRun({
|
|
647
|
+
id: req.params.id,
|
|
648
|
+
path: requestPath
|
|
649
|
+
}, {
|
|
650
|
+
internal_completion: !!(options && options.internal_completion)
|
|
651
|
+
})
|
|
652
|
+
}
|
|
643
653
|
this.ondata({
|
|
644
654
|
id: req.params.id || requestPath,
|
|
645
655
|
type: "disconnect"
|
|
@@ -1386,7 +1396,7 @@ class Api {
|
|
|
1386
1396
|
console.log(msg)
|
|
1387
1397
|
} else {
|
|
1388
1398
|
// 11. actually make the rpc call
|
|
1389
|
-
|
|
1399
|
+
const runEndpoint = () => this.run(resolved.method, rpc, (stream, type) => {
|
|
1390
1400
|
let m = {
|
|
1391
1401
|
id: request.id || request.path,
|
|
1392
1402
|
caller: request.caller,
|
|
@@ -1410,6 +1420,9 @@ class Api {
|
|
|
1410
1420
|
// }
|
|
1411
1421
|
this.ondata(m)
|
|
1412
1422
|
})
|
|
1423
|
+
result = this.logSessions
|
|
1424
|
+
? await this.logSessions.withRunContext(request, runEndpoint)
|
|
1425
|
+
: await runEndpoint()
|
|
1413
1426
|
}
|
|
1414
1427
|
|
|
1415
1428
|
|
|
@@ -1423,6 +1436,9 @@ class Api {
|
|
|
1423
1436
|
rpc,
|
|
1424
1437
|
rawrpc
|
|
1425
1438
|
})
|
|
1439
|
+
if (this.logSessions) {
|
|
1440
|
+
await this.logSessions.finishRun(request, { internal_completion: true })
|
|
1441
|
+
}
|
|
1426
1442
|
|
|
1427
1443
|
// if there's an error, set the PINOKIO_SCRIPT_DEFAULT to false
|
|
1428
1444
|
|
|
@@ -1604,6 +1620,9 @@ class Api {
|
|
|
1604
1620
|
rpc,
|
|
1605
1621
|
rawrpc
|
|
1606
1622
|
})
|
|
1623
|
+
if (this.logSessions) {
|
|
1624
|
+
await this.logSessions.finishRun(request, { internal_completion: true })
|
|
1625
|
+
}
|
|
1607
1626
|
return // halt when there's an error
|
|
1608
1627
|
}
|
|
1609
1628
|
}
|
|
@@ -1657,6 +1676,9 @@ class Api {
|
|
|
1657
1676
|
let response = await this.step(request, rawrpc, input, step, total, args)
|
|
1658
1677
|
if (response) {
|
|
1659
1678
|
if (response.cancelled) {
|
|
1679
|
+
if (this.logSessions) {
|
|
1680
|
+
await this.logSessions.finishRun(response.request || request)
|
|
1681
|
+
}
|
|
1660
1682
|
return
|
|
1661
1683
|
}
|
|
1662
1684
|
if (response.rawrpc) {
|
|
@@ -1693,6 +1715,9 @@ class Api {
|
|
|
1693
1715
|
if (this.kernel && typeof this.kernel.markAppLaunchFailed === "function") {
|
|
1694
1716
|
this.kernel.markAppLaunchFailed(request.path, e)
|
|
1695
1717
|
}
|
|
1718
|
+
if (this.logSessions) {
|
|
1719
|
+
await this.logSessions.finishRun(request, { internal_completion: true })
|
|
1720
|
+
}
|
|
1696
1721
|
ondata({ raw: e.toString() })
|
|
1697
1722
|
}
|
|
1698
1723
|
}, concurrency)
|
|
@@ -1992,6 +2017,9 @@ class Api {
|
|
|
1992
2017
|
return
|
|
1993
2018
|
}
|
|
1994
2019
|
|
|
2020
|
+
if (this.logSessions) {
|
|
2021
|
+
await this.logSessions.startRun(request)
|
|
2022
|
+
}
|
|
1995
2023
|
this.queue(request, steps[0], initialPayload, 0, steps.length, cwd, initialPayload)
|
|
1996
2024
|
|
|
1997
2025
|
} else {
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const { AsyncLocalStorage } = require('async_hooks')
|
|
5
|
+
const Environment = require('./environment')
|
|
6
|
+
|
|
7
|
+
class AppLogSessions {
|
|
8
|
+
constructor({ kernel, now = () => new Date().toISOString(), randomHex = () => crypto.randomBytes(3).toString('hex') }) {
|
|
9
|
+
this.kernel = kernel
|
|
10
|
+
this.now = now
|
|
11
|
+
this.randomHex = randomHex
|
|
12
|
+
this.context = new AsyncLocalStorage()
|
|
13
|
+
this.reservations = new Map()
|
|
14
|
+
this.routineSessions = new Map()
|
|
15
|
+
this.sessions = new Map()
|
|
16
|
+
this.activeRunsByKey = new Map()
|
|
17
|
+
this.indexQueues = new Map()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
apiRoot() {
|
|
21
|
+
if (this.kernel && typeof this.kernel.path === 'function') {
|
|
22
|
+
return path.resolve(this.kernel.path('api'))
|
|
23
|
+
}
|
|
24
|
+
return path.resolve(this.kernel.homedir || '', 'api')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toPosix(value) {
|
|
28
|
+
return String(value || '').split(path.sep).filter(Boolean).join('/')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
isPathWithin(parentPath, childPath) {
|
|
32
|
+
const relative = path.relative(path.resolve(parentPath), path.resolve(childPath))
|
|
33
|
+
return !relative || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
sessionDir(appRoot) {
|
|
37
|
+
return path.resolve(appRoot, 'logs', 'sessions')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
indexPath(appRoot) {
|
|
41
|
+
return path.resolve(this.sessionDir(appRoot), 'index.json')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
manifestPath(appRoot, sessionId) {
|
|
45
|
+
if (!this.validSessionId(sessionId)) {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
return path.resolve(this.sessionDir(appRoot), `${sessionId}.json`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sessionKey(appRoot, sessionId) {
|
|
52
|
+
return `${path.resolve(appRoot)}\u0000${sessionId}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
reservationKey(target) {
|
|
56
|
+
return `${path.resolve(target.appRoot)}\u0000${path.resolve(target.scriptPath)}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
validSessionId(sessionId) {
|
|
60
|
+
return typeof sessionId === 'string' && /^[A-Za-z0-9._-]+$/.test(sessionId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async resolveAppRoot(appRoot) {
|
|
64
|
+
if (this.kernel && typeof this.kernel.exists === 'function') {
|
|
65
|
+
try {
|
|
66
|
+
const root = await Environment.get_root({ path: appRoot }, this.kernel)
|
|
67
|
+
if (root && root.root) {
|
|
68
|
+
return path.resolve(root.root)
|
|
69
|
+
}
|
|
70
|
+
} catch (_) {}
|
|
71
|
+
}
|
|
72
|
+
return appRoot
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async resolveScript(scriptPath) {
|
|
76
|
+
if (typeof scriptPath !== 'string' || !scriptPath.trim()) {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
const absolute = path.resolve(scriptPath.split('?')[0])
|
|
80
|
+
const apiRoot = this.apiRoot()
|
|
81
|
+
if (!this.isPathWithin(apiRoot, absolute)) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
const relative = path.relative(apiRoot, absolute)
|
|
85
|
+
const parts = relative.split(path.sep).filter(Boolean)
|
|
86
|
+
if (parts.length < 2 || !parts[0]) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
const appRoot = await this.resolveAppRoot(path.resolve(apiRoot, parts[0]))
|
|
90
|
+
return {
|
|
91
|
+
appRoot,
|
|
92
|
+
scriptPath: absolute,
|
|
93
|
+
script: this.toPosix(parts.slice(1).join(path.sep))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
requestKeys(requestOrRun) {
|
|
98
|
+
const keys = []
|
|
99
|
+
if (!requestOrRun || typeof requestOrRun !== 'object') {
|
|
100
|
+
return keys
|
|
101
|
+
}
|
|
102
|
+
for (const key of [requestOrRun.id, requestOrRun.path]) {
|
|
103
|
+
if (typeof key === 'string' && key.trim() && !keys.includes(key)) {
|
|
104
|
+
keys.push(key)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return keys
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
createSessionId(timestamp) {
|
|
111
|
+
const safeTime = String(timestamp || this.now()).replace(/[^A-Za-z0-9._-]/g, '-')
|
|
112
|
+
return `${safeTime}-${this.randomHex()}`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async readJson(filePath) {
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(await fs.promises.readFile(filePath, 'utf8'))
|
|
118
|
+
} catch (_) {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async writeJson(filePath, value) {
|
|
124
|
+
try {
|
|
125
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
|
|
126
|
+
await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2))
|
|
127
|
+
} catch (_) {}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async loadIndex(appRoot) {
|
|
131
|
+
const parsed = await this.readJson(this.indexPath(appRoot))
|
|
132
|
+
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.sessions)) {
|
|
133
|
+
return { version: 1, latest_session: null, sessions: [] }
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
version: 1,
|
|
137
|
+
latest_session: this.validSessionId(parsed.latest_session) ? parsed.latest_session : null,
|
|
138
|
+
sessions: parsed.sessions
|
|
139
|
+
.filter((session) => session && typeof session === 'object' && this.validSessionId(session.id))
|
|
140
|
+
.map((session) => ({
|
|
141
|
+
id: session.id,
|
|
142
|
+
created_at: typeof session.created_at === 'string' ? session.created_at : null,
|
|
143
|
+
updated_at: typeof session.updated_at === 'string' ? session.updated_at : null,
|
|
144
|
+
runs: Array.isArray(session.runs) ? session.runs.filter((run) => typeof run === 'string') : []
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async loadSession(appRoot, sessionId) {
|
|
150
|
+
if (!this.validSessionId(sessionId)) {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
const cacheKey = this.sessionKey(appRoot, sessionId)
|
|
154
|
+
if (this.sessions.has(cacheKey)) {
|
|
155
|
+
return this.sessions.get(cacheKey)
|
|
156
|
+
}
|
|
157
|
+
const manifestPath = this.manifestPath(appRoot, sessionId)
|
|
158
|
+
const parsed = manifestPath ? await this.readJson(manifestPath) : null
|
|
159
|
+
if (!parsed || typeof parsed !== 'object' || parsed.id !== sessionId || !Array.isArray(parsed.runs)) {
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
parsed.appRoot = appRoot
|
|
163
|
+
this.sessions.set(cacheKey, parsed)
|
|
164
|
+
return parsed
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async updateIndex(appRoot, updater) {
|
|
168
|
+
const previous = this.indexQueues.get(appRoot) || Promise.resolve()
|
|
169
|
+
const next = previous.catch(() => {}).then(async () => {
|
|
170
|
+
const index = await this.loadIndex(appRoot)
|
|
171
|
+
const updated = updater(index) || index
|
|
172
|
+
await this.writeJson(this.indexPath(appRoot), updated)
|
|
173
|
+
})
|
|
174
|
+
this.indexQueues.set(appRoot, next.catch(() => {}))
|
|
175
|
+
return next
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async persistSession(session, options = {}) {
|
|
179
|
+
if (!session || !session.appRoot || !this.validSessionId(session.id)) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
const manifest = {
|
|
183
|
+
version: 1,
|
|
184
|
+
id: session.id,
|
|
185
|
+
created_at: session.created_at,
|
|
186
|
+
updated_at: session.updated_at,
|
|
187
|
+
runs: session.runs.map((run) => ({
|
|
188
|
+
script: run.script,
|
|
189
|
+
started_at: run.started_at || null,
|
|
190
|
+
ended_at: run.ended_at || null,
|
|
191
|
+
logs: Array.isArray(run.logs) ? run.logs : []
|
|
192
|
+
}))
|
|
193
|
+
}
|
|
194
|
+
await this.writeJson(this.manifestPath(session.appRoot, session.id), manifest)
|
|
195
|
+
|
|
196
|
+
const summary = {
|
|
197
|
+
id: session.id,
|
|
198
|
+
created_at: session.created_at,
|
|
199
|
+
updated_at: session.updated_at,
|
|
200
|
+
runs: session.runs.map((run) => run.script).filter(Boolean)
|
|
201
|
+
}
|
|
202
|
+
await this.updateIndex(session.appRoot, (index) => {
|
|
203
|
+
const promote = !!options.promote || !index.latest_session
|
|
204
|
+
if (promote) {
|
|
205
|
+
const remaining = index.sessions.filter((entry) => entry.id !== session.id)
|
|
206
|
+
index.latest_session = session.id
|
|
207
|
+
index.sessions = [summary, ...remaining]
|
|
208
|
+
return index
|
|
209
|
+
}
|
|
210
|
+
let replaced = false
|
|
211
|
+
index.sessions = index.sessions.map((entry) => {
|
|
212
|
+
if (entry.id === session.id) {
|
|
213
|
+
replaced = true
|
|
214
|
+
return summary
|
|
215
|
+
}
|
|
216
|
+
return entry
|
|
217
|
+
})
|
|
218
|
+
if (!replaced) {
|
|
219
|
+
index.sessions.push(summary)
|
|
220
|
+
}
|
|
221
|
+
return index
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async createSession(target, timestamp) {
|
|
226
|
+
const session = {
|
|
227
|
+
version: 1,
|
|
228
|
+
id: this.createSessionId(timestamp),
|
|
229
|
+
appRoot: target.appRoot,
|
|
230
|
+
created_at: timestamp,
|
|
231
|
+
updated_at: timestamp,
|
|
232
|
+
runs: []
|
|
233
|
+
}
|
|
234
|
+
this.sessions.set(this.sessionKey(session.appRoot, session.id), session)
|
|
235
|
+
return session
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
clearReservations(appRoot, sessionId = '') {
|
|
239
|
+
for (const [key, reservation] of this.reservations.entries()) {
|
|
240
|
+
if (reservation.appRoot !== appRoot) {
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
if (sessionId && reservation.session_id !== sessionId) {
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
this.reservations.delete(key)
|
|
247
|
+
}
|
|
248
|
+
if (!sessionId) {
|
|
249
|
+
this.routineSessions.delete(appRoot)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
if (this.routineSessions.get(appRoot) === sessionId) {
|
|
253
|
+
this.routineSessions.delete(appRoot)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async reserveLaunch(scriptPathOrRequest, options = {}) {
|
|
258
|
+
const scriptPath = typeof scriptPathOrRequest === 'object' && scriptPathOrRequest
|
|
259
|
+
? scriptPathOrRequest.path || scriptPathOrRequest.uri
|
|
260
|
+
: scriptPathOrRequest
|
|
261
|
+
const target = await this.resolveScript(scriptPath)
|
|
262
|
+
if (!target) {
|
|
263
|
+
return null
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let session = null
|
|
267
|
+
const routineSessionId = this.routineSessions.get(target.appRoot)
|
|
268
|
+
if (this.validSessionId(routineSessionId)) {
|
|
269
|
+
session = await this.loadSession(target.appRoot, routineSessionId)
|
|
270
|
+
}
|
|
271
|
+
if (!session && options.existingRoutineOnly) {
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
const reservation = {
|
|
275
|
+
session_id: session ? session.id : '',
|
|
276
|
+
appRoot: target.appRoot,
|
|
277
|
+
scriptPath: target.scriptPath,
|
|
278
|
+
script: target.script
|
|
279
|
+
}
|
|
280
|
+
this.reservations.set(this.reservationKey(target), reservation)
|
|
281
|
+
return reservation
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async startRun(request) {
|
|
285
|
+
const target = await this.resolveScript(request && request.path)
|
|
286
|
+
if (!target) {
|
|
287
|
+
return null
|
|
288
|
+
}
|
|
289
|
+
const context = this.context.getStore()
|
|
290
|
+
const reservationKey = this.reservationKey(target)
|
|
291
|
+
const reservation = this.reservations.get(reservationKey)
|
|
292
|
+
let session = null
|
|
293
|
+
let promote = false
|
|
294
|
+
let routine = false
|
|
295
|
+
|
|
296
|
+
if (context && context.appRoot === target.appRoot && this.validSessionId(context.session_id)) {
|
|
297
|
+
session = await this.loadSession(target.appRoot, context.session_id)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!session && reservation) {
|
|
301
|
+
this.reservations.delete(reservationKey)
|
|
302
|
+
if (this.validSessionId(reservation.session_id)) {
|
|
303
|
+
session = await this.loadSession(target.appRoot, reservation.session_id)
|
|
304
|
+
}
|
|
305
|
+
if (!session) {
|
|
306
|
+
const timestamp = this.now()
|
|
307
|
+
session = await this.createSession(target, timestamp)
|
|
308
|
+
promote = true
|
|
309
|
+
}
|
|
310
|
+
routine = true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!session) {
|
|
314
|
+
this.clearReservations(target.appRoot)
|
|
315
|
+
const timestamp = this.now()
|
|
316
|
+
session = await this.createSession(target, timestamp)
|
|
317
|
+
promote = true
|
|
318
|
+
}
|
|
319
|
+
if (routine) {
|
|
320
|
+
this.routineSessions.set(target.appRoot, session.id)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const startedAt = session.runs.length === 0 ? session.created_at : this.now()
|
|
324
|
+
const run = {
|
|
325
|
+
id: `${session.id}:${session.runs.length}`,
|
|
326
|
+
session_id: session.id,
|
|
327
|
+
appRoot: target.appRoot,
|
|
328
|
+
scriptPath: target.scriptPath,
|
|
329
|
+
script: target.script,
|
|
330
|
+
started_at: startedAt,
|
|
331
|
+
ended_at: null,
|
|
332
|
+
logs: []
|
|
333
|
+
}
|
|
334
|
+
session.updated_at = startedAt
|
|
335
|
+
session.runs.push(run)
|
|
336
|
+
for (const key of this.requestKeys({ ...request, path: target.scriptPath })) {
|
|
337
|
+
this.activeRunsByKey.set(key, run)
|
|
338
|
+
}
|
|
339
|
+
await this.persistSession(session, { promote })
|
|
340
|
+
return run
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
activeRun(requestOrRun) {
|
|
344
|
+
if (requestOrRun && requestOrRun.session_id && requestOrRun.scriptPath) {
|
|
345
|
+
return requestOrRun
|
|
346
|
+
}
|
|
347
|
+
for (const key of this.requestKeys(requestOrRun)) {
|
|
348
|
+
const run = this.activeRunsByKey.get(key)
|
|
349
|
+
if (run) {
|
|
350
|
+
return run
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return null
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
withRunContext(requestOrRun, fn) {
|
|
357
|
+
const run = this.activeRun(requestOrRun)
|
|
358
|
+
if (!run || typeof fn !== 'function') {
|
|
359
|
+
return typeof fn === 'function' ? fn() : undefined
|
|
360
|
+
}
|
|
361
|
+
return this.context.run({
|
|
362
|
+
session_id: run.session_id,
|
|
363
|
+
appRoot: run.appRoot
|
|
364
|
+
}, fn)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async finishRun(requestOrRun, options = {}) {
|
|
368
|
+
const run = this.activeRun(requestOrRun)
|
|
369
|
+
if (!run || run.ended_at) {
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
const session = await this.loadSession(run.appRoot, run.session_id)
|
|
373
|
+
if (!session) {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const endedAt = this.now()
|
|
377
|
+
run.ended_at = endedAt
|
|
378
|
+
session.updated_at = endedAt
|
|
379
|
+
for (const key of this.requestKeys({ ...requestOrRun, path: run.scriptPath })) {
|
|
380
|
+
this.activeRunsByKey.delete(key)
|
|
381
|
+
}
|
|
382
|
+
await this.persistSession(session)
|
|
383
|
+
|
|
384
|
+
if (!options || !options.internal_completion) {
|
|
385
|
+
this.clearReservations(run.appRoot, session.id)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async recordLogFile({ scriptPath, logFile, run: capturedRun = null }) {
|
|
390
|
+
const target = await this.resolveScript(scriptPath)
|
|
391
|
+
if (!target || typeof logFile !== 'string' || !logFile.trim()) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
const run = capturedRun
|
|
395
|
+
&& capturedRun.session_id
|
|
396
|
+
&& capturedRun.appRoot === target.appRoot
|
|
397
|
+
&& capturedRun.scriptPath === target.scriptPath
|
|
398
|
+
? capturedRun
|
|
399
|
+
: this.activeRunsByKey.get(target.scriptPath)
|
|
400
|
+
if (!run) {
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
const logsRoot = path.resolve(target.appRoot, 'logs')
|
|
404
|
+
const sessionsRoot = this.sessionDir(target.appRoot)
|
|
405
|
+
const absoluteLog = path.resolve(logFile)
|
|
406
|
+
if (!this.isPathWithin(logsRoot, absoluteLog) || this.isPathWithin(sessionsRoot, absoluteLog)) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
if (path.basename(absoluteLog) === 'latest') {
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
const relativeLog = this.toPosix(path.relative(target.appRoot, absoluteLog))
|
|
413
|
+
if (!run.logs.some((entry) => entry.path === relativeLog)) {
|
|
414
|
+
run.logs.push({ path: relativeLog })
|
|
415
|
+
const session = await this.loadSession(run.appRoot, run.session_id)
|
|
416
|
+
if (session) {
|
|
417
|
+
session.updated_at = this.now()
|
|
418
|
+
await this.persistSession(session)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = AppLogSessions
|
package/kernel/index.js
CHANGED
package/kernel/procs.js
CHANGED
|
@@ -73,13 +73,15 @@ class Procs {
|
|
|
73
73
|
let pids = new Set()
|
|
74
74
|
let s = stdout.trim()
|
|
75
75
|
const lines = s.split('\n');
|
|
76
|
+
const listeningForeignAddresses = new Set(['0.0.0.0:0', '[::]:0', '*:*', '*:0'])
|
|
76
77
|
for(let line of lines) {
|
|
77
78
|
if (isWin) {
|
|
78
79
|
// Skip headers
|
|
79
80
|
try {
|
|
80
81
|
if (!line.startsWith(' TCP')) continue;
|
|
81
82
|
const parts = line.trim().split(/\s+/);
|
|
82
|
-
const [ , localAddress,
|
|
83
|
+
const [ , localAddress, foreignAddress ] = parts;
|
|
84
|
+
const pid = parts[parts.length - 1]
|
|
83
85
|
|
|
84
86
|
let pid_int = parseInt(pid)
|
|
85
87
|
if (pid_int === 0 || pid_int === 4) {
|
|
@@ -88,7 +90,7 @@ class Procs {
|
|
|
88
90
|
continue
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
if (
|
|
93
|
+
if (!listeningForeignAddresses.has(foreignAddress)) continue;
|
|
92
94
|
const chunks = /(.+):([0-9]+)/.exec(localAddress)
|
|
93
95
|
let host = chunks[1]
|
|
94
96
|
let port = chunks[2]
|
|
@@ -32,10 +32,12 @@ class LocalhostStaticRouter extends Processor {
|
|
|
32
32
|
continue
|
|
33
33
|
}
|
|
34
34
|
const hasIndex = fs.existsSync(indexPath)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
if (!hasIndex) {
|
|
36
|
+
delete this.router.rewrite_mapping[api_name]
|
|
37
|
+
continue
|
|
38
38
|
}
|
|
39
|
+
const fileServerOptions = {}
|
|
40
|
+
fileServerOptions.index_names = ["index.html"]
|
|
39
41
|
const effectiveFileServerOptions = Object.keys(fileServerOptions).length ? { ...fileServerOptions } : undefined
|
|
40
42
|
for(let domain in config.dns) {
|
|
41
43
|
let localhost_match
|