@metasession.co/devaudit-cli 0.1.63 → 0.1.65
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/dist/index.js +174 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +15 -3
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.test.sh +30 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/SKILL.md +30 -2
- package/sdlc/files/_common/skills/e2e-test-engineer/references/common-patterns.md +27 -0
- package/sdlc/files/_common/skills/e2e-test-engineer/references/test-tags.ts +55 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +17 -0
- package/sdlc/files/ci/ci.yml.template +13 -1
- package/sdlc/files/ci/compliance-evidence.yml.template +15 -1
- package/sdlc/files/ci/python/ci.yml.template +48 -8
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ function emitJsonResult(payload) {
|
|
|
56
56
|
|
|
57
57
|
// package.json
|
|
58
58
|
var package_default = {
|
|
59
|
-
version: "0.1.
|
|
59
|
+
version: "0.1.65"};
|
|
60
60
|
|
|
61
61
|
// src/lib/version.ts
|
|
62
62
|
var CLI_VERSION = package_default.version;
|
|
@@ -599,6 +599,9 @@ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
|
599
599
|
var DEFAULT_MAX_ATTEMPTS = 5;
|
|
600
600
|
var DEFAULT_UPLOAD_MAX_TIME_SECONDS = 120;
|
|
601
601
|
var INITIAL_BACKOFF_MS = 1e3;
|
|
602
|
+
var DEFAULT_PRESIGNED_THRESHOLD_BYTES = 26214400;
|
|
603
|
+
var DEFAULT_PRESIGNED_MAX_ATTEMPTS = 3;
|
|
604
|
+
var DEFAULT_PRESIGNED_UPLOAD_MAX_TIME_SECONDS = 300;
|
|
602
605
|
function maxAttempts() {
|
|
603
606
|
const raw = Number.parseInt(process.env["UPLOAD_MAX_ATTEMPTS"] ?? "", 10);
|
|
604
607
|
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MAX_ATTEMPTS;
|
|
@@ -648,6 +651,7 @@ function buildUploadForm(file, buf, opts) {
|
|
|
648
651
|
if (opts.releaseTitle) form.set("releaseTitle", opts.releaseTitle);
|
|
649
652
|
if (opts.changeType) form.set("changeType", opts.changeType);
|
|
650
653
|
if (opts.gateStatus) form.set("gateStatus", opts.gateStatus);
|
|
654
|
+
if (opts.sdlcStage) form.set("sdlcStage", opts.sdlcStage);
|
|
651
655
|
return form;
|
|
652
656
|
}
|
|
653
657
|
function uploadFailureMessage(err, timeoutSeconds) {
|
|
@@ -724,6 +728,164 @@ async function uploadOne(file, buf, opts) {
|
|
|
724
728
|
}
|
|
725
729
|
return { file, ok: false, status: 0, error: "max retries exhausted" };
|
|
726
730
|
}
|
|
731
|
+
var PRESIGNED_FALLBACK = /* @__PURE__ */ Symbol("presigned-fallback");
|
|
732
|
+
async function uploadPresigned(file, buf, opts) {
|
|
733
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
734
|
+
const presignUrl = `${baseUrl}/api/evidence/upload-url`;
|
|
735
|
+
const completeUrl = `${baseUrl}/api/evidence/upload-complete`;
|
|
736
|
+
const maxAttemptsPresigned = DEFAULT_PRESIGNED_MAX_ATTEMPTS;
|
|
737
|
+
const timeoutSeconds = uploadMaxTimeSeconds();
|
|
738
|
+
const presignedTimeoutSeconds = DEFAULT_PRESIGNED_UPLOAD_MAX_TIME_SECONDS;
|
|
739
|
+
const mimeType = deriveMimeType(file);
|
|
740
|
+
const metadata = opts.metadata ?? {};
|
|
741
|
+
let uploadUrl = "";
|
|
742
|
+
let evidenceId = "";
|
|
743
|
+
let attempt = 1;
|
|
744
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
745
|
+
while (attempt <= maxAttemptsPresigned) {
|
|
746
|
+
const controller = new AbortController();
|
|
747
|
+
const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
|
|
748
|
+
let res;
|
|
749
|
+
try {
|
|
750
|
+
res = await fetch(presignUrl, {
|
|
751
|
+
method: "POST",
|
|
752
|
+
headers: {
|
|
753
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
754
|
+
"content-type": "application/json"
|
|
755
|
+
},
|
|
756
|
+
body: JSON.stringify({
|
|
757
|
+
projectSlug: opts.projectSlug,
|
|
758
|
+
requirementId: opts.requirementId,
|
|
759
|
+
evidenceType: opts.evidenceType,
|
|
760
|
+
fileName: basename(file),
|
|
761
|
+
fileSizeBytes: buf.byteLength,
|
|
762
|
+
mimeType,
|
|
763
|
+
metadata,
|
|
764
|
+
...opts.releaseVersion ? { releaseVersion: opts.releaseVersion } : {},
|
|
765
|
+
...opts.createReleaseIfMissing ? { createReleaseIfMissing: true } : {},
|
|
766
|
+
...opts.releaseBranch ? { releaseBranch: opts.releaseBranch } : {},
|
|
767
|
+
...opts.environment ? { environment: opts.environment } : {},
|
|
768
|
+
...opts.evidenceCategory ? { evidenceCategory: opts.evidenceCategory } : {},
|
|
769
|
+
...opts.sdlcStage ? { sdlcStage: opts.sdlcStage } : {}
|
|
770
|
+
}),
|
|
771
|
+
signal: controller.signal
|
|
772
|
+
});
|
|
773
|
+
} catch (err) {
|
|
774
|
+
if (attempt < maxAttemptsPresigned) {
|
|
775
|
+
await delay(backoff);
|
|
776
|
+
backoff *= 2;
|
|
777
|
+
attempt += 1;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
return { file, ok: false, status: 0, error: uploadFailureMessage(err, timeoutSeconds) };
|
|
781
|
+
} finally {
|
|
782
|
+
clearTimeout(timer);
|
|
783
|
+
}
|
|
784
|
+
if (res.ok) {
|
|
785
|
+
const body = await res.json().catch(() => null);
|
|
786
|
+
if (body?.uploadUrl && body?.evidenceId) {
|
|
787
|
+
uploadUrl = body.uploadUrl;
|
|
788
|
+
evidenceId = body.evidenceId;
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
return PRESIGNED_FALLBACK;
|
|
792
|
+
}
|
|
793
|
+
if (RETRYABLE_STATUSES.has(res.status) && attempt < maxAttemptsPresigned) {
|
|
794
|
+
await delay(backoff);
|
|
795
|
+
backoff *= 2;
|
|
796
|
+
attempt += 1;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const errText = await res.text().catch(() => "(no body)");
|
|
800
|
+
return { file, ok: false, status: res.status, error: `presigned step 1: ${errText}` };
|
|
801
|
+
}
|
|
802
|
+
if (!uploadUrl || !evidenceId) {
|
|
803
|
+
return { file, ok: false, status: 0, error: "presigned step 1: no URL after retries" };
|
|
804
|
+
}
|
|
805
|
+
attempt = 1;
|
|
806
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
807
|
+
while (attempt <= maxAttemptsPresigned) {
|
|
808
|
+
const controller = new AbortController();
|
|
809
|
+
const timer = setTimeout(() => controller.abort(), presignedTimeoutSeconds * 1e3);
|
|
810
|
+
let res;
|
|
811
|
+
try {
|
|
812
|
+
res = await fetch(uploadUrl, {
|
|
813
|
+
method: "PUT",
|
|
814
|
+
headers: { "content-type": mimeType },
|
|
815
|
+
body: new Uint8Array(buf),
|
|
816
|
+
signal: controller.signal
|
|
817
|
+
});
|
|
818
|
+
} catch (err) {
|
|
819
|
+
if (attempt < maxAttemptsPresigned) {
|
|
820
|
+
await delay(backoff);
|
|
821
|
+
backoff *= 2;
|
|
822
|
+
attempt += 1;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
return { file, ok: false, status: 0, error: `presigned step 2: ${uploadFailureMessage(err, presignedTimeoutSeconds)}` };
|
|
826
|
+
} finally {
|
|
827
|
+
clearTimeout(timer);
|
|
828
|
+
}
|
|
829
|
+
if (res.ok) break;
|
|
830
|
+
if (RETRYABLE_STATUSES.has(res.status) && attempt < maxAttemptsPresigned) {
|
|
831
|
+
await delay(backoff);
|
|
832
|
+
backoff *= 2;
|
|
833
|
+
attempt += 1;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
return { file, ok: false, status: res.status, error: `presigned step 2: HTTP ${res.status}` };
|
|
837
|
+
}
|
|
838
|
+
attempt = 1;
|
|
839
|
+
backoff = INITIAL_BACKOFF_MS;
|
|
840
|
+
while (attempt <= maxAttemptsPresigned) {
|
|
841
|
+
const controller = new AbortController();
|
|
842
|
+
const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
|
|
843
|
+
let res;
|
|
844
|
+
try {
|
|
845
|
+
res = await fetch(completeUrl, {
|
|
846
|
+
method: "POST",
|
|
847
|
+
headers: {
|
|
848
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
849
|
+
"content-type": "application/json"
|
|
850
|
+
},
|
|
851
|
+
body: JSON.stringify({ evidenceId }),
|
|
852
|
+
signal: controller.signal
|
|
853
|
+
});
|
|
854
|
+
} catch (err) {
|
|
855
|
+
if (attempt < maxAttemptsPresigned) {
|
|
856
|
+
await delay(backoff);
|
|
857
|
+
backoff *= 2;
|
|
858
|
+
attempt += 1;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
return { file, ok: false, status: 0, error: `presigned step 3: ${uploadFailureMessage(err, timeoutSeconds)}` };
|
|
862
|
+
} finally {
|
|
863
|
+
clearTimeout(timer);
|
|
864
|
+
}
|
|
865
|
+
if (res.ok) {
|
|
866
|
+
const body = await res.json().catch(() => null);
|
|
867
|
+
return { file, ok: true, status: res.status, body };
|
|
868
|
+
}
|
|
869
|
+
if (RETRYABLE_STATUSES.has(res.status) && attempt < maxAttemptsPresigned) {
|
|
870
|
+
await delay(backoff);
|
|
871
|
+
backoff *= 2;
|
|
872
|
+
attempt += 1;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
return { file, ok: false, status: res.status, error: `presigned step 3: HTTP ${res.status}` };
|
|
876
|
+
}
|
|
877
|
+
return { file, ok: false, status: 0, error: "presigned step 3: max retries exhausted" };
|
|
878
|
+
}
|
|
879
|
+
function deriveMimeType(file) {
|
|
880
|
+
const lower = file.toLowerCase();
|
|
881
|
+
if (lower.endsWith(".zip")) return "application/zip";
|
|
882
|
+
if (lower.endsWith(".json")) return "application/json";
|
|
883
|
+
if (lower.endsWith(".png")) return "image/png";
|
|
884
|
+
if (lower.endsWith(".pdf")) return "application/pdf";
|
|
885
|
+
if (lower.endsWith(".md")) return "text/markdown";
|
|
886
|
+
if (lower.endsWith(".html")) return "text/html";
|
|
887
|
+
return "application/octet-stream";
|
|
888
|
+
}
|
|
727
889
|
async function uploadEvidence(opts) {
|
|
728
890
|
const files = await collectFiles(opts.filePath);
|
|
729
891
|
if (files.length === 0) {
|
|
@@ -736,6 +898,13 @@ async function uploadEvidence(opts) {
|
|
|
736
898
|
results.push({ file, ok: true, status: 0, skipped: true });
|
|
737
899
|
continue;
|
|
738
900
|
}
|
|
901
|
+
if (buf.byteLength >= DEFAULT_PRESIGNED_THRESHOLD_BYTES) {
|
|
902
|
+
const presignedResult = await uploadPresigned(file, buf, opts);
|
|
903
|
+
if (presignedResult !== PRESIGNED_FALLBACK) {
|
|
904
|
+
results.push(presignedResult);
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
739
908
|
results.push(await uploadOne(file, buf, opts));
|
|
740
909
|
}
|
|
741
910
|
return results;
|
|
@@ -839,6 +1008,7 @@ async function runPush(options) {
|
|
|
839
1008
|
...options.releaseTitle !== void 0 ? { releaseTitle: options.releaseTitle } : {},
|
|
840
1009
|
...options.changeType !== void 0 ? { changeType: options.changeType } : {},
|
|
841
1010
|
...options.gateStatus !== void 0 ? { gateStatus: options.gateStatus } : {},
|
|
1011
|
+
...options.sdlcStage !== void 0 ? { sdlcStage: options.sdlcStage } : {},
|
|
842
1012
|
metadata
|
|
843
1013
|
});
|
|
844
1014
|
let okCount = 0;
|
|
@@ -1951,7 +2121,7 @@ async function syncSkills(ctx) {
|
|
|
1951
2121
|
}
|
|
1952
2122
|
return { name: "Claude Code skills", filesSynced: count, message: `${count} synced to .claude/skills/` };
|
|
1953
2123
|
}
|
|
1954
|
-
var HELPER_FILES = ["evidence.ts", "evidence-shot-core.ts"];
|
|
2124
|
+
var HELPER_FILES = ["evidence.ts", "evidence-shot-core.ts", "test-tags.ts"];
|
|
1955
2125
|
async function syncEvidenceHelper(ctx) {
|
|
1956
2126
|
if (ctx.stack !== "node") {
|
|
1957
2127
|
return { name: "E2E evidence helper", filesSynced: 0, skipped: true };
|
|
@@ -2977,7 +3147,7 @@ async function main(argv) {
|
|
|
2977
3147
|
...globals.dryRun !== void 0 ? { dryRun: Boolean(globals.dryRun) } : {}
|
|
2978
3148
|
});
|
|
2979
3149
|
});
|
|
2980
|
-
program.command("push <project-slug> <requirement-id> <evidence-type> <file>").description("Upload evidence file(s) to DevAudit (port of scripts/upload-evidence.sh)").option("--release <version>", "release version (e.g. v1.0.0)").option("--create-release-if-missing", "auto-create the release as 'draft' if absent").option("--environment <env>", "uat | production").option("--category <cat>", "ci_pipeline | local_dev | planning | test_report | security_scan | release_artifact").option("--git-sha <sha>", "attached to metadata.gitSha").option("--ci-run-id <id>", "attached to metadata.ciRunId").option("--branch <name>", "git branch \u2014 sent as releaseBranch + metadata.branch").option("--release-title <text>", "human title for the release row (releaseTitle; portal no-clobbers)").option("--change-type <type>", "conventional-commit prefix for the release row (changeType)").option("--gate-status <status>", "passed | failed | skipped (gateStatus)").option(
|
|
3150
|
+
program.command("push <project-slug> <requirement-id> <evidence-type> <file>").description("Upload evidence file(s) to DevAudit (port of scripts/upload-evidence.sh)").option("--release <version>", "release version (e.g. v1.0.0)").option("--create-release-if-missing", "auto-create the release as 'draft' if absent").option("--environment <env>", "uat | production").option("--category <cat>", "ci_pipeline | local_dev | planning | test_report | security_scan | release_artifact").option("--git-sha <sha>", "attached to metadata.gitSha").option("--ci-run-id <id>", "attached to metadata.ciRunId").option("--branch <name>", "git branch \u2014 sent as releaseBranch + metadata.branch").option("--release-title <text>", "human title for the release row (releaseTitle; portal no-clobbers)").option("--change-type <type>", "conventional-commit prefix for the release row (changeType)").option("--gate-status <status>", "passed | failed | skipped (gateStatus)").option("--sdlc-stage <stage>", "SDLC stage 1-5 (sdlcStage)").option(
|
|
2981
3151
|
"--meta-key <pair>",
|
|
2982
3152
|
"repeatable key=value merged into the metadata JSON",
|
|
2983
3153
|
(val, acc) => [...acc, val],
|
|
@@ -3000,6 +3170,7 @@ async function main(argv) {
|
|
|
3000
3170
|
...opts.releaseTitle !== void 0 ? { releaseTitle: opts.releaseTitle } : {},
|
|
3001
3171
|
...opts.changeType !== void 0 ? { changeType: opts.changeType } : {},
|
|
3002
3172
|
...opts.gateStatus !== void 0 ? { gateStatus: opts.gateStatus } : {},
|
|
3173
|
+
...opts.sdlcStage !== void 0 ? { sdlcStage: opts.sdlcStage } : {},
|
|
3003
3174
|
...opts.metaKey !== void 0 && opts.metaKey.length > 0 ? { metaKeys: opts.metaKey } : {},
|
|
3004
3175
|
...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
|
|
3005
3176
|
...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
|