@rmartz/pr-policy 0.8.0 → 0.9.1

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.
@@ -7,7 +7,7 @@ import {
7
7
  postVerdict,
8
8
  resolveRepoTarget,
9
9
  selectChecks
10
- } from "../chunk-5VYRZAUT.js";
10
+ } from "../chunk-TFZMH2KF.js";
11
11
 
12
12
  // src/bin/pr-policy.ts
13
13
  import { readFile } from "fs/promises";
@@ -892,10 +892,17 @@ function substantiveWorkflowChanges(changes) {
892
892
  return changes.filter((change) => !isShippedReusableWorkflow(change) && !isPinBumpOnly(change)).map((change) => change.path);
893
893
  }
894
894
 
895
+ // src/checks/test-paths.ts
896
+ var TEST_DIRS = /* @__PURE__ */ new Set(["test", "tests", "__tests__", "__snapshots__", "__mocks__", "e2e"]);
897
+ var TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.(py|go)$|^conftest\.py$/;
898
+ function isTestPath(path) {
899
+ const parts = path.split("/");
900
+ const name = parts.at(-1) ?? "";
901
+ return parts.slice(0, -1).some((dir) => TEST_DIRS.has(dir)) || TEST_FILE.test(name);
902
+ }
903
+
895
904
  // src/checks/uat/trivial.ts
896
905
  var TRIVIAL_CATEGORIES = ["docs", "tests", "ci", "dependencies", "metadata"];
897
- var TEST_DIRS = /* @__PURE__ */ new Set(["test", "tests", "__tests__", "__snapshots__", "__mocks__", "e2e"]);
898
- var TEST_FILE = /\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.py$|^conftest\.py$/;
899
906
  var LOCKFILES = /* @__PURE__ */ new Set([
900
907
  "pnpm-lock.yaml",
901
908
  "package-lock.json",
@@ -930,9 +937,8 @@ function isDependencyOnlyManifest(change) {
930
937
  function categoryOf(path, pr) {
931
938
  const parts = path.split("/");
932
939
  const name = parts[parts.length - 1] ?? "";
933
- const dirs = parts.slice(0, -1);
934
940
  if (name.endsWith(".md") || parts[0] === "docs") return "docs";
935
- if (dirs.some((dir) => TEST_DIRS.has(dir)) || TEST_FILE.test(name)) return "tests";
941
+ if (isTestPath(path)) return "tests";
936
942
  if (path.startsWith(".github/workflows/") || path.startsWith(".github/actions/")) {
937
943
  const change = pr.workflowChanges.find((file) => file.path === path);
938
944
  return change !== void 0 && isShippedReusableWorkflow(change) ? void 0 : "ci";
@@ -1149,6 +1155,35 @@ function isReleasePleaseTitle(title) {
1149
1155
  return RELEASE_PLEASE_TITLE.test(title);
1150
1156
  }
1151
1157
 
1158
+ // src/checks/title/type-paths.ts
1159
+ function isMarkdownPath(path) {
1160
+ return path.toLowerCase().endsWith(".md");
1161
+ }
1162
+ var quote = (paths) => {
1163
+ const shown = paths.slice(0, 5).map((path) => `\`${path}\``);
1164
+ if (paths.length > 5) shown.push(`and ${paths.length - 5} more`);
1165
+ return shown.join(", ");
1166
+ };
1167
+ function typePathViolation(type, changedFiles) {
1168
+ const paths = [...new Set(changedFiles)];
1169
+ if (type === "docs") {
1170
+ const offending = paths.filter((path) => !isMarkdownPath(path));
1171
+ if (offending.length === 0) return void 0;
1172
+ return `A \`docs\` PR may change only Markdown files, but this one changes ${quote(offending)}. \`docs\` doesn't release, so that change would never ship. Retitle to the type of the non-docs change, or split it into its own PR.`;
1173
+ }
1174
+ if (type === "refactor") {
1175
+ const offending = paths.filter((path) => isTestPath(path) && !isMarkdownPath(path));
1176
+ if (offending.length === 0) return void 0;
1177
+ return `A \`refactor\` PR must leave the tests untouched, so they can confirm behaviour is unchanged, but this one changes ${quote(offending)}. Land the test changes first as a \`test:\` PR, then refactor against them.`;
1178
+ }
1179
+ if (type === "test") {
1180
+ const offending = paths.filter((path) => !isTestPath(path) && !isMarkdownPath(path));
1181
+ if (offending.length === 0) return void 0;
1182
+ return `A \`test\` PR may change only test files and docs, so it locks in current behaviour without changing it, but this one changes ${quote(offending)}. \`test\` doesn't release, so that change would never ship. Retitle to the type of the non-test change, or split it into its own PR.`;
1183
+ }
1184
+ return void 0;
1185
+ }
1186
+
1152
1187
  // src/checks/title/index.ts
1153
1188
  var TITLE_CHECK = "title";
1154
1189
  var FUNCTIONAL = FUNCTIONAL_TYPES.map((type) => `\`${type}\``).join("/");
@@ -1202,6 +1237,8 @@ function decideTitle(pr) {
1202
1237
  )
1203
1238
  );
1204
1239
  }
1240
+ const pathViolation = typePathViolation(title.type, pr.changedFiles);
1241
+ if (pathViolation !== void 0) findings.push(block2(pathViolation));
1205
1242
  const mayRetitle = !functional && title.type !== "ci";
1206
1243
  const workflows = substantiveWorkflowChanges(pr.workflowChanges);
1207
1244
  if (workflows.length > 0 && mayRetitle) {
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  sensitiveBumps,
49
49
  statusBody,
50
50
  titleCheck
51
- } from "./chunk-5VYRZAUT.js";
51
+ } from "./chunk-TFZMH2KF.js";
52
52
  export {
53
53
  AMBIGUOUS_INDICATORS,
54
54
  BLOCKED_LABEL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmartz/pr-policy",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Read-only PR content classifiers — CI-change classification, title-type rules, and more — reported as one blocking pr-policy check-run. Distributed as a pinned Action kept current by Dependabot.",
5
5
  "keywords": [
6
6
  "pr-policy",