pinokiod 7.3.1 → 7.3.3
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/github/index.js +444 -0
- package/kernel/api/index.js +199 -11
- package/kernel/api/process/index.js +124 -44
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/api/uri/index.js +51 -0
- package/kernel/bin/git.js +9 -10
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/bin/zip.js +9 -1
- package/kernel/connect/providers/github/README.md +5 -4
- package/kernel/environment.js +195 -92
- package/kernel/git.js +98 -19
- package/kernel/gitconfig_template +7 -0
- package/kernel/gpu/amd.js +72 -0
- package/kernel/gpu/apple.js +8 -0
- package/kernel/gpu/common.js +12 -0
- package/kernel/gpu/intel.js +47 -0
- package/kernel/gpu/nvidia.js +8 -0
- package/kernel/index.js +11 -1
- package/kernel/managed_skills.js +871 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +316 -0
- package/kernel/resource_usage/gpu.js +349 -0
- package/kernel/resource_usage/index.js +322 -0
- package/kernel/resource_usage/macos_footprint.js +197 -0
- package/kernel/resource_usage/preferences.js +92 -0
- package/kernel/resource_usage/process_tree.js +303 -0
- package/kernel/scripts/git/create +4 -4
- package/kernel/scripts/git/fork +7 -8
- package/kernel/shell.js +23 -2
- package/kernel/shells.js +41 -0
- package/kernel/sysinfo.js +62 -9
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/index.js +984 -156
- package/server/lib/app_log_report.js +543 -0
- package/server/lib/content_validation.js +55 -33
- package/server/lib/launcher_instruction_bootstrap.js +4 -96
- package/server/lib/terminal_session_helpers.js +0 -3
- package/server/public/common.js +77 -31
- package/server/public/create-launcher.js +4 -32
- package/server/public/logs.js +1428 -0
- package/server/public/nav.js +7 -0
- package/server/public/plugin-detail.js +93 -10
- package/server/public/privacy_filter_worker.js +391 -0
- package/server/public/style.css +1104 -154
- package/server/public/task-launcher.js +8 -29
- package/server/public/universal-launcher.css +8 -6
- package/server/public/universal-launcher.js +3 -27
- package/server/routes/apps.js +195 -1
- package/server/views/app.ejs +3041 -717
- package/server/views/autolaunch.ejs +917 -0
- package/server/views/bootstrap.ejs +7 -1
- package/server/views/d.ejs +408 -65
- package/server/views/editor.ejs +85 -19
- package/server/views/index.ejs +661 -111
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/logs.ejs +164 -86
- package/server/views/net.ejs +7 -1
- package/server/views/partials/d_terminal_column.ejs +2 -2
- package/server/views/partials/d_terminal_options.ejs +0 -8
- package/server/views/partials/fs_status.ejs +47 -0
- package/server/views/partials/home_action_modal.ejs +86 -0
- package/server/views/partials/home_run_menu.ejs +87 -0
- package/server/views/partials/main_sidebar.ejs +2 -0
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/plugin_detail.ejs +19 -4
- package/server/views/plugins.ejs +201 -3
- package/server/views/pre.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/shell.ejs +40 -18
- package/server/views/skills.ejs +506 -0
- package/server/views/terminal.ejs +45 -19
- package/spec/INSTRUCTION_SYNC.md +20 -10
- package/system/plugin/antigravity-cli/antigravity.png +0 -0
- package/system/plugin/antigravity-cli/common.js +155 -0
- package/system/plugin/antigravity-cli/install.js +272 -0
- package/system/plugin/antigravity-cli/pinokio.js +13 -0
- package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
- package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +47 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +58 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +23 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +42 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +49 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +23 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +15 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +23 -0
- package/system/plugin/qwen/pinokio.js +34 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +20 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +23 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
- package/test/antigravity-cli-plugin.test.js +185 -0
- package/test/app-api.test.js +239 -0
- package/test/app-log-report.test.js +67 -0
- package/test/environment-cache-preflight.test.js +98 -0
- package/test/git-bin.test.js +59 -0
- package/test/git-defaults.test.js +97 -0
- package/test/github-api.test.js +158 -0
- package/test/github-connection.test.js +117 -0
- package/test/huggingface-bin.test.js +25 -0
- package/test/managed-skills.test.js +351 -0
- package/test/plugin-action-functions.test.js +337 -0
- package/test/plugin-dev-iframe.test.js +17 -0
- package/test/plugin-sources.test.js +203 -0
- package/test/privacy-filter-worker-heuristics.test.js +69 -0
- package/test/process-wait.test.js +169 -0
- package/test/script-api.test.js +97 -0
- package/test/shell-api.test.js +134 -0
- package/test/shell-run-template.test.js +209 -0
- package/test/storage-api.test.js +137 -0
- package/test/uri-api.test.js +100 -0
|
@@ -4,33 +4,13 @@
|
|
|
4
4
|
const CATEGORY_ORDER = ["CLI", "IDE"];
|
|
5
5
|
const TOOL_PREFERENCE_KEY = "pinokio.universalLauncher.tool";
|
|
6
6
|
const TOOL_VALUE_ALIASES = {
|
|
7
|
-
claude: "
|
|
8
|
-
codex: "
|
|
9
|
-
|
|
7
|
+
claude: "pinokio/run/plugin/claude",
|
|
8
|
+
codex: "pinokio/run/plugin/codex",
|
|
9
|
+
antigravity: "pinokio/run/plugin/antigravity-cli",
|
|
10
|
+
"antigravity-cli": "pinokio/run/plugin/antigravity-cli",
|
|
11
|
+
"code/claude": "pinokio/run/plugin/claude",
|
|
12
|
+
"code/codex": "pinokio/run/plugin/codex"
|
|
10
13
|
};
|
|
11
|
-
const FALLBACK_TOOLS = [
|
|
12
|
-
{
|
|
13
|
-
value: "code/claude",
|
|
14
|
-
label: "Claude Code",
|
|
15
|
-
iconSrc: "/asset/plugin/code/claude/claude.png",
|
|
16
|
-
isDefault: true,
|
|
17
|
-
category: "CLI"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
value: "code/codex",
|
|
21
|
-
label: "OpenAI Codex",
|
|
22
|
-
iconSrc: "/asset/plugin/code/codex/openai.webp",
|
|
23
|
-
isDefault: false,
|
|
24
|
-
category: "CLI"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
value: "code/gemini",
|
|
28
|
-
label: "Google Gemini CLI",
|
|
29
|
-
iconSrc: "/asset/plugin/code/gemini/gemini.jpeg",
|
|
30
|
-
isDefault: false,
|
|
31
|
-
category: "CLI"
|
|
32
|
-
}
|
|
33
|
-
];
|
|
34
14
|
const TASK_INSTALL_SHELL_CLIENT = {
|
|
35
15
|
cols: 120,
|
|
36
16
|
rows: 32
|
|
@@ -440,10 +420,9 @@
|
|
|
440
420
|
throw new Error(String(response.status));
|
|
441
421
|
}
|
|
442
422
|
const payload = await response.json();
|
|
443
|
-
|
|
444
|
-
return tools.length > 0 ? tools : FALLBACK_TOOLS.slice();
|
|
423
|
+
return mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
|
|
445
424
|
} catch (_) {
|
|
446
|
-
return
|
|
425
|
+
return [];
|
|
447
426
|
}
|
|
448
427
|
}
|
|
449
428
|
|
|
@@ -55,7 +55,7 @@ body.dark {
|
|
|
55
55
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions {
|
|
56
56
|
display: flex;
|
|
57
57
|
flex-direction: column;
|
|
58
|
-
gap:
|
|
58
|
+
gap: 4px;
|
|
59
59
|
align-items: stretch;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -110,15 +110,15 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .univer
|
|
|
110
110
|
justify-content: flex-start !important;
|
|
111
111
|
text-align: left;
|
|
112
112
|
gap: 3px;
|
|
113
|
-
height:
|
|
114
|
-
min-height:
|
|
113
|
+
height: 30px;
|
|
114
|
+
min-height: 30px;
|
|
115
115
|
margin: 0;
|
|
116
116
|
padding: 0 8px;
|
|
117
117
|
border: 1px solid var(--universal-quick-action-bg) !important;
|
|
118
118
|
border-radius: 5px !important;
|
|
119
119
|
background: var(--universal-quick-action-bg) !important;
|
|
120
120
|
border-color: var(--universal-quick-action-bg) !important;
|
|
121
|
-
color:
|
|
121
|
+
color: #fff !important;
|
|
122
122
|
font-size: 12px;
|
|
123
123
|
font-weight: 700;
|
|
124
124
|
line-height: 1;
|
|
@@ -134,7 +134,7 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > .univer
|
|
|
134
134
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn:active {
|
|
135
135
|
background: var(--universal-quick-action-bg-hover) !important;
|
|
136
136
|
border-color: var(--universal-quick-action-bg-hover) !important;
|
|
137
|
-
color:
|
|
137
|
+
color: #fff !important;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > #explore,
|
|
@@ -146,10 +146,12 @@ body.dark .main-sidebar .btn-tab.quick-actions.universal-quick-actions > #explor
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn i,
|
|
149
|
+
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn .quick-action-icon,
|
|
149
150
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .btn .caption,
|
|
150
151
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn i,
|
|
152
|
+
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn .quick-action-icon,
|
|
151
153
|
.main-sidebar .btn-tab.quick-actions.universal-quick-actions > .universal-create-menu > summary.btn .caption {
|
|
152
|
-
color:
|
|
154
|
+
color: #fff !important;
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
.main-sidebar .universal-create-caret {
|
|
@@ -20,29 +20,6 @@
|
|
|
20
20
|
SSH_ASKPASS: '',
|
|
21
21
|
GCM_INTERACTIVE: 'never',
|
|
22
22
|
};
|
|
23
|
-
const FALLBACK_TOOLS = [
|
|
24
|
-
{
|
|
25
|
-
value: 'code/claude',
|
|
26
|
-
label: 'Claude Code',
|
|
27
|
-
iconSrc: '/asset/plugin/code/claude/claude.png',
|
|
28
|
-
isDefault: true,
|
|
29
|
-
category: 'CLI',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
value: 'code/codex',
|
|
33
|
-
label: 'OpenAI Codex',
|
|
34
|
-
iconSrc: '/asset/plugin/code/codex/openai.webp',
|
|
35
|
-
isDefault: false,
|
|
36
|
-
category: 'CLI',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
value: 'code/gemini',
|
|
40
|
-
label: 'Google Gemini CLI',
|
|
41
|
-
iconSrc: '/asset/plugin/code/gemini/gemini.jpeg',
|
|
42
|
-
isDefault: false,
|
|
43
|
-
category: 'CLI',
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
23
|
const INTENTS = {
|
|
47
24
|
create_app: {
|
|
48
25
|
label: 'Create app',
|
|
@@ -267,12 +244,11 @@
|
|
|
267
244
|
return res.json();
|
|
268
245
|
})
|
|
269
246
|
.then((payload) => {
|
|
270
|
-
|
|
271
|
-
return tools.length > 0 ? tools : FALLBACK_TOOLS.slice();
|
|
247
|
+
return mapPluginMenuToTools(payload && Array.isArray(payload.menu) ? payload.menu : []);
|
|
272
248
|
})
|
|
273
249
|
.catch((error) => {
|
|
274
|
-
console.warn('
|
|
275
|
-
return
|
|
250
|
+
console.warn('Failed to load plugins for universal launcher', error);
|
|
251
|
+
return [];
|
|
276
252
|
})
|
|
277
253
|
.finally(() => {
|
|
278
254
|
loadingTools = null;
|
package/server/routes/apps.js
CHANGED
|
@@ -6,13 +6,25 @@ const axios = require('axios')
|
|
|
6
6
|
const multer = require('multer')
|
|
7
7
|
const FormData = require('form-data')
|
|
8
8
|
const sanitize = require('sanitize-filename')
|
|
9
|
+
const AppLogReportService = require('../lib/app_log_report')
|
|
9
10
|
|
|
10
11
|
const DEFAULT_PEER_PORT = 42000
|
|
11
12
|
const DEFAULT_PEER_TIMEOUT_MS = 2500
|
|
12
13
|
const DEFAULT_PEER_UPLOAD_TIMEOUT_MS = 30000
|
|
14
|
+
const REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES = 1024 * 1024
|
|
15
|
+
const REGISTRY_DRAFT_IMPORT_TIMEOUT_MS = 60000
|
|
13
16
|
const IPV4_HOST_PATTERN = /^(?:\d{1,3}\.){3}\d{1,3}$/
|
|
14
17
|
const PINOKIO_REF_PROTOCOL = 'pinokio:'
|
|
15
18
|
|
|
19
|
+
const draftImportUpload = multer({
|
|
20
|
+
storage: multer.memoryStorage(),
|
|
21
|
+
limits: {
|
|
22
|
+
fieldSize: REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES,
|
|
23
|
+
fields: 6,
|
|
24
|
+
files: 0
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
16
28
|
const isQualifiedHost = (value = '') => {
|
|
17
29
|
return IPV4_HOST_PATTERN.test(String(value || '').trim())
|
|
18
30
|
}
|
|
@@ -57,6 +69,26 @@ const parseQualifiedAppId = (value = '') => {
|
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
const normalizeRegistryBase = (value = '') => {
|
|
73
|
+
const fallback = 'https://pinokio.co'
|
|
74
|
+
let url
|
|
75
|
+
try {
|
|
76
|
+
url = new URL(value || fallback)
|
|
77
|
+
} catch (_) {
|
|
78
|
+
throw new Error('Invalid registry URL')
|
|
79
|
+
}
|
|
80
|
+
const host = url.hostname.toLowerCase()
|
|
81
|
+
const allowedHttps = new Set(['pinokio.co', 'www.pinokio.co', 'beta.pinokio.co', 'api.pinokio.co'])
|
|
82
|
+
const allowedLocal = new Set(['localhost', '127.0.0.1', '::1', '[::1]'])
|
|
83
|
+
if (url.protocol === 'https:' && allowedHttps.has(host)) {
|
|
84
|
+
return url.origin
|
|
85
|
+
}
|
|
86
|
+
if (url.protocol === 'http:' && allowedLocal.has(host)) {
|
|
87
|
+
return url.origin
|
|
88
|
+
}
|
|
89
|
+
throw new Error('Registry URL is not allowed')
|
|
90
|
+
}
|
|
91
|
+
|
|
60
92
|
const isLoopbackHost = (value = '') => {
|
|
61
93
|
const normalized = String(value || '').trim().toLowerCase()
|
|
62
94
|
return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1' || normalized === '[::1]'
|
|
@@ -135,10 +167,11 @@ const buildPinokioRef = ({ host, port, scope, id }) => {
|
|
|
135
167
|
return `pinokio://${normalizedHost}:${normalizedPort}/${encodedPath}`
|
|
136
168
|
}
|
|
137
169
|
|
|
138
|
-
module.exports = function registerAppRoutes(app, { registry, preferences, appSearch, appLogs, getTheme }) {
|
|
170
|
+
module.exports = function registerAppRoutes(app, { registry, preferences, appSearch, appLogs, appLogReports, getTheme }) {
|
|
139
171
|
if (!app || !registry || !preferences || !appSearch || !appLogs) {
|
|
140
172
|
throw new Error('App routes require app, registry, preferences, appSearch, and appLogs')
|
|
141
173
|
}
|
|
174
|
+
const appLogReportService = appLogReports || new AppLogReportService({ registry })
|
|
142
175
|
|
|
143
176
|
const router = express.Router()
|
|
144
177
|
const upload = multer()
|
|
@@ -1042,5 +1075,166 @@ module.exports = function registerAppRoutes(app, { registry, preferences, appSea
|
|
|
1042
1075
|
})
|
|
1043
1076
|
}))
|
|
1044
1077
|
|
|
1078
|
+
router.get('/apps/logs/:app_id/report', asyncHandler(async (req, res) => {
|
|
1079
|
+
const parsedAppId = parseQualifiedAppId(req.params.app_id)
|
|
1080
|
+
const requestedAppId = parsedAppId.app_id || req.params.app_id
|
|
1081
|
+
const remoteHost = parsedAppId.qualified ? parsedAppId.host : null
|
|
1082
|
+
if (remoteHost && remoteHost !== currentPeerHost()) {
|
|
1083
|
+
try {
|
|
1084
|
+
const params = {}
|
|
1085
|
+
const tail = registry.parseTailCount(req.query.tail, 800)
|
|
1086
|
+
if (Number.isFinite(tail) && tail > 0) {
|
|
1087
|
+
params.tail = String(tail)
|
|
1088
|
+
}
|
|
1089
|
+
if (req.query.redaction === 'none') {
|
|
1090
|
+
params.redaction = 'none'
|
|
1091
|
+
}
|
|
1092
|
+
const response = await axios.get(`http://${remoteHost}:${peerPort()}/apps/logs/${encodeURIComponent(requestedAppId)}/report`, {
|
|
1093
|
+
timeout: DEFAULT_PEER_TIMEOUT_MS,
|
|
1094
|
+
headers: peerRequestHeaders(req),
|
|
1095
|
+
params
|
|
1096
|
+
})
|
|
1097
|
+
const payload = response && response.data && typeof response.data === 'object'
|
|
1098
|
+
? { ...response.data }
|
|
1099
|
+
: {}
|
|
1100
|
+
payload.app_id = qualifyAppId(requestedAppId, remoteHost)
|
|
1101
|
+
payload.source = buildSource(remoteHost, false)
|
|
1102
|
+
payload.ref = buildPinokioRef({
|
|
1103
|
+
host: remoteHost,
|
|
1104
|
+
port: peerPort(),
|
|
1105
|
+
scope: 'api',
|
|
1106
|
+
id: requestedAppId
|
|
1107
|
+
})
|
|
1108
|
+
res.json(payload)
|
|
1109
|
+
return
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
if (error && error.response) {
|
|
1112
|
+
res.status(error.response.status).json(error.response.data)
|
|
1113
|
+
return
|
|
1114
|
+
}
|
|
1115
|
+
res.status(502).json({
|
|
1116
|
+
error: 'Peer log report unavailable',
|
|
1117
|
+
app_id: qualifyAppId(requestedAppId, remoteHost),
|
|
1118
|
+
source: buildSource(remoteHost, false)
|
|
1119
|
+
})
|
|
1120
|
+
return
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
const appId = registry.normalizeAppId(requestedAppId)
|
|
1124
|
+
if (!appId) {
|
|
1125
|
+
res.status(400).json({ error: 'Invalid app_id' })
|
|
1126
|
+
return
|
|
1127
|
+
}
|
|
1128
|
+
const status = await registry.buildAppStatus(appId, {
|
|
1129
|
+
source: req.$source || null
|
|
1130
|
+
})
|
|
1131
|
+
if (!status) {
|
|
1132
|
+
res.status(404).json({ error: 'App not found', app_id: appId })
|
|
1133
|
+
return
|
|
1134
|
+
}
|
|
1135
|
+
const tail = registry.parseTailCount(req.query.tail, 800)
|
|
1136
|
+
const report = await appLogReportService.buildReport({
|
|
1137
|
+
appId,
|
|
1138
|
+
status,
|
|
1139
|
+
tail,
|
|
1140
|
+
redact: req.query.redaction !== 'none'
|
|
1141
|
+
})
|
|
1142
|
+
if (!report) {
|
|
1143
|
+
res.status(404).json({ error: 'No log report available', app_id: appId })
|
|
1144
|
+
return
|
|
1145
|
+
}
|
|
1146
|
+
res.json({
|
|
1147
|
+
app_id: appId,
|
|
1148
|
+
ref: buildPinokioRef({
|
|
1149
|
+
host: currentPeerHost() || '127.0.0.1',
|
|
1150
|
+
port: peerPort(),
|
|
1151
|
+
scope: 'api',
|
|
1152
|
+
id: appId
|
|
1153
|
+
}),
|
|
1154
|
+
source: buildSource(currentPeerHost(), true),
|
|
1155
|
+
...report
|
|
1156
|
+
})
|
|
1157
|
+
}))
|
|
1158
|
+
|
|
1159
|
+
router.post('/apps/logs/:app_id/drafts', draftImportUpload.none(), asyncHandler(async (req, res) => {
|
|
1160
|
+
const parsedAppId = parseQualifiedAppId(req.params.app_id)
|
|
1161
|
+
const requestedAppId = parsedAppId.app_id || req.params.app_id
|
|
1162
|
+
const remoteHost = parsedAppId.qualified ? parsedAppId.host : null
|
|
1163
|
+
if (remoteHost && remoteHost !== currentPeerHost()) {
|
|
1164
|
+
res.status(400).json({ error: 'Draft import is available for local workspaces.' })
|
|
1165
|
+
return
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const appId = registry.normalizeAppId(requestedAppId)
|
|
1169
|
+
if (!appId) {
|
|
1170
|
+
res.status(400).json({ error: 'Invalid app_id' })
|
|
1171
|
+
return
|
|
1172
|
+
}
|
|
1173
|
+
const status = await registry.buildAppStatus(appId, {
|
|
1174
|
+
source: req.$source || null
|
|
1175
|
+
})
|
|
1176
|
+
if (!status) {
|
|
1177
|
+
res.status(404).json({ error: 'App not found', app_id: appId })
|
|
1178
|
+
return
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const body = req.body || {}
|
|
1182
|
+
const token = typeof body.token === 'string' ? body.token.trim() : ''
|
|
1183
|
+
let registryBase
|
|
1184
|
+
try {
|
|
1185
|
+
registryBase = normalizeRegistryBase(typeof body.registry === 'string' ? body.registry.trim() : '')
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
res.status(400).json({ error: error && error.message ? error.message : 'Invalid registry URL' })
|
|
1188
|
+
return
|
|
1189
|
+
}
|
|
1190
|
+
const metadataB64 = typeof body.metadata_b64 === 'string' ? body.metadata_b64.trim() : ''
|
|
1191
|
+
if (!token) {
|
|
1192
|
+
res.status(400).json({ error: 'Registry authorization token is required.' })
|
|
1193
|
+
return
|
|
1194
|
+
}
|
|
1195
|
+
if (!metadataB64) {
|
|
1196
|
+
res.status(400).json({ error: 'Draft metadata is required.' })
|
|
1197
|
+
return
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
let metadata
|
|
1201
|
+
try {
|
|
1202
|
+
metadata = JSON.parse(Buffer.from(metadataB64, 'base64').toString('utf8'))
|
|
1203
|
+
} catch (_) {
|
|
1204
|
+
res.status(400).json({ error: 'Draft metadata is invalid.' })
|
|
1205
|
+
return
|
|
1206
|
+
}
|
|
1207
|
+
if (!metadata || typeof metadata !== 'object' || typeof metadata.body !== 'string' || !metadata.body.trim()) {
|
|
1208
|
+
res.status(400).json({ error: 'Draft body is required.' })
|
|
1209
|
+
return
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const repoUrl = appLogReportService.sanitizeRemoteUrl(metadata.appRepoUrl || metadata.repoUrl || appLogReportService.readGitRemote(status.path))
|
|
1213
|
+
const enrichedMetadata = {
|
|
1214
|
+
...metadata,
|
|
1215
|
+
appRepoUrl: repoUrl || metadata.appRepoUrl || metadata.repoUrl || undefined,
|
|
1216
|
+
repoUrl: repoUrl || metadata.repoUrl || undefined
|
|
1217
|
+
}
|
|
1218
|
+
const enrichedMetadataB64 = Buffer.from(JSON.stringify(enrichedMetadata), 'utf8').toString('base64')
|
|
1219
|
+
if (Buffer.byteLength(enrichedMetadataB64) > REGISTRY_DRAFT_IMPORT_FIELD_LIMIT_BYTES) {
|
|
1220
|
+
res.status(413).json({ error: 'Draft is too large for registry import.' })
|
|
1221
|
+
return
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const form = new FormData()
|
|
1225
|
+
form.append('metadata_b64', enrichedMetadataB64)
|
|
1226
|
+
const response = await axios.post(`${registryBase}/registry-bridge/draft-imports`, form, {
|
|
1227
|
+
headers: {
|
|
1228
|
+
...form.getHeaders(),
|
|
1229
|
+
Authorization: `Bearer ${token}`
|
|
1230
|
+
},
|
|
1231
|
+
timeout: REGISTRY_DRAFT_IMPORT_TIMEOUT_MS,
|
|
1232
|
+
maxBodyLength: Infinity,
|
|
1233
|
+
maxContentLength: Infinity,
|
|
1234
|
+
validateStatus: () => true
|
|
1235
|
+
})
|
|
1236
|
+
res.status(response.status).json(response.data || {})
|
|
1237
|
+
}))
|
|
1238
|
+
|
|
1045
1239
|
app.use(router)
|
|
1046
1240
|
}
|