@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 CHANGED
@@ -56,7 +56,7 @@ function emitJsonResult(payload) {
56
56
 
57
57
  // package.json
58
58
  var package_default = {
59
- version: "0.3.26"};
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.runner,
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: verifyBranchProtection }
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(absPath, installerRoot);
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(` DEPRECATED: stack/host keys missing from sdlc-config.json \u2014 defaulted to ${stack}+${host}.`);
2896
+ log.warn(
2897
+ ` DEPRECATED: stack/host keys missing from sdlc-config.json \u2014 defaulted to ${stack}+${host}.`
2898
+ );
2783
2899
  }
2784
- const ctx = { installerRoot, projectPath: absPath, projectName, stack, host };
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(` [${key}] ${result.name}: SKIPPED${result.message ? ` (${result.message})` : ""}`);
2915
+ log.log(
2916
+ ` [${key}] ${result.name}: SKIPPED${result.message ? ` (${result.message})` : ""}`
2917
+ );
2793
2918
  } else {
2794
- log.log(` [${key}] ${result.name}: ${result.filesSynced} file(s)${result.message ? ` \u2014 ${result.message}` : ""}`);
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 warnings = await runValidation(absPath);
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 { project: projectName, stack, host, sections, totalFilesSynced: total, warnings };
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(`ERROR syncing ${p}: ${err instanceof Error ? err.message : String(err)}`);
2956
+ log.error(
2957
+ `ERROR syncing ${p}: ${err instanceof Error ? err.message : String(err)}`
2958
+ );
2818
2959
  throw err;
2819
2960
  }
2820
2961
  }