@metasession.co/devaudit-cli 0.1.62 → 0.1.64
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metasession.co/devaudit-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "DevAudit CLI — installs, syncs, and operates the Metasession SDLC across consumer projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^0.8.2",
|
|
36
|
-
"@metasession.co/devaudit-plugin-sdk": "
|
|
36
|
+
"@metasession.co/devaudit-plugin-sdk": "file:../plugin-sdk",
|
|
37
37
|
"ajv": "^8.20.0",
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"consola": "^3.2.3",
|
|
@@ -244,6 +244,13 @@ MAX_ATTEMPTS=${UPLOAD_MAX_ATTEMPTS:-5}
|
|
|
244
244
|
INITIAL_BACKOFF_SECONDS=${UPLOAD_INITIAL_BACKOFF_SECONDS:-1}
|
|
245
245
|
UPLOAD_CONNECT_TIMEOUT_SECONDS=${UPLOAD_CONNECT_TIMEOUT_SECONDS:-10}
|
|
246
246
|
UPLOAD_MAX_TIME_SECONDS=${UPLOAD_MAX_TIME_SECONDS:-120}
|
|
247
|
+
# DevAudit-Installer#189 — files above this size use the presigned R2 URL
|
|
248
|
+
# upload flow (3-step: request URL → PUT to R2 → notify portal) instead of
|
|
249
|
+
# the multipart POST. Avoids the portal's FormData body parser limit.
|
|
250
|
+
# 25MB = 26214400 bytes.
|
|
251
|
+
PRESIGNED_THRESHOLD_BYTES=${PRESIGNED_THRESHOLD:-26214400}
|
|
252
|
+
PRESIGNED_MAX_ATTEMPTS=${PRESIGNED_MAX_ATTEMPTS:-3}
|
|
253
|
+
PRESIGNED_UPLOAD_MAX_TIME_SECONDS=${PRESIGNED_UPLOAD_MAX_TIME_SECONDS:-300}
|
|
247
254
|
|
|
248
255
|
is_unedited_starter_stub() {
|
|
249
256
|
# Match BOTH banner phrasings the SDLC has shipped (v0.1.36 changed
|
|
@@ -253,6 +260,153 @@ is_unedited_starter_stub() {
|
|
|
253
260
|
grep -aqE 'STARTER TEMPLATE.+REPLACE BEFORE' "$1"
|
|
254
261
|
}
|
|
255
262
|
|
|
263
|
+
# DevAudit-Installer#189 — Presigned R2 URL upload for large files (>25MB).
|
|
264
|
+
# 3-step flow: (1) request presigned URL from portal, (2) PUT file to R2,
|
|
265
|
+
# (3) notify portal that upload is complete. Each step retries on 429/5xx
|
|
266
|
+
# and connection errors. Returns 0 on success, 1 on failure.
|
|
267
|
+
upload_presigned() {
|
|
268
|
+
local file="$1"
|
|
269
|
+
local filename
|
|
270
|
+
filename=$(basename "$file")
|
|
271
|
+
local file_size
|
|
272
|
+
file_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file")
|
|
273
|
+
|
|
274
|
+
# Derive a MIME type from the extension (best-effort).
|
|
275
|
+
local mime_type="application/octet-stream"
|
|
276
|
+
case "$filename" in
|
|
277
|
+
*.zip) mime_type="application/zip" ;;
|
|
278
|
+
*.json) mime_type="application/json" ;;
|
|
279
|
+
*.png) mime_type="image/png" ;;
|
|
280
|
+
*.pdf) mime_type="application/pdf" ;;
|
|
281
|
+
*.md) mime_type="text/markdown" ;;
|
|
282
|
+
*.html) mime_type="text/html" ;;
|
|
283
|
+
esac
|
|
284
|
+
|
|
285
|
+
local presign_url="${DEVAUDIT_BASE_URL}/api/evidence/upload-url"
|
|
286
|
+
local complete_url="${DEVAUDIT_BASE_URL}/api/evidence/upload-complete"
|
|
287
|
+
|
|
288
|
+
# --- Step 1: Request presigned upload URL ---
|
|
289
|
+
local attempt backoff http_code curl_exit resp_body upload_url evidence_id
|
|
290
|
+
attempt=1
|
|
291
|
+
backoff=$INITIAL_BACKOFF_SECONDS
|
|
292
|
+
upload_url=""
|
|
293
|
+
evidence_id=""
|
|
294
|
+
while [ "$attempt" -le "$PRESIGNED_MAX_ATTEMPTS" ]; do
|
|
295
|
+
resp_body=$(mktemp)
|
|
296
|
+
http_code=$(curl -s -o "$resp_body" -w "%{http_code}" \
|
|
297
|
+
-X POST -L --max-redirs 3 \
|
|
298
|
+
--connect-timeout "$UPLOAD_CONNECT_TIMEOUT_SECONDS" \
|
|
299
|
+
--max-time "$UPLOAD_MAX_TIME_SECONDS" \
|
|
300
|
+
"$presign_url" \
|
|
301
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
302
|
+
-H "Content-Type: application/json" \
|
|
303
|
+
-d "{
|
|
304
|
+
\"projectSlug\": \"${PROJECT_SLUG}\",
|
|
305
|
+
\"requirementId\": \"${REQUIREMENT_ID}\",
|
|
306
|
+
\"evidenceType\": \"${EVIDENCE_TYPE}\",
|
|
307
|
+
\"fileName\": \"${filename}\",
|
|
308
|
+
\"fileSizeBytes\": ${file_size},
|
|
309
|
+
\"mimeType\": \"${mime_type}\",
|
|
310
|
+
\"metadata\": ${METADATA},
|
|
311
|
+
\"releaseVersion\": \"${RELEASE_VERSION}\",
|
|
312
|
+
\"createReleaseIfMissing\": \"${CREATE_RELEASE_IF_MISSING}\",
|
|
313
|
+
\"releaseBranch\": \"${BRANCH}\",
|
|
314
|
+
\"environment\": \"${ENVIRONMENT}\",
|
|
315
|
+
\"evidenceCategory\": \"${EVIDENCE_CATEGORY}\",
|
|
316
|
+
\"sdlcStage\": \"${SDLC_STAGE}\"
|
|
317
|
+
}") || curl_exit=$?
|
|
318
|
+
curl_exit=${curl_exit:-0}
|
|
319
|
+
if [ "$curl_exit" -eq 0 ] && [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
320
|
+
upload_url=$(jq -r '.uploadUrl // empty' "$resp_body" 2>/dev/null || true)
|
|
321
|
+
evidence_id=$(jq -r '.evidenceId // empty' "$resp_body" 2>/dev/null || true)
|
|
322
|
+
rm -f "$resp_body"
|
|
323
|
+
if [ -n "$upload_url" ] && [ -n "$evidence_id" ] && [ "$upload_url" != "null" ] && [ "$evidence_id" != "null" ]; then
|
|
324
|
+
break
|
|
325
|
+
fi
|
|
326
|
+
# Portal responded 2xx but didn't return presigned URL fields —
|
|
327
|
+
# presigned URL flow not configured. Fall back to multipart.
|
|
328
|
+
echo -n "(portal did not return presigned URL, falling back to multipart) "
|
|
329
|
+
return 255
|
|
330
|
+
fi
|
|
331
|
+
rm -f "$resp_body"
|
|
332
|
+
if [ "$curl_exit" -ne 0 ] || [ "$http_code" = "429" ] || { [ "$http_code" -ge 500 ] && [ "$http_code" -lt 600 ]; }; then
|
|
333
|
+
if [ "$attempt" -lt "$PRESIGNED_MAX_ATTEMPTS" ]; then
|
|
334
|
+
echo -n "(step 1: HTTP ${http_code}, retry in ${backoff}s) "
|
|
335
|
+
sleep "$backoff"
|
|
336
|
+
attempt=$((attempt + 1))
|
|
337
|
+
backoff=$((backoff * 2))
|
|
338
|
+
continue
|
|
339
|
+
fi
|
|
340
|
+
fi
|
|
341
|
+
# Non-retriable error (4xx other than 429).
|
|
342
|
+
echo -n "(step 1 failed: HTTP ${http_code}) "
|
|
343
|
+
return 1
|
|
344
|
+
done
|
|
345
|
+
|
|
346
|
+
if [ -z "$upload_url" ] || [ -z "$evidence_id" ]; then
|
|
347
|
+
echo -n "(step 1: no presigned URL after ${attempt} attempts) "
|
|
348
|
+
return 1
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# --- Step 2: Upload directly to R2 via presigned URL ---
|
|
352
|
+
attempt=1
|
|
353
|
+
backoff=$INITIAL_BACKOFF_SECONDS
|
|
354
|
+
while [ "$attempt" -le "$PRESIGNED_MAX_ATTEMPTS" ]; do
|
|
355
|
+
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
356
|
+
-X PUT \
|
|
357
|
+
-H "Content-Type: ${mime_type}" \
|
|
358
|
+
--connect-timeout "$UPLOAD_CONNECT_TIMEOUT_SECONDS" \
|
|
359
|
+
--max-time "$PRESIGNED_UPLOAD_MAX_TIME_SECONDS" \
|
|
360
|
+
--data-binary @"$file" \
|
|
361
|
+
"$upload_url") || curl_exit=$?
|
|
362
|
+
curl_exit=${curl_exit:-0}
|
|
363
|
+
if [ "$curl_exit" -eq 0 ] && [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
364
|
+
break
|
|
365
|
+
fi
|
|
366
|
+
if [ "$curl_exit" -ne 0 ] || [ "$http_code" = "429" ] || { [ "$http_code" -ge 500 ] && [ "$http_code" -lt 600 ]; }; then
|
|
367
|
+
if [ "$attempt" -lt "$PRESIGNED_MAX_ATTEMPTS" ]; then
|
|
368
|
+
echo -n "(step 2: HTTP ${http_code}, retry in ${backoff}s) "
|
|
369
|
+
sleep "$backoff"
|
|
370
|
+
attempt=$((attempt + 1))
|
|
371
|
+
backoff=$((backoff * 2))
|
|
372
|
+
continue
|
|
373
|
+
fi
|
|
374
|
+
fi
|
|
375
|
+
echo -n "(step 2 failed: HTTP ${http_code}) "
|
|
376
|
+
return 1
|
|
377
|
+
done
|
|
378
|
+
|
|
379
|
+
# --- Step 3: Notify portal that upload is complete ---
|
|
380
|
+
attempt=1
|
|
381
|
+
backoff=$INITIAL_BACKOFF_SECONDS
|
|
382
|
+
while [ "$attempt" -le "$PRESIGNED_MAX_ATTEMPTS" ]; do
|
|
383
|
+
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
384
|
+
-X POST -L --max-redirs 3 \
|
|
385
|
+
--connect-timeout "$UPLOAD_CONNECT_TIMEOUT_SECONDS" \
|
|
386
|
+
--max-time "$UPLOAD_MAX_TIME_SECONDS" \
|
|
387
|
+
"$complete_url" \
|
|
388
|
+
-H "Authorization: Bearer ${DEVAUDIT_API_KEY}" \
|
|
389
|
+
-H "Content-Type: application/json" \
|
|
390
|
+
-d "{\"evidenceId\": \"${evidence_id}\"}") || curl_exit=$?
|
|
391
|
+
curl_exit=${curl_exit:-0}
|
|
392
|
+
if [ "$curl_exit" -eq 0 ] && [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
393
|
+
return 0
|
|
394
|
+
fi
|
|
395
|
+
if [ "$curl_exit" -ne 0 ] || [ "$http_code" = "429" ] || { [ "$http_code" -ge 500 ] && [ "$http_code" -lt 600 ]; }; then
|
|
396
|
+
if [ "$attempt" -lt "$PRESIGNED_MAX_ATTEMPTS" ]; then
|
|
397
|
+
echo -n "(step 3: HTTP ${http_code}, retry in ${backoff}s) "
|
|
398
|
+
sleep "$backoff"
|
|
399
|
+
attempt=$((attempt + 1))
|
|
400
|
+
backoff=$((backoff * 2))
|
|
401
|
+
continue
|
|
402
|
+
fi
|
|
403
|
+
fi
|
|
404
|
+
echo -n "(step 3 failed: HTTP ${http_code}) "
|
|
405
|
+
return 1
|
|
406
|
+
done
|
|
407
|
+
return 1
|
|
408
|
+
}
|
|
409
|
+
|
|
256
410
|
for FILE in "${FILES[@]}"; do
|
|
257
411
|
FILENAME=$(basename "$FILE")
|
|
258
412
|
if is_unedited_starter_stub "$FILE"; then
|
|
@@ -262,6 +416,27 @@ for FILE in "${FILES[@]}"; do
|
|
|
262
416
|
fi
|
|
263
417
|
FILE_SIZE=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE")
|
|
264
418
|
echo -n "Uploading ${FILENAME}... "
|
|
419
|
+
|
|
420
|
+
# DevAudit-Installer#189 — large files use the presigned R2 URL flow
|
|
421
|
+
# to bypass the portal's multipart body parser limit. If the portal
|
|
422
|
+
# doesn't support presigned URLs (returns 255), fall back to multipart.
|
|
423
|
+
if [ "$FILE_SIZE" -ge "$PRESIGNED_THRESHOLD_BYTES" ]; then
|
|
424
|
+
PRESIGNED_RESULT=0
|
|
425
|
+
upload_presigned "$FILE" || PRESIGNED_RESULT=$?
|
|
426
|
+
if [ "$PRESIGNED_RESULT" -eq 0 ]; then
|
|
427
|
+
echo "OK (${FILE_SIZE} bytes, presigned R2 upload)"
|
|
428
|
+
SUCCEEDED=$((SUCCEEDED + 1))
|
|
429
|
+
TOTAL_SIZE=$((TOTAL_SIZE + FILE_SIZE))
|
|
430
|
+
continue
|
|
431
|
+
elif [ "$PRESIGNED_RESULT" -ne 255 ]; then
|
|
432
|
+
echo "FAILED (presigned upload after ${PRESIGNED_MAX_ATTEMPTS} attempts)"
|
|
433
|
+
FAILED=$((FAILED + 1))
|
|
434
|
+
continue
|
|
435
|
+
fi
|
|
436
|
+
# Result 255 — portal doesn't support presigned URLs, fall through
|
|
437
|
+
# to the existing multipart flow.
|
|
438
|
+
fi
|
|
439
|
+
|
|
265
440
|
# `-L` follows 3xx redirects (devaudit-installer#143). The portal host
|
|
266
441
|
# has moved before (devaudit.metasession.co → devaudit.ai); without -L
|
|
267
442
|
# every consumer's CI silently fails on a stale base URL. `--max-redirs 3`
|
|
@@ -639,6 +639,13 @@ jobs:
|
|
|
639
639
|
(cd e2e-artifacts && zip -qr playwright-report.zip playwright-report/) 2>/dev/null || true
|
|
640
640
|
fi
|
|
641
641
|
if [ -f e2e-artifacts/playwright-report.zip ]; then
|
|
642
|
+
# DevAudit-Installer#189 — the Playwright HTML report zip
|
|
643
|
+
# (~50–100MB) exceeds the portal's multipart body parser
|
|
644
|
+
# limit. The JSON results above are the canonical evidence;
|
|
645
|
+
# the HTML report is a human-facing convenience. Soft-fail
|
|
646
|
+
# so the job doesn't block on a portal-side size limit.
|
|
647
|
+
# Once the portal's presigned R2 URL flow is live (#189
|
|
648
|
+
# Option C), this can be reverted to a hard fail.
|
|
642
649
|
if bash scripts/upload-evidence.sh \
|
|
643
650
|
{{PROJECT_SLUG}} "$REQ" test_report \
|
|
644
651
|
e2e-artifacts/playwright-report.zip \
|
|
@@ -647,8 +654,7 @@ jobs:
|
|
|
647
654
|
then
|
|
648
655
|
:
|
|
649
656
|
else
|
|
650
|
-
echo "::warning::playwright HTML report upload failed for ${REQ}"
|
|
651
|
-
UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
|
|
657
|
+
echo "::warning::playwright HTML report upload failed for ${REQ} (non-blocking — JSON results are canonical evidence)"
|
|
652
658
|
fi
|
|
653
659
|
fi
|
|
654
660
|
done
|
|
@@ -190,22 +190,42 @@ jobs:
|
|
|
190
190
|
# PRs to develop should report Quality Gates without mutating DevAudit
|
|
191
191
|
# releases or evidence. Release registration remains a develop-push /
|
|
192
192
|
# manual-dispatch side effect.
|
|
193
|
-
if: ${{ github.event_name != 'pull_request'
|
|
193
|
+
if: ${{ github.event_name != 'pull_request' }}
|
|
194
194
|
outputs:
|
|
195
195
|
version: ${{ steps.version.outputs.version }}
|
|
196
196
|
env:
|
|
197
|
-
|
|
197
|
+
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
198
198
|
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
199
199
|
steps:
|
|
200
200
|
- uses: actions/checkout@v6
|
|
201
201
|
|
|
202
|
-
- name:
|
|
202
|
+
- name: Resolve DevAudit base URL
|
|
203
203
|
run: |
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
# Prefer sdlc-config.json devaudit.base_url (PR-visible) over the
|
|
205
|
+
# deprecated repo Variable — matching the node template and
|
|
206
|
+
# compliance-evidence.yml. DevAudit-Installer#156: gating this job
|
|
207
|
+
# on `vars.DEVAUDIT_BASE_URL` silently skipped release registration
|
|
208
|
+
# + evidence upload for consumers that moved base_url into
|
|
209
|
+
# sdlc-config.json (the v1.23.0 direction).
|
|
210
|
+
BASE=""
|
|
211
|
+
if [ -f sdlc-config.json ]; then
|
|
212
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
213
|
+
[ -n "$CONFIG_URL" ] && BASE="$CONFIG_URL"
|
|
214
|
+
fi
|
|
215
|
+
if [ -n "$BASE" ]; then
|
|
216
|
+
echo "Using devaudit.base_url from sdlc-config.json: $BASE"
|
|
217
|
+
elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
|
|
218
|
+
BASE="$DEVAUDIT_BASE_URL_VAR"
|
|
219
|
+
echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url for PR-visible config."
|
|
220
|
+
else
|
|
221
|
+
echo "::warning::No DevAudit base URL configured — skipping release registration. Set devaudit.base_url in sdlc-config.json."
|
|
222
|
+
fi
|
|
223
|
+
if [ -n "$BASE" ] && [ -z "${DEVAUDIT_API_KEY}" ]; then
|
|
224
|
+
echo "::error::DEVAUDIT_API_KEY (secret) must be set when a DevAudit base URL is configured."
|
|
206
225
|
exit 1
|
|
207
226
|
fi
|
|
208
|
-
echo "BASE=${
|
|
227
|
+
echo "BASE=${BASE%/}" >> "$GITHUB_ENV"
|
|
228
|
+
echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
|
|
209
229
|
|
|
210
230
|
- name: Determine release version
|
|
211
231
|
id: version
|
|
@@ -294,13 +314,33 @@ jobs:
|
|
|
294
314
|
# evidence — `status=failed` is itself the audit trail. `!cancelled()`
|
|
295
315
|
# still guards against partial state on operator-cancel.
|
|
296
316
|
# DevAudit-Installer#96.
|
|
297
|
-
if: ${{ always() && !cancelled() &&
|
|
317
|
+
if: ${{ always() && !cancelled() && needs.register-release.result == 'success' }}
|
|
298
318
|
env:
|
|
299
|
-
|
|
319
|
+
DEVAUDIT_BASE_URL_VAR: ${{ vars.DEVAUDIT_BASE_URL }}
|
|
300
320
|
DEVAUDIT_API_KEY: ${{ secrets.DEVAUDIT_API_KEY }}
|
|
301
321
|
steps:
|
|
302
322
|
- uses: actions/checkout@v6
|
|
303
323
|
|
|
324
|
+
- name: Resolve DevAudit base URL
|
|
325
|
+
run: |
|
|
326
|
+
# Prefer sdlc-config.json devaudit.base_url over the deprecated repo
|
|
327
|
+
# Variable (DevAudit-Installer#156). When neither is set the upload
|
|
328
|
+
# step below no-ops via `if: env.DEVAUDIT_BASE_URL != ''`.
|
|
329
|
+
BASE=""
|
|
330
|
+
if [ -f sdlc-config.json ]; then
|
|
331
|
+
CONFIG_URL=$(jq -r '.devaudit.base_url // empty' sdlc-config.json 2>/dev/null || true)
|
|
332
|
+
[ -n "$CONFIG_URL" ] && BASE="$CONFIG_URL"
|
|
333
|
+
fi
|
|
334
|
+
if [ -n "$BASE" ]; then
|
|
335
|
+
echo "Using devaudit.base_url from sdlc-config.json: $BASE"
|
|
336
|
+
elif [ -n "$DEVAUDIT_BASE_URL_VAR" ]; then
|
|
337
|
+
BASE="$DEVAUDIT_BASE_URL_VAR"
|
|
338
|
+
echo "::warning::Using repo Variable DEVAUDIT_BASE_URL (deprecated in v1.23.0). Move base_url to sdlc-config.json devaudit.base_url for PR-visible config."
|
|
339
|
+
else
|
|
340
|
+
echo "::warning::No DevAudit base URL configured — skipping evidence upload. Set devaudit.base_url in sdlc-config.json."
|
|
341
|
+
fi
|
|
342
|
+
echo "DEVAUDIT_BASE_URL=${BASE%/}" >> "$GITHUB_ENV"
|
|
343
|
+
|
|
304
344
|
# Download to workspace root: upload-artifact@v4 preserves the file's
|
|
305
345
|
# workspace-relative path (e.g. mission-control-api/ci-evidence/sast.json
|
|
306
346
|
# for a subdir project). Downloading with path: '.' restores files to
|