split-by-codeowners 1.0.0 → 1.0.3

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/action.yml CHANGED
@@ -91,6 +91,16 @@ inputs:
91
91
  required: false
92
92
  default: "Automated changes bucketed by CODEOWNERS.\n\nOwners: {owners}\nBucket key: {bucket_key}\n\nFiles:\n{files}\n"
93
93
 
94
+ pr_body_mode:
95
+ description: "PR body mode for PR creation: custom | template | template_with_bucket | none"
96
+ required: false
97
+ default: "custom"
98
+
99
+ pr_template_path:
100
+ description: "Path to pull request template file (used when pr_body_mode=template*)."
101
+ required: false
102
+ default: ".github/pull_request_template.md"
103
+
94
104
  draft:
95
105
  description: "Create PRs as drafts."
96
106
  required: false
package/dist/index.js CHANGED
@@ -27470,6 +27470,7 @@ const codeowners_1 = __nccwpck_require__(3586);
27470
27470
  const git_1 = __nccwpck_require__(1243);
27471
27471
  const github_1 = __nccwpck_require__(9248);
27472
27472
  const ghcli_1 = __nccwpck_require__(8064);
27473
+ const pr_template_1 = __nccwpck_require__(2456);
27473
27474
  function formatTemplate(tpl, vars) {
27474
27475
  let out = tpl;
27475
27476
  for (const [k, v] of Object.entries(vars))
@@ -27589,8 +27590,22 @@ async function runSplit(config, logger) {
27589
27590
  (0, git_1.pushBranch)(config.remoteName, branch, worktreeDir);
27590
27591
  const ownersStr = b.owners.length ? b.owners.join(", ") : "(unowned)";
27591
27592
  const filesStr = b.files.map(f => `- ${f.file}`).join("\n");
27593
+ const bucketInfo = formatTemplate("Automated changes bucketed by CODEOWNERS.\n\nOwners: {owners}\nBucket key: {bucket_key}\n\nFiles:\n{files}\n", { owners: ownersStr, bucket_key: b.key, files: filesStr });
27592
27594
  const title = formatTemplate(config.prTitle, { owners: ownersStr, bucket_key: b.key });
27593
- const body = formatTemplate(config.prBody, { owners: ownersStr, bucket_key: b.key, files: filesStr });
27595
+ let body;
27596
+ if (config.prBodyMode === "none") {
27597
+ body = undefined;
27598
+ }
27599
+ else if (config.prBodyMode === "custom") {
27600
+ body = formatTemplate(config.prBody, { owners: ownersStr, bucket_key: b.key, files: filesStr });
27601
+ }
27602
+ else {
27603
+ const template = (0, pr_template_1.readPrTemplate)(worktreeDir, config.prTemplatePath) ?? "";
27604
+ body =
27605
+ config.prBodyMode === "template_with_bucket"
27606
+ ? (template ? template.trimEnd() + "\n\n---\n\n" + bucketInfo : bucketInfo)
27607
+ : (template || bucketInfo);
27608
+ }
27594
27609
  const pr = useGhCli
27595
27610
  ? (() => {
27596
27611
  return (0, ghcli_1.upsertPullRequestViaGh)({
@@ -27598,7 +27613,7 @@ async function runSplit(config, logger) {
27598
27613
  base: baseBranch,
27599
27614
  head: branch,
27600
27615
  title,
27601
- body,
27616
+ body: body ?? "",
27602
27617
  draft: config.draft,
27603
27618
  bucketKey: b.key
27604
27619
  });
@@ -27609,7 +27624,7 @@ async function runSplit(config, logger) {
27609
27624
  base: baseBranch,
27610
27625
  head: branch,
27611
27626
  title,
27612
- body,
27627
+ body: body ?? "",
27613
27628
  draft: config.draft,
27614
27629
  bucketKey: b.key
27615
27630
  });
@@ -27714,6 +27729,35 @@ exports.parseCodeowners = parseCodeowners;
27714
27729
  exports.ownersForFile = ownersForFile;
27715
27730
  const node_fs_1 = __importDefault(__nccwpck_require__(3024));
27716
27731
  const minimatch_1 = __nccwpck_require__(6507);
27732
+ function splitPatternOwners(line) {
27733
+ // CODEOWNERS lines are: <pattern><whitespace><owner>...
27734
+ // Patterns may contain spaces if escaped as `\ `.
27735
+ // We parse by finding the first whitespace not escaped by a backslash.
27736
+ let i = 0;
27737
+ let escaped = false;
27738
+ for (; i < line.length; i++) {
27739
+ const ch = line[i];
27740
+ if (escaped) {
27741
+ escaped = false;
27742
+ continue;
27743
+ }
27744
+ if (ch === "\\") {
27745
+ escaped = true;
27746
+ continue;
27747
+ }
27748
+ if (/\s/.test(ch))
27749
+ break;
27750
+ }
27751
+ const rawPattern = line.slice(0, i).trim();
27752
+ const rest = line.slice(i).trim();
27753
+ if (!rawPattern || !rest)
27754
+ return null;
27755
+ const pattern = rawPattern.replaceAll("\\ ", " ");
27756
+ const owners = rest.split(/\s+/).filter(Boolean);
27757
+ if (!owners.length)
27758
+ return null;
27759
+ return { pattern, owners };
27760
+ }
27717
27761
  function parseCodeowners(path) {
27718
27762
  const text = node_fs_1.default.readFileSync(path, "utf8");
27719
27763
  const rules = [];
@@ -27721,18 +27765,60 @@ function parseCodeowners(path) {
27721
27765
  const line = raw.trim();
27722
27766
  if (!line || line.startsWith("#"))
27723
27767
  continue;
27724
- const parts = line.split(/\s+/).filter(Boolean);
27725
- if (parts.length < 2)
27768
+ const parsed = splitPatternOwners(line);
27769
+ if (!parsed)
27726
27770
  continue;
27727
- rules.push({ pattern: parts[0], owners: parts.slice(1) });
27771
+ rules.push({ pattern: parsed.pattern, owners: parsed.owners });
27728
27772
  }
27729
27773
  return rules;
27730
27774
  }
27775
+ function hasGlobMagic(p) {
27776
+ // Good-enough heuristic for gitignore-style globs.
27777
+ return /[*?\[]/.test(p);
27778
+ }
27731
27779
  function matches(file, pattern) {
27732
- const f = file.replace(/\\/g, "/");
27733
- // crude but works for most repos:
27734
- const pat = pattern.startsWith("/") ? pattern.slice(1) : pattern;
27735
- return (0, minimatch_1.minimatch)(f, pat, { dot: true, matchBase: !pattern.startsWith("/") });
27780
+ // GitHub CODEOWNERS patterns are gitignore-like:
27781
+ // - Leading `/` anchors to repo root
27782
+ // - Pattern without `/` matches a basename anywhere
27783
+ // - A directory pattern matches everything under it (e.g. `/frontend/admin` should match `/frontend/admin/**`)
27784
+ const f = file.replace(/\\/g, "/").replace(/^\.?\//, "");
27785
+ let pat = pattern.replace(/\\/g, "/").trim();
27786
+ if (!pat)
27787
+ return false;
27788
+ const anchored = pat.startsWith("/");
27789
+ if (anchored)
27790
+ pat = pat.slice(1);
27791
+ // Directory patterns: trailing `/` means directory; also treat non-glob literal paths as directory-or-file match.
27792
+ const trailingSlash = pat.endsWith("/");
27793
+ if (trailingSlash)
27794
+ pat = pat.slice(0, -1);
27795
+ const isLiteral = !hasGlobMagic(pat);
27796
+ const hasSlash = pat.includes("/");
27797
+ // Literal path: match exact file OR directory subtree.
27798
+ if (isLiteral && (anchored || hasSlash)) {
27799
+ return f === pat || f.startsWith(pat + "/");
27800
+ }
27801
+ // Literal basename: match basename OR directory subtree anywhere.
27802
+ if (isLiteral && !hasSlash) {
27803
+ const base = f.split("/").pop() ?? f;
27804
+ if (base === pat)
27805
+ return true;
27806
+ return f.includes("/" + pat + "/");
27807
+ }
27808
+ // Glob patterns: use minimatch.
27809
+ // - If anchored: match from repo root.
27810
+ // - If not anchored and no slash: match basename anywhere.
27811
+ // - If not anchored but includes a slash: match relative to repo root (like a root CODEOWNERS / .gitignore).
27812
+ const matchBase = !anchored && !hasSlash;
27813
+ const mm = (0, minimatch_1.minimatch)(f, pat, { dot: true, matchBase });
27814
+ if (mm)
27815
+ return true;
27816
+ // If the pattern explicitly denotes a directory (trailing slash), also match directory subtree.
27817
+ if (trailingSlash) {
27818
+ // Convert `dir/` to subtree match.
27819
+ return (0, minimatch_1.minimatch)(f, pat + "/**", { dot: true, matchBase: false });
27820
+ }
27821
+ return false;
27736
27822
  }
27737
27823
  function ownersForFile(file, rules) {
27738
27824
  let hit;
@@ -28039,7 +28125,7 @@ async function upsertPullRequest(params) {
28039
28125
  head,
28040
28126
  base,
28041
28127
  title,
28042
- body,
28128
+ body: body || "",
28043
28129
  draft
28044
28130
  });
28045
28131
  return { bucket_key: bucketKey, branch: head, number: created.data.number, url: created.data.html_url };
@@ -28111,6 +28197,8 @@ async function run() {
28111
28197
  prTitle: core.getInput("pr_title") || "chore: automated changes ({owners})",
28112
28198
  prBody: core.getInput("pr_body") ||
28113
28199
  "Automated changes bucketed by CODEOWNERS.\n\nOwners: {owners}\nBucket key: {bucket_key}\n\nFiles:\n{files}\n",
28200
+ prBodyMode: (core.getInput("pr_body_mode") || "custom"),
28201
+ prTemplatePath: core.getInput("pr_template_path") || ".github/pull_request_template.md",
28114
28202
  draft: (0, buckets_1.parseBool)(core.getInput("draft")),
28115
28203
  remoteName: "origin",
28116
28204
  };
@@ -28127,6 +28215,40 @@ async function run() {
28127
28215
  run();
28128
28216
 
28129
28217
 
28218
+ /***/ }),
28219
+
28220
+ /***/ 2456:
28221
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
28222
+
28223
+ "use strict";
28224
+
28225
+ var __importDefault = (this && this.__importDefault) || function (mod) {
28226
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28227
+ };
28228
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
28229
+ exports.readPrTemplate = readPrTemplate;
28230
+ const node_fs_1 = __importDefault(__nccwpck_require__(3024));
28231
+ const node_path_1 = __importDefault(__nccwpck_require__(6760));
28232
+ function readPrTemplate(cwd, templatePath) {
28233
+ const candidates = [];
28234
+ if (templatePath && templatePath.trim()) {
28235
+ candidates.push(templatePath.trim());
28236
+ }
28237
+ else {
28238
+ candidates.push(".github/pull_request_template.md");
28239
+ candidates.push(".github/PULL_REQUEST_TEMPLATE.md");
28240
+ candidates.push("pull_request_template.md");
28241
+ }
28242
+ for (const rel of candidates) {
28243
+ const abs = node_path_1.default.resolve(cwd, rel);
28244
+ if (node_fs_1.default.existsSync(abs) && node_fs_1.default.statSync(abs).isFile()) {
28245
+ return node_fs_1.default.readFileSync(abs, "utf8");
28246
+ }
28247
+ }
28248
+ return null;
28249
+ }
28250
+
28251
+
28130
28252
  /***/ }),
28131
28253
 
28132
28254
  /***/ 2613: