@metasession.co/devaudit-cli 0.3.9 → 0.3.11
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 +70 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/HOST_ADAPTER.md +17 -0
- package/sdlc/SKILLS.md +8 -0
- package/sdlc/files/_common/joining-an-existing-project.md +5 -4
- package/sdlc/files/_common/scripts/close-out-contract.test.sh +71 -0
- package/sdlc/files/_common/scripts/prepare-release-pr.sh +99 -0
- package/sdlc/files/_common/scripts/validate-commits.sh +32 -7
- package/sdlc/files/_common/scripts/validate-commits.test.sh +143 -0
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +39 -3
- package/sdlc/files/ci/ci-status-fallback.yml.template +4 -0
- package/sdlc/files/ci/ci.yml.template +2 -2
- package/sdlc/files/ci/compliance-evidence.yml.template +49 -4
- package/sdlc/files/ci/incident-export.yml.template +7 -3
- package/sdlc/files/ci/label-retention.yml.template +3 -1
- package/sdlc/files/ci/periodic-review.yml.template +5 -3
- package/sdlc/files/ci/post-deploy-prod.yml.template +6 -1
- package/sdlc/files/ci/quality-gates-provenance.yml.template +1 -1
- package/sdlc/files/hosts/_schema/adapter.schema.json +31 -0
- package/sdlc/files/hosts/railway/adapter.json +8 -1
- package/sdlc/files/stacks/node/hooks/pre-push +13 -13
- package/sdlc/package.json +1 -1
- package/sdlc/src/bin/devaudit-sdlc.js +490 -54
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command, Option } from 'commander';
|
|
|
3
3
|
import { createConsola } from 'consola';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import { join, resolve, basename, dirname, relative } from 'path';
|
|
6
|
-
import { promises } from 'fs';
|
|
6
|
+
import { promises, openAsBlob } from 'fs';
|
|
7
7
|
import envPaths from 'env-paths';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
9
9
|
import { validateManifest } from '@metasession.co/devaudit-plugin-sdk';
|
|
@@ -56,7 +56,7 @@ function emitJsonResult(payload) {
|
|
|
56
56
|
|
|
57
57
|
// package.json
|
|
58
58
|
var package_default = {
|
|
59
|
-
version: "0.3.
|
|
59
|
+
version: "0.3.11"};
|
|
60
60
|
|
|
61
61
|
// src/lib/version.ts
|
|
62
62
|
var CLI_VERSION = package_default.version;
|
|
@@ -602,6 +602,7 @@ var INITIAL_BACKOFF_MS = 1e3;
|
|
|
602
602
|
var DEFAULT_PRESIGNED_THRESHOLD_BYTES = 26214400;
|
|
603
603
|
var DEFAULT_PRESIGNED_MAX_ATTEMPTS = 3;
|
|
604
604
|
var DEFAULT_PRESIGNED_UPLOAD_MAX_TIME_SECONDS = 300;
|
|
605
|
+
var STUB_PREFIX_BYTES = 4096;
|
|
605
606
|
function maxAttempts() {
|
|
606
607
|
const raw = Number.parseInt(process.env["UPLOAD_MAX_ATTEMPTS"] ?? "", 10);
|
|
607
608
|
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_MAX_ATTEMPTS;
|
|
@@ -629,16 +630,63 @@ async function collectFiles(filePath) {
|
|
|
629
630
|
throw new Error(`${filePath} is neither a file nor a directory.`);
|
|
630
631
|
}
|
|
631
632
|
var STUB_BANNER = /STARTER TEMPLATE.+REPLACE BEFORE/;
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
633
|
+
var TEXT_STUB_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
634
|
+
".md",
|
|
635
|
+
".markdown",
|
|
636
|
+
".txt",
|
|
637
|
+
".json",
|
|
638
|
+
".yml",
|
|
639
|
+
".yaml",
|
|
640
|
+
".csv",
|
|
641
|
+
".html",
|
|
642
|
+
".xml",
|
|
643
|
+
".log"
|
|
644
|
+
]);
|
|
635
645
|
function delay(ms) {
|
|
636
646
|
return new Promise((res) => setTimeout(res, ms));
|
|
637
647
|
}
|
|
638
|
-
function
|
|
648
|
+
function extensionOf(file) {
|
|
649
|
+
const dot = file.lastIndexOf(".");
|
|
650
|
+
return dot === -1 ? "" : file.slice(dot).toLowerCase();
|
|
651
|
+
}
|
|
652
|
+
function isTextLikeEvidenceFile(file) {
|
|
653
|
+
return TEXT_STUB_EXTENSIONS.has(extensionOf(file));
|
|
654
|
+
}
|
|
655
|
+
async function readFilePrefix(file, bytes) {
|
|
656
|
+
const handle = await promises.open(file, "r");
|
|
657
|
+
try {
|
|
658
|
+
const buf = Buffer.alloc(bytes);
|
|
659
|
+
const { bytesRead } = await handle.read(buf, 0, bytes, 0);
|
|
660
|
+
return buf.subarray(0, bytesRead).toString("utf-8");
|
|
661
|
+
} finally {
|
|
662
|
+
await handle.close();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async function isUneditedStub(file) {
|
|
666
|
+
if (!isTextLikeEvidenceFile(file)) return false;
|
|
667
|
+
const prefix = await readFilePrefix(file, STUB_PREFIX_BYTES);
|
|
668
|
+
return STUB_BANNER.test(prefix);
|
|
669
|
+
}
|
|
670
|
+
async function createUploadSource(file) {
|
|
671
|
+
const mimeType = deriveMimeType(file);
|
|
672
|
+
const { size } = await promises.stat(file);
|
|
673
|
+
try {
|
|
674
|
+
return {
|
|
675
|
+
blob: await openAsBlob(file, { type: mimeType }),
|
|
676
|
+
size,
|
|
677
|
+
mimeType
|
|
678
|
+
};
|
|
679
|
+
} catch {
|
|
680
|
+
return {
|
|
681
|
+
blob: new Blob([await promises.readFile(file)], { type: mimeType }),
|
|
682
|
+
size,
|
|
683
|
+
mimeType
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function buildUploadForm(file, source, opts) {
|
|
639
688
|
const form = new FormData();
|
|
640
|
-
|
|
641
|
-
form.set("file", blob, basename(file));
|
|
689
|
+
form.set("file", source.blob, basename(file));
|
|
642
690
|
form.set("projectSlug", opts.projectSlug);
|
|
643
691
|
form.set("requirementId", opts.requirementId);
|
|
644
692
|
form.set("evidenceType", opts.evidenceType);
|
|
@@ -684,7 +732,7 @@ async function probeBaseUrlDrift(baseUrl) {
|
|
|
684
732
|
return null;
|
|
685
733
|
}
|
|
686
734
|
}
|
|
687
|
-
async function uploadOne(file,
|
|
735
|
+
async function uploadOne(file, source, opts) {
|
|
688
736
|
const attempts = maxAttempts();
|
|
689
737
|
const timeoutSeconds = uploadMaxTimeSeconds();
|
|
690
738
|
const url = `${opts.baseUrl.replace(/\/$/, "")}/api/evidence/upload`;
|
|
@@ -698,7 +746,7 @@ async function uploadOne(file, buf, opts) {
|
|
|
698
746
|
res = await fetch(url, {
|
|
699
747
|
method: "POST",
|
|
700
748
|
headers: { authorization: `Bearer ${opts.apiKey}` },
|
|
701
|
-
body: buildUploadForm(file,
|
|
749
|
+
body: buildUploadForm(file, source, opts),
|
|
702
750
|
signal: controller.signal
|
|
703
751
|
});
|
|
704
752
|
} catch (err) {
|
|
@@ -731,14 +779,13 @@ async function uploadOne(file, buf, opts) {
|
|
|
731
779
|
return { file, ok: false, status: 0, error: "max retries exhausted" };
|
|
732
780
|
}
|
|
733
781
|
var PRESIGNED_FALLBACK = /* @__PURE__ */ Symbol("presigned-fallback");
|
|
734
|
-
async function uploadPresigned(file,
|
|
782
|
+
async function uploadPresigned(file, source, opts) {
|
|
735
783
|
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
736
784
|
const presignUrl = `${baseUrl}/api/evidence/upload-url`;
|
|
737
785
|
const completeUrl = `${baseUrl}/api/evidence/upload-complete`;
|
|
738
786
|
const maxAttemptsPresigned = DEFAULT_PRESIGNED_MAX_ATTEMPTS;
|
|
739
787
|
const timeoutSeconds = uploadMaxTimeSeconds();
|
|
740
788
|
const presignedTimeoutSeconds = DEFAULT_PRESIGNED_UPLOAD_MAX_TIME_SECONDS;
|
|
741
|
-
const mimeType = deriveMimeType(file);
|
|
742
789
|
const metadata = opts.metadata ?? {};
|
|
743
790
|
let uploadUrl = "";
|
|
744
791
|
let evidenceId = "";
|
|
@@ -760,8 +807,8 @@ async function uploadPresigned(file, buf, opts) {
|
|
|
760
807
|
requirementId: opts.requirementId,
|
|
761
808
|
evidenceType: opts.evidenceType,
|
|
762
809
|
fileName: basename(file),
|
|
763
|
-
fileSizeBytes:
|
|
764
|
-
mimeType,
|
|
810
|
+
fileSizeBytes: source.size,
|
|
811
|
+
mimeType: source.mimeType,
|
|
765
812
|
metadata,
|
|
766
813
|
...opts.releaseVersion ? { releaseVersion: opts.releaseVersion } : {},
|
|
767
814
|
...opts.createReleaseIfMissing ? { createReleaseIfMissing: true } : {},
|
|
@@ -816,8 +863,8 @@ async function uploadPresigned(file, buf, opts) {
|
|
|
816
863
|
try {
|
|
817
864
|
res = await fetch(uploadUrl, {
|
|
818
865
|
method: "PUT",
|
|
819
|
-
headers: { "content-type": mimeType },
|
|
820
|
-
body:
|
|
866
|
+
headers: { "content-type": source.mimeType },
|
|
867
|
+
body: source.blob,
|
|
821
868
|
signal: controller.signal
|
|
822
869
|
});
|
|
823
870
|
} catch (err) {
|
|
@@ -898,19 +945,19 @@ async function uploadEvidence(opts) {
|
|
|
898
945
|
}
|
|
899
946
|
const results = [];
|
|
900
947
|
for (const file of files) {
|
|
901
|
-
|
|
902
|
-
if (isUneditedStub(buf)) {
|
|
948
|
+
if (await isUneditedStub(file)) {
|
|
903
949
|
results.push({ file, ok: true, status: 0, skipped: true });
|
|
904
950
|
continue;
|
|
905
951
|
}
|
|
906
|
-
|
|
907
|
-
|
|
952
|
+
const source = await createUploadSource(file);
|
|
953
|
+
if (source.size >= DEFAULT_PRESIGNED_THRESHOLD_BYTES) {
|
|
954
|
+
const presignedResult = await uploadPresigned(file, source, opts);
|
|
908
955
|
if (presignedResult !== PRESIGNED_FALLBACK) {
|
|
909
956
|
results.push(presignedResult);
|
|
910
957
|
continue;
|
|
911
958
|
}
|
|
912
959
|
}
|
|
913
|
-
results.push(await uploadOne(file,
|
|
960
|
+
results.push(await uploadOne(file, source, opts));
|
|
914
961
|
}
|
|
915
962
|
return results;
|
|
916
963
|
}
|
|
@@ -2455,7 +2502,8 @@ async function syncCiTemplates(ctx) {
|
|
|
2455
2502
|
var SENTINEL_ENTRIES = [
|
|
2456
2503
|
".e2e-gate-passed",
|
|
2457
2504
|
".e2e-evidence-wired",
|
|
2458
|
-
".sdlc-implementer-invoked"
|
|
2505
|
+
".sdlc-implementer-invoked",
|
|
2506
|
+
".sdlc-pr-watch.json"
|
|
2459
2507
|
];
|
|
2460
2508
|
var MARKER = "# DevAudit sentinel files (devaudit-installer#226)";
|
|
2461
2509
|
async function syncGitignore(ctx) {
|