artifacty 0.1.2 → 0.3.0
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/AGENTS.md +4 -2
- package/README.md +34 -8
- package/docs/artifact-schema-v1.md +27 -5
- package/docs/integrations.md +21 -4
- package/docs/sarif-csv-artifact-plan.md +44 -65
- package/package.json +1 -1
- package/src/cli.js +62 -10
- package/src/client/editor.js +76 -4
- package/src/lib/converters.js +429 -44
- package/src/lib/i18n.js +8 -0
- package/src/lib/installer.js +58 -4
- package/src/lib/render.js +492 -4
- package/src/lib/storage.js +490 -33
- package/src/mcp-server.js +18 -8
- package/src/server.js +69 -10
package/src/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createArtifact,
|
|
10
10
|
createStore,
|
|
11
11
|
getArtifact,
|
|
12
|
-
|
|
12
|
+
listArtifactsPage,
|
|
13
13
|
listAuditEvents,
|
|
14
14
|
MAX_ARTIFACT_BYTES,
|
|
15
15
|
restoreArtifact,
|
|
@@ -177,15 +177,19 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
177
177
|
query: url.searchParams.get("q") || "",
|
|
178
178
|
tag: url.searchParams.get("tag") || "",
|
|
179
179
|
sourceAgent: url.searchParams.get("sourceAgent") || "",
|
|
180
|
-
includeArchived: url.searchParams.get("includeArchived") === "true"
|
|
180
|
+
includeArchived: url.searchParams.get("includeArchived") === "true",
|
|
181
|
+
limit: url.searchParams.get("limit") || undefined,
|
|
182
|
+
offset: url.searchParams.get("offset") || undefined
|
|
181
183
|
};
|
|
182
|
-
const
|
|
184
|
+
const page = await listArtifactsPage(store, {
|
|
183
185
|
query: filters.query || undefined,
|
|
184
186
|
tag: filters.tag || undefined,
|
|
185
187
|
sourceAgent: filters.sourceAgent || undefined,
|
|
186
|
-
includeArchived: filters.includeArchived
|
|
188
|
+
includeArchived: filters.includeArchived,
|
|
189
|
+
limit: filters.limit,
|
|
190
|
+
offset: filters.offset
|
|
187
191
|
});
|
|
188
|
-
return sendHtml(response, renderDashboard({ artifacts, baseUrl, filters, locale, currentPath }), 200, headOnly);
|
|
192
|
+
return sendHtml(response, renderDashboard({ artifacts: page.artifacts, baseUrl, filters, pagination: page, locale, currentPath }), 200, headOnly);
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
if (method === "GET" && pathname === "/new") {
|
|
@@ -240,14 +244,19 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
if (method === "GET" && pathname === "/api/artifacts") {
|
|
243
|
-
const
|
|
247
|
+
const page = await listArtifactsPage(store, {
|
|
244
248
|
query: url.searchParams.get("q") || undefined,
|
|
245
249
|
tag: url.searchParams.get("tag") || undefined,
|
|
246
250
|
sourceAgent: url.searchParams.get("sourceAgent") || undefined,
|
|
247
251
|
includeArchived: url.searchParams.get("includeArchived") === "true",
|
|
248
|
-
limit: url.searchParams.get("limit") || undefined
|
|
252
|
+
limit: url.searchParams.get("limit") || undefined,
|
|
253
|
+
offset: url.searchParams.get("offset") || undefined
|
|
249
254
|
});
|
|
250
|
-
return sendJson(response, {
|
|
255
|
+
return sendJson(response, {
|
|
256
|
+
artifacts: page.artifacts,
|
|
257
|
+
pagination: paginationJson(page),
|
|
258
|
+
search: page.search
|
|
259
|
+
}, 200, headOnly);
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
if (method === "GET" && pathname === "/api/audit") {
|
|
@@ -410,12 +419,13 @@ export async function handleRequest({ request, response, store, host, port, secu
|
|
|
410
419
|
});
|
|
411
420
|
|
|
412
421
|
if (pathname.endsWith("/raw")) {
|
|
422
|
+
const raw = rawArtifactResponse(artifact);
|
|
413
423
|
response.writeHead(200, {
|
|
414
|
-
"content-type":
|
|
424
|
+
"content-type": raw.contentType,
|
|
415
425
|
"cache-control": "no-store",
|
|
416
426
|
"x-content-type-options": "nosniff"
|
|
417
427
|
});
|
|
418
|
-
response.end(headOnly ? undefined :
|
|
428
|
+
response.end(headOnly ? undefined : raw.body);
|
|
419
429
|
return;
|
|
420
430
|
}
|
|
421
431
|
|
|
@@ -441,6 +451,17 @@ export function decorateArtifactUrls(artifact, baseUrl) {
|
|
|
441
451
|
};
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
function paginationJson(page) {
|
|
455
|
+
return {
|
|
456
|
+
total: page.total,
|
|
457
|
+
limit: page.limit,
|
|
458
|
+
offset: page.offset,
|
|
459
|
+
hasMore: page.hasMore,
|
|
460
|
+
nextOffset: page.nextOffset,
|
|
461
|
+
previousOffset: page.previousOffset
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
444
465
|
export async function readJsonBody(request) {
|
|
445
466
|
const raw = await readBody(request, MAX_ARTIFACT_BYTES + 1024);
|
|
446
467
|
if (!raw.trim()) {
|
|
@@ -514,6 +535,44 @@ export function sendHtml(response, html, statusCode = 200, headOnly = false, con
|
|
|
514
535
|
response.end(headOnly ? undefined : html);
|
|
515
536
|
}
|
|
516
537
|
|
|
538
|
+
function rawArtifactResponse(artifact) {
|
|
539
|
+
if (artifact.version.format === "image" || artifact.version.format === "video") {
|
|
540
|
+
const decoded = decodeMediaContent(artifact.content);
|
|
541
|
+
if (decoded) {
|
|
542
|
+
return {
|
|
543
|
+
body: decoded,
|
|
544
|
+
contentType: mediaContentType(artifact)
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
body: artifact.content,
|
|
550
|
+
contentType: artifact.version.contentType
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function mediaContentType(artifact) {
|
|
555
|
+
const metadataType = String(artifact.version.metadata?.mimeType || "").toLowerCase();
|
|
556
|
+
if (metadataType.startsWith("image/") || metadataType.startsWith("video/")) {
|
|
557
|
+
return metadataType;
|
|
558
|
+
}
|
|
559
|
+
const versionType = String(artifact.version.contentType || "").toLowerCase().split(";")[0];
|
|
560
|
+
if (versionType.startsWith("image/") || versionType.startsWith("video/")) {
|
|
561
|
+
return versionType;
|
|
562
|
+
}
|
|
563
|
+
return artifact.version.format === "video" ? "video/mp4" : "image/png";
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function decodeMediaContent(content) {
|
|
567
|
+
const value = String(content || "").trim();
|
|
568
|
+
const dataUrl = /^data:[^;,]+;base64,([A-Za-z0-9+/=_-\s]+)$/i.exec(value);
|
|
569
|
+
const base64 = (dataUrl ? dataUrl[1] : value).replace(/\s+/g, "").replaceAll("-", "+").replaceAll("_", "/");
|
|
570
|
+
if (!base64 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
return Buffer.from(base64, "base64");
|
|
574
|
+
}
|
|
575
|
+
|
|
517
576
|
function defaultContentSecurityPolicy() {
|
|
518
577
|
return [
|
|
519
578
|
"default-src 'self' data: blob:",
|