@sentinelqa/uploader 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/cli.js +98 -69
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -190,6 +190,26 @@ const extractTestsFromReport = (reportJson) => {
190
190
  const computeRunStatus = (tests) => {
191
191
  return tests.some((t) => t.status === "failed") ? "failed" : "passed";
192
192
  };
193
+ const bestEffortComplete = async (appUrl, ingestToken, runId, status, tests) => {
194
+ try {
195
+ await fetch(`${appUrl}/api/runs/${runId}/complete`, {
196
+ method: "POST",
197
+ headers: {
198
+ "content-type": "application/json",
199
+ authorization: `Bearer ${ingestToken}`
200
+ },
201
+ body: JSON.stringify({
202
+ status,
203
+ finishedAt: new Date().toISOString(),
204
+ tests,
205
+ artifacts: []
206
+ })
207
+ });
208
+ }
209
+ catch {
210
+ // Ignore best-effort failures; original error will be surfaced.
211
+ }
212
+ };
193
213
  const detectGitLabEnv = () => {
194
214
  const isGitLab = readEnv("GITLAB_CI") === "true" || !!readEnv("CI_PROJECT_ID");
195
215
  if (!isGitLab)
@@ -374,77 +394,86 @@ const main = async () => {
374
394
  return { ...artifact, sizeBytes: stat.size, contentType };
375
395
  });
376
396
  let finalArtifacts = artifactsWithMeta;
377
- if (byoBucket) {
378
- const s3Region = readEnv("SENTINEL_S3_REGION") || readEnv("AWS_REGION") || readEnv("S3_REGION");
379
- if (!s3Region)
380
- fail("SENTINEL_S3_REGION is required for BYO S3.");
381
- const s3Endpoint = readEnv("SENTINEL_S3_ENDPOINT") || readEnv("S3_ENDPOINT");
382
- const accessKeyId = readEnv("SENTINEL_S3_ACCESS_KEY_ID") || readEnv("AWS_ACCESS_KEY_ID");
383
- const secretAccessKey = readEnv("SENTINEL_S3_SECRET_ACCESS_KEY") || readEnv("AWS_SECRET_ACCESS_KEY");
384
- if (!accessKeyId || !secretAccessKey) {
385
- fail("SENTINEL_S3_ACCESS_KEY_ID and SENTINEL_S3_SECRET_ACCESS_KEY are required.");
386
- }
387
- const s3Client = new client_s3_1.S3Client({
388
- region: s3Region,
389
- ...(s3Endpoint ? { endpoint: s3Endpoint } : {}),
390
- credentials: { accessKeyId, secretAccessKey }
391
- });
392
- await withRetry(async () => {
393
- await uploadArtifacts(s3Client, byoBucket, finalArtifacts);
394
- }, "upload artifacts");
395
- finalArtifacts = finalArtifacts.map((artifact) => ({
396
- ...artifact,
397
- bucket: byoBucket
398
- }));
399
- }
400
- else {
401
- const presignRes = await withRetry(() => fetch(`${appUrl}/api/uploads/presign`, {
402
- method: "POST",
403
- headers: {
404
- "content-type": "application/json",
405
- authorization: `Bearer ${ingestToken}`
406
- },
407
- body: JSON.stringify({
408
- items: finalArtifacts.map((artifact) => ({
409
- relPath: artifact.objectKey,
410
- contentType: artifact.contentType,
411
- sizeBytes: artifact.sizeBytes,
412
- kind: artifact.type
413
- }))
414
- })
415
- }), "POST /api/uploads/presign");
416
- if (!presignRes.ok) {
417
- const body = await presignRes.text();
418
- fail(`POST /api/uploads/presign failed (${presignRes.status}): ${body}`);
397
+ try {
398
+ if (byoBucket) {
399
+ const s3Region = readEnv("SENTINEL_S3_REGION") ||
400
+ readEnv("AWS_REGION") ||
401
+ readEnv("S3_REGION");
402
+ if (!s3Region)
403
+ fail("SENTINEL_S3_REGION is required for BYO S3.");
404
+ const s3Endpoint = readEnv("SENTINEL_S3_ENDPOINT") || readEnv("S3_ENDPOINT");
405
+ const accessKeyId = readEnv("SENTINEL_S3_ACCESS_KEY_ID") || readEnv("AWS_ACCESS_KEY_ID");
406
+ const secretAccessKey = readEnv("SENTINEL_S3_SECRET_ACCESS_KEY") ||
407
+ readEnv("AWS_SECRET_ACCESS_KEY");
408
+ if (!accessKeyId || !secretAccessKey) {
409
+ fail("SENTINEL_S3_ACCESS_KEY_ID and SENTINEL_S3_SECRET_ACCESS_KEY are required.");
410
+ }
411
+ const s3Client = new client_s3_1.S3Client({
412
+ region: s3Region,
413
+ ...(s3Endpoint ? { endpoint: s3Endpoint } : {}),
414
+ credentials: { accessKeyId, secretAccessKey }
415
+ });
416
+ await withRetry(async () => {
417
+ await uploadArtifacts(s3Client, byoBucket, finalArtifacts);
418
+ }, "upload artifacts");
419
+ finalArtifacts = finalArtifacts.map((artifact) => ({
420
+ ...artifact,
421
+ bucket: byoBucket
422
+ }));
419
423
  }
420
- const presignData = await presignRes.json();
421
- const uploadMap = new Map((presignData.items || []).map((item) => [item.relPath, item]));
422
- await withRetry(async () => {
423
- for (const artifact of finalArtifacts) {
424
- const item = uploadMap.get(artifact.objectKey);
425
- if (!item?.uploadUrl || !item?.objectKey) {
426
- throw new Error(`Missing upload URL for ${artifact.objectKey}`);
427
- }
428
- await withRetry(async () => {
429
- const body = fs_1.default.createReadStream(artifact.filePath);
430
- const res = await fetch(item.uploadUrl, {
431
- method: "PUT",
432
- headers: {
433
- "content-type": artifact.contentType
434
- },
435
- body
436
- });
437
- if (!res.ok) {
438
- throw new Error(`Upload failed (${res.status}) for ${artifact.objectKey}`);
439
- }
440
- }, `upload ${artifact.objectKey}`);
441
- artifact.objectKey = item.objectKey;
424
+ else {
425
+ const presignRes = await withRetry(() => fetch(`${appUrl}/api/uploads/presign`, {
426
+ method: "POST",
427
+ headers: {
428
+ "content-type": "application/json",
429
+ authorization: `Bearer ${ingestToken}`
430
+ },
431
+ body: JSON.stringify({
432
+ items: finalArtifacts.map((artifact) => ({
433
+ relPath: artifact.objectKey,
434
+ contentType: artifact.contentType,
435
+ sizeBytes: artifact.sizeBytes,
436
+ kind: artifact.type
437
+ }))
438
+ })
439
+ }), "POST /api/uploads/presign");
440
+ if (!presignRes.ok) {
441
+ const body = await presignRes.text();
442
+ fail(`POST /api/uploads/presign failed (${presignRes.status}): ${body}`);
442
443
  }
443
- }, "upload artifacts");
444
- finalArtifacts = finalArtifacts.map((artifact) => ({
445
- ...artifact,
446
- bucket: presignData.bucket
447
- }));
444
+ const presignData = await presignRes.json();
445
+ const uploadMap = new Map((presignData.items || []).map((item) => [item.relPath, item]));
446
+ await withRetry(async () => {
447
+ for (const artifact of finalArtifacts) {
448
+ const item = uploadMap.get(artifact.objectKey);
449
+ if (!item?.uploadUrl || !item?.objectKey) {
450
+ throw new Error(`Missing upload URL for ${artifact.objectKey}`);
451
+ }
452
+ await withRetry(async () => {
453
+ const body = fs_1.default.createReadStream(artifact.filePath);
454
+ const res = await fetch(item.uploadUrl, {
455
+ method: "PUT",
456
+ headers: {
457
+ "content-type": artifact.contentType
458
+ },
459
+ body
460
+ });
461
+ if (!res.ok) {
462
+ throw new Error(`Upload failed (${res.status}) for ${artifact.objectKey}`);
463
+ }
464
+ }, `upload ${artifact.objectKey}`);
465
+ artifact.objectKey = item.objectKey;
466
+ }
467
+ }, "upload artifacts");
468
+ finalArtifacts = finalArtifacts.map((artifact) => ({
469
+ ...artifact,
470
+ bucket: presignData.bucket
471
+ }));
472
+ }
473
+ }
474
+ catch (err) {
475
+ await bestEffortComplete(appUrl, ingestToken, runId, "failed", tests);
476
+ fail(err?.message || String(err));
448
477
  }
449
478
  const uploadDurationMs = Date.now() - uploadStart;
450
479
  const completeRes = await withRetry(() => fetch(`${appUrl}/api/runs/${runId}/complete`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentinelqa/uploader",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "Sentinel uploader CLI for CI/CD debugging artifacts",
6
6
  "license": "MIT",