mason-context 0.12.0 → 0.14.0

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.
@@ -133,7 +133,7 @@ async function readStoreJson(root, relative) {
133
133
  return parsed;
134
134
  } catch (error) {
135
135
  if (error.code === "ENOENT") return null;
136
- throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`);
136
+ throw new Error(`Invalid Mason store ${relative}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
137
137
  }
138
138
  }
139
139
  async function writeStoreJson(root, relative, value) {
@@ -622,24 +622,24 @@ async function isDirty(resolvedRoot, relPath) {
622
622
  }
623
623
  }
624
624
  async function discoverDocs(resolvedRoot) {
625
- const docs = [];
626
- for (const candidate of DOC_CANDIDATES) {
625
+ const docs = await Promise.all(DOC_CANDIDATES.map(async (candidate) => {
627
626
  let content;
628
627
  try {
629
628
  content = await fs4.readFile(path7.join(resolvedRoot, candidate), "utf-8");
630
629
  } catch {
631
- continue;
630
+ return null;
632
631
  }
633
- docs.push({
632
+ const [lastCommit, dirty] = await Promise.all([lastCommitOf(resolvedRoot, candidate), isDirty(resolvedRoot, candidate)]);
633
+ return {
634
634
  path: candidate,
635
635
  content,
636
636
  lineCount: content.split("\n").length,
637
- lastCommit: await lastCommitOf(resolvedRoot, candidate),
638
- dirty: await isDirty(resolvedRoot, candidate),
637
+ lastCommit,
638
+ dirty,
639
639
  claims: extractClaims(content)
640
- });
641
- }
642
- return docs;
640
+ };
641
+ }));
642
+ return docs.filter((doc) => doc !== null);
643
643
  }
644
644
 
645
645
  // src/audit/types.ts
@@ -814,12 +814,19 @@ async function checkNewModules(ctx) {
814
814
  }
815
815
  });
816
816
  };
817
- for (const topDir of await listSubdirs(ctx.root)) {
818
- const absTop = path9.join(ctx.root, topDir);
817
+ for (const candidate of await moduleCandidates(ctx.root, combinedDocs)) {
818
+ await flag(candidate.dir, candidate.sourceFileCount);
819
+ }
820
+ return result;
821
+ }
822
+ async function moduleCandidates(root, combinedDocs) {
823
+ const candidates = [];
824
+ for (const topDir of await listSubdirs(root)) {
825
+ const absTop = path9.join(root, topDir);
819
826
  const topMentioned = isMentioned(combinedDocs, topDir);
820
827
  if (!topMentioned) {
821
828
  const count2 = await countSourceFiles(absTop);
822
- if (count2 >= 1) await flag(topDir, count2);
829
+ if (count2 >= 1) candidates.push({ dir: topDir, sourceFileCount: count2 });
823
830
  continue;
824
831
  }
825
832
  const subdirs = await listSubdirs(absTop);
@@ -829,11 +836,11 @@ async function checkNewModules(ctx) {
829
836
  if (isMentioned(combinedDocs, sub)) continue;
830
837
  const count2 = await countSourceFiles(path9.join(absTop, sub));
831
838
  if (count2 >= SECOND_LEVEL_MIN_SOURCE_FILES) {
832
- await flag(`${topDir}/${sub}`, count2);
839
+ candidates.push({ dir: `${topDir}/${sub}`, sourceFileCount: count2 });
833
840
  }
834
841
  }
835
842
  }
836
- return result;
843
+ return candidates;
837
844
  }
838
845
 
839
846
  // src/audit/checks/stale-count.ts
@@ -1016,15 +1023,7 @@ async function checkDeadCommands(ctx) {
1016
1023
  const loadWorkspaceScripts = async () => {
1017
1024
  if (workspaceScripts !== null) return workspaceScripts;
1018
1025
  workspaceScripts = /* @__PURE__ */ new Set();
1019
- const manifests = await fg4("**/package.json", {
1020
- cwd: ctx.root,
1021
- ignore: [
1022
- "**/node_modules/**",
1023
- "**/dist/**",
1024
- "**/build/**",
1025
- "package.json"
1026
- ]
1027
- });
1026
+ const manifests = await commandManifests(ctx.root);
1028
1027
  manifestsChecked = ["package.json", ...manifests.sort()];
1029
1028
  for (const manifest of manifests) {
1030
1029
  const scripts = await scriptsOf(path11.join(ctx.root, manifest));
@@ -1052,6 +1051,64 @@ async function checkDeadCommands(ctx) {
1052
1051
  }
1053
1052
  return result;
1054
1053
  }
1054
+ function commandManifests(root) {
1055
+ return fg4("**/package.json", {
1056
+ cwd: root,
1057
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", ".mason/reports/**", "package.json"]
1058
+ });
1059
+ }
1060
+
1061
+ // src/audit/release-metadata.ts
1062
+ import { execFile as execFile6 } from "child_process";
1063
+ import { promisify as promisify6 } from "util";
1064
+ var exec6 = promisify6(execFile6);
1065
+ async function git(root, args) {
1066
+ return (await exec6("git", args, { cwd: root, timeout: 1e4, maxBuffer: 2 * 1024 * 1024 })).stdout;
1067
+ }
1068
+ function withoutAndroidReleaseValues(text2) {
1069
+ if (/\/\*|"""|'''/.test(text2) || !/id\s*\(?\s*["']com\.android\.(application|library)["']/.test(text2)) return null;
1070
+ const scopes = [];
1071
+ const normalized = [];
1072
+ let assignments = 0;
1073
+ for (const line of text2.split("\n")) {
1074
+ const code = line.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/.*$/g, (token) => token.startsWith("//") ? "" : '""');
1075
+ const assignment = line.match(/^(\s*)(versionName|versionCode)(\s*(?:=\s*|\s+))("[A-Za-z0-9._+-]+"|'[A-Za-z0-9._+-]+'|\d+)(\s*)$/);
1076
+ if (assignment && scopes.join("/") === "android/defaultConfig" && (assignment[2] === "versionCode" ? /^\d+$/.test(assignment[4]) : /^["']/.test(assignment[4]))) {
1077
+ normalized.push(assignment[1] + assignment[2] + assignment[3] + "<release-value>" + assignment[5]);
1078
+ assignments++;
1079
+ } else {
1080
+ if (/\bversion(?:Name|Code)\b/.test(line)) return null;
1081
+ normalized.push(line);
1082
+ }
1083
+ const named = code.match(/^\s*(android|defaultConfig)\s*\{\s*$/)?.[1];
1084
+ for (const brace of code.matchAll(/[{}]/g)) {
1085
+ if (brace[0] === "{") scopes.push(named ?? "unknown");
1086
+ else if (!scopes.length) return null;
1087
+ else scopes.pop();
1088
+ }
1089
+ }
1090
+ return assignments && !scopes.length ? normalized.join("\n") : null;
1091
+ }
1092
+ async function releaseMetadataOnly(root, commit) {
1093
+ if (!commit.files.length || !commit.files.every((file) => /(^|\/)build\.gradle(?:\.kts)?$/.test(file))) return false;
1094
+ try {
1095
+ const parents = (await git(root, ["rev-list", "--parents", "-n", "1", commit.hash])).trim().split(/\s+/);
1096
+ if (parents.length !== 2) return false;
1097
+ for (const file of commit.files) {
1098
+ const raw = await git(root, ["diff", "--raw", "-z", "--no-renames", "--no-ext-diff", "--no-textconv", parents[1], commit.hash, "--", file]);
1099
+ if (!/^:(100644|100755) \1 [a-f0-9]+ [a-f0-9]+ M\0/.test(raw)) return false;
1100
+ const [before, after] = await Promise.all([
1101
+ git(root, ["show", parents[1] + ":" + file]),
1102
+ git(root, ["show", commit.hash + ":" + file])
1103
+ ]);
1104
+ const previous = withoutAndroidReleaseValues(before);
1105
+ if (previous === null || previous !== withoutAndroidReleaseValues(after)) return false;
1106
+ }
1107
+ return true;
1108
+ } catch {
1109
+ return false;
1110
+ }
1111
+ }
1055
1112
 
1056
1113
  // src/audit/checks/deps-changed.ts
1057
1114
  var MANIFEST_COMMITS_CAP = 10;
@@ -1072,6 +1129,7 @@ var MANIFEST_PATHSPECS = [
1072
1129
  async function checkDepsChanged(ctx) {
1073
1130
  const result = emptyResult();
1074
1131
  result.suppressedAdvisories = [];
1132
+ const releaseOnly = /* @__PURE__ */ new Map();
1075
1133
  for (const doc of ctx.docs) {
1076
1134
  if (!doc.lastCommit) {
1077
1135
  result.skipped.push({
@@ -1101,6 +1159,15 @@ async function checkDepsChanged(ctx) {
1101
1159
  });
1102
1160
  continue;
1103
1161
  }
1162
+ const relevant = [];
1163
+ for (const commit of range.commits) {
1164
+ if (!releaseOnly.has(commit.hash) && releaseOnly.size < 100) {
1165
+ releaseOnly.set(commit.hash, await releaseMetadataOnly(ctx.root, commit));
1166
+ }
1167
+ if (!releaseOnly.get(commit.hash)) relevant.push(commit);
1168
+ }
1169
+ range.commits = relevant;
1170
+ range.total = relevant.length;
1104
1171
  if (range.total === 0) continue;
1105
1172
  const latest = range.commits[0];
1106
1173
  (doc.dirty ? result.suppressedAdvisories : result.advisories).push({
@@ -1381,9 +1448,8 @@ function emptyResult() {
1381
1448
  // src/audit/audit.ts
1382
1449
  async function computeAudit(rootDir, options = {}) {
1383
1450
  const resolvedRoot = path15.resolve(rootDir);
1384
- const docs = await discoverDocs(resolvedRoot);
1451
+ const [docs, headHash] = await Promise.all([discoverDocs(resolvedRoot), getCurrentGitHash(resolvedRoot)]);
1385
1452
  if (docs.length === 0) return null;
1386
- const headHash = await getCurrentGitHash(resolvedRoot);
1387
1453
  const report = {
1388
1454
  version: 1,
1389
1455
  root: resolvedRoot,