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/dist-cli/index.js CHANGED
@@ -1829,6 +1829,7 @@ const codeowners_1 = __nccwpck_require__(586);
1829
1829
  const git_1 = __nccwpck_require__(243);
1830
1830
  const github_1 = __nccwpck_require__(248);
1831
1831
  const ghcli_1 = __nccwpck_require__(64);
1832
+ const pr_template_1 = __nccwpck_require__(456);
1832
1833
  function formatTemplate(tpl, vars) {
1833
1834
  let out = tpl;
1834
1835
  for (const [k, v] of Object.entries(vars))
@@ -1948,8 +1949,22 @@ async function runSplit(config, logger) {
1948
1949
  (0, git_1.pushBranch)(config.remoteName, branch, worktreeDir);
1949
1950
  const ownersStr = b.owners.length ? b.owners.join(", ") : "(unowned)";
1950
1951
  const filesStr = b.files.map(f => `- ${f.file}`).join("\n");
1952
+ 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 });
1951
1953
  const title = formatTemplate(config.prTitle, { owners: ownersStr, bucket_key: b.key });
1952
- const body = formatTemplate(config.prBody, { owners: ownersStr, bucket_key: b.key, files: filesStr });
1954
+ let body;
1955
+ if (config.prBodyMode === "none") {
1956
+ body = undefined;
1957
+ }
1958
+ else if (config.prBodyMode === "custom") {
1959
+ body = formatTemplate(config.prBody, { owners: ownersStr, bucket_key: b.key, files: filesStr });
1960
+ }
1961
+ else {
1962
+ const template = (0, pr_template_1.readPrTemplate)(worktreeDir, config.prTemplatePath) ?? "";
1963
+ body =
1964
+ config.prBodyMode === "template_with_bucket"
1965
+ ? (template ? template.trimEnd() + "\n\n---\n\n" + bucketInfo : bucketInfo)
1966
+ : (template || bucketInfo);
1967
+ }
1953
1968
  const pr = useGhCli
1954
1969
  ? (() => {
1955
1970
  return (0, ghcli_1.upsertPullRequestViaGh)({
@@ -1957,7 +1972,7 @@ async function runSplit(config, logger) {
1957
1972
  base: baseBranch,
1958
1973
  head: branch,
1959
1974
  title,
1960
- body,
1975
+ body: body ?? "",
1961
1976
  draft: config.draft,
1962
1977
  bucketKey: b.key
1963
1978
  });
@@ -1968,7 +1983,7 @@ async function runSplit(config, logger) {
1968
1983
  base: baseBranch,
1969
1984
  head: branch,
1970
1985
  title,
1971
- body,
1986
+ body: body ?? "",
1972
1987
  draft: config.draft,
1973
1988
  bucketKey: b.key
1974
1989
  });
@@ -2073,6 +2088,35 @@ exports.parseCodeowners = parseCodeowners;
2073
2088
  exports.ownersForFile = ownersForFile;
2074
2089
  const node_fs_1 = __importDefault(__nccwpck_require__(24));
2075
2090
  const minimatch_1 = __nccwpck_require__(507);
2091
+ function splitPatternOwners(line) {
2092
+ // CODEOWNERS lines are: <pattern><whitespace><owner>...
2093
+ // Patterns may contain spaces if escaped as `\ `.
2094
+ // We parse by finding the first whitespace not escaped by a backslash.
2095
+ let i = 0;
2096
+ let escaped = false;
2097
+ for (; i < line.length; i++) {
2098
+ const ch = line[i];
2099
+ if (escaped) {
2100
+ escaped = false;
2101
+ continue;
2102
+ }
2103
+ if (ch === "\\") {
2104
+ escaped = true;
2105
+ continue;
2106
+ }
2107
+ if (/\s/.test(ch))
2108
+ break;
2109
+ }
2110
+ const rawPattern = line.slice(0, i).trim();
2111
+ const rest = line.slice(i).trim();
2112
+ if (!rawPattern || !rest)
2113
+ return null;
2114
+ const pattern = rawPattern.replaceAll("\\ ", " ");
2115
+ const owners = rest.split(/\s+/).filter(Boolean);
2116
+ if (!owners.length)
2117
+ return null;
2118
+ return { pattern, owners };
2119
+ }
2076
2120
  function parseCodeowners(path) {
2077
2121
  const text = node_fs_1.default.readFileSync(path, "utf8");
2078
2122
  const rules = [];
@@ -2080,18 +2124,60 @@ function parseCodeowners(path) {
2080
2124
  const line = raw.trim();
2081
2125
  if (!line || line.startsWith("#"))
2082
2126
  continue;
2083
- const parts = line.split(/\s+/).filter(Boolean);
2084
- if (parts.length < 2)
2127
+ const parsed = splitPatternOwners(line);
2128
+ if (!parsed)
2085
2129
  continue;
2086
- rules.push({ pattern: parts[0], owners: parts.slice(1) });
2130
+ rules.push({ pattern: parsed.pattern, owners: parsed.owners });
2087
2131
  }
2088
2132
  return rules;
2089
2133
  }
2134
+ function hasGlobMagic(p) {
2135
+ // Good-enough heuristic for gitignore-style globs.
2136
+ return /[*?\[]/.test(p);
2137
+ }
2090
2138
  function matches(file, pattern) {
2091
- const f = file.replace(/\\/g, "/");
2092
- // crude but works for most repos:
2093
- const pat = pattern.startsWith("/") ? pattern.slice(1) : pattern;
2094
- return (0, minimatch_1.minimatch)(f, pat, { dot: true, matchBase: !pattern.startsWith("/") });
2139
+ // GitHub CODEOWNERS patterns are gitignore-like:
2140
+ // - Leading `/` anchors to repo root
2141
+ // - Pattern without `/` matches a basename anywhere
2142
+ // - A directory pattern matches everything under it (e.g. `/frontend/admin` should match `/frontend/admin/**`)
2143
+ const f = file.replace(/\\/g, "/").replace(/^\.?\//, "");
2144
+ let pat = pattern.replace(/\\/g, "/").trim();
2145
+ if (!pat)
2146
+ return false;
2147
+ const anchored = pat.startsWith("/");
2148
+ if (anchored)
2149
+ pat = pat.slice(1);
2150
+ // Directory patterns: trailing `/` means directory; also treat non-glob literal paths as directory-or-file match.
2151
+ const trailingSlash = pat.endsWith("/");
2152
+ if (trailingSlash)
2153
+ pat = pat.slice(0, -1);
2154
+ const isLiteral = !hasGlobMagic(pat);
2155
+ const hasSlash = pat.includes("/");
2156
+ // Literal path: match exact file OR directory subtree.
2157
+ if (isLiteral && (anchored || hasSlash)) {
2158
+ return f === pat || f.startsWith(pat + "/");
2159
+ }
2160
+ // Literal basename: match basename OR directory subtree anywhere.
2161
+ if (isLiteral && !hasSlash) {
2162
+ const base = f.split("/").pop() ?? f;
2163
+ if (base === pat)
2164
+ return true;
2165
+ return f.includes("/" + pat + "/");
2166
+ }
2167
+ // Glob patterns: use minimatch.
2168
+ // - If anchored: match from repo root.
2169
+ // - If not anchored and no slash: match basename anywhere.
2170
+ // - If not anchored but includes a slash: match relative to repo root (like a root CODEOWNERS / .gitignore).
2171
+ const matchBase = !anchored && !hasSlash;
2172
+ const mm = (0, minimatch_1.minimatch)(f, pat, { dot: true, matchBase });
2173
+ if (mm)
2174
+ return true;
2175
+ // If the pattern explicitly denotes a directory (trailing slash), also match directory subtree.
2176
+ if (trailingSlash) {
2177
+ // Convert `dir/` to subtree match.
2178
+ return (0, minimatch_1.minimatch)(f, pat + "/**", { dot: true, matchBase: false });
2179
+ }
2180
+ return false;
2095
2181
  }
2096
2182
  function ownersForFile(file, rules) {
2097
2183
  let hit;
@@ -2398,13 +2484,47 @@ async function upsertPullRequest(params) {
2398
2484
  head,
2399
2485
  base,
2400
2486
  title,
2401
- body,
2487
+ body: body || "",
2402
2488
  draft
2403
2489
  });
2404
2490
  return { bucket_key: bucketKey, branch: head, number: created.data.number, url: created.data.html_url };
2405
2491
  }
2406
2492
 
2407
2493
 
2494
+ /***/ }),
2495
+
2496
+ /***/ 456:
2497
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
2498
+
2499
+ "use strict";
2500
+
2501
+ var __importDefault = (this && this.__importDefault) || function (mod) {
2502
+ return (mod && mod.__esModule) ? mod : { "default": mod };
2503
+ };
2504
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2505
+ exports.readPrTemplate = readPrTemplate;
2506
+ const node_fs_1 = __importDefault(__nccwpck_require__(24));
2507
+ const node_path_1 = __importDefault(__nccwpck_require__(760));
2508
+ function readPrTemplate(cwd, templatePath) {
2509
+ const candidates = [];
2510
+ if (templatePath && templatePath.trim()) {
2511
+ candidates.push(templatePath.trim());
2512
+ }
2513
+ else {
2514
+ candidates.push(".github/pull_request_template.md");
2515
+ candidates.push(".github/PULL_REQUEST_TEMPLATE.md");
2516
+ candidates.push("pull_request_template.md");
2517
+ }
2518
+ for (const rel of candidates) {
2519
+ const abs = node_path_1.default.resolve(cwd, rel);
2520
+ if (node_fs_1.default.existsSync(abs) && node_fs_1.default.statSync(abs).isFile()) {
2521
+ return node_fs_1.default.readFileSync(abs, "utf8");
2522
+ }
2523
+ }
2524
+ return null;
2525
+ }
2526
+
2527
+
2408
2528
  /***/ }),
2409
2529
 
2410
2530
  /***/ 421:
@@ -13236,6 +13356,8 @@ function printHelp() {
13236
13356
  " --commit-message <msg> (default: chore: automated changes)",
13237
13357
  " --pr-title <tpl> Supports {owners} and {bucket_key}",
13238
13358
  " --pr-body <tpl> Supports {owners}, {bucket_key}, {files}",
13359
+ " --pr-body-mode <mode> custom|template|template_with_bucket|none (default: custom)",
13360
+ " --pr-template-path <path> (default: .github/pull_request_template.md)",
13239
13361
  " --draft <true|false> (default: false)",
13240
13362
  "",
13241
13363
  "Examples:",
@@ -13284,6 +13406,8 @@ async function main() {
13284
13406
  let commitMessage = "chore: automated changes";
13285
13407
  let prTitle = "chore: automated changes ({owners})";
13286
13408
  let prBody = "Automated changes bucketed by CODEOWNERS.\n\nOwners: {owners}\nBucket key: {bucket_key}\n\nFiles:\n{files}\n";
13409
+ let prBodyMode = "template";
13410
+ let prTemplatePath = ".github/pull_request_template.md";
13287
13411
  let draft = false;
13288
13412
  // parse args
13289
13413
  for (let i = 0; i < argv.length; i++) {
@@ -13323,6 +13447,10 @@ async function main() {
13323
13447
  prTitle = takeArg(argv, i++, a);
13324
13448
  else if (a === "--pr-body")
13325
13449
  prBody = takeArg(argv, i++, a);
13450
+ else if (a === "--pr-body-mode")
13451
+ prBodyMode = takeArg(argv, i++, a);
13452
+ else if (a === "--pr-template-path")
13453
+ prTemplatePath = takeArg(argv, i++, a);
13326
13454
  else if (a === "--draft")
13327
13455
  draft = (0, buckets_2.parseBool)(takeArg(argv, i++, a));
13328
13456
  else if (a.startsWith("-"))
@@ -13346,6 +13474,8 @@ async function main() {
13346
13474
  commitMessage,
13347
13475
  prTitle,
13348
13476
  prBody,
13477
+ prBodyMode,
13478
+ prTemplatePath,
13349
13479
  draft,
13350
13480
  remoteName: "origin"
13351
13481
  };