pinokiod 7.3.15 → 7.4.1
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 +111 -15
- package/kernel/api/script/index.js +10 -0
- package/kernel/autolaunch.js +111 -0
- package/kernel/bin/ffmpeg.js +9 -791
- package/kernel/environment.js +89 -1
- package/kernel/git.js +9 -19
- package/kernel/index.js +142 -43
- package/kernel/launch_requirements.js +1115 -0
- package/kernel/procs.js +1 -1
- package/kernel/ready.js +231 -0
- package/kernel/script.js +16 -0
- package/kernel/shells.js +9 -1
- package/kernel/workspace_status.js +111 -45
- package/package.json +2 -3
- package/server/autolaunch.js +725 -0
- package/server/index.js +244 -411
- package/server/views/app.ejs +267 -160
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
- package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
- package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
- package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
- package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
- package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
- package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
- package/server/views/partials/home_action_modal.ejs +4 -1
- package/server/views/partials/launch_requirements_status_client.ejs +271 -0
- package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
- package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
- package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
- package/server/views/terminal.ejs +196 -2
- package/test/home-autolaunch-live-ui.test.js +455 -0
- package/test/launch-requirements-browser.test.js +579 -0
- package/test/launch-requirements-contract-coverage.test.js +627 -0
- package/test/launch-requirements-real-browser.js +625 -0
- package/test/launch-requirements-status-client.test.js +132 -0
- package/test/launch-requirements.test.js +1806 -0
- package/test/launch-settings-ui.test.js +370 -0
- package/test/procs.test.js +49 -0
- package/test/ready-state.test.js +49 -0
- package/test/server-autolaunch.test.js +1052 -0
- package/test/startup-git-index-benchmark.js +409 -0
- package/test/startup-git-index-browser.js +320 -0
- package/test/startup-git-index-performance.test.js +380 -0
- package/test/startup-git-index-refactor.test.js +450 -0
- package/test/startup-git-index-route.test.js +588 -0
- package/test/universal-launcher.smoke.spec.js +10 -9
- package/test/workspace-gitignore-benchmark.js +815 -0
- package/test/workspace-gitignore-path-scoped.test.js +256 -0
- package/script/verify-ffmpeg.js +0 -459
- package/spec/INSTRUCTION_SYNC.md +0 -432
package/server/index.js
CHANGED
|
@@ -28,6 +28,7 @@ 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 ServerAutolaunch = require('./autolaunch')
|
|
31
32
|
const Git = require("../kernel/git")
|
|
32
33
|
const TerminalApi = require('../kernel/api/terminal')
|
|
33
34
|
const PluginSources = require("../kernel/plugin_sources")
|
|
@@ -192,28 +193,9 @@ class Server {
|
|
|
192
193
|
/(^|\/)\.pytest_cache\//,
|
|
193
194
|
/(^|\/)\.git\//
|
|
194
195
|
]
|
|
195
|
-
this.apiGitRefreshTimer = null
|
|
196
196
|
this.workspaceStatus = new WorkspaceStatusManager({
|
|
197
197
|
enableWatchers: process.env.PINOKIO_DISABLE_WATCH === '1' ? false : true,
|
|
198
198
|
fallbackIntervalMs: 60000,
|
|
199
|
-
onEvent: (workspaceName, events) => {
|
|
200
|
-
if (!this.kernel.homedir) return
|
|
201
|
-
if (!events || events.length === 0) return
|
|
202
|
-
const apiRoot = this.kernel.path('api')
|
|
203
|
-
const topLevel = events.some(({ path: evtPath, type }) => {
|
|
204
|
-
if (!evtPath) return false
|
|
205
|
-
const rel = path.relative(apiRoot, evtPath)
|
|
206
|
-
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return false
|
|
207
|
-
const parts = rel.split(path.sep)
|
|
208
|
-
return parts.length === 1 && (type === 'create' || type === 'delete')
|
|
209
|
-
})
|
|
210
|
-
if (topLevel) {
|
|
211
|
-
if (this.apiGitRefreshTimer) clearTimeout(this.apiGitRefreshTimer)
|
|
212
|
-
this.apiGitRefreshTimer = setTimeout(() => {
|
|
213
|
-
this.kernel.git.repos(apiRoot).catch(() => {})
|
|
214
|
-
}, 1000)
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
199
|
})
|
|
218
200
|
this.appRegistry = new AppRegistryService({ kernel: this.kernel })
|
|
219
201
|
this.appPreferences = new AppPreferencesService({ kernel: this.kernel })
|
|
@@ -228,6 +210,7 @@ class Server {
|
|
|
228
210
|
})
|
|
229
211
|
this.desktopEventRouter = createDesktopEventRouter({ kernel: this.kernel })
|
|
230
212
|
this.injectRouter = createInjectRouter({ kernel: this.kernel })
|
|
213
|
+
this.autolaunch = new ServerAutolaunch(this)
|
|
231
214
|
|
|
232
215
|
// sometimes the C:\Windows\System32 is not in PATH, need to add
|
|
233
216
|
let platform = os.platform()
|
|
@@ -395,6 +378,73 @@ class Server {
|
|
|
395
378
|
console.error('Failed to emit fatal notification:', err)
|
|
396
379
|
}
|
|
397
380
|
}
|
|
381
|
+
async refreshTopLevelAppInventory() {
|
|
382
|
+
try {
|
|
383
|
+
if (this.kernel && this.kernel.api && typeof this.kernel.api.linkGit === "function") {
|
|
384
|
+
await this.kernel.api.linkGit()
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.warn("App inventory refresh error:", error && error.message ? error.message : error)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async findApiRepoByGitUrl(gitUrl) {
|
|
391
|
+
if (!gitUrl || typeof gitUrl !== "string") return null
|
|
392
|
+
const candidates = [gitUrl]
|
|
393
|
+
if (gitUrl.endsWith(".git")) {
|
|
394
|
+
candidates.push(gitUrl.slice(0, -4))
|
|
395
|
+
} else {
|
|
396
|
+
candidates.push(`${gitUrl}.git`)
|
|
397
|
+
}
|
|
398
|
+
const mapping = this.kernel && this.kernel.git ? this.kernel.git.mapping : null
|
|
399
|
+
const removeStaleMapping = (candidate) => {
|
|
400
|
+
if (!mapping) return
|
|
401
|
+
delete mapping[candidate]
|
|
402
|
+
if (candidate.endsWith(".git")) {
|
|
403
|
+
delete mapping[candidate.slice(0, -4)]
|
|
404
|
+
} else {
|
|
405
|
+
delete mapping[`${candidate}.git`]
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const isUsableRepoPath = async (repoPath) => {
|
|
409
|
+
if (!repoPath || typeof repoPath !== "string") return false
|
|
410
|
+
if (this.kernel.git && typeof this.kernel.git.hasGitMetadata === "function") {
|
|
411
|
+
return this.kernel.git.hasGitMetadata(repoPath)
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const stat = await fs.promises.stat(repoPath)
|
|
415
|
+
return stat.isDirectory()
|
|
416
|
+
} catch (_) {
|
|
417
|
+
return false
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (const candidate of candidates) {
|
|
422
|
+
const found = this.kernel.git.find(candidate)
|
|
423
|
+
if (found && await isUsableRepoPath(found.path)) return found
|
|
424
|
+
if (found) removeStaleMapping(candidate)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (this.kernel.api && this.kernel.api.gitPath) {
|
|
428
|
+
for (const candidate of candidates) {
|
|
429
|
+
const repoPath = this.kernel.api.gitPath[candidate]
|
|
430
|
+
if (repoPath && await isUsableRepoPath(repoPath)) {
|
|
431
|
+
const found = { path: repoPath }
|
|
432
|
+
try {
|
|
433
|
+
found.head = await this.kernel.git.getHead(repoPath)
|
|
434
|
+
} catch (_) {}
|
|
435
|
+
this.kernel.git.mapping[candidate] = found
|
|
436
|
+
return found
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
await this.kernel.git.repos(this.kernel.path("api"))
|
|
442
|
+
for (const candidate of candidates) {
|
|
443
|
+
const found = this.kernel.git.find(candidate)
|
|
444
|
+
if (found) return found
|
|
445
|
+
}
|
|
446
|
+
return null
|
|
447
|
+
}
|
|
398
448
|
stop() {
|
|
399
449
|
if (this.resourceUsage && typeof this.resourceUsage.stop === 'function') {
|
|
400
450
|
this.resourceUsage.stop()
|
|
@@ -759,6 +809,11 @@ class Server {
|
|
|
759
809
|
shortcuts: x.shortcuts,
|
|
760
810
|
index: x.index,
|
|
761
811
|
running_scripts: x.running_scripts,
|
|
812
|
+
autolaunch_starting: x.autolaunch_starting ? true : false,
|
|
813
|
+
autolaunch_waiting: x.autolaunch_waiting ? true : false,
|
|
814
|
+
autolaunch_blocked: x.autolaunch_blocked ? true : false,
|
|
815
|
+
autolaunch_status_label: typeof x.autolaunch_status_label === "string" ? x.autolaunch_status_label : "",
|
|
816
|
+
autolaunch_script: typeof x.autolaunch_script === "string" ? x.autolaunch_script : "",
|
|
762
817
|
//icon: (x.isDirectory() ? "fa-solid fa-folder" : "fa-regular fa-file"),
|
|
763
818
|
name,
|
|
764
819
|
uri,
|
|
@@ -1170,296 +1225,6 @@ class Server {
|
|
|
1170
1225
|
list: this.getPeers(),
|
|
1171
1226
|
}
|
|
1172
1227
|
}
|
|
1173
|
-
normalizeAutolaunchAppId(value) {
|
|
1174
|
-
if (typeof value !== "string") {
|
|
1175
|
-
return ""
|
|
1176
|
-
}
|
|
1177
|
-
const id = value.trim()
|
|
1178
|
-
if (!id || id === "." || id === ".." || id.includes("\0") || /[\\/]/.test(id)) {
|
|
1179
|
-
return ""
|
|
1180
|
-
}
|
|
1181
|
-
return id
|
|
1182
|
-
}
|
|
1183
|
-
normalizeAutolaunchScriptPath(value) {
|
|
1184
|
-
if (typeof value !== "string") {
|
|
1185
|
-
return ""
|
|
1186
|
-
}
|
|
1187
|
-
let script = value.trim().replace(/\\/g, "/")
|
|
1188
|
-
if (!script || script.includes("\0")) {
|
|
1189
|
-
return ""
|
|
1190
|
-
}
|
|
1191
|
-
script = script.split("#")[0].split("?")[0].trim()
|
|
1192
|
-
if (!script || /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(script) || script.startsWith("//")) {
|
|
1193
|
-
return ""
|
|
1194
|
-
}
|
|
1195
|
-
if (script.startsWith("/")) {
|
|
1196
|
-
return ""
|
|
1197
|
-
}
|
|
1198
|
-
const normalized = path.posix.normalize(script).replace(/^\.\/+/, "")
|
|
1199
|
-
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || path.posix.isAbsolute(normalized)) {
|
|
1200
|
-
return ""
|
|
1201
|
-
}
|
|
1202
|
-
return normalized
|
|
1203
|
-
}
|
|
1204
|
-
isAutolaunchScriptFilename(filename) {
|
|
1205
|
-
const ext = path.extname(filename || "").toLowerCase()
|
|
1206
|
-
if (![".js", ".json", ".mjs", ".cjs"].includes(ext)) {
|
|
1207
|
-
return false
|
|
1208
|
-
}
|
|
1209
|
-
const base = path.basename(filename || "").toLowerCase()
|
|
1210
|
-
return !["package.json", "pinokio.js", "pinokio.json", "pinokio_meta.json"].includes(base)
|
|
1211
|
-
}
|
|
1212
|
-
stripAutolaunchLabel(value) {
|
|
1213
|
-
if (typeof value !== "string") {
|
|
1214
|
-
return ""
|
|
1215
|
-
}
|
|
1216
|
-
return value
|
|
1217
|
-
.replace(/<[^>]*>/g, " ")
|
|
1218
|
-
.replace(/\s+/g, " ")
|
|
1219
|
-
.trim()
|
|
1220
|
-
}
|
|
1221
|
-
async getAutolaunchAppById(appId) {
|
|
1222
|
-
const id = this.normalizeAutolaunchAppId(appId)
|
|
1223
|
-
if (!id) {
|
|
1224
|
-
return null
|
|
1225
|
-
}
|
|
1226
|
-
const apps = await this.kernel.api.listApps()
|
|
1227
|
-
return apps.find((app) => app && app.id === id) || null
|
|
1228
|
-
}
|
|
1229
|
-
async getAutolaunchEnvInfo(app) {
|
|
1230
|
-
const appRoot = path.resolve(this.kernel.api.userdir, app.id)
|
|
1231
|
-
const gotRoot = await Environment.get_root({ path: appRoot }, this.kernel)
|
|
1232
|
-
const envRoot = gotRoot && gotRoot.root ? gotRoot.root : appRoot
|
|
1233
|
-
const envPath = path.resolve(envRoot, "ENVIRONMENT")
|
|
1234
|
-
const env = await Util.parse_env(envPath)
|
|
1235
|
-
const value = typeof env.PINOKIO_SCRIPT_AUTOLAUNCH === "string"
|
|
1236
|
-
? env.PINOKIO_SCRIPT_AUTOLAUNCH.trim()
|
|
1237
|
-
: ""
|
|
1238
|
-
const exists = await this.exists(envPath)
|
|
1239
|
-
return {
|
|
1240
|
-
appRoot,
|
|
1241
|
-
envRoot,
|
|
1242
|
-
envPath,
|
|
1243
|
-
envRelpath: gotRoot && gotRoot.relpath ? gotRoot.relpath : "",
|
|
1244
|
-
exists,
|
|
1245
|
-
value,
|
|
1246
|
-
enabled: value.length > 0
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
async buildAutolaunchAppState(app) {
|
|
1250
|
-
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
1251
|
-
return {
|
|
1252
|
-
id: app.id,
|
|
1253
|
-
name: app.name,
|
|
1254
|
-
title: app.title || app.name || app.id,
|
|
1255
|
-
description: app.description || "",
|
|
1256
|
-
icon: app.icon || "/pinokio-black.png",
|
|
1257
|
-
workspace_path: app.workspace_path,
|
|
1258
|
-
launcher_path: app.launcher_path,
|
|
1259
|
-
launcher_root: app.launcher_root || "",
|
|
1260
|
-
env_path: envInfo.envPath,
|
|
1261
|
-
autolaunch: envInfo.value,
|
|
1262
|
-
autolaunch_enabled: envInfo.enabled
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
async buildAutolaunchAppsState() {
|
|
1266
|
-
const apps = await this.kernel.api.listApps()
|
|
1267
|
-
const states = []
|
|
1268
|
-
for (const app of apps) {
|
|
1269
|
-
states.push(await this.buildAutolaunchAppState(app))
|
|
1270
|
-
}
|
|
1271
|
-
return states
|
|
1272
|
-
}
|
|
1273
|
-
async resolveAutolaunchScript(appRoot, script) {
|
|
1274
|
-
const normalized = this.normalizeAutolaunchScriptPath(script)
|
|
1275
|
-
if (!normalized || !this.isAutolaunchScriptFilename(normalized)) {
|
|
1276
|
-
return null
|
|
1277
|
-
}
|
|
1278
|
-
const scriptPath = path.resolve(appRoot, normalized)
|
|
1279
|
-
if (!this.is_subpath(appRoot, scriptPath)) {
|
|
1280
|
-
return null
|
|
1281
|
-
}
|
|
1282
|
-
let stat
|
|
1283
|
-
try {
|
|
1284
|
-
stat = await fs.promises.stat(scriptPath)
|
|
1285
|
-
} catch (_) {
|
|
1286
|
-
return null
|
|
1287
|
-
}
|
|
1288
|
-
if (!stat || !stat.isFile()) {
|
|
1289
|
-
return null
|
|
1290
|
-
}
|
|
1291
|
-
return {
|
|
1292
|
-
script: normalized,
|
|
1293
|
-
path: scriptPath
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
flattenAutolaunchMenu(menu, trail = []) {
|
|
1297
|
-
const items = []
|
|
1298
|
-
if (!Array.isArray(menu)) {
|
|
1299
|
-
return items
|
|
1300
|
-
}
|
|
1301
|
-
for (const menuitem of menu) {
|
|
1302
|
-
if (!menuitem || typeof menuitem !== "object") {
|
|
1303
|
-
continue
|
|
1304
|
-
}
|
|
1305
|
-
const label = this.stripAutolaunchLabel(menuitem.text || menuitem.name || menuitem.html || "")
|
|
1306
|
-
const nextTrail = label ? trail.concat(label) : trail
|
|
1307
|
-
if (Array.isArray(menuitem.menu)) {
|
|
1308
|
-
items.push(...this.flattenAutolaunchMenu(menuitem.menu, nextTrail))
|
|
1309
|
-
} else {
|
|
1310
|
-
items.push({ item: menuitem, group: trail.join(" / ") })
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
return items
|
|
1314
|
-
}
|
|
1315
|
-
async addAutolaunchCandidate(candidates, seen, candidate, appRoot) {
|
|
1316
|
-
const resolved = await this.resolveAutolaunchScript(appRoot, candidate.script)
|
|
1317
|
-
if (!resolved || seen.has(resolved.script)) {
|
|
1318
|
-
return null
|
|
1319
|
-
}
|
|
1320
|
-
seen.add(resolved.script)
|
|
1321
|
-
const label = this.stripAutolaunchLabel(candidate.label || "") || resolved.script
|
|
1322
|
-
const menuDefault = !!candidate.menu_default
|
|
1323
|
-
const item = {
|
|
1324
|
-
script: resolved.script,
|
|
1325
|
-
label,
|
|
1326
|
-
group: candidate.group || "",
|
|
1327
|
-
icon: candidate.icon || "",
|
|
1328
|
-
source: candidate.source || "local",
|
|
1329
|
-
menu_default: menuDefault,
|
|
1330
|
-
has_params: !!candidate.has_params
|
|
1331
|
-
}
|
|
1332
|
-
candidates.push(item)
|
|
1333
|
-
return item
|
|
1334
|
-
}
|
|
1335
|
-
async collectAutolaunchScriptFiles(root, appRoot) {
|
|
1336
|
-
const results = []
|
|
1337
|
-
const ignoredDirs = new Set([
|
|
1338
|
-
".git",
|
|
1339
|
-
".venv",
|
|
1340
|
-
"__pycache__",
|
|
1341
|
-
"app",
|
|
1342
|
-
"cache",
|
|
1343
|
-
"data",
|
|
1344
|
-
"env",
|
|
1345
|
-
"logs",
|
|
1346
|
-
"models",
|
|
1347
|
-
"node_modules",
|
|
1348
|
-
"output",
|
|
1349
|
-
"outputs",
|
|
1350
|
-
"venv"
|
|
1351
|
-
])
|
|
1352
|
-
const maxResults = 500
|
|
1353
|
-
const walk = async (dir, depth) => {
|
|
1354
|
-
if (depth > 4 || results.length >= maxResults) {
|
|
1355
|
-
return
|
|
1356
|
-
}
|
|
1357
|
-
let entries
|
|
1358
|
-
try {
|
|
1359
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true })
|
|
1360
|
-
} catch (_) {
|
|
1361
|
-
return
|
|
1362
|
-
}
|
|
1363
|
-
for (const entry of entries) {
|
|
1364
|
-
if (!entry || !entry.name || entry.name.includes("\0")) {
|
|
1365
|
-
continue
|
|
1366
|
-
}
|
|
1367
|
-
const fullPath = path.resolve(dir, entry.name)
|
|
1368
|
-
if (entry.isDirectory()) {
|
|
1369
|
-
if (!ignoredDirs.has(entry.name)) {
|
|
1370
|
-
await walk(fullPath, depth + 1)
|
|
1371
|
-
}
|
|
1372
|
-
continue
|
|
1373
|
-
}
|
|
1374
|
-
if (!entry.isFile() || !this.isAutolaunchScriptFilename(entry.name)) {
|
|
1375
|
-
continue
|
|
1376
|
-
}
|
|
1377
|
-
if (!this.is_subpath(appRoot, fullPath)) {
|
|
1378
|
-
continue
|
|
1379
|
-
}
|
|
1380
|
-
const rel = path.relative(appRoot, fullPath).split(path.sep).join("/")
|
|
1381
|
-
results.push(rel)
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
await walk(root, 0)
|
|
1385
|
-
results.sort((a, b) => a.localeCompare(b))
|
|
1386
|
-
return results
|
|
1387
|
-
}
|
|
1388
|
-
async buildAutolaunchCandidates(app) {
|
|
1389
|
-
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
1390
|
-
const appRoot = envInfo.appRoot
|
|
1391
|
-
const launcher = await this.kernel.api.launcher(app.id)
|
|
1392
|
-
const launcherRoot = launcher && launcher.launcher_root
|
|
1393
|
-
? path.resolve(appRoot, launcher.launcher_root)
|
|
1394
|
-
: appRoot
|
|
1395
|
-
const menuCandidates = []
|
|
1396
|
-
const otherCandidates = []
|
|
1397
|
-
const seen = new Set()
|
|
1398
|
-
|
|
1399
|
-
try {
|
|
1400
|
-
let config = await this.kernel.api.meta(app.id)
|
|
1401
|
-
config = await this.processMenu(app.id, safeStructuredClone(config || {}))
|
|
1402
|
-
const flat = this.flattenAutolaunchMenu(config && config.menu ? config.menu : [])
|
|
1403
|
-
for (const entry of flat) {
|
|
1404
|
-
const menuitem = entry.item
|
|
1405
|
-
if (!menuitem || typeof menuitem.href !== "string") {
|
|
1406
|
-
continue
|
|
1407
|
-
}
|
|
1408
|
-
let href = menuitem.href.trim()
|
|
1409
|
-
const apiPrefix = `/api/${app.id}/`
|
|
1410
|
-
if (href.startsWith(apiPrefix)) {
|
|
1411
|
-
href = href.slice(apiPrefix.length)
|
|
1412
|
-
} else if (href.startsWith("/")) {
|
|
1413
|
-
continue
|
|
1414
|
-
}
|
|
1415
|
-
const localScript = this.normalizeAutolaunchScriptPath(href)
|
|
1416
|
-
if (!localScript) {
|
|
1417
|
-
continue
|
|
1418
|
-
}
|
|
1419
|
-
const scriptPath = path.resolve(launcherRoot, localScript)
|
|
1420
|
-
if (!this.is_subpath(appRoot, scriptPath)) {
|
|
1421
|
-
continue
|
|
1422
|
-
}
|
|
1423
|
-
const script = path.relative(appRoot, scriptPath).split(path.sep).join("/")
|
|
1424
|
-
await this.addAutolaunchCandidate(menuCandidates, seen, {
|
|
1425
|
-
script,
|
|
1426
|
-
label: menuitem.text || menuitem.name || script,
|
|
1427
|
-
group: entry.group,
|
|
1428
|
-
icon: typeof menuitem.icon === "string" ? menuitem.icon : "",
|
|
1429
|
-
source: "menu",
|
|
1430
|
-
menu_default: !!menuitem.default,
|
|
1431
|
-
has_params: !!(menuitem.params && typeof menuitem.params === "object")
|
|
1432
|
-
}, appRoot)
|
|
1433
|
-
}
|
|
1434
|
-
} catch (error) {
|
|
1435
|
-
console.warn("[autolaunch] failed to resolve menu candidates", app.id, error && error.message ? error.message : error)
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
if (envInfo.value) {
|
|
1439
|
-
await this.addAutolaunchCandidate(menuCandidates, seen, {
|
|
1440
|
-
script: envInfo.value,
|
|
1441
|
-
label: envInfo.value,
|
|
1442
|
-
source: "current"
|
|
1443
|
-
}, appRoot)
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
const localScripts = await this.collectAutolaunchScriptFiles(launcherRoot, appRoot)
|
|
1447
|
-
for (const script of localScripts) {
|
|
1448
|
-
await this.addAutolaunchCandidate(otherCandidates, seen, {
|
|
1449
|
-
script,
|
|
1450
|
-
label: script,
|
|
1451
|
-
source: "local"
|
|
1452
|
-
}, appRoot)
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
return {
|
|
1456
|
-
app: await this.buildAutolaunchAppState(app),
|
|
1457
|
-
launcher_root: path.relative(appRoot, launcherRoot).split(path.sep).join("/"),
|
|
1458
|
-
menu: menuCandidates,
|
|
1459
|
-
other: otherCandidates,
|
|
1460
|
-
current: envInfo.value
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
1228
|
async renderInvalidContentPage(req, res, invalid, options = {}) {
|
|
1464
1229
|
const type = invalid && typeof invalid.type === "string" ? invalid.type : "app"
|
|
1465
1230
|
const sidebarSelected = options.sidebarSelected || (type === "plugin" ? "plugins" : type === "task" ? "tasks" : "home")
|
|
@@ -1718,7 +1483,7 @@ class Server {
|
|
|
1718
1483
|
let autolaunchAppState = null
|
|
1719
1484
|
try {
|
|
1720
1485
|
const appRoot = this.kernel.path("api", name)
|
|
1721
|
-
autolaunchAppState = await this.
|
|
1486
|
+
autolaunchAppState = await this.autolaunch.buildAppState({
|
|
1722
1487
|
id: name,
|
|
1723
1488
|
name,
|
|
1724
1489
|
title: config && config.title ? config.title : name,
|
|
@@ -1766,6 +1531,12 @@ class Server {
|
|
|
1766
1531
|
config,
|
|
1767
1532
|
protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
|
|
1768
1533
|
autolaunch_app: autolaunchAppState,
|
|
1534
|
+
launch_requirements_status_enabled: !!(
|
|
1535
|
+
autolaunchAppState &&
|
|
1536
|
+
autolaunchAppState.autolaunch &&
|
|
1537
|
+
Array.isArray(autolaunchAppState.autolaunch_depends) &&
|
|
1538
|
+
autolaunchAppState.autolaunch_depends.length > 0
|
|
1539
|
+
),
|
|
1769
1540
|
// sidebar_url: "/pinokio/sidebar/" + name,
|
|
1770
1541
|
home: req.originalUrl,
|
|
1771
1542
|
run_tab,
|
|
@@ -2014,7 +1785,7 @@ class Server {
|
|
|
2014
1785
|
let check = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
2015
1786
|
return check
|
|
2016
1787
|
}
|
|
2017
|
-
async getRepoHeadStatus(repoRelPath) {
|
|
1788
|
+
async getRepoHeadStatus(repoRelPath, options = {}) {
|
|
2018
1789
|
const repoParam = repoRelPath || ""
|
|
2019
1790
|
const dir = repoParam ? this.kernel.path("api", repoParam) : this.kernel.path("api")
|
|
2020
1791
|
|
|
@@ -2027,19 +1798,21 @@ class Server {
|
|
|
2027
1798
|
return { changes: [], git_commit_url: null }
|
|
2028
1799
|
}
|
|
2029
1800
|
|
|
2030
|
-
let gitIgnoreEngine = null
|
|
2031
1801
|
let workspaceName = null
|
|
1802
|
+
let workspaceRoot = null
|
|
2032
1803
|
let workspaceRelBase = ''
|
|
1804
|
+
let gitIgnoreContext = null
|
|
2033
1805
|
try {
|
|
2034
1806
|
if (this.workspaceStatus && this.kernel && typeof this.kernel.path === 'function') {
|
|
2035
1807
|
if (repoParam && repoParam.length > 0) {
|
|
2036
1808
|
const parts = repoParam.split('/')
|
|
2037
1809
|
workspaceName = parts[0]
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1810
|
+
workspaceRoot = this.kernel.path("api", workspaceName)
|
|
1811
|
+
gitIgnoreContext = options && options.gitIgnoreContext
|
|
1812
|
+
? options.gitIgnoreContext
|
|
1813
|
+
: (typeof this.workspaceStatus.createPathScopedGitIgnoreContext === 'function'
|
|
1814
|
+
? this.workspaceStatus.createPathScopedGitIgnoreContext(workspaceRoot)
|
|
1815
|
+
: null)
|
|
2043
1816
|
if (parts.length > 1) {
|
|
2044
1817
|
workspaceRelBase = parts.slice(1).join('/')
|
|
2045
1818
|
}
|
|
@@ -2116,16 +1889,16 @@ class Server {
|
|
|
2116
1889
|
if (!filepath) {
|
|
2117
1890
|
continue
|
|
2118
1891
|
}
|
|
2119
|
-
if (
|
|
1892
|
+
if (!shouldIncludePath(filepath)) {
|
|
1893
|
+
continue
|
|
1894
|
+
}
|
|
1895
|
+
if (gitIgnoreContext && workspaceName && this.workspaceStatus && typeof this.workspaceStatus.isPathScopedGitIgnored === 'function') {
|
|
2120
1896
|
const workspaceRelative = workspaceRelBase ? `${workspaceRelBase}/${filepath}` : filepath
|
|
2121
1897
|
const normalizedWorkspaceRel = normalizePath(workspaceRelative)
|
|
2122
|
-
if (
|
|
1898
|
+
if (await this.workspaceStatus.isPathScopedGitIgnored(gitIgnoreContext, normalizedWorkspaceRel)) {
|
|
2123
1899
|
continue
|
|
2124
1900
|
}
|
|
2125
1901
|
}
|
|
2126
|
-
if (!shouldIncludePath(filepath)) {
|
|
2127
|
-
continue
|
|
2128
|
-
}
|
|
2129
1902
|
const normalizedFile = normalizePath(filepath)
|
|
2130
1903
|
const absolutePath = path.join(dir, filepath)
|
|
2131
1904
|
let stats
|
|
@@ -2774,12 +2547,15 @@ class Server {
|
|
|
2774
2547
|
async computeWorkspaceGitStatus(workspaceName) {
|
|
2775
2548
|
const workspacePath = this.kernel.path("api", workspaceName)
|
|
2776
2549
|
const repos = await this.kernel.git.repos(workspacePath)
|
|
2550
|
+
const gitIgnoreContext = this.workspaceStatus && typeof this.workspaceStatus.createPathScopedGitIgnoreContext === 'function'
|
|
2551
|
+
? this.workspaceStatus.createPathScopedGitIgnoreContext(workspacePath)
|
|
2552
|
+
: null
|
|
2777
2553
|
|
|
2778
2554
|
const statuses = []
|
|
2779
2555
|
for (const repo of repos) {
|
|
2780
2556
|
const repoParam = repo.gitParentRelPath || workspaceName
|
|
2781
2557
|
try {
|
|
2782
|
-
const { changes, git_commit_url, git_history_url, git_fork_url, git_push_url } = await this.getRepoHeadStatus(repoParam)
|
|
2558
|
+
const { changes, git_commit_url, git_history_url, git_fork_url, git_push_url } = await this.getRepoHeadStatus(repoParam, { gitIgnoreContext })
|
|
2783
2559
|
const historyUrl = git_history_url || (repoParam ? `/info/git/HEAD/${repoParam}` : `/info/git/HEAD/${workspaceName}`)
|
|
2784
2560
|
statuses.push({
|
|
2785
2561
|
name: repo.name,
|
|
@@ -3320,6 +3096,7 @@ class Server {
|
|
|
3320
3096
|
const result = {
|
|
3321
3097
|
portal: this.portal,
|
|
3322
3098
|
projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
|
|
3099
|
+
name: (pathComponents.length > 0 ? pathComponents[0] : ''),
|
|
3323
3100
|
protection_app_id: protectionAppId,
|
|
3324
3101
|
protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
|
|
3325
3102
|
active_process_wait: activeProcessWait ? {
|
|
@@ -3371,6 +3148,9 @@ class Server {
|
|
|
3371
3148
|
taskSaveWorkspacesRoot: this.kernel.path("workspaces"),
|
|
3372
3149
|
script_id: (req.base ? `${full_filepath}?cwd=${req.query.cwd}` : null),
|
|
3373
3150
|
script_path: (req.base ? full_filepath : null),
|
|
3151
|
+
launch_requirements_status_enabled: this.kernel && typeof this.kernel.hasLaunchRequirementConfig === "function"
|
|
3152
|
+
? await this.kernel.hasLaunchRequirementConfig(filepath)
|
|
3153
|
+
: false,
|
|
3374
3154
|
}
|
|
3375
3155
|
|
|
3376
3156
|
res.render(template, result)
|
|
@@ -3596,6 +3376,9 @@ class Server {
|
|
|
3596
3376
|
|
|
3597
3377
|
|
|
3598
3378
|
let index = 0
|
|
3379
|
+
const homeStartupDisplayGraph = !this.kernel.launch_complete
|
|
3380
|
+
? await this.autolaunch.buildHomeStartupDisplayGraph()
|
|
3381
|
+
: null
|
|
3599
3382
|
for(let i=0; i<items.length; i++) {
|
|
3600
3383
|
let item = items[i]
|
|
3601
3384
|
let launcher = await this.kernel.api.launcher(item.name)
|
|
@@ -3793,7 +3576,14 @@ class Server {
|
|
|
3793
3576
|
if (this.is_subpath(api_path, key)) {
|
|
3794
3577
|
// scripts inside api folder
|
|
3795
3578
|
if (this.is_subpath(p, key)) {
|
|
3796
|
-
|
|
3579
|
+
const progress = this.kernel && typeof this.kernel.getScriptProgress === "function"
|
|
3580
|
+
? this.kernel.getScriptProgress(key)
|
|
3581
|
+
: null
|
|
3582
|
+
items[i].running_scripts.push({
|
|
3583
|
+
path: path.relative(this.kernel.homedir, key),
|
|
3584
|
+
name: path.relative(p, key),
|
|
3585
|
+
...(progress || {})
|
|
3586
|
+
})
|
|
3797
3587
|
}
|
|
3798
3588
|
} else {
|
|
3799
3589
|
// other global scripts
|
|
@@ -3810,6 +3600,14 @@ class Server {
|
|
|
3810
3600
|
}
|
|
3811
3601
|
}
|
|
3812
3602
|
items[i].terminal_online_count = userTerminalShellMatches.length + pluginTerminalRunCount
|
|
3603
|
+
if (items[i].running && !this.kernel.launch_complete) {
|
|
3604
|
+
const status = this.kernel.autolaunch_status && this.kernel.autolaunch_status.apps
|
|
3605
|
+
? this.kernel.autolaunch_status.apps[items[i].name]
|
|
3606
|
+
: null
|
|
3607
|
+
if (status && !["ready", "blocked"].includes(status.state || "")) {
|
|
3608
|
+
await this.autolaunch.applyHomeStartingState(items[i], items[i].index, homeStartupDisplayGraph)
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3813
3611
|
if (!items[i].running && shellMatches && shellMatches.length > 0) {
|
|
3814
3612
|
running.push(items[i])
|
|
3815
3613
|
items[i].running = true
|
|
@@ -3818,9 +3616,14 @@ class Server {
|
|
|
3818
3616
|
index++
|
|
3819
3617
|
}
|
|
3820
3618
|
if (!items[i].running) {
|
|
3821
|
-
items[i]
|
|
3822
|
-
|
|
3823
|
-
|
|
3619
|
+
if (await this.autolaunch.applyHomeStartingState(items[i], index, homeStartupDisplayGraph)) {
|
|
3620
|
+
running.push(items[i])
|
|
3621
|
+
index++
|
|
3622
|
+
} else {
|
|
3623
|
+
items[i].index = index
|
|
3624
|
+
index++;
|
|
3625
|
+
notRunning.push(items[i])
|
|
3626
|
+
}
|
|
3824
3627
|
}
|
|
3825
3628
|
}
|
|
3826
3629
|
}
|
|
@@ -6346,7 +6149,6 @@ class Server {
|
|
|
6346
6149
|
let str = await Environment.ENV("system", this.kernel.homedir, this.kernel)
|
|
6347
6150
|
await fs.promises.writeFile(path.resolve(this.kernel.homedir, "ENVIRONMENT"), str)
|
|
6348
6151
|
}
|
|
6349
|
-
this.workspaceStatus.ensureWatcher('api', this.kernel.path('api')).catch(() => {})
|
|
6350
6152
|
}
|
|
6351
6153
|
|
|
6352
6154
|
|
|
@@ -6613,88 +6415,64 @@ class Server {
|
|
|
6613
6415
|
}
|
|
6614
6416
|
})
|
|
6615
6417
|
*/
|
|
6616
|
-
this.
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6418
|
+
this.autolaunch.registerRoutes()
|
|
6419
|
+
this.app.get("/pinokio/launch-requirements/:app", ex(async (req, res) => {
|
|
6420
|
+
const appId = this.kernel && typeof this.kernel.normalizeAppId === "function"
|
|
6421
|
+
? this.kernel.normalizeAppId(req.params.app)
|
|
6422
|
+
: ""
|
|
6423
|
+
const status = this.kernel && typeof this.kernel.getLaunchRequirementsStatus === "function"
|
|
6424
|
+
? this.kernel.getLaunchRequirementsStatus(appId || req.params.app)
|
|
6425
|
+
: null
|
|
6426
|
+
res.json({
|
|
6427
|
+
ok: true,
|
|
6428
|
+
status
|
|
6429
|
+
})
|
|
6624
6430
|
}))
|
|
6625
|
-
this.app.post("/
|
|
6626
|
-
const
|
|
6627
|
-
|
|
6628
|
-
|
|
6431
|
+
this.app.post("/pinokio/launch-requirements/:app/cancel", ex(async (req, res) => {
|
|
6432
|
+
const appId = this.kernel && typeof this.kernel.normalizeAppId === "function"
|
|
6433
|
+
? this.kernel.normalizeAppId(req.params.app)
|
|
6434
|
+
: ""
|
|
6435
|
+
if (!appId) {
|
|
6436
|
+
res.status(400).json({ ok: false, error: "Invalid app." })
|
|
6629
6437
|
return
|
|
6630
6438
|
}
|
|
6631
|
-
const
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6439
|
+
const status = this.kernel && typeof this.kernel.getLaunchRequirementsStatus === "function"
|
|
6440
|
+
? this.kernel.getLaunchRequirementsStatus(appId)
|
|
6441
|
+
: null
|
|
6442
|
+
let script = status && typeof status.script === "string" ? status.script.trim() : ""
|
|
6443
|
+
if (!script) {
|
|
6444
|
+
const appRoot = this.kernel.path("api", appId)
|
|
6445
|
+
const env = await Environment.get(appRoot, this.kernel).catch(() => ({}))
|
|
6446
|
+
script = Environment.getScriptAutolaunch(env)
|
|
6447
|
+
}
|
|
6448
|
+
const launchPath = script ? path.resolve(this.kernel.path("api", appId), script) : ""
|
|
6449
|
+
const cancelled = !!(launchPath && this.kernel.launchRequirements && typeof this.kernel.launchRequirements.cancel === "function" && this.kernel.launchRequirements.cancel(launchPath, { force: true }))
|
|
6450
|
+
const running = this.kernel && this.kernel.api && this.kernel.api.running ? this.kernel.api.running : {}
|
|
6451
|
+
const runningKeys = launchPath
|
|
6452
|
+
? Object.keys(running).filter((key) => {
|
|
6453
|
+
const value = String(key || "")
|
|
6454
|
+
return value === launchPath || value.startsWith(`${launchPath}?`) || value.startsWith(`${launchPath}&`)
|
|
6637
6455
|
})
|
|
6456
|
+
: []
|
|
6457
|
+
for (const key of runningKeys) {
|
|
6458
|
+
if (this.kernel && this.kernel.api && typeof this.kernel.api.stop === "function") {
|
|
6459
|
+
await this.kernel.api.stop({ params: { id: key } })
|
|
6638
6460
|
}
|
|
6639
|
-
res.json({
|
|
6640
|
-
ok: true,
|
|
6641
|
-
app: await this.buildAutolaunchAppState(app)
|
|
6642
|
-
})
|
|
6643
|
-
return
|
|
6644
6461
|
}
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6462
|
+
if ((cancelled || runningKeys.length > 0) && this.kernel && this.kernel.api && typeof this.kernel.api.ondata === "function") {
|
|
6463
|
+
this.kernel.api.ondata({
|
|
6464
|
+
id: launchPath,
|
|
6465
|
+
type: "launch.requirements.control",
|
|
6466
|
+
data: {
|
|
6467
|
+
action: "cancelled",
|
|
6468
|
+
app_id: appId
|
|
6469
|
+
}
|
|
6652
6470
|
})
|
|
6653
|
-
return
|
|
6654
6471
|
}
|
|
6655
|
-
const initialized = await Environment.init({ name: app.id }, this.kernel)
|
|
6656
|
-
await Util.update_env(initialized.env_path, {
|
|
6657
|
-
PINOKIO_SCRIPT_AUTOLAUNCH: resolved.script
|
|
6658
|
-
})
|
|
6659
6472
|
res.json({
|
|
6660
6473
|
ok: true,
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
}))
|
|
6664
|
-
this.app.post("/autolaunch/disable-all", ex(async (req, res) => {
|
|
6665
|
-
const apps = await this.kernel.api.listApps()
|
|
6666
|
-
let disabled = 0
|
|
6667
|
-
for (const app of apps) {
|
|
6668
|
-
const envInfo = await this.getAutolaunchEnvInfo(app)
|
|
6669
|
-
if (envInfo.exists && envInfo.value) {
|
|
6670
|
-
await Util.update_env(envInfo.envPath, {
|
|
6671
|
-
PINOKIO_SCRIPT_AUTOLAUNCH: ""
|
|
6672
|
-
})
|
|
6673
|
-
disabled++
|
|
6674
|
-
}
|
|
6675
|
-
}
|
|
6676
|
-
res.json({
|
|
6677
|
-
ok: true,
|
|
6678
|
-
disabled,
|
|
6679
|
-
apps: await this.buildAutolaunchAppsState()
|
|
6680
|
-
})
|
|
6681
|
-
}))
|
|
6682
|
-
this.app.get("/autolaunch", ex(async (req, res) => {
|
|
6683
|
-
const peerAccess = await this.composePeerAccessPayload()
|
|
6684
|
-
const list = this.getPeers()
|
|
6685
|
-
const apps = await this.buildAutolaunchAppsState()
|
|
6686
|
-
const appsJson = JSON.stringify(apps).replace(/</g, "\\u003c")
|
|
6687
|
-
res.render("autolaunch", {
|
|
6688
|
-
current_host: this.kernel.peer.host,
|
|
6689
|
-
...peerAccess,
|
|
6690
|
-
apps,
|
|
6691
|
-
appsJson,
|
|
6692
|
-
enabledCount: apps.filter((app) => app.autolaunch_enabled).length,
|
|
6693
|
-
portal: this.portal,
|
|
6694
|
-
logo: this.logo,
|
|
6695
|
-
theme: this.theme,
|
|
6696
|
-
agent: req.agent,
|
|
6697
|
-
list,
|
|
6474
|
+
cancelled,
|
|
6475
|
+
stopped: runningKeys.length > 0
|
|
6698
6476
|
})
|
|
6699
6477
|
}))
|
|
6700
6478
|
this.app.get("/tools", ex(async (req, res) => {
|
|
@@ -7230,6 +7008,7 @@ class Server {
|
|
|
7230
7008
|
res.json({ ok: false, error: err && err.message ? err.message : 'Clone failed' })
|
|
7231
7009
|
return
|
|
7232
7010
|
}
|
|
7011
|
+
await this.refreshTopLevelAppInventory()
|
|
7233
7012
|
res.json({ ok: true, redirect: `/p/${encodeURIComponent(folder)}` })
|
|
7234
7013
|
return
|
|
7235
7014
|
}
|
|
@@ -7258,6 +7037,7 @@ class Server {
|
|
|
7258
7037
|
if (this.kernel.git && this.kernel.git.activeSnapshot) {
|
|
7259
7038
|
this.kernel.git.activeSnapshot[folder] = { id: snapshot.id, remoteKey }
|
|
7260
7039
|
}
|
|
7040
|
+
await this.refreshTopLevelAppInventory()
|
|
7261
7041
|
res.json({ ok: true, redirect: `/p/${encodeURIComponent(folder)}` })
|
|
7262
7042
|
}))
|
|
7263
7043
|
this.app.post("/checkpoints/delete", ex(async (req, res) => {
|
|
@@ -10929,6 +10709,9 @@ class Server {
|
|
|
10929
10709
|
tool: selectedTool,
|
|
10930
10710
|
uploadToken: ""
|
|
10931
10711
|
})
|
|
10712
|
+
if (payload && payload.ok) {
|
|
10713
|
+
await this.refreshTopLevelAppInventory()
|
|
10714
|
+
}
|
|
10932
10715
|
res.redirect(payload.url)
|
|
10933
10716
|
} catch (error) {
|
|
10934
10717
|
await renderTaskLaunchPage(req, res, task, {
|
|
@@ -11217,6 +11000,7 @@ class Server {
|
|
|
11217
11000
|
return
|
|
11218
11001
|
}
|
|
11219
11002
|
|
|
11003
|
+
await this.refreshTopLevelAppInventory()
|
|
11220
11004
|
res.json({
|
|
11221
11005
|
ok: true,
|
|
11222
11006
|
folder: folderName,
|
|
@@ -11532,6 +11316,7 @@ class Server {
|
|
|
11532
11316
|
})
|
|
11533
11317
|
|
|
11534
11318
|
if (intent === "create_app") {
|
|
11319
|
+
await this.refreshTopLevelAppInventory()
|
|
11535
11320
|
res.json({
|
|
11536
11321
|
ok: true,
|
|
11537
11322
|
url: `/initialize/${encodeURIComponent(requestedFolderName)}`
|
|
@@ -11581,6 +11366,10 @@ class Server {
|
|
|
11581
11366
|
name: typeof body.name === "string" ? body.name : "",
|
|
11582
11367
|
id: typeof body.id === "string" ? body.id : ""
|
|
11583
11368
|
})
|
|
11369
|
+
const intent = typeof body.intent === "string" ? body.intent.trim().toLowerCase() : ""
|
|
11370
|
+
if (intent === "create_app") {
|
|
11371
|
+
await this.refreshTopLevelAppInventory()
|
|
11372
|
+
}
|
|
11584
11373
|
res.json({
|
|
11585
11374
|
ok: true,
|
|
11586
11375
|
...finalized
|
|
@@ -11602,6 +11391,9 @@ class Server {
|
|
|
11602
11391
|
tool: body.tool,
|
|
11603
11392
|
uploadToken: body.uploadToken
|
|
11604
11393
|
})
|
|
11394
|
+
if (payload && payload.ok) {
|
|
11395
|
+
await this.refreshTopLevelAppInventory()
|
|
11396
|
+
}
|
|
11605
11397
|
res.json(payload)
|
|
11606
11398
|
} catch (error) {
|
|
11607
11399
|
const status = Number.isInteger(error && error.status) ? error.status : 500
|
|
@@ -14614,16 +14406,10 @@ class Server {
|
|
|
14614
14406
|
}))
|
|
14615
14407
|
this.app.get("/gitdiff/:ref/*", ex(async (req, res) => {
|
|
14616
14408
|
let fullpath = this.kernel.path("api", req.params[0])
|
|
14617
|
-
let dir
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
return
|
|
14621
|
-
})
|
|
14622
|
-
for(let d of dirs) {
|
|
14623
|
-
if (fullpath.startsWith(d)) {
|
|
14624
|
-
dir = d
|
|
14625
|
-
break
|
|
14626
|
-
}
|
|
14409
|
+
let dir = await this.kernel.git.findRepoRootForPath(fullpath)
|
|
14410
|
+
if (!dir) {
|
|
14411
|
+
res.status(404).json({ error: "Repository not found" })
|
|
14412
|
+
return
|
|
14627
14413
|
}
|
|
14628
14414
|
let filepath = path.relative(dir, fullpath)
|
|
14629
14415
|
let binary = false;
|
|
@@ -15686,7 +15472,7 @@ class Server {
|
|
|
15686
15472
|
}))
|
|
15687
15473
|
this.app.get("/info/api", ex(async (req,res) => {
|
|
15688
15474
|
// api related info
|
|
15689
|
-
let repo = this.
|
|
15475
|
+
let repo = await this.findApiRepoByGitUrl(req.query.git)
|
|
15690
15476
|
if (repo) {
|
|
15691
15477
|
let repos = await this.kernel.git.repos(repo.path)
|
|
15692
15478
|
repos = repos.filter((r) => {
|
|
@@ -15876,6 +15662,7 @@ class Server {
|
|
|
15876
15662
|
resolve()
|
|
15877
15663
|
}, 2000)
|
|
15878
15664
|
})
|
|
15665
|
+
await this.refreshTopLevelAppInventory()
|
|
15879
15666
|
res.json({ success: true })
|
|
15880
15667
|
}
|
|
15881
15668
|
} catch(err) {
|
|
@@ -15974,6 +15761,43 @@ class Server {
|
|
|
15974
15761
|
delete info.memory
|
|
15975
15762
|
res.json(info)
|
|
15976
15763
|
}))
|
|
15764
|
+
this.app.get("/pinokio/home_status", ex((req, res) => {
|
|
15765
|
+
const runningMap = this.kernel && this.kernel.api && this.kernel.api.running
|
|
15766
|
+
? this.kernel.api.running
|
|
15767
|
+
: {}
|
|
15768
|
+
const apiRoot = this.kernel.path("api")
|
|
15769
|
+
const runningApps = new Set()
|
|
15770
|
+
const runningScripts = []
|
|
15771
|
+
for (const key of Object.keys(runningMap)) {
|
|
15772
|
+
if (typeof key !== "string" || !path.isAbsolute(key) || !this.is_subpath(apiRoot, key)) {
|
|
15773
|
+
continue
|
|
15774
|
+
}
|
|
15775
|
+
const relativePath = path.relative(apiRoot, key)
|
|
15776
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
15777
|
+
continue
|
|
15778
|
+
}
|
|
15779
|
+
const app = relativePath.split(path.sep)[0]
|
|
15780
|
+
if (!app) {
|
|
15781
|
+
continue
|
|
15782
|
+
}
|
|
15783
|
+
runningApps.add(app)
|
|
15784
|
+
runningScripts.push({
|
|
15785
|
+
id: key,
|
|
15786
|
+
app,
|
|
15787
|
+
path: relativePath,
|
|
15788
|
+
home_path: path.relative(this.kernel.homedir, key),
|
|
15789
|
+
script_path: path.relative(path.resolve(apiRoot, app), key),
|
|
15790
|
+
...((this.kernel && typeof this.kernel.getScriptProgress === "function" && this.kernel.getScriptProgress(key)) || {})
|
|
15791
|
+
})
|
|
15792
|
+
}
|
|
15793
|
+
res.json({
|
|
15794
|
+
launch_complete: this.kernel.launch_complete,
|
|
15795
|
+
running_apps: Array.from(runningApps),
|
|
15796
|
+
running_scripts: runningScripts,
|
|
15797
|
+
autolaunch: this.kernel.autolaunch_status || { apps: {} },
|
|
15798
|
+
startup: this.getStartupStatus()
|
|
15799
|
+
})
|
|
15800
|
+
}))
|
|
15977
15801
|
this.app.get("/pinokio/home", ex((req, res) => {
|
|
15978
15802
|
res.json({
|
|
15979
15803
|
path: this.kernel.homedir ? path.resolve(this.kernel.homedir) : null
|
|
@@ -16123,6 +15947,7 @@ class Server {
|
|
|
16123
15947
|
*/
|
|
16124
15948
|
|
|
16125
15949
|
let formData = req.body
|
|
15950
|
+
let appInventoryChanged = false
|
|
16126
15951
|
for(let key in req.files) {
|
|
16127
15952
|
let file = req.files[key]
|
|
16128
15953
|
formData[file.fieldname] = file.buffer
|
|
@@ -16137,6 +15962,7 @@ class Server {
|
|
|
16137
15962
|
let new_path = this.kernel.path("api", formData.new_path)
|
|
16138
15963
|
|
|
16139
15964
|
await fs.promises.cp(old_path, new_path, { recursive: true })
|
|
15965
|
+
appInventoryChanged = true
|
|
16140
15966
|
|
|
16141
15967
|
// 2. edit meta in the new_path
|
|
16142
15968
|
await this.kernel.api.updateMeta(formData, formData.new_path)
|
|
@@ -16149,6 +15975,7 @@ class Server {
|
|
|
16149
15975
|
let old_path = this.kernel.path("api", formData.old_path)
|
|
16150
15976
|
let new_path = this.kernel.path("api", formData.new_path)
|
|
16151
15977
|
await fs.promises.rename(old_path, new_path)
|
|
15978
|
+
appInventoryChanged = true
|
|
16152
15979
|
}
|
|
16153
15980
|
|
|
16154
15981
|
// 2. edit meta in the new_path
|
|
@@ -16165,15 +15992,20 @@ class Server {
|
|
|
16165
15992
|
let old_path = this.kernel.path("api", formData.old_path)
|
|
16166
15993
|
let new_path = this.kernel.path("api", formData.new_path)
|
|
16167
15994
|
await fs.promises.cp(old_path, new_path, { recursive: true })
|
|
15995
|
+
appInventoryChanged = true
|
|
16168
15996
|
} else if (formData.move) {
|
|
16169
15997
|
// 2. move only
|
|
16170
15998
|
let old_path = this.kernel.path("api", formData.old_path)
|
|
16171
15999
|
let new_path = this.kernel.path("api", formData.new_path)
|
|
16172
16000
|
await fs.promises.rename(old_path, new_path)
|
|
16001
|
+
appInventoryChanged = true
|
|
16173
16002
|
} else {
|
|
16174
16003
|
// nothing
|
|
16175
16004
|
}
|
|
16176
16005
|
}
|
|
16006
|
+
if (appInventoryChanged) {
|
|
16007
|
+
await this.refreshTopLevelAppInventory()
|
|
16008
|
+
}
|
|
16177
16009
|
res.json({
|
|
16178
16010
|
success: true,
|
|
16179
16011
|
reload: formData.new_path,
|
|
@@ -16403,6 +16235,7 @@ class Server {
|
|
|
16403
16235
|
path: this.kernel.api.userdir
|
|
16404
16236
|
}, (stream) => {
|
|
16405
16237
|
})
|
|
16238
|
+
await this.refreshTopLevelAppInventory()
|
|
16406
16239
|
res.json({
|
|
16407
16240
|
result: "success"
|
|
16408
16241
|
})
|