pinokiod 7.1.50 → 7.1.52
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/package.json +1 -1
- package/server/index.js +389 -62
- package/server/lib/content_validation.js +601 -0
- package/server/lib/task_packages.js +132 -31
- package/server/public/install.js +156 -69
- package/server/public/tab-idle-notifier.js +87 -2
- package/server/public/tab-link-popover.css +12 -0
- package/server/public/tab-link-popover.js +39 -0
- package/server/public/task-launcher.css +17 -0
- package/server/public/task-launcher.js +236 -0
- package/server/public/universal-launcher.css +29 -0
- package/server/public/universal-launcher.js +154 -37
- package/server/views/app.ejs +160 -17
- package/server/views/invalid_content.ejs +124 -0
- package/server/views/settings.ejs +132 -42
- package/server/views/task_install.ejs +11 -1
- package/server/lib/install_validation.js +0 -237
- package/test/docs-task-screenshots.js +0 -317
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -47,6 +47,12 @@ const LOG_STREAM_INITIAL_BYTES = 512 * 1024
|
|
|
47
47
|
const LOG_STREAM_KEEPALIVE_MS = 25000
|
|
48
48
|
const DEFAULT_REGISTRY_URL = 'https://beta.pinokio.co'
|
|
49
49
|
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])
|
|
50
|
+
const NON_INTERACTIVE_GIT_ENV = {
|
|
51
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
52
|
+
GIT_ASKPASS: "",
|
|
53
|
+
SSH_ASKPASS: "",
|
|
54
|
+
GCM_INTERACTIVE: "never"
|
|
55
|
+
}
|
|
50
56
|
|
|
51
57
|
const ex = fn => (req, res, next) => {
|
|
52
58
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
@@ -72,7 +78,7 @@ const { createDesktopEventRouter } = require("./lib/desktop_event_router")
|
|
|
72
78
|
const { createInjectRouter, resolveInjectList } = require("./lib/inject_router")
|
|
73
79
|
const { createTaskPackageService } = require("./lib/task_packages")
|
|
74
80
|
const { createTaskWorkspaceLinkService } = require("./lib/task_workspace_links")
|
|
75
|
-
const {
|
|
81
|
+
const { createContentValidationService } = require("./lib/content_validation")
|
|
76
82
|
const AppRegistryService = require("./lib/app_registry")
|
|
77
83
|
const AppLogService = require("./lib/app_logs")
|
|
78
84
|
const AppSearchService = require("./lib/app_search")
|
|
@@ -1016,11 +1022,74 @@ class Server {
|
|
|
1016
1022
|
//
|
|
1017
1023
|
// return current_urls
|
|
1018
1024
|
}
|
|
1025
|
+
async buildShellSidebarContext(req) {
|
|
1026
|
+
const peerAccess = await this.composePeerAccessPayload()
|
|
1027
|
+
return {
|
|
1028
|
+
current_host: this.kernel.peer.host,
|
|
1029
|
+
...peerAccess,
|
|
1030
|
+
portal: this.portal,
|
|
1031
|
+
logo: this.logo,
|
|
1032
|
+
theme: this.theme,
|
|
1033
|
+
agent: req.agent,
|
|
1034
|
+
list: this.getPeers(),
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async renderInvalidContentPage(req, res, invalid, options = {}) {
|
|
1038
|
+
const type = invalid && typeof invalid.type === "string" ? invalid.type : "app"
|
|
1039
|
+
const sidebarSelected = options.sidebarSelected || (type === "plugin" ? "plugins" : type === "task" ? "tasks" : "home")
|
|
1040
|
+
const sidebarContext = await this.buildShellSidebarContext(req)
|
|
1041
|
+
const backHref = typeof options.backHref === "string"
|
|
1042
|
+
? options.backHref
|
|
1043
|
+
: (type === "plugin" ? "/plugins" : type === "task" ? "/tasks" : "/home")
|
|
1044
|
+
const backLabel = typeof options.backLabel === "string"
|
|
1045
|
+
? options.backLabel
|
|
1046
|
+
: "Back"
|
|
1047
|
+
const folderPath = invalid && typeof invalid.folderPath === "string" ? invalid.folderPath : ""
|
|
1048
|
+
const manifestPath = invalid && typeof invalid.manifestPath === "string" ? invalid.manifestPath : ""
|
|
1049
|
+
let folderExists = false
|
|
1050
|
+
let manifestExists = false
|
|
1051
|
+
if (folderPath) {
|
|
1052
|
+
try {
|
|
1053
|
+
await fs.promises.stat(folderPath)
|
|
1054
|
+
folderExists = true
|
|
1055
|
+
} catch (_) {}
|
|
1056
|
+
}
|
|
1057
|
+
if (manifestPath) {
|
|
1058
|
+
try {
|
|
1059
|
+
await fs.promises.stat(manifestPath)
|
|
1060
|
+
manifestExists = true
|
|
1061
|
+
} catch (_) {}
|
|
1062
|
+
}
|
|
1063
|
+
res.status(Number.isInteger(options.status) ? options.status : 422).render("invalid_content", {
|
|
1064
|
+
...sidebarContext,
|
|
1065
|
+
theme: this.theme,
|
|
1066
|
+
agent: req.agent,
|
|
1067
|
+
invalid: {
|
|
1068
|
+
...invalid,
|
|
1069
|
+
folderExists,
|
|
1070
|
+
manifestExists,
|
|
1071
|
+
},
|
|
1072
|
+
sidebarSelected,
|
|
1073
|
+
backHref,
|
|
1074
|
+
backLabel,
|
|
1075
|
+
})
|
|
1076
|
+
}
|
|
1019
1077
|
|
|
1020
1078
|
async chrome(req, res, type, options) {
|
|
1021
1079
|
console.log("Chrome")
|
|
1022
1080
|
|
|
1023
1081
|
let name = req.params.name
|
|
1082
|
+
if (this.contentValidation) {
|
|
1083
|
+
const validation = await this.contentValidation.validateAppByName(name)
|
|
1084
|
+
if (validation && !validation.valid) {
|
|
1085
|
+
await this.renderInvalidContentPage(req, res, validation, {
|
|
1086
|
+
sidebarSelected: "home",
|
|
1087
|
+
backHref: "/home",
|
|
1088
|
+
backLabel: "Back to Home",
|
|
1089
|
+
})
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1024
1093
|
console.time("bin check")
|
|
1025
1094
|
let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
|
|
1026
1095
|
bin: this.kernel.bin.preset("dev"),
|
|
@@ -1188,9 +1257,10 @@ class Server {
|
|
|
1188
1257
|
}
|
|
1189
1258
|
|
|
1190
1259
|
let editor_tab = `/pinokio/fileview/${encodeURIComponent(name)}`
|
|
1260
|
+
const tabsStorageKey = `${name}:${type}`
|
|
1191
1261
|
let savedTabs = []
|
|
1192
|
-
if (Array.isArray(this.tabs[
|
|
1193
|
-
savedTabs = this.tabs[
|
|
1262
|
+
if (Array.isArray(this.tabs[tabsStorageKey])) {
|
|
1263
|
+
savedTabs = this.tabs[tabsStorageKey].filter((entry) => {
|
|
1194
1264
|
const href = typeof entry === "string"
|
|
1195
1265
|
? entry
|
|
1196
1266
|
: (entry && typeof entry.href === "string" ? entry.href : "")
|
|
@@ -2320,6 +2390,26 @@ class Server {
|
|
|
2320
2390
|
filepath = full_filepath
|
|
2321
2391
|
}
|
|
2322
2392
|
|
|
2393
|
+
if ((req.action || req.originalUrl.startsWith("/run/")) && this.contentValidation) {
|
|
2394
|
+
const validation = await this.contentValidation.validateRunPath(pathComponents)
|
|
2395
|
+
if (validation && !validation.valid) {
|
|
2396
|
+
await this.renderInvalidContentPage(req, res, validation, {
|
|
2397
|
+
sidebarSelected: validation.type === "plugin" ? "plugins" : validation.type === "task" ? "tasks" : "home",
|
|
2398
|
+
backHref: validation.type === "plugin"
|
|
2399
|
+
? (validation.detailUrl || "/plugins")
|
|
2400
|
+
: validation.type === "task"
|
|
2401
|
+
? (validation.detailUrl || "/tasks")
|
|
2402
|
+
: (validation.detailUrl || "/home"),
|
|
2403
|
+
backLabel: validation.type === "plugin"
|
|
2404
|
+
? "Back to Plugin"
|
|
2405
|
+
: validation.type === "task"
|
|
2406
|
+
? "Back to Task"
|
|
2407
|
+
: "Back to App",
|
|
2408
|
+
})
|
|
2409
|
+
return
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2323
2413
|
// check if it's a folder or a file
|
|
2324
2414
|
let p = "/api" // run mode
|
|
2325
2415
|
let _p = "/_api" // edit mode
|
|
@@ -6523,7 +6613,8 @@ class Server {
|
|
|
6523
6613
|
try {
|
|
6524
6614
|
await this.kernel.exec({
|
|
6525
6615
|
message: [`git clone "${safeRemote}" "${folder}"`],
|
|
6526
|
-
path: apiRoot
|
|
6616
|
+
path: apiRoot,
|
|
6617
|
+
env: { ...NON_INTERACTIVE_GIT_ENV }
|
|
6527
6618
|
}, () => {})
|
|
6528
6619
|
} catch (err) {
|
|
6529
6620
|
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
@@ -6785,6 +6876,42 @@ class Server {
|
|
|
6785
6876
|
}
|
|
6786
6877
|
return plugins.find((plugin) => plugin && plugin.pluginKey === targetKey) || null
|
|
6787
6878
|
}
|
|
6879
|
+
const buildSerializedPluginFromValidation = (validation) => {
|
|
6880
|
+
const context = validation && validation.context && typeof validation.context === "object"
|
|
6881
|
+
? validation.context
|
|
6882
|
+
: null
|
|
6883
|
+
if (!context || !context.pluginPath) {
|
|
6884
|
+
return null
|
|
6885
|
+
}
|
|
6886
|
+
const normalizedPluginPath = normalizePluginPath(context.pluginPath)
|
|
6887
|
+
if (!normalizedPluginPath) {
|
|
6888
|
+
return null
|
|
6889
|
+
}
|
|
6890
|
+
const config = context.config && typeof context.config === "object" ? context.config : {}
|
|
6891
|
+
const category = classifyPluginMenuItem(config)
|
|
6892
|
+
return {
|
|
6893
|
+
index: -1,
|
|
6894
|
+
title: context.title || "Plugin",
|
|
6895
|
+
description: typeof config.description === "string" ? config.description : "",
|
|
6896
|
+
href: `/run${normalizedPluginPath}`,
|
|
6897
|
+
link: typeof config.link === "string" ? config.link : "",
|
|
6898
|
+
image: context.image || null,
|
|
6899
|
+
icon: null,
|
|
6900
|
+
default: false,
|
|
6901
|
+
pluginPath: normalizedPluginPath,
|
|
6902
|
+
pluginKey: normalizePluginLookupKey(normalizedPluginPath),
|
|
6903
|
+
extraParams: [],
|
|
6904
|
+
defaultCwd: "",
|
|
6905
|
+
ownerApp: null,
|
|
6906
|
+
hasInstall: !!context.hasInstall,
|
|
6907
|
+
hasUninstall: !!context.hasUninstall,
|
|
6908
|
+
hasUpdate: !!context.hasUpdate,
|
|
6909
|
+
category,
|
|
6910
|
+
categoryTitle: category === "ide" ? "Desktop Plugin" : "Terminal Plugin",
|
|
6911
|
+
categorySubtitle: category === "ide" ? "Launch externally" : "Launch in Pinokio",
|
|
6912
|
+
detailUrl: `/plugin?path=${encodeURIComponent(normalizedPluginPath)}`,
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6788
6915
|
const isPathInsideRoot = (candidatePath, rootPath) => {
|
|
6789
6916
|
const relative = path.relative(rootPath, candidatePath)
|
|
6790
6917
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
@@ -7134,8 +7261,17 @@ class Server {
|
|
|
7134
7261
|
res.redirect("/plugins")
|
|
7135
7262
|
return
|
|
7136
7263
|
}
|
|
7264
|
+
const validation = await contentValidation.validatePluginByPath(requestedPath)
|
|
7265
|
+
if (!validation.valid) {
|
|
7266
|
+
await this.renderInvalidContentPage(req, res, validation, {
|
|
7267
|
+
sidebarSelected: "plugins",
|
|
7268
|
+
backHref: "/plugins",
|
|
7269
|
+
backLabel: "Back to Plugins",
|
|
7270
|
+
})
|
|
7271
|
+
return
|
|
7272
|
+
}
|
|
7137
7273
|
const plugins = await loadSerializedPlugins()
|
|
7138
|
-
const plugin = findPluginByPath(plugins, requestedPath)
|
|
7274
|
+
const plugin = findPluginByPath(plugins, requestedPath) || buildSerializedPluginFromValidation(validation)
|
|
7139
7275
|
if (!plugin) {
|
|
7140
7276
|
res.status(404).send("Plugin not found.")
|
|
7141
7277
|
return
|
|
@@ -7994,8 +8130,19 @@ class Server {
|
|
|
7994
8130
|
try {
|
|
7995
8131
|
await this.kernel.exec({
|
|
7996
8132
|
message: [`git clone --depth 1 --single-branch ${shellQuote(normalizedRef)} ${shellQuote(targetPath)}`],
|
|
7997
|
-
path: path.resolve(rootDir)
|
|
8133
|
+
path: path.resolve(rootDir),
|
|
8134
|
+
env: { ...NON_INTERACTIVE_GIT_ENV }
|
|
7998
8135
|
}, () => {})
|
|
8136
|
+
let cloned = false
|
|
8137
|
+
try {
|
|
8138
|
+
const stat = await fs.promises.stat(targetPath)
|
|
8139
|
+
cloned = stat.isDirectory()
|
|
8140
|
+
} catch (_) {}
|
|
8141
|
+
if (!cloned) {
|
|
8142
|
+
const cloneError = new Error("Failed to clone repository.")
|
|
8143
|
+
cloneError.status = 500
|
|
8144
|
+
throw cloneError
|
|
8145
|
+
}
|
|
7999
8146
|
return targetPath
|
|
8000
8147
|
} catch (error) {
|
|
8001
8148
|
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
@@ -8004,6 +8151,112 @@ class Server {
|
|
|
8004
8151
|
throw nextError
|
|
8005
8152
|
}
|
|
8006
8153
|
}
|
|
8154
|
+
const prepareLauncherDownload = async ({ intent, ref, name }) => {
|
|
8155
|
+
const normalizedIntent = typeof intent === "string" ? intent.trim().toLowerCase() : ""
|
|
8156
|
+
if (normalizedIntent !== "create_app" && normalizedIntent !== "create_plugin" && normalizedIntent !== "ask") {
|
|
8157
|
+
const error = new Error("Unsupported download target.")
|
|
8158
|
+
error.status = 400
|
|
8159
|
+
throw error
|
|
8160
|
+
}
|
|
8161
|
+
if (normalizedIntent === "ask") {
|
|
8162
|
+
const preparedTask = await taskPackages.prepareRemoteTaskPackageInstall({ ref })
|
|
8163
|
+
if (preparedTask.existing) {
|
|
8164
|
+
return {
|
|
8165
|
+
existing: true,
|
|
8166
|
+
url: buildTaskPath({ id: preparedTask.id })
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
return {
|
|
8170
|
+
existing: false,
|
|
8171
|
+
clone: {
|
|
8172
|
+
message: `git clone --depth 1 --single-branch ${shellQuote(ref)} ${shellQuote(preparedTask.dir)}`,
|
|
8173
|
+
path: taskPackages.tasksRoot(),
|
|
8174
|
+
env: { ...NON_INTERACTIVE_GIT_ENV }
|
|
8175
|
+
},
|
|
8176
|
+
finalize: {
|
|
8177
|
+
intent: normalizedIntent,
|
|
8178
|
+
id: preparedTask.id,
|
|
8179
|
+
ref: preparedTask.ref
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
|
|
8184
|
+
const folderName = typeof name === "string" ? name.trim() : ""
|
|
8185
|
+
const normalizedRef = normalizeLauncherDownloadRef(ref)
|
|
8186
|
+
if (!normalizedRef) {
|
|
8187
|
+
const error = new Error("Git URL is required.")
|
|
8188
|
+
error.status = 400
|
|
8189
|
+
throw error
|
|
8190
|
+
}
|
|
8191
|
+
if (!folderName) {
|
|
8192
|
+
const error = new Error("Folder name is required.")
|
|
8193
|
+
error.status = 400
|
|
8194
|
+
throw error
|
|
8195
|
+
}
|
|
8196
|
+
|
|
8197
|
+
const rootDir = normalizedIntent === "create_plugin"
|
|
8198
|
+
? path.resolve(this.kernel.path("plugin"))
|
|
8199
|
+
: path.resolve(this.kernel.path("api"))
|
|
8200
|
+
const targetPath = await createLauncherTargetFolder(rootDir, folderName, {
|
|
8201
|
+
createDirectory: false,
|
|
8202
|
+
initializeGit: false
|
|
8203
|
+
})
|
|
8204
|
+
return {
|
|
8205
|
+
existing: false,
|
|
8206
|
+
clone: {
|
|
8207
|
+
message: `git clone --depth 1 --single-branch ${shellQuote(normalizedRef)} ${shellQuote(targetPath)}`,
|
|
8208
|
+
path: path.resolve(rootDir),
|
|
8209
|
+
env: { ...NON_INTERACTIVE_GIT_ENV }
|
|
8210
|
+
},
|
|
8211
|
+
finalize: {
|
|
8212
|
+
intent: normalizedIntent,
|
|
8213
|
+
name: folderName
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
const finalizeLauncherDownload = async ({ intent, ref, name, id }) => {
|
|
8218
|
+
const normalizedIntent = typeof intent === "string" ? intent.trim().toLowerCase() : ""
|
|
8219
|
+
if (normalizedIntent === "ask") {
|
|
8220
|
+
const task = await taskPackages.finalizeRemoteTaskPackageInstall({ id, ref })
|
|
8221
|
+
return {
|
|
8222
|
+
url: buildTaskPath({ id: task.id })
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
if (normalizedIntent !== "create_app" && normalizedIntent !== "create_plugin") {
|
|
8226
|
+
const error = new Error("Unsupported download target.")
|
|
8227
|
+
error.status = 400
|
|
8228
|
+
throw error
|
|
8229
|
+
}
|
|
8230
|
+
const folderName = typeof name === "string" ? name.trim() : ""
|
|
8231
|
+
if (!folderName) {
|
|
8232
|
+
const error = new Error("Folder name is required.")
|
|
8233
|
+
error.status = 400
|
|
8234
|
+
throw error
|
|
8235
|
+
}
|
|
8236
|
+
const rootDir = normalizedIntent === "create_plugin"
|
|
8237
|
+
? path.resolve(this.kernel.path("plugin"))
|
|
8238
|
+
: path.resolve(this.kernel.path("api"))
|
|
8239
|
+
const targetPath = path.resolve(rootDir, folderName)
|
|
8240
|
+
let cloned = false
|
|
8241
|
+
try {
|
|
8242
|
+
const stat = await fs.promises.stat(targetPath)
|
|
8243
|
+
cloned = stat.isDirectory()
|
|
8244
|
+
} catch (_) {}
|
|
8245
|
+
if (!cloned) {
|
|
8246
|
+
const error = new Error("Failed to clone repository.")
|
|
8247
|
+
error.status = 500
|
|
8248
|
+
throw error
|
|
8249
|
+
}
|
|
8250
|
+
if (normalizedIntent === "create_app") {
|
|
8251
|
+
return {
|
|
8252
|
+
url: `/initialize/${encodeURIComponent(folderName)}`
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
const relativePluginPath = `plugin/${folderName}`
|
|
8256
|
+
return {
|
|
8257
|
+
url: `/plugin?path=${encodeURIComponent(relativePluginPath)}&downloaded=1`
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8007
8260
|
const createUniversalLauncherSessionId = () => {
|
|
8008
8261
|
if (typeof crypto.randomUUID === "function") {
|
|
8009
8262
|
return crypto.randomUUID()
|
|
@@ -8067,9 +8320,10 @@ class Server {
|
|
|
8067
8320
|
const taskPackages = createTaskPackageService({
|
|
8068
8321
|
kernel: this.kernel
|
|
8069
8322
|
})
|
|
8070
|
-
const
|
|
8323
|
+
const contentValidation = createContentValidationService({
|
|
8071
8324
|
kernel: this.kernel
|
|
8072
8325
|
})
|
|
8326
|
+
this.contentValidation = contentValidation
|
|
8073
8327
|
const taskWorkspaceLinks = createTaskWorkspaceLinkService({
|
|
8074
8328
|
kernel: this.kernel
|
|
8075
8329
|
})
|
|
@@ -8299,6 +8553,52 @@ class Server {
|
|
|
8299
8553
|
list: this.getPeers(),
|
|
8300
8554
|
}
|
|
8301
8555
|
}
|
|
8556
|
+
const resolveTaskForOpen = async ({ id, ref }) => {
|
|
8557
|
+
const normalizedId = typeof id === "string" ? taskPackages.normalizeTaskId(id) : ""
|
|
8558
|
+
if (normalizedId) {
|
|
8559
|
+
const index = await taskPackages.readTaskIndex()
|
|
8560
|
+
const existsInIndex = Array.isArray(index && index.items) && index.items.some((entry) => entry && entry.id === normalizedId)
|
|
8561
|
+
if (!existsInIndex) {
|
|
8562
|
+
return { missing: true }
|
|
8563
|
+
}
|
|
8564
|
+
const validation = await contentValidation.validateTaskById(normalizedId)
|
|
8565
|
+
if (!validation.valid) {
|
|
8566
|
+
return { invalid: validation }
|
|
8567
|
+
}
|
|
8568
|
+
const task = await taskPackages.readTaskPackageById(normalizedId)
|
|
8569
|
+
return {
|
|
8570
|
+
task: {
|
|
8571
|
+
...task,
|
|
8572
|
+
ref: ""
|
|
8573
|
+
}
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
|
|
8577
|
+
const normalizedRef = typeof ref === "string" ? taskPackages.normalizeTaskRef(ref) : ""
|
|
8578
|
+
if (!normalizedRef) {
|
|
8579
|
+
return { missing: true }
|
|
8580
|
+
}
|
|
8581
|
+
const match = await taskPackages.findTaskIndexEntryByRef(normalizedRef)
|
|
8582
|
+
if (!match || !match.entry) {
|
|
8583
|
+
return { missing: true }
|
|
8584
|
+
}
|
|
8585
|
+
const validation = await contentValidation.validateTaskById(match.entry.id)
|
|
8586
|
+
if (!validation.valid) {
|
|
8587
|
+
return {
|
|
8588
|
+
invalid: {
|
|
8589
|
+
...validation,
|
|
8590
|
+
detailUrl: buildTaskPath({ ref: match.ref })
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
const task = await taskPackages.readTaskPackageById(match.entry.id)
|
|
8595
|
+
return {
|
|
8596
|
+
task: {
|
|
8597
|
+
...task,
|
|
8598
|
+
ref: match.ref
|
|
8599
|
+
}
|
|
8600
|
+
}
|
|
8601
|
+
}
|
|
8302
8602
|
const renderTaskBuilderPage = async (req, res, options = {}) => {
|
|
8303
8603
|
const defaults = options.defaults && typeof options.defaults === "object" ? options.defaults : {}
|
|
8304
8604
|
const sidebarContext = await buildTaskSidebarContext()
|
|
@@ -9528,11 +9828,19 @@ class Server {
|
|
|
9528
9828
|
return
|
|
9529
9829
|
}
|
|
9530
9830
|
|
|
9531
|
-
const
|
|
9831
|
+
const resolvedTask = await resolveTaskForOpen({
|
|
9532
9832
|
id: requestedId,
|
|
9533
9833
|
ref: requestedRef
|
|
9534
9834
|
})
|
|
9535
|
-
if (
|
|
9835
|
+
if (resolvedTask.invalid) {
|
|
9836
|
+
await this.renderInvalidContentPage(req, res, resolvedTask.invalid, {
|
|
9837
|
+
sidebarSelected: "tasks",
|
|
9838
|
+
backHref: "/tasks",
|
|
9839
|
+
backLabel: "Back to Tasks",
|
|
9840
|
+
})
|
|
9841
|
+
return
|
|
9842
|
+
}
|
|
9843
|
+
if (!resolvedTask.task) {
|
|
9536
9844
|
if (requestedId && !requestedRef) {
|
|
9537
9845
|
res.status(404).send("Task not found.")
|
|
9538
9846
|
return
|
|
@@ -9546,6 +9854,7 @@ class Server {
|
|
|
9546
9854
|
return
|
|
9547
9855
|
}
|
|
9548
9856
|
|
|
9857
|
+
const task = resolvedTask.task
|
|
9549
9858
|
await renderTaskLaunchPage(req, res, task, {
|
|
9550
9859
|
selectedTool,
|
|
9551
9860
|
inputValues,
|
|
@@ -9589,14 +9898,23 @@ class Server {
|
|
|
9589
9898
|
return
|
|
9590
9899
|
}
|
|
9591
9900
|
|
|
9592
|
-
const
|
|
9901
|
+
const resolvedTask = await resolveTaskForOpen({
|
|
9593
9902
|
id: requestedId,
|
|
9594
9903
|
ref: requestedRef
|
|
9595
9904
|
})
|
|
9596
|
-
if (
|
|
9905
|
+
if (resolvedTask.invalid) {
|
|
9906
|
+
await this.renderInvalidContentPage(req, res, resolvedTask.invalid, {
|
|
9907
|
+
sidebarSelected: "tasks",
|
|
9908
|
+
backHref: "/tasks",
|
|
9909
|
+
backLabel: "Back to Tasks",
|
|
9910
|
+
})
|
|
9911
|
+
return
|
|
9912
|
+
}
|
|
9913
|
+
if (!resolvedTask.task) {
|
|
9597
9914
|
res.status(404).send("Task not found.")
|
|
9598
9915
|
return
|
|
9599
9916
|
}
|
|
9917
|
+
const task = resolvedTask.task
|
|
9600
9918
|
|
|
9601
9919
|
const missingRequired = task.inputs.filter((input) => {
|
|
9602
9920
|
if (!input.required) {
|
|
@@ -10238,20 +10556,6 @@ class Server {
|
|
|
10238
10556
|
folderName: requestedFolderName,
|
|
10239
10557
|
ref
|
|
10240
10558
|
})
|
|
10241
|
-
const installRoot = intent === "create_plugin" ? "plugin" : "api"
|
|
10242
|
-
const validation = await installValidation.validateInstalledDirectory({
|
|
10243
|
-
absolutePath: targetPath,
|
|
10244
|
-
installRoot
|
|
10245
|
-
})
|
|
10246
|
-
if (!validation.valid) {
|
|
10247
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
10248
|
-
res.status(422).json({
|
|
10249
|
-
ok: false,
|
|
10250
|
-
error: validation.message || "Downloaded content is invalid.",
|
|
10251
|
-
validation
|
|
10252
|
-
})
|
|
10253
|
-
return
|
|
10254
|
-
}
|
|
10255
10559
|
|
|
10256
10560
|
if (intent === "create_app") {
|
|
10257
10561
|
res.json({
|
|
@@ -10270,8 +10574,48 @@ class Server {
|
|
|
10270
10574
|
const status = Number.isInteger(error && error.status) ? error.status : 500
|
|
10271
10575
|
res.status(status).json({
|
|
10272
10576
|
ok: false,
|
|
10273
|
-
error: error && error.message ? error.message : "Failed to download from Git URL."
|
|
10274
|
-
|
|
10577
|
+
error: error && error.message ? error.message : "Failed to download from Git URL."
|
|
10578
|
+
})
|
|
10579
|
+
}
|
|
10580
|
+
}))
|
|
10581
|
+
this.app.post("/launcher/download/prepare", ex(async (req, res) => {
|
|
10582
|
+
try {
|
|
10583
|
+
const body = req.body && typeof req.body === "object" ? req.body : {}
|
|
10584
|
+
const prepared = await prepareLauncherDownload({
|
|
10585
|
+
intent: typeof body.intent === "string" ? body.intent : "",
|
|
10586
|
+
ref: typeof body.ref === "string" ? body.ref : "",
|
|
10587
|
+
name: typeof body.name === "string" ? body.name : ""
|
|
10588
|
+
})
|
|
10589
|
+
res.json({
|
|
10590
|
+
ok: true,
|
|
10591
|
+
...prepared
|
|
10592
|
+
})
|
|
10593
|
+
} catch (error) {
|
|
10594
|
+
const status = Number.isInteger(error && error.status) ? error.status : 500
|
|
10595
|
+
res.status(status).json({
|
|
10596
|
+
ok: false,
|
|
10597
|
+
error: error && error.message ? error.message : "Failed to prepare download."
|
|
10598
|
+
})
|
|
10599
|
+
}
|
|
10600
|
+
}))
|
|
10601
|
+
this.app.post("/launcher/download/finalize", ex(async (req, res) => {
|
|
10602
|
+
try {
|
|
10603
|
+
const body = req.body && typeof req.body === "object" ? req.body : {}
|
|
10604
|
+
const finalized = await finalizeLauncherDownload({
|
|
10605
|
+
intent: typeof body.intent === "string" ? body.intent : "",
|
|
10606
|
+
ref: typeof body.ref === "string" ? body.ref : "",
|
|
10607
|
+
name: typeof body.name === "string" ? body.name : "",
|
|
10608
|
+
id: typeof body.id === "string" ? body.id : ""
|
|
10609
|
+
})
|
|
10610
|
+
res.json({
|
|
10611
|
+
ok: true,
|
|
10612
|
+
...finalized
|
|
10613
|
+
})
|
|
10614
|
+
} catch (error) {
|
|
10615
|
+
const status = Number.isInteger(error && error.status) ? error.status : 500
|
|
10616
|
+
res.status(status).json({
|
|
10617
|
+
ok: false,
|
|
10618
|
+
error: error && error.message ? error.message : "Failed to finalize download."
|
|
10275
10619
|
})
|
|
10276
10620
|
}
|
|
10277
10621
|
}))
|
|
@@ -13078,6 +13422,17 @@ class Server {
|
|
|
13078
13422
|
}
|
|
13079
13423
|
}))
|
|
13080
13424
|
this.app.get("/initialize/:name", ex(async (req, res) => {
|
|
13425
|
+
if (this.contentValidation) {
|
|
13426
|
+
const validation = await this.contentValidation.validateAppByName(req.params.name)
|
|
13427
|
+
if (validation && !validation.valid) {
|
|
13428
|
+
await this.renderInvalidContentPage(req, res, validation, {
|
|
13429
|
+
sidebarSelected: "home",
|
|
13430
|
+
backHref: "/home",
|
|
13431
|
+
backLabel: "Back to Home",
|
|
13432
|
+
})
|
|
13433
|
+
return
|
|
13434
|
+
}
|
|
13435
|
+
}
|
|
13081
13436
|
let launcher = await this.kernel.api.launcher(req.params.name)
|
|
13082
13437
|
let config = launcher.script
|
|
13083
13438
|
if (config) {
|
|
@@ -13884,7 +14239,6 @@ class Server {
|
|
|
13884
14239
|
} else {
|
|
13885
14240
|
config = { menu: [] }
|
|
13886
14241
|
}
|
|
13887
|
-
err = e.stack
|
|
13888
14242
|
}
|
|
13889
14243
|
await this.renderMenu(req, uri, name, config, [])
|
|
13890
14244
|
|
|
@@ -14359,7 +14713,12 @@ class Server {
|
|
|
14359
14713
|
res.json(mem)
|
|
14360
14714
|
}))
|
|
14361
14715
|
this.app.post("/pinokio/tabs", ex(async (req, res) => {
|
|
14362
|
-
|
|
14716
|
+
const workspaceName = typeof req.body.name === "string" ? req.body.name : ""
|
|
14717
|
+
const viewName = typeof req.body.view === "string" ? req.body.view : ""
|
|
14718
|
+
const storageKey = workspaceName && viewName ? `${workspaceName}:${viewName}` : workspaceName
|
|
14719
|
+
if (storageKey) {
|
|
14720
|
+
this.tabs[storageKey] = req.body.tabs
|
|
14721
|
+
}
|
|
14363
14722
|
res.json({ success: true })
|
|
14364
14723
|
}))
|
|
14365
14724
|
this.app.get("/pinokio/browser", ex(async (req, res) => {
|
|
@@ -14680,40 +15039,8 @@ class Server {
|
|
|
14680
15039
|
res.redirect("/pinokio/install")
|
|
14681
15040
|
}))
|
|
14682
15041
|
this.app.post("/pinokio/install/validate", ex(async (req, res) => {
|
|
14683
|
-
const body = req.body && typeof req.body === "object" ? req.body : {}
|
|
14684
|
-
const normalizedPath = installValidation.normalizeRelativeInstallPath(
|
|
14685
|
-
typeof body.relativePath === "string" ? body.relativePath : "",
|
|
14686
|
-
"api"
|
|
14687
|
-
)
|
|
14688
|
-
const normalizedFolderName = installValidation.validateInstallFolderName(
|
|
14689
|
-
typeof body.folderName === "string" ? body.folderName : ""
|
|
14690
|
-
)
|
|
14691
|
-
|
|
14692
|
-
if (!normalizedPath || !normalizedFolderName) {
|
|
14693
|
-
return res.status(400).json({
|
|
14694
|
-
ok: false,
|
|
14695
|
-
error: "Invalid install destination."
|
|
14696
|
-
})
|
|
14697
|
-
}
|
|
14698
|
-
|
|
14699
|
-
const validation = await installValidation.validateInstalledFolder({
|
|
14700
|
-
relativePath: normalizedPath,
|
|
14701
|
-
folderName: normalizedFolderName,
|
|
14702
|
-
fallbackRoot: "api"
|
|
14703
|
-
})
|
|
14704
|
-
if (!validation.valid) {
|
|
14705
|
-
const targetPath = path.resolve(this.kernel.homedir, normalizedPath, normalizedFolderName)
|
|
14706
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
14707
|
-
return res.status(422).json({
|
|
14708
|
-
ok: false,
|
|
14709
|
-
error: validation.message || "Downloaded content is invalid.",
|
|
14710
|
-
validation
|
|
14711
|
-
})
|
|
14712
|
-
}
|
|
14713
|
-
|
|
14714
15042
|
res.json({
|
|
14715
|
-
ok: true
|
|
14716
|
-
validation
|
|
15043
|
+
ok: true
|
|
14717
15044
|
})
|
|
14718
15045
|
}))
|
|
14719
15046
|
this.app.get("/pinokio/install", ex((req, res) => {
|