codeowners-git 2.0.2 → 2.2.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.
Files changed (3) hide show
  1. package/README.md +362 -3
  2. package/dist/cli.js +630 -46
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -2287,7 +2301,7 @@ var require_ignore = __commonJS((exports, module) => {
2287
2301
  }
2288
2302
  }, {
2289
2303
  key: "filter",
2290
- value: function filter(paths) {
2304
+ value: function filter2(paths) {
2291
2305
  var _this = this;
2292
2306
  return make_array(paths).filter(function(path) {
2293
2307
  return _this._filter(path);
@@ -2438,7 +2452,7 @@ var require_ignore = __commonJS((exports, module) => {
2438
2452
  }
2439
2453
  if (typeof process !== "undefined" && (process.env && process.env.IGNORE_TEST_WIN32 || process.platform === "win32")) {
2440
2454
  filter = IgnoreBase.prototype._filter;
2441
- make_posix = function make_posix(str) {
2455
+ make_posix = function make_posix2(str) {
2442
2456
  return /^\\\\\?\\/.test(str) || /[^\x00-\x80]+/.test(str) ? str : str.replace(/\\/g, "/");
2443
2457
  };
2444
2458
  IgnoreBase.prototype._filter = function(path, slices) {
@@ -3039,25 +3053,25 @@ var require_minimatch = __commonJS((exports, module) => {
3039
3053
  return minimatch;
3040
3054
  }
3041
3055
  var orig = minimatch;
3042
- var m = function minimatch(p, pattern, options) {
3056
+ var m = function minimatch2(p, pattern, options) {
3043
3057
  return orig(p, pattern, ext(def, options));
3044
3058
  };
3045
- m.Minimatch = function Minimatch(pattern, options) {
3059
+ m.Minimatch = function Minimatch2(pattern, options) {
3046
3060
  return new orig.Minimatch(pattern, ext(def, options));
3047
3061
  };
3048
3062
  m.Minimatch.defaults = function defaults(options) {
3049
3063
  return orig.defaults(ext(def, options)).Minimatch;
3050
3064
  };
3051
- m.filter = function filter(pattern, options) {
3065
+ m.filter = function filter2(pattern, options) {
3052
3066
  return orig.filter(pattern, ext(def, options));
3053
3067
  };
3054
3068
  m.defaults = function defaults(options) {
3055
3069
  return orig.defaults(ext(def, options));
3056
3070
  };
3057
- m.makeRe = function makeRe(pattern, options) {
3071
+ m.makeRe = function makeRe2(pattern, options) {
3058
3072
  return orig.makeRe(pattern, ext(def, options));
3059
3073
  };
3060
- m.braceExpand = function braceExpand(pattern, options) {
3074
+ m.braceExpand = function braceExpand2(pattern, options) {
3061
3075
  return orig.braceExpand(pattern, ext(def, options));
3062
3076
  };
3063
3077
  m.match = function(list, pattern, options) {
@@ -6584,7 +6598,7 @@ var require_colors = __commonJS((exports, module) => {
6584
6598
  colors.stripColors = colors.strip = function(str) {
6585
6599
  return ("" + str).replace(/\x1B\[\d+m/g, "");
6586
6600
  };
6587
- var stylize = colors.stylize = function stylize(str, style) {
6601
+ var stylize = colors.stylize = function stylize2(str, style) {
6588
6602
  if (!colors.enabled) {
6589
6603
  return str + "";
6590
6604
  }
@@ -6602,8 +6616,8 @@ var require_colors = __commonJS((exports, module) => {
6602
6616
  return str.replace(matchOperatorsRe, "\\$&");
6603
6617
  };
6604
6618
  function build(_styles) {
6605
- var builder = function builder() {
6606
- return applyStyle2.apply(builder, arguments);
6619
+ var builder = function builder2() {
6620
+ return applyStyle2.apply(builder2, arguments);
6607
6621
  };
6608
6622
  builder._styles = _styles;
6609
6623
  builder.__proto__ = proto2;
@@ -6622,7 +6636,7 @@ var require_colors = __commonJS((exports, module) => {
6622
6636
  });
6623
6637
  return ret;
6624
6638
  }();
6625
- var proto2 = defineProps(function colors() {}, styles3);
6639
+ var proto2 = defineProps(function colors2() {}, styles3);
6626
6640
  function applyStyle2() {
6627
6641
  var args = Array.prototype.slice.call(arguments);
6628
6642
  var str = args.map(function(arg) {
@@ -6681,7 +6695,7 @@ var require_colors = __commonJS((exports, module) => {
6681
6695
  });
6682
6696
  return ret;
6683
6697
  }
6684
- var sequencer = function sequencer(map2, str) {
6698
+ var sequencer = function sequencer2(map2, str) {
6685
6699
  var exploded = str.split("");
6686
6700
  exploded = exploded.map(map2);
6687
6701
  return exploded.join("");
@@ -11612,7 +11626,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
11612
11626
  var __esm = (fn, res) => function __init() {
11613
11627
  return fn && (res = (0, fn[__getOwnPropNames2(fn)[0]])(fn = 0)), res;
11614
11628
  };
11615
- var __commonJS2 = (cb, mod) => function __require() {
11629
+ var __commonJS2 = (cb, mod) => function __require2() {
11616
11630
  return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11617
11631
  };
11618
11632
  var __export = (target, all) => {
@@ -16080,6 +16094,26 @@ var import_cli_table3 = __toESM(require_table(), 1);
16080
16094
  var DEFAULT_COLUMN_WIDTH = 50;
16081
16095
  var MAX_LINE_LENGTH = 80;
16082
16096
  var MAX_PATH_LENGTH = 60;
16097
+ var _silent = false;
16098
+ var _origConsoleLog = console.log;
16099
+ var _origConsoleWarn = console.warn;
16100
+ var _origConsoleError = console.error;
16101
+ var noop = () => {};
16102
+ var setSilent = (silent) => {
16103
+ _silent = silent;
16104
+ if (silent) {
16105
+ console.log = noop;
16106
+ console.warn = noop;
16107
+ console.error = noop;
16108
+ } else {
16109
+ console.log = _origConsoleLog;
16110
+ console.warn = _origConsoleWarn;
16111
+ console.error = _origConsoleError;
16112
+ }
16113
+ };
16114
+ var outputJson = (data) => {
16115
+ _origConsoleLog(JSON.stringify(data, null, 2));
16116
+ };
16083
16117
  var log = {
16084
16118
  success: (message) => console.log(source_default.green(`✓ ${message}`)),
16085
16119
  error: (message) => console.error(source_default.red(`✗ ${message}`)),
@@ -16154,6 +16188,14 @@ var hasUnstagedChanges = async () => {
16154
16188
  const unstagedFiles = await getUnstagedFiles();
16155
16189
  return unstagedFiles.length > 0;
16156
16190
  };
16191
+ var getStagedFiles = async () => {
16192
+ const status = await git.status();
16193
+ return status.files.filter((file) => file.index !== " " && file.index !== "?").map((file) => file.path);
16194
+ };
16195
+ var hasStagedChanges = async () => {
16196
+ const stagedFiles = await getStagedFiles();
16197
+ return stagedFiles.length > 0;
16198
+ };
16157
16199
  var branchExists = async (branchName) => {
16158
16200
  try {
16159
16201
  const branches = await git.branch();
@@ -16240,7 +16282,8 @@ var pushBranch = async (branchName, {
16240
16282
  remote = "origin",
16241
16283
  upstream,
16242
16284
  force = false,
16243
- noVerify = false
16285
+ noVerify = false,
16286
+ silent = false
16244
16287
  } = {}) => {
16245
16288
  const targetUpstream = upstream || branchName;
16246
16289
  log.info(`Pushing branch "${branchName}" to ${remote}/${targetUpstream}...`);
@@ -16253,8 +16296,9 @@ var pushBranch = async (branchName, {
16253
16296
  if (noVerify) {
16254
16297
  pushArgs.push("--no-verify");
16255
16298
  }
16299
+ const stdioOption = silent ? ["pipe", "pipe", "pipe"] : ["inherit", "inherit", "inherit"];
16256
16300
  const gitProcess = spawn2("git", ["push", ...pushArgs], {
16257
- stdio: ["inherit", "inherit", "inherit"]
16301
+ stdio: stdioOption
16258
16302
  });
16259
16303
  return new Promise((resolve, reject) => {
16260
16304
  gitProcess.on("close", (code) => {
@@ -16328,6 +16372,16 @@ var getChangedFilesBetween = async (source, target) => {
16328
16372
  throw new Error(`Failed to get changed files between ${source} and ${target || "base"}: ${error}`);
16329
16373
  }
16330
16374
  };
16375
+ var stageFiles = async (files) => {
16376
+ if (files.length === 0)
16377
+ return;
16378
+ try {
16379
+ await git.add(files);
16380
+ log.info(`Staged ${files.length} file${files.length !== 1 ? "s" : ""}`);
16381
+ } catch (error) {
16382
+ throw new Error(`Failed to stage files: ${error}`);
16383
+ }
16384
+ };
16331
16385
  var extractFilesFromRef = async (ref, files) => {
16332
16386
  try {
16333
16387
  for (const file of files) {
@@ -16430,6 +16484,9 @@ var getOwnerFiles = async (ownerPattern, includeUnowned = false, pathPattern, ex
16430
16484
  // src/commands/list.ts
16431
16485
  var listCodeowners = async (options) => {
16432
16486
  try {
16487
+ if (options.json) {
16488
+ setSilent(true);
16489
+ }
16433
16490
  if (await hasUnstagedChanges()) {
16434
16491
  const unstagedFiles = await getUnstagedFiles();
16435
16492
  log.warn("Warning: Unstaged changes detected (these will be ignored):");
@@ -16462,6 +16519,34 @@ Only staged files will be processed.`);
16462
16519
  return matchFn(owners, patterns);
16463
16520
  });
16464
16521
  }
16522
+ if (options.json) {
16523
+ let grouped;
16524
+ if (options.group) {
16525
+ grouped = {};
16526
+ for (const { file, owners } of filteredFiles) {
16527
+ if (owners.length === 0) {
16528
+ grouped["(unowned)"] = grouped["(unowned)"] || [];
16529
+ grouped["(unowned)"].push(file);
16530
+ } else {
16531
+ for (const owner of owners) {
16532
+ grouped[owner] = grouped[owner] || [];
16533
+ grouped[owner].push(file);
16534
+ }
16535
+ }
16536
+ }
16537
+ }
16538
+ outputJson({
16539
+ command: "list",
16540
+ ...grouped ? { grouped } : { files: filteredFiles.map(({ file, owners }) => ({ file, owners })) },
16541
+ filters: {
16542
+ include: options.include || null,
16543
+ pathPattern: options.pathPattern || null,
16544
+ exclusive: options.exclusive || false,
16545
+ coOwned: options.coOwned || false
16546
+ }
16547
+ });
16548
+ return;
16549
+ }
16465
16550
  if (options.group) {
16466
16551
  const ownerGroups = new Map;
16467
16552
  for (const { file, owners } of filteredFiles) {
@@ -16528,6 +16613,10 @@ Only staged files will be processed.`);
16528
16613
  ]);
16529
16614
  }
16530
16615
  } catch (err) {
16616
+ if (options.json) {
16617
+ outputJson({ command: "list", error: String(err) });
16618
+ process.exit(1);
16619
+ }
16531
16620
  log.error(err);
16532
16621
  process.exit(1);
16533
16622
  }
@@ -16617,11 +16706,16 @@ var createPullRequest = async (options) => {
16617
16706
  });
16618
16707
  };
16619
16708
  var createPRWithTemplate = async (title, branchName, options = {}) => {
16620
- const template = await findPRTemplate();
16621
16709
  let body = "";
16622
- if (template) {
16623
- body = template.content;
16624
- log.info("Using PR template for pull request body");
16710
+ if (options.prBody) {
16711
+ body = options.prBody;
16712
+ log.info("Using provided PR body");
16713
+ } else {
16714
+ const template = await findPRTemplate();
16715
+ if (template) {
16716
+ body = template.content;
16717
+ log.info("Using PR template for pull request body");
16718
+ }
16625
16719
  }
16626
16720
  return createPullRequest({
16627
16721
  title,
@@ -16780,6 +16874,11 @@ var branch = async (options) => {
16780
16874
  let operationState = options.operationState || null;
16781
16875
  const isSubOperation = !!options.operationState;
16782
16876
  let autoRecoverySucceeded = false;
16877
+ let tempBranch = null;
16878
+ let sourceOriginalBranch = null;
16879
+ if (options.json && !isSubOperation) {
16880
+ setSilent(true);
16881
+ }
16783
16882
  try {
16784
16883
  if (!options.branch || !options.message || !options.include) {
16785
16884
  throw new Error("Missing required options for branch creation");
@@ -16790,7 +16889,37 @@ var branch = async (options) => {
16790
16889
  if (options.pr && options.draftPr) {
16791
16890
  throw new Error("Cannot use both --pr and --draft-pr options");
16792
16891
  }
16793
- if (await hasUnstagedChanges()) {
16892
+ if (options.source && !isSubOperation) {
16893
+ if (await hasStagedChanges()) {
16894
+ throw new Error("Cannot use --source when there are staged changes. " + "Either commit/unstage your changes first, or omit --source to use staged files.");
16895
+ }
16896
+ sourceOriginalBranch = await getCurrentBranch();
16897
+ log.info(`Extracting changes from source: ${options.source}`);
16898
+ const defaultBranch = await getDefaultBranch();
16899
+ const compareTarget = options.compareMain ? defaultBranch : undefined;
16900
+ if (compareTarget) {
16901
+ log.info(`Comparing ${options.source} against ${compareTarget}...`);
16902
+ }
16903
+ let sourceFiles = await getChangedFilesBetween(options.source, compareTarget);
16904
+ if (sourceFiles.length === 0) {
16905
+ throw new Error(`No changed files found in ${options.source}`);
16906
+ }
16907
+ sourceFiles = filterByPathPatterns(sourceFiles, options.pathPattern);
16908
+ if (sourceFiles.length === 0) {
16909
+ throw new Error(options.pathPattern ? `No changed files found matching pattern: ${options.pathPattern}` : `No changed files found in ${options.source}`);
16910
+ }
16911
+ log.info(`Found ${sourceFiles.length} changed file${sourceFiles.length !== 1 ? "s" : ""} in source`);
16912
+ tempBranch = `cg-temp-${Date.now()}`;
16913
+ log.info(`Creating temporary branch "${tempBranch}" from "${defaultBranch}"...`);
16914
+ await checkout(defaultBranch);
16915
+ await createBranch(tempBranch);
16916
+ log.info("Extracting files from source...");
16917
+ await extractFilesFromRef(options.source, sourceFiles);
16918
+ await stageFiles(sourceFiles);
16919
+ log.info(`Files extracted and staged on temporary branch. Proceeding with branch creation...
16920
+ `);
16921
+ }
16922
+ if (!(options.source && !isSubOperation) && await hasUnstagedChanges()) {
16794
16923
  const unstagedFiles = await getUnstagedFiles();
16795
16924
  log.warn("Warning: Unstaged changes detected (these will be ignored):");
16796
16925
  unstagedFiles.forEach((file) => log.warn(` - ${file}`));
@@ -16829,6 +16958,93 @@ Only staged files will be processed.`);
16829
16958
  log.file(`Files to be committed:
16830
16959
  ${filesToCommit.join(`
16831
16960
  `)}`);
16961
+ if (options.dryRun) {
16962
+ const allStagedFiles = await getChangedFiles();
16963
+ const excludedFiles = allStagedFiles.filter((f) => !filesToCommit.includes(f));
16964
+ const branchAlreadyExistsDry = await branchExists(options.branch);
16965
+ if (options.json && !isSubOperation) {
16966
+ outputJson({
16967
+ command: "branch",
16968
+ dryRun: true,
16969
+ owner: options.include,
16970
+ branch: options.branch,
16971
+ branchExists: branchAlreadyExistsDry,
16972
+ message: options.message,
16973
+ files: filesToCommit,
16974
+ excludedFiles,
16975
+ options: {
16976
+ push: options.push || false,
16977
+ remote: options.remote || "origin",
16978
+ force: options.force || false,
16979
+ pr: options.pr || false,
16980
+ draftPr: options.draftPr || false,
16981
+ noVerify: !options.verify,
16982
+ append: options.append || false,
16983
+ exclusive: options.exclusive || false,
16984
+ coOwned: options.coOwned || false,
16985
+ pathPattern: options.pathPattern || null
16986
+ }
16987
+ });
16988
+ return {
16989
+ success: true,
16990
+ branchName: options.branch,
16991
+ owner: options.include,
16992
+ files: filesToCommit,
16993
+ pushed: false
16994
+ };
16995
+ }
16996
+ if (!isSubOperation) {
16997
+ log.header("Dry Run Preview — branch");
16998
+ console.log("");
16999
+ }
17000
+ const detailsTable = new import_cli_table32.default({
17001
+ style: { head: ["cyan"] },
17002
+ wordWrap: true
17003
+ });
17004
+ detailsTable.push({ [source_default.bold("Owner pattern")]: options.include }, { [source_default.bold("Branch name")]: options.branch }, {
17005
+ [source_default.bold("Branch exists")]: branchAlreadyExistsDry ? options.append ? "Yes (--append: will add commit)" : "Yes (will fail without --append)" : "No (will be created)"
17006
+ }, { [source_default.bold("Commit message")]: options.message }, {
17007
+ [source_default.bold("Files matched")]: `${filesToCommit.length} file${filesToCommit.length !== 1 ? "s" : ""}`
17008
+ }, {
17009
+ [source_default.bold("Files excluded")]: `${excludedFiles.length} staged file${excludedFiles.length !== 1 ? "s" : ""} not matching`
17010
+ }, { [source_default.bold("No-verify")]: !options.verify ? "Yes" : "No" }, {
17011
+ [source_default.bold("Push")]: options.push ? `Yes → ${options.remote || "origin"}${options.force ? " (force)" : ""}` : "No"
17012
+ }, {
17013
+ [source_default.bold("Pull request")]: options.pr ? "Yes" : options.draftPr ? "Yes (draft)" : "No"
17014
+ });
17015
+ if (options.pathPattern) {
17016
+ detailsTable.push({
17017
+ [source_default.bold("Path filter")]: options.pathPattern
17018
+ });
17019
+ }
17020
+ if (options.exclusive) {
17021
+ detailsTable.push({
17022
+ [source_default.bold("Exclusive mode")]: "Yes (only files solely owned by this owner)"
17023
+ });
17024
+ }
17025
+ if (options.coOwned) {
17026
+ detailsTable.push({
17027
+ [source_default.bold("Co-owned mode")]: "Yes (only files with multiple owners)"
17028
+ });
17029
+ }
17030
+ console.log(detailsTable.toString());
17031
+ console.log(source_default.bold.green(`
17032
+ Files to be committed (${filesToCommit.length}):`));
17033
+ filesToCommit.forEach((file) => console.log(` ${source_default.green("+")} ${file}`));
17034
+ if (excludedFiles.length > 0) {
17035
+ console.log(source_default.bold.dim(`
17036
+ Excluded staged files (${excludedFiles.length}):`));
17037
+ excludedFiles.forEach((file) => console.log(` ${source_default.dim("-")} ${source_default.dim(file)}`));
17038
+ }
17039
+ console.log("");
17040
+ return {
17041
+ success: true,
17042
+ branchName: options.branch,
17043
+ owner: options.include,
17044
+ files: filesToCommit,
17045
+ pushed: false
17046
+ };
17047
+ }
16832
17048
  const branchAlreadyExists = await branchExists(options.branch);
16833
17049
  if (branchAlreadyExists && !options.append) {
16834
17050
  throw new Error(`Branch "${options.branch}" already exists. Use --append to add commits to it, or use a different name.`);
@@ -16883,7 +17099,8 @@ Only staged files will be processed.`);
16883
17099
  remote: options.remote,
16884
17100
  upstream: options.upstream,
16885
17101
  force: options.force,
16886
- noVerify: !options.verify
17102
+ noVerify: !options.verify,
17103
+ silent: !!options.json
16887
17104
  });
16888
17105
  pushed = true;
16889
17106
  if (operationState) {
@@ -16902,7 +17119,8 @@ Only staged files will be processed.`);
16902
17119
  const defaultBranch = await getDefaultBranch();
16903
17120
  const prResult = await createPRWithTemplate(options.message, options.branch, {
16904
17121
  draft: options.draftPr,
16905
- base: defaultBranch
17122
+ base: defaultBranch,
17123
+ prBody: options.prBody
16906
17124
  });
16907
17125
  if (prResult) {
16908
17126
  prUrl = prResult.url;
@@ -16949,7 +17167,7 @@ Only staged files will be processed.`);
16949
17167
  Files committed:`);
16950
17168
  filesToCommit.forEach((file) => console.log(` - ${file}`));
16951
17169
  }
16952
- return {
17170
+ const result = {
16953
17171
  success: true,
16954
17172
  branchName: options.branch,
16955
17173
  owner: options.include,
@@ -16958,6 +17176,15 @@ Files committed:`);
16958
17176
  prUrl,
16959
17177
  prNumber
16960
17178
  };
17179
+ if (options.json && !isSubOperation) {
17180
+ outputJson({
17181
+ command: "branch",
17182
+ dryRun: false,
17183
+ ...result,
17184
+ error: null
17185
+ });
17186
+ }
17187
+ return result;
16961
17188
  } catch (operationError) {
16962
17189
  log.error(`Operation failed: ${operationError}`);
16963
17190
  if (operationState && !isSubOperation) {
@@ -17027,6 +17254,21 @@ Files committed:`);
17027
17254
  error: String(err)
17028
17255
  };
17029
17256
  }
17257
+ if (options.json && !isSubOperation) {
17258
+ outputJson({
17259
+ command: "branch",
17260
+ dryRun: false,
17261
+ success: false,
17262
+ branchName: options.branch ?? "",
17263
+ owner: options.include ?? "",
17264
+ files: filesToCommit,
17265
+ pushed,
17266
+ prUrl: prUrl || null,
17267
+ prNumber: prNumber || null,
17268
+ error: String(err)
17269
+ });
17270
+ process.exit(1);
17271
+ }
17030
17272
  if (operationState && !autoRecoverySucceeded) {
17031
17273
  log.info(`
17032
17274
  Auto-recovery failed. Manual recovery options:`);
@@ -17037,19 +17279,33 @@ Auto-recovery failed. Manual recovery options:`);
17037
17279
  process.exit(1);
17038
17280
  } finally {
17039
17281
  try {
17040
- if (originalBranch) {
17041
- const currentBranch = await getCurrentBranch();
17042
- if (currentBranch !== originalBranch) {
17043
- await checkout(originalBranch);
17282
+ if (tempBranch) {
17283
+ if (sourceOriginalBranch) {
17284
+ const currentBranch = await getCurrentBranch();
17285
+ if (currentBranch !== sourceOriginalBranch) {
17286
+ log.info(`Returning to original branch "${sourceOriginalBranch}"...`);
17287
+ await checkout(sourceOriginalBranch);
17288
+ }
17289
+ }
17290
+ log.info(`Cleaning up temporary branch "${tempBranch}"...`);
17291
+ await deleteBranch(tempBranch, true);
17292
+ } else {
17293
+ if (originalBranch) {
17294
+ const currentBranch = await getCurrentBranch();
17295
+ if (currentBranch !== originalBranch) {
17296
+ await checkout(originalBranch);
17297
+ }
17044
17298
  }
17045
17299
  }
17046
17300
  } catch (finalError) {
17047
17301
  log.error(`Error during final cleanup: ${finalError}`);
17048
17302
  log.info("Some manual cleanup may be required.");
17303
+ if (tempBranch) {
17304
+ log.info(`You can manually delete the temp branch with: git branch -D ${tempBranch}`);
17305
+ }
17049
17306
  }
17050
17307
  }
17051
17308
  };
17052
-
17053
17309
  // node_modules/@inquirer/core/dist/esm/lib/key.js
17054
17310
  var isUpKey = (key) => key.name === "up" || key.name === "k" || key.ctrl && key.name === "p";
17055
17311
  var isDownKey = (key) => key.name === "down" || key.name === "j" || key.ctrl && key.name === "n";
@@ -18337,9 +18593,21 @@ Operation ID: ${op.id}`);
18337
18593
  log.info(formatBranchState(op));
18338
18594
  }
18339
18595
  };
18340
- var performRecovery = async (state, keepBranches) => {
18596
+ var performRecovery = async (state, keepBranches, options) => {
18341
18597
  log.header(`Recovering from operation ${state.id}`);
18342
18598
  let hadWarnings = false;
18599
+ if (!options?.skipDirtyCheck) {
18600
+ const hasUnstaged = await hasUnstagedChanges();
18601
+ const hasStaged = await hasStagedChanges();
18602
+ if (hasUnstaged || hasStaged) {
18603
+ log.warn("Working directory has uncommitted changes.");
18604
+ log.warn("Recovery may overwrite files in your working directory.");
18605
+ log.info("Consider committing or stashing your changes first:");
18606
+ log.info(" git stash push -m 'before recovery'");
18607
+ throw new Error("Working directory is not clean. Commit or stash changes before recovering.");
18608
+ }
18609
+ }
18610
+ const branchesWithRestoreFailure = new Set;
18343
18611
  const currentBranch = await getCurrentBranch();
18344
18612
  if (currentBranch !== state.originalBranch) {
18345
18613
  try {
@@ -18370,7 +18638,8 @@ Restoring files from branches...`);
18370
18638
  }
18371
18639
  } catch (error) {
18372
18640
  log.error(`Failed to restore files from ${branch2.name}: ${error}`);
18373
- log.info(`Files may still be accessible from the branch if it exists`);
18641
+ log.warn(`Branch ${branch2.name} will be kept to prevent data loss`);
18642
+ branchesWithRestoreFailure.add(branch2.name);
18374
18643
  hadWarnings = true;
18375
18644
  }
18376
18645
  }
@@ -18381,6 +18650,12 @@ Restoring files from branches...`);
18381
18650
  Cleaning up created branches...`);
18382
18651
  for (const branch2 of state.branches) {
18383
18652
  if (branch2.created) {
18653
+ if (branchesWithRestoreFailure.has(branch2.name)) {
18654
+ log.warn(`Skipping deletion of ${branch2.name} — file restoration failed, branch preserved to prevent data loss`);
18655
+ log.info(` To recover files manually: git checkout ${branch2.name} -- <file>`);
18656
+ log.info(` To delete manually when done: git branch -D ${branch2.name}`);
18657
+ continue;
18658
+ }
18384
18659
  try {
18385
18660
  const exists2 = await branchExists(branch2.name);
18386
18661
  if (exists2) {
@@ -18478,6 +18753,11 @@ Operation details:`);
18478
18753
  var import_cli_table33 = __toESM(require_table(), 1);
18479
18754
  var multiBranch = async (options) => {
18480
18755
  let operationState = null;
18756
+ let tempBranch = null;
18757
+ let sourceOriginalBranch = null;
18758
+ if (options.json) {
18759
+ setSilent(true);
18760
+ }
18481
18761
  try {
18482
18762
  if (!options.branch || !options.message) {
18483
18763
  throw new Error("Missing required options for multi-branch creation");
@@ -18491,7 +18771,37 @@ var multiBranch = async (options) => {
18491
18771
  if (options.pr && options.draftPr) {
18492
18772
  throw new Error("Cannot use both --pr and --draft-pr options");
18493
18773
  }
18494
- if (await hasUnstagedChanges()) {
18774
+ if (options.source) {
18775
+ if (await hasStagedChanges()) {
18776
+ throw new Error("Cannot use --source when there are staged changes. " + "Either commit/unstage your changes first, or omit --source to use staged files.");
18777
+ }
18778
+ sourceOriginalBranch = await getCurrentBranch();
18779
+ log.info(`Extracting changes from source: ${options.source}`);
18780
+ const defaultBranch = await getDefaultBranch();
18781
+ const compareTarget = options.compareMain ? defaultBranch : undefined;
18782
+ if (compareTarget) {
18783
+ log.info(`Comparing ${options.source} against ${compareTarget}...`);
18784
+ }
18785
+ let sourceFiles = await getChangedFilesBetween(options.source, compareTarget);
18786
+ if (sourceFiles.length === 0) {
18787
+ throw new Error(`No changed files found in ${options.source}`);
18788
+ }
18789
+ sourceFiles = filterByPathPatterns(sourceFiles, options.pathPattern);
18790
+ if (sourceFiles.length === 0) {
18791
+ throw new Error(options.pathPattern ? `No changed files found matching pattern: ${options.pathPattern}` : `No changed files found in ${options.source}`);
18792
+ }
18793
+ log.info(`Found ${sourceFiles.length} changed file${sourceFiles.length !== 1 ? "s" : ""} in source`);
18794
+ tempBranch = `cg-temp-${Date.now()}`;
18795
+ log.info(`Creating temporary branch "${tempBranch}" from "${defaultBranch}"...`);
18796
+ await checkout(defaultBranch);
18797
+ await createBranch(tempBranch);
18798
+ log.info("Extracting files from source...");
18799
+ await extractFilesFromRef(options.source, sourceFiles);
18800
+ await stageFiles(sourceFiles);
18801
+ log.info(`Files extracted and staged on temporary branch. Proceeding with multi-branch split...
18802
+ `);
18803
+ }
18804
+ if (!options.source && await hasUnstagedChanges()) {
18495
18805
  const unstagedFiles = await getUnstagedFiles();
18496
18806
  log.warn("Warning: Unstaged changes detected (these will be ignored):");
18497
18807
  unstagedFiles.forEach((file) => log.warn(` - ${file}`));
@@ -18556,6 +18866,137 @@ Only staged files will be processed.`);
18556
18866
  }
18557
18867
  log.info(`Processing ${codeowners2.length} codeowners after filtering: ${codeowners2.join(", ")}`);
18558
18868
  }
18869
+ if (options.dryRun) {
18870
+ const previews = [];
18871
+ const allCoveredFiles = new Set;
18872
+ for (const owner of codeowners2) {
18873
+ const sanitizedOwner = owner.replace(/[^a-zA-Z0-9-_@]/g, "-").replace(/^@/, "");
18874
+ const branchName = `${options.branch}/${sanitizedOwner}`;
18875
+ const commitMessage = `${options.message} - ${owner}`;
18876
+ const ownerFiles = await getOwnerFiles(owner, owner === options.defaultOwner, options.pathPattern, options.exclusive || false, options.coOwned || false);
18877
+ for (const f of ownerFiles)
18878
+ allCoveredFiles.add(f);
18879
+ previews.push({
18880
+ owner,
18881
+ branchName,
18882
+ commitMessage,
18883
+ files: ownerFiles
18884
+ });
18885
+ }
18886
+ const uncoveredFiles = changedFiles.filter((f) => !allCoveredFiles.has(f));
18887
+ if (options.json) {
18888
+ outputJson({
18889
+ command: "multi-branch",
18890
+ dryRun: true,
18891
+ owners: previews.map((p) => ({
18892
+ owner: p.owner,
18893
+ branch: p.branchName,
18894
+ message: p.commitMessage,
18895
+ files: p.files
18896
+ })),
18897
+ uncoveredFiles,
18898
+ filesWithoutOwners: options.defaultOwner ? [] : filesWithoutOwners,
18899
+ totalFiles: changedFiles.length,
18900
+ coveredFiles: allCoveredFiles.size,
18901
+ options: {
18902
+ baseBranch: options.branch,
18903
+ baseMessage: options.message,
18904
+ push: options.push || false,
18905
+ remote: options.remote || "origin",
18906
+ force: options.force || false,
18907
+ pr: options.pr || false,
18908
+ draftPr: options.draftPr || false,
18909
+ noVerify: !options.verify,
18910
+ append: options.append || false,
18911
+ exclusive: options.exclusive || false,
18912
+ coOwned: options.coOwned || false,
18913
+ pathPattern: options.pathPattern || null,
18914
+ defaultOwner: options.defaultOwner || null
18915
+ }
18916
+ });
18917
+ return;
18918
+ }
18919
+ log.header("Dry Run Preview — multi-branch");
18920
+ console.log("");
18921
+ const settingsTable = new import_cli_table33.default({
18922
+ style: { head: ["cyan"] },
18923
+ wordWrap: true
18924
+ });
18925
+ settingsTable.push({ [source_default.bold("Base branch name")]: options.branch }, { [source_default.bold("Base commit message")]: options.message }, { [source_default.bold("Total codeowners")]: `${codeowners2.length}` }, { [source_default.bold("No-verify")]: !options.verify ? "Yes" : "No" }, {
18926
+ [source_default.bold("Push")]: options.push ? `Yes → ${options.remote || "origin"}${options.force ? " (force)" : ""}` : "No"
18927
+ }, {
18928
+ [source_default.bold("Pull request")]: options.pr ? "Yes" : options.draftPr ? "Yes (draft)" : "No"
18929
+ }, { [source_default.bold("Append mode")]: options.append ? "Yes" : "No" });
18930
+ if (options.pathPattern) {
18931
+ settingsTable.push({
18932
+ [source_default.bold("Path filter")]: options.pathPattern
18933
+ });
18934
+ }
18935
+ if (options.exclusive) {
18936
+ settingsTable.push({
18937
+ [source_default.bold("Exclusive mode")]: "Yes (only files solely owned by each owner)"
18938
+ });
18939
+ }
18940
+ if (options.coOwned) {
18941
+ settingsTable.push({
18942
+ [source_default.bold("Co-owned mode")]: "Yes (only files with multiple owners)"
18943
+ });
18944
+ }
18945
+ if (options.defaultOwner) {
18946
+ settingsTable.push({
18947
+ [source_default.bold("Default owner")]: options.defaultOwner
18948
+ });
18949
+ }
18950
+ console.log(settingsTable.toString());
18951
+ console.log("");
18952
+ const summaryTable = new import_cli_table33.default({
18953
+ head: ["Owner", "Branch", "Files", "Commit Message"],
18954
+ colWidths: [22, 35, 8, 45],
18955
+ wordWrap: true,
18956
+ style: { head: ["cyan"] }
18957
+ });
18958
+ for (const p of previews) {
18959
+ summaryTable.push([
18960
+ p.owner,
18961
+ p.branchName,
18962
+ `${p.files.length}`,
18963
+ p.commitMessage
18964
+ ]);
18965
+ }
18966
+ console.log(summaryTable.toString());
18967
+ console.log(source_default.bold.cyan(`
18968
+ Files by branch:`));
18969
+ for (const p of previews) {
18970
+ if (p.files.length > 0) {
18971
+ console.log(`
18972
+ ${source_default.bold(p.branchName)} ${source_default.dim(`(${p.owner})`)} — ${p.files.length} file${p.files.length !== 1 ? "s" : ""}:`);
18973
+ p.files.forEach((file) => console.log(` ${source_default.green("+")} ${file}`));
18974
+ } else {
18975
+ console.log(`
18976
+ ${source_default.bold(p.branchName)} ${source_default.dim(`(${p.owner})`)} — ${source_default.yellow("0 files (branch will be skipped)")}`);
18977
+ }
18978
+ }
18979
+ if (uncoveredFiles.length > 0) {
18980
+ console.log(source_default.bold.yellow(`
18981
+ Uncovered staged files (${uncoveredFiles.length}) — not included in any branch:`));
18982
+ uncoveredFiles.forEach((file) => console.log(` ${source_default.yellow("!")} ${file}`));
18983
+ }
18984
+ if (filesWithoutOwners.length > 0 && !options.defaultOwner) {
18985
+ console.log(source_default.bold.yellow(`
18986
+ Files without CODEOWNERS (${filesWithoutOwners.length}):`));
18987
+ filesWithoutOwners.forEach((file) => console.log(` ${source_default.yellow("?")} ${file}`));
18988
+ console.log(source_default.dim(" Tip: Use --default-owner <owner> to assign these files"));
18989
+ }
18990
+ console.log(source_default.bold.cyan(`
18991
+ Summary:`));
18992
+ console.log(` Branches to create: ${source_default.bold(`${previews.length}`)}`);
18993
+ console.log(` Total files covered: ${source_default.bold(`${allCoveredFiles.size}`)} of ${changedFiles.length} staged`);
18994
+ if (uncoveredFiles.length > 0) {
18995
+ console.log(` Uncovered files: ${source_default.yellow(`${uncoveredFiles.length}`)}`);
18996
+ }
18997
+ console.log("");
18998
+ return;
18999
+ }
18559
19000
  const results = [];
18560
19001
  for (const owner of codeowners2) {
18561
19002
  const sanitizedOwner = owner.replace(/[^a-zA-Z0-9-_@]/g, "-").replace(/^@/, "");
@@ -18579,7 +19020,9 @@ Only staged files will be processed.`);
18579
19020
  operationState: operationState || undefined,
18580
19021
  pathPattern: options.pathPattern,
18581
19022
  exclusive: options.exclusive,
18582
- coOwned: options.coOwned
19023
+ coOwned: options.coOwned,
19024
+ json: options.json,
19025
+ prBody: options.prBody
18583
19026
  });
18584
19027
  results.push(result);
18585
19028
  }
@@ -18642,7 +19085,36 @@ Note: ${failureCount} branch(es) failed. Files were auto-restored to working dir
18642
19085
  log.info(`State preserved for reference. Run 'codeowners-git recover --id ${operationState.id}' if needed.`);
18643
19086
  }
18644
19087
  }
19088
+ if (options.json) {
19089
+ outputJson({
19090
+ command: "multi-branch",
19091
+ dryRun: false,
19092
+ success: failureCount === 0,
19093
+ totalOwners: codeowners2.length,
19094
+ successCount,
19095
+ failureCount,
19096
+ results: results.map((r) => ({
19097
+ owner: r.owner,
19098
+ branch: r.branchName,
19099
+ success: r.success,
19100
+ files: r.files,
19101
+ pushed: r.pushed,
19102
+ prUrl: r.prUrl || null,
19103
+ prNumber: r.prNumber || null,
19104
+ error: r.error || null
19105
+ }))
19106
+ });
19107
+ }
18645
19108
  } catch (err) {
19109
+ if (options.json) {
19110
+ outputJson({
19111
+ command: "multi-branch",
19112
+ dryRun: false,
19113
+ success: false,
19114
+ error: String(err)
19115
+ });
19116
+ process.exit(1);
19117
+ }
18646
19118
  log.error(`Multi-branch operation failed: ${err}`);
18647
19119
  if (operationState) {
18648
19120
  log.info(`
@@ -18650,7 +19122,7 @@ Attempting auto-recovery...`);
18650
19122
  const currentState = loadOperationState(operationState.id);
18651
19123
  if (currentState) {
18652
19124
  try {
18653
- const recovered = await performRecovery(currentState, false);
19125
+ const recovered = await performRecovery(currentState, false, { skipDirtyCheck: true });
18654
19126
  if (recovered) {
18655
19127
  log.success("Auto-recovery completed successfully");
18656
19128
  } else {
@@ -18666,11 +19138,32 @@ Manual recovery options:`);
18666
19138
  }
18667
19139
  }
18668
19140
  process.exit(1);
19141
+ } finally {
19142
+ if (tempBranch) {
19143
+ try {
19144
+ if (sourceOriginalBranch) {
19145
+ const currentBranch = await getCurrentBranch();
19146
+ if (currentBranch !== sourceOriginalBranch) {
19147
+ log.info(`Returning to original branch "${sourceOriginalBranch}"...`);
19148
+ await checkout(sourceOriginalBranch);
19149
+ }
19150
+ }
19151
+ log.info(`Cleaning up temporary branch "${tempBranch}"...`);
19152
+ await deleteBranch(tempBranch, true);
19153
+ } catch (cleanupError) {
19154
+ log.warn(`Failed to clean up temporary branch "${tempBranch}": ${cleanupError}`);
19155
+ log.info(`You can manually delete it with: git branch -D ${tempBranch}`);
19156
+ }
19157
+ }
18669
19158
  }
18670
19159
  };
18671
19160
 
18672
19161
  // src/commands/extract.ts
19162
+ var import_cli_table34 = __toESM(require_table(), 1);
18673
19163
  var extract = async (options) => {
19164
+ if (options.json) {
19165
+ setSilent(true);
19166
+ }
18674
19167
  try {
18675
19168
  if (!options.source) {
18676
19169
  log.error("Missing required option: --source");
@@ -18758,8 +19251,82 @@ Only staged files will be processed.`);
18758
19251
  }
18759
19252
  log.info(`Filtered to ${filesToExtract.length} file${filesToExtract.length !== 1 ? "s" : ""}`);
18760
19253
  }
19254
+ if (options.dryRun) {
19255
+ const excludedFiles = changedFiles.filter((f) => !filesToExtract.includes(f));
19256
+ if (options.json) {
19257
+ outputJson({
19258
+ command: "extract",
19259
+ dryRun: true,
19260
+ source: options.source,
19261
+ compareTarget: compareTarget || null,
19262
+ files: filesToExtract,
19263
+ excludedFiles,
19264
+ totalChanged: changedFiles.length,
19265
+ options: {
19266
+ include: options.include || null,
19267
+ pathPattern: options.pathPattern || null,
19268
+ exclusive: options.exclusive || false,
19269
+ coOwned: options.coOwned || false,
19270
+ compareMain: options.compareMain || false
19271
+ }
19272
+ });
19273
+ return;
19274
+ }
19275
+ log.header("Dry Run Preview — extract");
19276
+ console.log("");
19277
+ const detailsTable = new import_cli_table34.default({
19278
+ style: { head: ["cyan"] },
19279
+ wordWrap: true
19280
+ });
19281
+ detailsTable.push({ [source_default.bold("Source")]: options.source }, {
19282
+ [source_default.bold("Compare target")]: compareTarget || "auto-detected"
19283
+ }, {
19284
+ [source_default.bold("Files in source")]: `${changedFiles.length} changed file${changedFiles.length !== 1 ? "s" : ""}`
19285
+ }, {
19286
+ [source_default.bold("Files to extract")]: `${filesToExtract.length} file${filesToExtract.length !== 1 ? "s" : ""}`
19287
+ }, {
19288
+ [source_default.bold("Files excluded")]: `${excludedFiles.length} file${excludedFiles.length !== 1 ? "s" : ""} (filtered out)`
19289
+ });
19290
+ if (options.include) {
19291
+ detailsTable.push({
19292
+ [source_default.bold("Owner filter")]: `${options.include}${options.exclusive ? " (exclusive)" : ""}`
19293
+ });
19294
+ }
19295
+ if (options.pathPattern) {
19296
+ detailsTable.push({
19297
+ [source_default.bold("Path filter")]: options.pathPattern
19298
+ });
19299
+ }
19300
+ if (options.coOwned) {
19301
+ detailsTable.push({
19302
+ [source_default.bold("Co-owned mode")]: "Yes (only files with multiple owners)"
19303
+ });
19304
+ }
19305
+ console.log(detailsTable.toString());
19306
+ console.log(source_default.bold.green(`
19307
+ Files to be extracted (${filesToExtract.length}):`));
19308
+ filesToExtract.forEach((file) => console.log(` ${source_default.green("+")} ${file}`));
19309
+ if (excludedFiles.length > 0) {
19310
+ console.log(source_default.bold.dim(`
19311
+ Excluded files (${excludedFiles.length}):`));
19312
+ excludedFiles.forEach((file) => console.log(` ${source_default.dim("-")} ${source_default.dim(file)}`));
19313
+ }
19314
+ console.log("");
19315
+ return;
19316
+ }
18761
19317
  log.info("Extracting files to working directory...");
18762
19318
  await extractFilesFromRef(options.source, filesToExtract);
19319
+ if (options.json) {
19320
+ outputJson({
19321
+ command: "extract",
19322
+ dryRun: false,
19323
+ source: options.source,
19324
+ compareTarget: compareTarget || null,
19325
+ files: filesToExtract,
19326
+ totalChanged: changedFiles.length
19327
+ });
19328
+ return;
19329
+ }
18763
19330
  log.success(`
18764
19331
  ✓ Extracted ${filesToExtract.length} file${filesToExtract.length !== 1 ? "s" : ""} to working directory (unstaged)`);
18765
19332
  log.info(`
@@ -18771,13 +19338,17 @@ Next steps:`);
18771
19338
  log.info(" - Use 'cg branch' command to create a branch and commit");
18772
19339
  log.info(" - Example: cg branch -i @my-team -b my-branch -m 'Commit message' -p");
18773
19340
  } catch (err) {
19341
+ if (options.json) {
19342
+ outputJson({ command: "extract", error: String(err) });
19343
+ process.exit(1);
19344
+ }
18774
19345
  log.error(`
18775
19346
  ✗ Extraction failed: ${err}`);
18776
19347
  process.exit(1);
18777
19348
  }
18778
19349
  };
18779
19350
  // package.json
18780
- var version = "2.0.2";
19351
+ var version = "2.2.0";
18781
19352
 
18782
19353
  // src/commands/version.ts
18783
19354
  function getVersion() {
@@ -18798,15 +19369,28 @@ Force exiting...`);
18798
19369
 
18799
19370
  Received ${signal}. Gracefully shutting down...`);
18800
19371
  const incompleteOps = getIncompleteOperations();
18801
- if (incompleteOps.length > 0) {
18802
- log.warn(`Found ${incompleteOps.length} incomplete operation(s).`);
19372
+ if (incompleteOps.length === 0) {
19373
+ process.exit(130);
19374
+ return;
19375
+ }
19376
+ const mostRecent = incompleteOps[0];
19377
+ log.warn(`Found ${incompleteOps.length} incomplete operation(s).`);
19378
+ getCurrentBranch().then((currentBranch) => {
19379
+ if (currentBranch !== mostRecent.originalBranch) {
19380
+ log.info(`Returning to original branch: ${mostRecent.originalBranch}...`);
19381
+ return checkout(mostRecent.originalBranch).then(() => {
19382
+ log.success(`Returned to ${mostRecent.originalBranch}`);
19383
+ });
19384
+ }
19385
+ }).catch(() => {
19386
+ log.warn("Could not return to original branch automatically.");
19387
+ }).finally(() => {
18803
19388
  log.info(`
18804
- To recover from incomplete operations, run:`);
18805
- log.info(" codeowners-git recover --list # List all incomplete operations");
19389
+ To fully recover from incomplete operations, run:`);
18806
19390
  log.info(" codeowners-git recover --auto # Auto-recover most recent operation");
18807
- log.info(" codeowners-git recover --id <id> # Recover specific operation");
18808
- }
18809
- process.exit(130);
19391
+ log.info(" codeowners-git recover --list # List all incomplete operations");
19392
+ process.exit(130);
19393
+ });
18810
19394
  };
18811
19395
  process.on("SIGINT", () => handleShutdown("SIGINT"));
18812
19396
  process.on("SIGTERM", () => handleShutdown("SIGTERM"));
@@ -18816,7 +19400,7 @@ To recover from incomplete operations, run:`);
18816
19400
  setupSignalHandlers();
18817
19401
  var program2 = new Command;
18818
19402
  program2.name("codeowners-git (cg)").description("CLI tool for grouping and managing staged files by CODEOWNERS").version(getVersion());
18819
- program2.command("list").description("Lists all git changed files by CODEOWNER").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").option("-i, --include <patterns>", "Filter by owner patterns").option("-g, --group", "Group files by code owner").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").action((pattern, options) => {
19403
+ program2.command("list").description("Lists all git changed files by CODEOWNER").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").option("-i, --include <patterns>", "Filter by owner patterns").option("-g, --group", "Group files by code owner").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").option("--json", "Output results as JSON (suppresses all other output)").action((pattern, options) => {
18820
19404
  if (options.exclusive && options.coOwned) {
18821
19405
  console.error("Error: Cannot use both --exclusive and --co-owned options");
18822
19406
  process.exit(1);
@@ -18826,7 +19410,7 @@ program2.command("list").description("Lists all git changed files by CODEOWNER")
18826
19410
  pathPattern: pattern
18827
19411
  });
18828
19412
  });
18829
- program2.command("branch").description("Create new branch with codeowner changes").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-i, --include <patterns>", "Code owner pattern to filter files").requiredOption("-b, --branch <branch>", "Branch name").requiredOption("-m, --message <message>", "Commit message").option("-n, --no-verify", "Skip lint-staged or any other ci checks").option("-p, --push", "Push branch to remote after commit").option("-r, --remote <remote>", "Remote name to push to", "origin").option("-u, --upstream <upstream>", "Upstream branch name (defaults to local branch name)").option("-f, --force", "Force push to remote").option("-k, --keep-branch-on-failure", "Keep the created branch even if operation fails").option("--append", "Add commits to existing branch instead of creating a new one").option("--pr", "Create a pull request after pushing (requires --push)").option("--draft-pr", "Create a draft pull request after pushing (requires --push)").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").action((pattern, options) => {
19413
+ program2.command("branch").description("Create new branch with codeowner changes").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-i, --include <patterns>", "Code owner pattern to filter files").requiredOption("-b, --branch <branch>", "Branch name").requiredOption("-m, --message <message>", "Commit message").option("-n, --no-verify", "Skip lint-staged or any other ci checks").option("-p, --push", "Push branch to remote after commit").option("-r, --remote <remote>", "Remote name to push to", "origin").option("-u, --upstream <upstream>", "Upstream branch name (defaults to local branch name)").option("-f, --force", "Force push to remote").option("-k, --keep-branch-on-failure", "Keep the created branch even if operation fails").option("--append", "Add commits to existing branch instead of creating a new one").option("--pr", "Create a pull request after pushing (requires --push)").option("--draft-pr", "Create a draft pull request after pushing (requires --push)").option("--pr-body <body>", "Custom PR body text (overrides repo PR template, requires --pr or --draft-pr)").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").option("--dry-run", "Preview the operation without making any changes").option("--json", "Output results as JSON (suppresses all other output)").option("-s, --source <source>", "Source branch or commit to extract changes from (creates a temp branch from the default branch)").option("--compare-main", "Compare source against main branch instead of detecting merge-base (use with --source)").action((pattern, options) => {
18830
19414
  if (options.exclusive && options.coOwned) {
18831
19415
  console.error("Error: Cannot use both --exclusive and --co-owned options");
18832
19416
  process.exit(1);
@@ -18836,7 +19420,7 @@ program2.command("branch").description("Create new branch with codeowner changes
18836
19420
  pathPattern: pattern
18837
19421
  });
18838
19422
  });
18839
- program2.command("multi-branch").description("Create branches for all codeowners").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-b, --branch <branch>", "Base branch name (will be suffixed with codeowner name)").requiredOption("-m, --message <message>", "Base commit message (will be suffixed with codeowner name)").option("-n, --no-verify", "Skip lint-staged or any other ci checks").option("-p, --push", "Push branches to remote after commit").option("-r, --remote <remote>", "Remote name to push to", "origin").option("-u, --upstream <upstream>", "Upstream branch name pattern (defaults to local branch name)").option("-f, --force", "Force push to remote").option("-k, --keep-branch-on-failure", "Keep created branches even if operation fails").option("-d, --default-owner <defaultOwner>", "Default owner to use when no codeowners are found for changed files").option("--ignore <patterns>", "Comma-separated patterns to exclude codeowners (e.g., 'team-a,team-b')").option("--include <patterns>", "Comma-separated patterns to include codeowners (e.g., 'team-*,@org/*')").option("--append", "Add commits to existing branches instead of creating new ones").option("--pr", "Create pull requests after pushing (requires --push)").option("--draft-pr", "Create draft pull requests after pushing (requires --push)").option("-e, --exclusive", "Only include files where each owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").action((pattern, options) => {
19423
+ program2.command("multi-branch").description("Create branches for all codeowners").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-b, --branch <branch>", "Base branch name (will be suffixed with codeowner name)").requiredOption("-m, --message <message>", "Base commit message (will be suffixed with codeowner name)").option("-n, --no-verify", "Skip lint-staged or any other ci checks").option("-p, --push", "Push branches to remote after commit").option("-r, --remote <remote>", "Remote name to push to", "origin").option("-u, --upstream <upstream>", "Upstream branch name pattern (defaults to local branch name)").option("-f, --force", "Force push to remote").option("-k, --keep-branch-on-failure", "Keep created branches even if operation fails").option("-d, --default-owner <defaultOwner>", "Default owner to use when no codeowners are found for changed files").option("--ignore <patterns>", "Comma-separated patterns to exclude codeowners (e.g., 'team-a,team-b')").option("--include <patterns>", "Comma-separated patterns to include codeowners (e.g., 'team-*,@org/*')").option("--append", "Add commits to existing branches instead of creating new ones").option("--pr", "Create pull requests after pushing (requires --push)").option("--draft-pr", "Create draft pull requests after pushing (requires --push)").option("--pr-body <body>", "Custom PR body text (overrides repo PR template, requires --pr or --draft-pr)").option("-e, --exclusive", "Only include files where each owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").option("--dry-run", "Preview the operation without making any changes").option("--json", "Output results as JSON (suppresses all other output)").option("-s, --source <source>", "Source branch or commit to split (extracts changes onto a temp branch from the default branch)").option("--compare-main", "Compare source against main branch instead of detecting merge-base (use with --source)").action((pattern, options) => {
18840
19424
  if (options.exclusive && options.coOwned) {
18841
19425
  console.error("Error: Cannot use both --exclusive and --co-owned options");
18842
19426
  process.exit(1);
@@ -18846,7 +19430,7 @@ program2.command("multi-branch").description("Create branches for all codeowners
18846
19430
  pathPattern: pattern
18847
19431
  });
18848
19432
  });
18849
- program2.command("extract").description("Extract file changes from a branch or commit to working directory").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-s, --source <source>", "Source branch or commit to extract from").option("-i, --include <patterns>", "Filter extracted files by code owner pattern").option("--compare-main", "Compare source against main branch instead of detecting merge-base").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").action((pattern, options) => {
19433
+ program2.command("extract").description("Extract file changes from a branch or commit to working directory").argument("[pattern]", "Path pattern to filter files (micromatch syntax, comma-separated)").requiredOption("-s, --source <source>", "Source branch or commit to extract from").option("-i, --include <patterns>", "Filter extracted files by code owner pattern").option("--compare-main", "Compare source against main branch instead of detecting merge-base").option("-e, --exclusive", "Only include files where the owner is the sole owner (no co-owners)").option("-c, --co-owned", "Only include files with multiple owners (co-owned files)").option("--dry-run", "Preview the operation without making any changes").option("--json", "Output results as JSON (suppresses all other output)").action((pattern, options) => {
18850
19434
  if (options.exclusive && options.coOwned) {
18851
19435
  console.error("Error: Cannot use both --exclusive and --co-owned options");
18852
19436
  process.exit(1);