codeowners-git 1.1.0 → 1.3.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 +61 -3
  2. package/dist/cli.js +275 -64
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,9 +12,9 @@ Managing large-scale migrations in big monorepos with multiple codeowners can be
12
12
  - Creating compact, team-specific branches with only their affected files.
13
13
  - Streamlining the review process with smaller, targeted PRs.
14
14
 
15
- https://github.com/user-attachments/assets/7cc0a924-f03e-47f3-baad-63eca9e8e4a8
16
-
15
+ > **Note:** This tool works with **unstaged files**. Make sure to check if your files are unstaged before proceeding.
17
16
 
17
+ https://github.com/user-attachments/assets/7cc0a924-f03e-47f3-baad-63eca9e8e4a8
18
18
 
19
19
  ## Installation
20
20
 
@@ -85,13 +85,70 @@ Options:
85
85
  - `--branch, -b` Specify branch pattern
86
86
  - `--message, -m` Commit message for changes
87
87
  - `--no-verify, -n` Skips lint-staged and other checks before committing
88
+ - `--push, -p` Push branch to remote after commit
89
+ - `--remote, -r` Remote name to push to (default: "origin")
90
+ - `--upstream, -u` Upstream branch name (defaults to local branch name)
91
+ - `--force, -f` Force push to remote
92
+ - `--keep-branch-on-failure, -k` Keep the created branch even if operation fails
93
+
94
+ Example:
95
+
96
+ ```bash
97
+ codeowners-git branch -o @myteam -b "feature/new-feature" -m "Add new feature" -p
98
+ ```
99
+
100
+ ### `multi-branch`
101
+
102
+ Create branches for all codeowners with changes.
103
+
104
+ Usage:
105
+
106
+ ```bash
107
+ codeowners-git multi-branch [options]
108
+ ```
109
+
110
+ Options:
111
+
112
+ - `--branch, -b` Base branch name (will be suffixed with codeowner name)
113
+ - `--message, -m` Base commit message (will be suffixed with codeowner name)
114
+ - `--no-verify, -n` Skips lint-staged and other checks before committing
115
+ - `--push, -p` Push branches to remote after commit
116
+ - `--remote, -r` Remote name to push to (default: "origin")
117
+ - `--upstream, -u` Upstream branch name pattern (defaults to local branch name)
118
+ - `--force, -f` Force push to remote
119
+ - `--keep-branch-on-failure, -k` Keep created branches even if operation fails
120
+ - `--default-owner, -d` Default owner to use when no codeowners are found for changed files
121
+ - `--ignore` Comma-separated patterns to exclude codeowners (e.g., 'team-a,team-b')
122
+ - `--include` Comma-separated patterns to include codeowners (e.g., 'team-_,@org/_')
123
+
124
+ > **Note:** You cannot use both `--ignore` and `--include` options at the same time.
88
125
 
89
126
  Example:
90
127
 
91
128
  ```bash
92
- codeowners-git branch -o @myteam -b "feature/*" -m "Add feature branch ownership"
129
+ # Create branches for all codeowners
130
+ codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" -p
131
+
132
+ # Exclude specific teams
133
+ codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" --ignore "@ce-orca,@ce-ece"
134
+
135
+ # Include only specific patterns
136
+ codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" --include "@team-*"
137
+
138
+ # Use default owner when no codeowners found
139
+ codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" -d "@default-team"
93
140
  ```
94
141
 
142
+ This will:
143
+
144
+ 1. Find all codeowners for the staged files in your repository
145
+ 2. Apply any ignore/include filters if specified
146
+ 3. For each codeowner (e.g., @team-a, @team-b):
147
+ - Create a branch like `feature/new-feature/team-a`
148
+ - Commit only the files owned by that team
149
+ - Add a commit message like "Add new feature - @team-a"
150
+ - Push each branch to the remote if the `-p` flag is provided
151
+
95
152
  ## Contributing
96
153
 
97
154
  1. Clone the repository
@@ -111,6 +168,7 @@ bun test
111
168
  5. Submit a pull request
112
169
 
113
170
  ## Alternatives
171
+
114
172
  [@snyk/github-codeowners](https://github.com/snyk/github-codeowners)
115
173
 
116
174
  [codeowners](https://github.com/beaugunderson/codeowners)
package/dist/cli.js CHANGED
@@ -965,8 +965,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
965
965
  this._exitCallback = (err) => {
966
966
  if (err.code !== "commander.executeSubCommandAsync") {
967
967
  throw err;
968
- } else {
969
- }
968
+ } else {}
970
969
  };
971
970
  }
972
971
  return this;
@@ -2133,8 +2132,7 @@ var require_p_locate = __commonJS((exports, module) => {
2133
2132
  const limit = pLimit(opts.concurrency);
2134
2133
  const items = Array.from(iterable).map((el) => [el, limit(() => Promise.resolve(el).then(tester))]);
2135
2134
  const checkLimit = pLimit(opts.preserveOrder ? 1 : Infinity);
2136
- return Promise.all(items.map((el) => checkLimit(() => finder(el)))).then(() => {
2137
- }).catch((err) => err instanceof EndError ? err.value : Promise.reject(err));
2135
+ return Promise.all(items.map((el) => checkLimit(() => finder(el)))).then(() => {}).catch((err) => err instanceof EndError ? err.value : Promise.reject(err));
2138
2136
  };
2139
2137
  });
2140
2138
 
@@ -2992,8 +2990,7 @@ var require_minimatch = __commonJS((exports, module) => {
2992
2990
  var path = function() {
2993
2991
  try {
2994
2992
  return __require("path");
2995
- } catch (e) {
2996
- }
2993
+ } catch (e) {}
2997
2994
  }() || {
2998
2995
  sep: "/"
2999
2996
  };
@@ -3101,8 +3098,7 @@ var require_minimatch = __commonJS((exports, module) => {
3101
3098
  this.partial = !!options.partial;
3102
3099
  this.make();
3103
3100
  }
3104
- Minimatch.prototype.debug = function() {
3105
- };
3101
+ Minimatch.prototype.debug = function() {};
3106
3102
  Minimatch.prototype.make = make;
3107
3103
  function make() {
3108
3104
  var pattern = this.pattern;
@@ -3224,7 +3220,7 @@ var require_minimatch = __commonJS((exports, module) => {
3224
3220
  }
3225
3221
  }
3226
3222
  for (var i = 0, len = pattern.length, c;i < len && (c = pattern.charAt(i)); i++) {
3227
- this.debug("%s\t%s %s %j", pattern, i, re, c);
3223
+ this.debug("%s %s %s %j", pattern, i, re, c);
3228
3224
  if (escaping && reSpecials[c]) {
3229
3225
  re += "\\" + c;
3230
3226
  escaping = false;
@@ -3243,7 +3239,7 @@ var require_minimatch = __commonJS((exports, module) => {
3243
3239
  case "+":
3244
3240
  case "@":
3245
3241
  case "!":
3246
- this.debug("%s\t%s %s %j <-- stateChar", pattern, i, re, c);
3242
+ this.debug("%s %s %s %j <-- stateChar", pattern, i, re, c);
3247
3243
  if (inClass) {
3248
3244
  this.debug(" in class");
3249
3245
  if (c === "!" && i === classStart + 1)
@@ -3593,8 +3589,7 @@ var require_inherits_browser = __commonJS((exports, module) => {
3593
3589
  module.exports = function inherits(ctor, superCtor) {
3594
3590
  if (superCtor) {
3595
3591
  ctor.super_ = superCtor;
3596
- var TempCtor = function() {
3597
- };
3592
+ var TempCtor = function() {};
3598
3593
  TempCtor.prototype = superCtor.prototype;
3599
3594
  ctor.prototype = new TempCtor;
3600
3595
  ctor.prototype.constructor = ctor;
@@ -5331,8 +5326,7 @@ var require_browser = __commonJS((exports, module) => {
5331
5326
  });
5332
5327
  args.splice(lastC, 0, c);
5333
5328
  }
5334
- exports.log = console.debug || console.log || (() => {
5335
- });
5329
+ exports.log = console.debug || console.log || (() => {});
5336
5330
  function save(namespaces) {
5337
5331
  try {
5338
5332
  if (namespaces) {
@@ -5340,15 +5334,13 @@ var require_browser = __commonJS((exports, module) => {
5340
5334
  } else {
5341
5335
  exports.storage.removeItem("debug");
5342
5336
  }
5343
- } catch (error) {
5344
- }
5337
+ } catch (error) {}
5345
5338
  }
5346
5339
  function load() {
5347
5340
  let r;
5348
5341
  try {
5349
5342
  r = exports.storage.getItem("debug");
5350
- } catch (error) {
5351
- }
5343
+ } catch (error) {}
5352
5344
  if (!r && typeof process !== "undefined" && "env" in process) {
5353
5345
  r = process.env.DEBUG;
5354
5346
  }
@@ -5357,8 +5349,7 @@ var require_browser = __commonJS((exports, module) => {
5357
5349
  function localstorage() {
5358
5350
  try {
5359
5351
  return localStorage;
5360
- } catch (error) {
5361
- }
5352
+ } catch (error) {}
5362
5353
  }
5363
5354
  module.exports = require_common2()(exports);
5364
5355
  var { formatters } = module.exports;
@@ -5419,8 +5410,7 @@ var require_node = __commonJS((exports, module) => {
5419
5410
  exports.save = save;
5420
5411
  exports.load = load;
5421
5412
  exports.useColors = useColors;
5422
- exports.destroy = util.deprecate(() => {
5423
- }, "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
5413
+ exports.destroy = util.deprecate(() => {}, "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
5424
5414
  exports.colors = [6, 2, 3, 4, 5, 1];
5425
5415
  try {
5426
5416
  const supportsColor = require_supports_color();
@@ -5504,8 +5494,7 @@ var require_node = __commonJS((exports, module) => {
5504
5494
  221
5505
5495
  ];
5506
5496
  }
5507
- } catch (error) {
5508
- }
5497
+ } catch (error) {}
5509
5498
  exports.inspectOpts = Object.keys(process.env).filter((key) => {
5510
5499
  return /^debug_/i.test(key);
5511
5500
  }).reduce((obj, key) => {
@@ -6633,8 +6622,7 @@ var require_colors = __commonJS((exports, module) => {
6633
6622
  });
6634
6623
  return ret;
6635
6624
  }();
6636
- var proto2 = defineProps(function colors() {
6637
- }, styles3);
6625
+ var proto2 = defineProps(function colors() {}, styles3);
6638
6626
  function applyStyle2() {
6639
6627
  var args = Array.prototype.slice.call(arguments);
6640
6628
  var str = args.map(function(arg) {
@@ -6822,8 +6810,7 @@ var require_cell = __commonJS((exports, module) => {
6822
6810
  let content = utils.truncate(this.content, 10, this.truncate);
6823
6811
  if (!lineNum) {
6824
6812
  info(`${this.y}-${this.x}: ${this.rowSpan - lineNum}x${this.colSpan} Cell ${content}`);
6825
- } else {
6826
- }
6813
+ } else {}
6827
6814
  let padLen = Math.max(this.height - this.lines.length, 0);
6828
6815
  let padTop;
6829
6816
  switch (this.vAlign) {
@@ -6957,18 +6944,15 @@ var require_cell = __commonJS((exports, module) => {
6957
6944
  }
6958
6945
 
6959
6946
  class ColSpanCell {
6960
- constructor() {
6961
- }
6947
+ constructor() {}
6962
6948
  draw(lineNum) {
6963
6949
  if (typeof lineNum === "number") {
6964
6950
  debug2(`${this.y}-${this.x}: 1x1 ColSpanCell`);
6965
6951
  }
6966
6952
  return "";
6967
6953
  }
6968
- init() {
6969
- }
6970
- mergeTableOptions() {
6971
- }
6954
+ init() {}
6955
+ mergeTableOptions() {}
6972
6956
  }
6973
6957
 
6974
6958
  class RowSpanCell {
@@ -6991,8 +6975,7 @@ var require_cell = __commonJS((exports, module) => {
6991
6975
  debug2(`${this.y}-${this.x}: 1x${this.colSpan} RowSpanCell for ${this.originalCell.content}`);
6992
6976
  return this.originalCell.draw(this.offset + 1 + lineNum);
6993
6977
  }
6994
- mergeTableOptions() {
6995
- }
6978
+ mergeTableOptions() {}
6996
6979
  }
6997
6980
  function firstDefined(...args) {
6998
6981
  return args.filter((v) => v !== undefined && v !== null).shift();
@@ -10260,8 +10243,7 @@ var objectToString;
10260
10243
  var init_util = __esm({
10261
10244
  "src/lib/utils/util.ts"() {
10262
10245
  NULL = "\x00";
10263
- NOOP = () => {
10264
- };
10246
+ NOOP = () => {};
10265
10247
  objectToString = Object.prototype.toString.call.bind(Object.prototype.toString);
10266
10248
  }
10267
10249
  });
@@ -14458,8 +14440,7 @@ for (const model of usedModels) {
14458
14440
  }
14459
14441
  };
14460
14442
  }
14461
- var proto = Object.defineProperties(() => {
14462
- }, {
14443
+ var proto = Object.defineProperties(() => {}, {
14463
14444
  ...styles2,
14464
14445
  level: {
14465
14446
  enumerable: true,
@@ -14547,6 +14528,7 @@ var log = {
14547
14528
  success: (message) => console.log(source_default.green(`✓ ${message}`)),
14548
14529
  error: (message) => console.error(source_default.red(`✗ ${message}`)),
14549
14530
  info: (message) => console.log(source_default.bold(`ℹ ${message}`)),
14531
+ warn: (message) => console.warn(source_default.yellow(`⚠ ${message}`)),
14550
14532
  header: (message) => console.log(source_default.bold.cyan(`
14551
14533
  ${message}`)),
14552
14534
  file: (path) => console.log(`- ${source_default.dim(path)}`),
@@ -14600,38 +14582,128 @@ ${message}`)),
14600
14582
  };
14601
14583
 
14602
14584
  // src/utils/git.ts
14585
+ import { spawn as spawn2 } from "child_process";
14603
14586
  var git = esm_default();
14604
14587
  var getChangedFiles = async () => {
14605
14588
  const status = await git.status();
14606
14589
  return status.files.map((file) => file.path);
14607
14590
  };
14591
+ var branchExists = async (branchName) => {
14592
+ try {
14593
+ const branches = await git.branch();
14594
+ return branches.all.includes(branchName);
14595
+ } catch (error) {
14596
+ log.error(`Failed to check if branch exists: ${error}`);
14597
+ return false;
14598
+ }
14599
+ };
14608
14600
  var createBranch = async (branchName) => {
14609
14601
  log.info(`Switching to a new local branch: "${branchName}"`);
14610
- await git.checkoutLocalBranch(branchName);
14611
- log.info(`Now on branch: "${branchName}"`);
14602
+ try {
14603
+ await git.checkoutLocalBranch(branchName);
14604
+ log.info(`Now on branch: "${branchName}"`);
14605
+ } catch (error) {
14606
+ throw new Error(`Failed to create branch "${branchName}": ${error}`);
14607
+ }
14608
+ };
14609
+ var deleteBranch = async (branchName, force = false) => {
14610
+ try {
14611
+ if (await branchExists(branchName)) {
14612
+ log.info(`Deleting branch: "${branchName}"${force ? " (forced)" : ""}`);
14613
+ await git.branch([force ? "-D" : "-d", branchName]);
14614
+ log.info(`Branch "${branchName}" deleted successfully`);
14615
+ return true;
14616
+ }
14617
+ return false;
14618
+ } catch (error) {
14619
+ log.error(`Failed to delete branch "${branchName}": ${error}`);
14620
+ return false;
14621
+ }
14612
14622
  };
14613
14623
  var checkout = async (name) => {
14614
14624
  log.info(`Switching to branch: "${name}"`);
14615
- await git.checkout(name);
14625
+ try {
14626
+ await git.checkout(name);
14627
+ } catch (error) {
14628
+ throw new Error(`Failed to checkout branch "${name}": ${error}`);
14629
+ }
14616
14630
  };
14617
14631
  var commitChanges = async (files, { message, noVerify = false }) => {
14618
- log.info("Adding files to commit...");
14619
- await git.add(files);
14620
- const commitOptions = noVerify ? ["--no-verify"] : [];
14621
- log.info(`Running commit with message: "${message}"`);
14622
- await git.commit(message, [], {
14623
- ...noVerify ? { "--no-verify": null } : {}
14624
- });
14625
- log.info("Commit finished successfully.");
14632
+ try {
14633
+ log.info("Adding files to commit...");
14634
+ await git.add(files);
14635
+ log.info(`Running commit with message: "${message}"`);
14636
+ await git.commit(message, [], {
14637
+ ...noVerify ? { "--no-verify": null } : {}
14638
+ });
14639
+ log.info("Commit finished successfully.");
14640
+ } catch (error) {
14641
+ log.error(`Failed to commit changes: ${error}`);
14642
+ throw new Error(`Commit failed: ${error}`);
14643
+ }
14626
14644
  };
14627
14645
  var getCurrentBranch = async () => {
14628
- return await git.revparse(["--abbrev-ref", "HEAD"]);
14646
+ try {
14647
+ return await git.revparse(["--abbrev-ref", "HEAD"]);
14648
+ } catch (error) {
14649
+ throw new Error(`Failed to get current branch: ${error}`);
14650
+ }
14651
+ };
14652
+ var pushBranch = async (branchName, {
14653
+ remote = "origin",
14654
+ upstream,
14655
+ force = false,
14656
+ noVerify = false
14657
+ } = {}) => {
14658
+ const targetUpstream = upstream || branchName;
14659
+ log.info(`Pushing branch "${branchName}" to ${remote}/${targetUpstream}...`);
14660
+ try {
14661
+ const pushArgs = [];
14662
+ pushArgs.push(remote, `${branchName}:${targetUpstream}`);
14663
+ if (force) {
14664
+ pushArgs.push("--force");
14665
+ }
14666
+ if (noVerify) {
14667
+ pushArgs.push("--no-verify");
14668
+ }
14669
+ const gitProcess = spawn2("git", ["push", ...pushArgs], {
14670
+ stdio: ["inherit", "inherit", "inherit"]
14671
+ });
14672
+ return new Promise((resolve, reject) => {
14673
+ gitProcess.on("close", (code) => {
14674
+ if (code === 0) {
14675
+ log.success(`Successfully pushed to ${remote}/${targetUpstream}`);
14676
+ resolve();
14677
+ } else {
14678
+ const error = new Error(`Push failed with exit code ${code}`);
14679
+ log.error(`Failed to push to remote: ${error}`);
14680
+ reject(error);
14681
+ }
14682
+ });
14683
+ });
14684
+ } catch (error) {
14685
+ log.error(`Failed to push to remote: ${error}`);
14686
+ throw new Error(`Push failed: ${error}`);
14687
+ }
14629
14688
  };
14630
14689
 
14631
14690
  // src/utils/codeowners.ts
14632
- var codeowners = new import_codeowners.default;
14691
+ var codeowners;
14692
+ var getCodeownersInstance = () => {
14693
+ if (!codeowners) {
14694
+ try {
14695
+ codeowners = new import_codeowners.default;
14696
+ } catch (error) {
14697
+ return {
14698
+ getOwner: () => []
14699
+ };
14700
+ }
14701
+ }
14702
+ return codeowners;
14703
+ };
14633
14704
  var getOwner = (filePath) => {
14634
- const owner = codeowners.getOwner(filePath);
14705
+ const instance = getCodeownersInstance();
14706
+ const owner = instance.getOwner(filePath);
14635
14707
  return owner;
14636
14708
  };
14637
14709
  var getOwnerFiles = async (owner) => {
@@ -14703,35 +14775,173 @@ var listCodeowners = async (options) => {
14703
14775
 
14704
14776
  // src/commands/branch.ts
14705
14777
  var branch = async (options) => {
14778
+ let originalBranch = "";
14779
+ let stashId = null;
14780
+ let newBranchCreated = false;
14781
+ let filesToCommit = [];
14706
14782
  try {
14707
14783
  if (!options.branch || !options.message || !options.owner) {
14708
14784
  throw new Error("Missing required options for branch creation");
14709
14785
  }
14710
- const filesToCommit = await getOwnerFiles(options.owner);
14711
- if (filesToCommit.length <= 0) {
14712
- throw new Error(`No files found for ${options.owner}`);
14713
- }
14714
14786
  log.info("Starting branch creation process...");
14715
- const originalBranch = await getCurrentBranch();
14787
+ originalBranch = await getCurrentBranch();
14716
14788
  log.info(`Currently on branch: ${originalBranch}`);
14789
+ filesToCommit = await getOwnerFiles(options.owner);
14790
+ if (filesToCommit.length <= 0) {
14791
+ log.warn(`No files found for ${options.owner}. Skipping branch creation.`);
14792
+ return;
14793
+ }
14717
14794
  log.file(`Files to be committed:
14718
14795
  ${filesToCommit.join(`
14719
14796
  `)}`);
14797
+ if (await branchExists(options.branch)) {
14798
+ throw new Error(`Branch "${options.branch}" already exists. Use a different name or delete the existing branch first.`);
14799
+ }
14720
14800
  try {
14721
14801
  log.info(`Creating new branch "${options.branch}"...`);
14722
14802
  await createBranch(options.branch);
14723
- log.info(`Committing changes with message: "${options.message}" ${!options.verify}...`);
14803
+ newBranchCreated = true;
14804
+ log.info(`Committing changes with message: "${options.message}" ${!options.verify ? "(no-verify)" : ""}...`);
14724
14805
  await commitChanges(filesToCommit, {
14725
14806
  message: options.message ?? "",
14726
14807
  noVerify: !options.verify
14727
14808
  });
14728
- } finally {
14809
+ if (options.push) {
14810
+ await pushBranch(options.branch, {
14811
+ remote: options.remote,
14812
+ upstream: options.upstream,
14813
+ force: options.force,
14814
+ noVerify: !options.verify
14815
+ });
14816
+ }
14729
14817
  log.info(`Checking out original branch "${originalBranch}"...`);
14730
14818
  await checkout(originalBranch);
14731
- log.success(`Branch "${options.branch}" created and changes committed.`);
14819
+ log.success(options.push ? `Branch "${options.branch}" created, changes committed, and pushed to remote.` : `Branch "${options.branch}" created and changes committed.`);
14820
+ } catch (operationError) {
14821
+ log.error(`Operation failed: ${operationError}`);
14822
+ if (newBranchCreated) {
14823
+ try {
14824
+ log.info(`Returning to original branch "${originalBranch}"...`);
14825
+ await checkout(originalBranch);
14826
+ if (!options.keepBranchOnFailure) {
14827
+ log.info(`Cleaning up: Deleting branch "${options.branch}"...`);
14828
+ await deleteBranch(options.branch, true);
14829
+ } else {
14830
+ log.info(`Branch "${options.branch}" was kept despite the failure.`);
14831
+ }
14832
+ } catch (cleanupError) {
14833
+ log.error(`Error during cleanup: ${cleanupError}`);
14834
+ }
14835
+ }
14836
+ throw operationError;
14732
14837
  }
14733
14838
  } catch (err) {
14734
- log.error(err);
14839
+ log.error(`Branch operation failed: ${err}`);
14840
+ process.exit(1);
14841
+ } finally {
14842
+ try {
14843
+ if (originalBranch) {
14844
+ const currentBranch = await getCurrentBranch();
14845
+ if (currentBranch !== originalBranch) {
14846
+ await checkout(originalBranch);
14847
+ }
14848
+ }
14849
+ } catch (finalError) {
14850
+ log.error(`Error during final cleanup: ${finalError}`);
14851
+ log.info("Some manual cleanup may be required.");
14852
+ }
14853
+ }
14854
+ };
14855
+
14856
+ // src/commands/multi-branch.ts
14857
+ var import_micromatch2 = __toESM(require_micromatch(), 1);
14858
+ var multiBranch = async (options) => {
14859
+ try {
14860
+ if (!options.branch || !options.message) {
14861
+ throw new Error("Missing required options for multi-branch creation");
14862
+ }
14863
+ if (options.ignore && options.include) {
14864
+ throw new Error("Cannot use both --ignore and --include options at the same time");
14865
+ }
14866
+ log.info("Starting multi-branch creation process...");
14867
+ const changedFiles = await getChangedFiles();
14868
+ if (changedFiles.length === 0) {
14869
+ throw new Error("No changed files found in the repository");
14870
+ }
14871
+ const ownerSet = new Set;
14872
+ for (const file of changedFiles) {
14873
+ const owners = getOwner(file);
14874
+ for (const owner of owners) {
14875
+ ownerSet.add(owner);
14876
+ }
14877
+ }
14878
+ let codeowners2 = Array.from(ownerSet);
14879
+ if (codeowners2.length === 0) {
14880
+ log.warn("No codeowners found for the changed files");
14881
+ if (options.defaultOwner) {
14882
+ log.info(`Using default owner: ${options.defaultOwner}`);
14883
+ codeowners2.push(options.defaultOwner);
14884
+ } else {
14885
+ log.info("Continuing without creating any branches (use --default-owner to specify a fallback)");
14886
+ return;
14887
+ }
14888
+ } else {
14889
+ log.info(`Found ${codeowners2.length} codeowners: ${codeowners2.join(", ")}`);
14890
+ }
14891
+ if (options.ignore || options.include) {
14892
+ const originalCount = codeowners2.length;
14893
+ if (options.ignore) {
14894
+ const ignorePatterns = options.ignore.split(",").map((p) => p.trim());
14895
+ codeowners2 = codeowners2.filter((owner) => !import_micromatch2.default.isMatch(owner, ignorePatterns));
14896
+ log.info(`Filtered out ${originalCount - codeowners2.length} codeowners using ignore patterns: ${ignorePatterns.join(", ")}`);
14897
+ } else if (options.include) {
14898
+ const includePatterns = options.include.split(",").map((p) => p.trim());
14899
+ codeowners2 = codeowners2.filter((owner) => import_micromatch2.default.isMatch(owner, includePatterns));
14900
+ log.info(`Filtered to ${codeowners2.length} codeowners using include patterns: ${includePatterns.join(", ")}`);
14901
+ }
14902
+ if (codeowners2.length === 0) {
14903
+ log.warn("No codeowners left after filtering");
14904
+ return;
14905
+ }
14906
+ log.info(`Processing ${codeowners2.length} codeowners after filtering: ${codeowners2.join(", ")}`);
14907
+ }
14908
+ const results = {
14909
+ success: [],
14910
+ failure: []
14911
+ };
14912
+ for (const owner of codeowners2) {
14913
+ try {
14914
+ const sanitizedOwner = owner.replace(/[^a-zA-Z0-9-_@]/g, "-").replace(/^@/, "");
14915
+ const branchName = `${options.branch}/${sanitizedOwner}`;
14916
+ const commitMessage = `${options.message} - ${owner}`;
14917
+ log.info(`Creating branch for ${owner}...`);
14918
+ await branch({
14919
+ owner,
14920
+ branch: branchName,
14921
+ message: commitMessage,
14922
+ verify: options.verify,
14923
+ push: options.push,
14924
+ remote: options.remote,
14925
+ upstream: options.upstream,
14926
+ force: options.force,
14927
+ keepBranchOnFailure: options.keepBranchOnFailure
14928
+ });
14929
+ results.success.push(owner);
14930
+ } catch (error) {
14931
+ log.error(`Failed to create branch for ${owner}: ${error}`);
14932
+ results.failure.push(owner);
14933
+ }
14934
+ }
14935
+ log.header("Multi-branch creation summary");
14936
+ log.info(`Successfully created branches for ${results.success.length} of ${codeowners2.length} codeowners`);
14937
+ if (results.success.length) {
14938
+ log.success(`Successful: ${results.success.join(", ")}`);
14939
+ }
14940
+ if (results.failure.length) {
14941
+ log.error(`Failed: ${results.failure.join(", ")}`);
14942
+ }
14943
+ } catch (err) {
14944
+ log.error(`Multi-branch operation failed: ${err}`);
14735
14945
  process.exit(1);
14736
14946
  }
14737
14947
  };
@@ -14740,5 +14950,6 @@ var branch = async (options) => {
14740
14950
  var program2 = new Command;
14741
14951
  program2.name("codeowners").description("CLI tool for grouping and managing staged files by CODEOWNERS");
14742
14952
  program2.command("list").description("Lists all git changed files by CODEOWNER").option("-o, --owner <owner>", "Filter by specific code owner").option("-i, --include <patterns>", "Filter by owner patterns").action(listCodeowners);
14743
- program2.command("branch").description("Create new branch with codeowner changes").requiredOption("-o, --owner <owner>", "Code owner name").requiredOption("-b, --branch <branch>", "Branch name").requiredOption("-m, --message <message>", "Commit message").option("-n, --no-verify", "Skip lint-staged or any other ci checks").action(branch);
14953
+ program2.command("branch").description("Create new branch with codeowner changes").requiredOption("-o, --owner <owner>", "Code owner name").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").action(branch);
14954
+ program2.command("multi-branch").description("Create branches for all codeowners").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/*')").action(multiBranch);
14744
14955
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeowners-git",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "module": "src/cli.ts",
5
5
  "type": "module",
6
6
  "private": false,