@metasession.co/devaudit-cli 0.3.26 → 0.3.28
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 +152 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/sdlc/files/_common/scripts/close-out-release.sh +10 -13
- package/sdlc/files/_common/scripts/evaluate-npm-audit.sh +11 -4
- package/sdlc/files/_common/scripts/evaluate-npm-audit.test.sh +23 -3
- package/sdlc/files/_common/scripts/markdown-table.sh +126 -0
- package/sdlc/files/_common/scripts/markdown-table.test.sh +36 -0
- package/sdlc/files/_common/scripts/validate-compliance-artifacts.sh +15 -6
- package/sdlc/files/_common/skills/sdlc-implementer/SKILL.md +1 -1
- package/sdlc/files/ci/check-release-approval.yml.template +4 -0
- package/sdlc/files/ci/ci.yml.template +10 -2
- package/sdlc/files/ci/close-out-release.yml.template +3 -0
- package/sdlc/files/ci/compliance-evidence.yml.template +8 -5
- package/sdlc/files/ci/feature-e2e.yml.template +3 -2
- package/sdlc/files/ci/post-deploy-prod.yml.template +3 -0
- package/sdlc/files/ci/python/ci.yml.template +8 -5
- package/sdlc/files/ci/reconcile-deployment.yml.template +3 -0
- package/sdlc/package.json +1 -1
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.3.
|
|
59
|
+
version: "0.3.28"};
|
|
60
60
|
|
|
61
61
|
// src/lib/version.ts
|
|
62
62
|
var CLI_VERSION = package_default.version;
|
|
@@ -2515,6 +2515,10 @@ function buildDbUriStep(dbService, dbPort) {
|
|
|
2515
2515
|
' echo "Database on port: ${DB_PORT}"'
|
|
2516
2516
|
].join("\n");
|
|
2517
2517
|
}
|
|
2518
|
+
function resolveRunner(cfg) {
|
|
2519
|
+
if (cfg.runner !== "self-hosted") return cfg.runner;
|
|
2520
|
+
return "${{ inputs.runner_label || vars.CI_RUNNER_LABEL || 'self-hosted' }}";
|
|
2521
|
+
}
|
|
2518
2522
|
async function syncCiTemplates(ctx) {
|
|
2519
2523
|
const configPath = join(ctx.projectPath, "sdlc-config.json");
|
|
2520
2524
|
const workflowsDir = join(ctx.projectPath, ".github", "workflows");
|
|
@@ -2541,7 +2545,7 @@ async function syncCiTemplates(ctx) {
|
|
|
2541
2545
|
PYTHON_VERSION: String(cfg.python_version ?? ""),
|
|
2542
2546
|
WORKING_DIRECTORY: workingDirectory || ".",
|
|
2543
2547
|
WORKING_DIR_PREFIX: workingDirPrefix,
|
|
2544
|
-
RUNNER: cfg
|
|
2548
|
+
RUNNER: resolveRunner(cfg),
|
|
2545
2549
|
SOURCE_DIRS: cfg.source_dirs,
|
|
2546
2550
|
SAST_BASELINE: String(cfg.sast_baseline),
|
|
2547
2551
|
ACCEPTED_DEP_RISKS: cfg.accepted_dep_risks,
|
|
@@ -2750,6 +2754,112 @@ async function runValidation(projectPath) {
|
|
|
2750
2754
|
}
|
|
2751
2755
|
return warnings;
|
|
2752
2756
|
}
|
|
2757
|
+
var PATCH_DIRECTORY = ".devaudit-patches";
|
|
2758
|
+
async function gitApplyCheck(projectPath, patchPath, reverse = false) {
|
|
2759
|
+
const args = ["apply", "--check", "--whitespace=nowarn"];
|
|
2760
|
+
if (reverse) args.push("--reverse");
|
|
2761
|
+
args.push(patchPath);
|
|
2762
|
+
const result = await execa("git", args, { cwd: projectPath, reject: false });
|
|
2763
|
+
return result.exitCode === 0;
|
|
2764
|
+
}
|
|
2765
|
+
async function applyConsumerPatches(ctx) {
|
|
2766
|
+
const patchRoot = join(ctx.projectPath, PATCH_DIRECTORY);
|
|
2767
|
+
let entries;
|
|
2768
|
+
try {
|
|
2769
|
+
entries = await readdir(patchRoot, { withFileTypes: true });
|
|
2770
|
+
} catch (error) {
|
|
2771
|
+
if (error.code === "ENOENT") {
|
|
2772
|
+
return {
|
|
2773
|
+
name: "consumer patches",
|
|
2774
|
+
filesSynced: 0,
|
|
2775
|
+
skipped: true,
|
|
2776
|
+
message: `${PATCH_DIRECTORY}/ not present`
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
throw error;
|
|
2780
|
+
}
|
|
2781
|
+
const patchPaths = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".patch")).map((entry) => join(patchRoot, entry.name)).sort((left, right) => left.localeCompare(right));
|
|
2782
|
+
if (patchPaths.length === 0) {
|
|
2783
|
+
return {
|
|
2784
|
+
name: "consumer patches",
|
|
2785
|
+
filesSynced: 0,
|
|
2786
|
+
skipped: true,
|
|
2787
|
+
message: `no *.patch files in ${PATCH_DIRECTORY}/`
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
const applicable = [];
|
|
2791
|
+
const obsolete = [];
|
|
2792
|
+
const conflicts = [];
|
|
2793
|
+
for (const patchPath of patchPaths) {
|
|
2794
|
+
if (await gitApplyCheck(ctx.projectPath, patchPath)) {
|
|
2795
|
+
applicable.push(patchPath);
|
|
2796
|
+
} else if (await gitApplyCheck(ctx.projectPath, patchPath, true)) {
|
|
2797
|
+
obsolete.push(patchPath);
|
|
2798
|
+
} else {
|
|
2799
|
+
conflicts.push(patchPath);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
if (conflicts.length > 0) {
|
|
2803
|
+
const names = conflicts.map((path) => relative(patchRoot, path)).join(", ");
|
|
2804
|
+
throw new Error(
|
|
2805
|
+
`consumer patch conflict: ${names}. Upstream templates changed; re-roll or remove the patch before updating.`
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
if (applicable.length > 0) {
|
|
2809
|
+
const check = await execa(
|
|
2810
|
+
"git",
|
|
2811
|
+
["apply", "--check", "--whitespace=nowarn", ...applicable],
|
|
2812
|
+
{ cwd: ctx.projectPath, reject: false }
|
|
2813
|
+
);
|
|
2814
|
+
if (check.exitCode !== 0) {
|
|
2815
|
+
throw new Error(
|
|
2816
|
+
`consumer patches conflict when combined: ${check.stderr || check.stdout || "git apply --check failed"}`
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
const applied = [];
|
|
2820
|
+
for (const patchPath of applicable) {
|
|
2821
|
+
const apply = await execa(
|
|
2822
|
+
"git",
|
|
2823
|
+
["apply", "--whitespace=nowarn", patchPath],
|
|
2824
|
+
{
|
|
2825
|
+
cwd: ctx.projectPath,
|
|
2826
|
+
reject: false
|
|
2827
|
+
}
|
|
2828
|
+
);
|
|
2829
|
+
if (apply.exitCode === 0) {
|
|
2830
|
+
applied.push(patchPath);
|
|
2831
|
+
continue;
|
|
2832
|
+
}
|
|
2833
|
+
const rollbackErrors = [];
|
|
2834
|
+
for (const appliedPath of applied.reverse()) {
|
|
2835
|
+
const rollback = await execa(
|
|
2836
|
+
"git",
|
|
2837
|
+
["apply", "--reverse", "--whitespace=nowarn", appliedPath],
|
|
2838
|
+
{ cwd: ctx.projectPath, reject: false }
|
|
2839
|
+
);
|
|
2840
|
+
if (rollback.exitCode !== 0) {
|
|
2841
|
+
rollbackErrors.push(relative(patchRoot, appliedPath));
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
const rollbackMessage = rollbackErrors.length > 0 ? ` Rollback failed for: ${rollbackErrors.join(", ")}; restore these files before retrying.` : "";
|
|
2845
|
+
throw new Error(
|
|
2846
|
+
`consumer patches conflict when combined: ${apply.stderr || apply.stdout || "git apply failed"}.${rollbackMessage}`
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
const appliedNames = applicable.map((path) => relative(patchRoot, path));
|
|
2851
|
+
const obsoleteNames = obsolete.map((path) => relative(patchRoot, path));
|
|
2852
|
+
const details = [
|
|
2853
|
+
appliedNames.length > 0 ? `applied: ${appliedNames.join(", ")}` : "",
|
|
2854
|
+
obsoleteNames.length > 0 ? `obsolete/already upstream: ${obsoleteNames.join(", ")} (remove after review)` : ""
|
|
2855
|
+
].filter(Boolean);
|
|
2856
|
+
return {
|
|
2857
|
+
name: "consumer patches",
|
|
2858
|
+
filesSynced: applicable.length,
|
|
2859
|
+
message: details.join("; "),
|
|
2860
|
+
...obsoleteNames.length > 0 ? { warning: `${obsoleteNames.length} obsolete consumer patch(es)` } : {}
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2753
2863
|
|
|
2754
2864
|
// src/update/index.ts
|
|
2755
2865
|
var SECTION_RUNNERS = [
|
|
@@ -2765,7 +2875,8 @@ var SECTION_RUNNERS = [
|
|
|
2765
2875
|
{ key: "2g", run: syncGitignore },
|
|
2766
2876
|
{ key: "2h", run: syncSdlcEngine },
|
|
2767
2877
|
{ key: "2i", run: syncWorkflows },
|
|
2768
|
-
{ key: "2j", run:
|
|
2878
|
+
{ key: "2j", run: applyConsumerPatches },
|
|
2879
|
+
{ key: "2k", run: verifyBranchProtection }
|
|
2769
2880
|
];
|
|
2770
2881
|
async function syncProject(projectPath) {
|
|
2771
2882
|
const absPath = resolve(projectPath);
|
|
@@ -2776,36 +2887,64 @@ async function syncProject(projectPath) {
|
|
|
2776
2887
|
const log = logger();
|
|
2777
2888
|
const projectName = basename(absPath);
|
|
2778
2889
|
log.info(`--- Syncing to: ${projectName} (${absPath}) ---`);
|
|
2779
|
-
const { stack, host, deprecatedDefaults } = await resolveAdapters(
|
|
2890
|
+
const { stack, host, deprecatedDefaults } = await resolveAdapters(
|
|
2891
|
+
absPath,
|
|
2892
|
+
installerRoot
|
|
2893
|
+
);
|
|
2780
2894
|
log.info(` Stack: ${stack} | Host: ${host}`);
|
|
2781
2895
|
if (deprecatedDefaults) {
|
|
2782
|
-
log.warn(
|
|
2896
|
+
log.warn(
|
|
2897
|
+
` DEPRECATED: stack/host keys missing from sdlc-config.json \u2014 defaulted to ${stack}+${host}.`
|
|
2898
|
+
);
|
|
2783
2899
|
}
|
|
2784
|
-
const ctx = {
|
|
2900
|
+
const ctx = {
|
|
2901
|
+
installerRoot,
|
|
2902
|
+
projectPath: absPath,
|
|
2903
|
+
projectName,
|
|
2904
|
+
stack,
|
|
2905
|
+
host
|
|
2906
|
+
};
|
|
2785
2907
|
const sections = [];
|
|
2908
|
+
const sectionWarnings = [];
|
|
2786
2909
|
let total = 0;
|
|
2787
2910
|
for (const { key, run } of SECTION_RUNNERS) {
|
|
2788
2911
|
const result = await run(ctx);
|
|
2789
2912
|
sections.push(result);
|
|
2790
2913
|
total += result.filesSynced;
|
|
2791
2914
|
if (result.skipped) {
|
|
2792
|
-
log.log(
|
|
2915
|
+
log.log(
|
|
2916
|
+
` [${key}] ${result.name}: SKIPPED${result.message ? ` (${result.message})` : ""}`
|
|
2917
|
+
);
|
|
2793
2918
|
} else {
|
|
2794
|
-
log.log(
|
|
2919
|
+
log.log(
|
|
2920
|
+
` [${key}] ${result.name}: ${result.filesSynced} file(s)${result.message ? ` \u2014 ${result.message}` : ""}`
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
if (result.warning) {
|
|
2924
|
+
sectionWarnings.push(result.warning);
|
|
2925
|
+
log.warn(` [${key}] ${result.warning}`);
|
|
2795
2926
|
}
|
|
2796
2927
|
}
|
|
2797
2928
|
log.log("");
|
|
2798
2929
|
log.info(` Total: ${total} files synced`);
|
|
2799
2930
|
log.log("");
|
|
2800
2931
|
log.log(" --- Validation ---");
|
|
2801
|
-
const
|
|
2932
|
+
const validationWarnings = await runValidation(absPath);
|
|
2933
|
+
const warnings = [...sectionWarnings, ...validationWarnings];
|
|
2802
2934
|
if (warnings.length === 0) {
|
|
2803
2935
|
log.success(" All validation checks passed");
|
|
2804
2936
|
} else {
|
|
2805
2937
|
for (const w of warnings) log.warn(` ${w}`);
|
|
2806
2938
|
}
|
|
2807
2939
|
log.log("");
|
|
2808
|
-
return {
|
|
2940
|
+
return {
|
|
2941
|
+
project: projectName,
|
|
2942
|
+
stack,
|
|
2943
|
+
host,
|
|
2944
|
+
sections,
|
|
2945
|
+
totalFilesSynced: total,
|
|
2946
|
+
warnings
|
|
2947
|
+
};
|
|
2809
2948
|
}
|
|
2810
2949
|
async function syncAll(projectPaths) {
|
|
2811
2950
|
const reports = [];
|
|
@@ -2814,7 +2953,9 @@ async function syncAll(projectPaths) {
|
|
|
2814
2953
|
reports.push(await syncProject(p));
|
|
2815
2954
|
} catch (err) {
|
|
2816
2955
|
const log = logger();
|
|
2817
|
-
log.error(
|
|
2956
|
+
log.error(
|
|
2957
|
+
`ERROR syncing ${p}: ${err instanceof Error ? err.message : String(err)}`
|
|
2958
|
+
);
|
|
2818
2959
|
throw err;
|
|
2819
2960
|
}
|
|
2820
2961
|
}
|