codeowners-git 1.5.0 → 1.7.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 +63 -6
  2. package/dist/cli.js +164 -6
  3. package/package.json +10 -4
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Managing large-scale migrations in big monorepos with multiple codeowners can be overwhelming. Massive PRs touching thousands of files make it hard for teams to review changes efficiently.
8
8
 
9
- `codeowners-git` solves this by:
9
+ `codeowners-git` (or `cg` for short) solves this by:
10
10
 
11
11
  - Identifying files owned by specific teams using the CODEOWNERS file.
12
12
  - Creating compact, team-specific branches with only their affected files.
@@ -24,6 +24,8 @@ Run commands directly without installation:
24
24
 
25
25
  ```bash
26
26
  npx codeowners-git <command>
27
+ # or use the short alias
28
+ npx cg <command>
27
29
  ```
28
30
 
29
31
  ### Install globally via npm
@@ -36,6 +38,8 @@ Then run commands directly:
36
38
 
37
39
  ```bash
38
40
  codeowners-git <command>
41
+ # or use the short alias
42
+ cg <command>
39
43
  ```
40
44
 
41
45
  ## Configuration
@@ -46,6 +50,29 @@ The tool automatically detects CODEOWNERS files in:
46
50
  2. `docs/CODEOWNERS`
47
51
  3. `CODEOWNERS` (root directory)
48
52
 
53
+ ### Pull Request Features
54
+
55
+ The `--pr` and `--draft-pr` options require the [GitHub CLI (`gh`)](https://cli.github.com/) to be installed and authenticated:
56
+
57
+ ```bash
58
+ # Install GitHub CLI (macOS)
59
+ brew install gh
60
+
61
+ # Install GitHub CLI (Windows)
62
+ winget install --id GitHub.cli
63
+
64
+ # Install GitHub CLI (Linux)
65
+ sudo apt install gh
66
+
67
+ # Authenticate with GitHub
68
+ gh auth login
69
+ ```
70
+
71
+ The tool will automatically:
72
+ - Use PR templates if they exist in your repository (`.github/pull_request_template.md`, etc.)
73
+ - Set the PR title to your commit message
74
+ - Create PRs against the repository's default branch
75
+
49
76
  ## Commands
50
77
 
51
78
  ### `--version`
@@ -58,6 +85,8 @@ Usage:
58
85
  codeowners-git --version
59
86
  # or
60
87
  codeowners-git -V
88
+ # or using the short alias
89
+ cg --version
61
90
  ```
62
91
 
63
92
  ### `list`
@@ -68,6 +97,8 @@ Usage:
68
97
 
69
98
  ```bash
70
99
  codeowners-git list [options]
100
+ # or
101
+ cg list [options]
71
102
  ```
72
103
 
73
104
  Options:
@@ -79,6 +110,8 @@ Example:
79
110
 
80
111
  ```bash
81
112
  codeowners-git list -o @myteam
113
+ # or
114
+ cg list -o @myteam
82
115
  ```
83
116
 
84
117
  ### `branch`
@@ -89,6 +122,8 @@ Usage:
89
122
 
90
123
  ```bash
91
124
  codeowners-git branch [options]
125
+ # or
126
+ cg branch [options]
92
127
  ```
93
128
 
94
129
  Options:
@@ -103,15 +138,25 @@ Options:
103
138
  - `--force, -f` Force push to remote
104
139
  - `--keep-branch-on-failure, -k` Keep the created branch even if operation fails
105
140
  - `--append` Add commits to existing branch instead of creating a new one
141
+ - `--pr` Create a pull request after pushing (requires `--push` and GitHub CLI)
142
+ - `--draft-pr` Create a draft pull request after pushing (requires `--push` and GitHub CLI)
106
143
 
107
144
  Example:
108
145
 
109
146
  ```bash
110
147
  # Create a new branch
111
148
  codeowners-git branch -o @myteam -b "feature/new-feature" -m "Add new feature" -p
149
+ # or
150
+ cg branch -o @myteam -b "feature/new-feature" -m "Add new feature" -p
151
+
152
+ # Create a branch and automatically create a pull request
153
+ cg branch -o @myteam -b "feature/new-feature" -m "Add new feature" -p --pr
154
+
155
+ # Create a branch and automatically create a draft pull request
156
+ cg branch -o @myteam -b "feature/new-feature" -m "Add new feature" -p --draft-pr
112
157
 
113
158
  # Add more commits to the same branch later
114
- codeowners-git branch -o @myteam -b "feature/new-feature" -m "Add more changes" --append -p
159
+ cg branch -o @myteam -b "feature/new-feature" -m "Add more changes" --append -p
115
160
  ```
116
161
 
117
162
  ### `multi-branch`
@@ -122,6 +167,8 @@ Usage:
122
167
 
123
168
  ```bash
124
169
  codeowners-git multi-branch [options]
170
+ # or
171
+ cg multi-branch [options]
125
172
  ```
126
173
 
127
174
  Options:
@@ -138,6 +185,8 @@ Options:
138
185
  - `--ignore` Comma-separated patterns to exclude codeowners (e.g., 'team-a,team-b')
139
186
  - `--include` Comma-separated patterns to include codeowners (e.g., 'team-_,@org/_')
140
187
  - `--append` Add commits to existing branches instead of creating new ones
188
+ - `--pr` Create pull requests after pushing (requires `--push` and GitHub CLI)
189
+ - `--draft-pr` Create draft pull requests after pushing (requires `--push` and GitHub CLI)
141
190
 
142
191
  > **Note:** You cannot use both `--ignore` and `--include` options at the same time.
143
192
 
@@ -146,18 +195,26 @@ Example:
146
195
  ```bash
147
196
  # Create branches for all codeowners
148
197
  codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" -p
198
+ # or
199
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" -p
200
+
201
+ # Create branches and automatically create pull requests for each
202
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" -p --pr
203
+
204
+ # Create branches and automatically create draft pull requests for each
205
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" -p --draft-pr
149
206
 
150
207
  # Exclude specific teams
151
- codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" --ignore "@ce-orca,@ce-ece"
208
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" --ignore "@ce-orca,@ce-ece"
152
209
 
153
210
  # Include only specific patterns
154
- codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" --include "@team-*"
211
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" --include "@team-*"
155
212
 
156
213
  # Use default owner when no codeowners found
157
- codeowners-git multi-branch -b "feature/new-feature" -m "Add new feature" -d "@default-team"
214
+ cg multi-branch -b "feature/new-feature" -m "Add new feature" -d "@default-team"
158
215
 
159
216
  # Add more commits to existing branches
160
- codeowners-git multi-branch -b "feature/new-feature" -m "Add more changes" --append -p
217
+ cg multi-branch -b "feature/new-feature" -m "Add more changes" --append -p
161
218
  ```
162
219
 
163
220
  This will:
package/dist/cli.js CHANGED
@@ -14686,6 +14686,24 @@ var pushBranch = async (branchName, {
14686
14686
  throw new Error(`Push failed: ${error}`);
14687
14687
  }
14688
14688
  };
14689
+ var getDefaultBranch = async () => {
14690
+ try {
14691
+ const result = await git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
14692
+ const match = result.match(/refs\/remotes\/origin\/(.+)/);
14693
+ if (match) {
14694
+ return match[1].trim();
14695
+ }
14696
+ } catch {
14697
+ const branches = await git.branch(["-r"]);
14698
+ if (branches.all.includes("origin/main")) {
14699
+ return "main";
14700
+ }
14701
+ if (branches.all.includes("origin/master")) {
14702
+ return "master";
14703
+ }
14704
+ }
14705
+ return "main";
14706
+ };
14689
14707
 
14690
14708
  // src/utils/codeowners.ts
14691
14709
  var codeowners;
@@ -14776,6 +14794,105 @@ var listCodeowners = async (options) => {
14776
14794
  }
14777
14795
  };
14778
14796
 
14797
+ // src/utils/github.ts
14798
+ import { spawn as spawn3 } from "child_process";
14799
+ import { readFile } from "fs/promises";
14800
+ var isGitHubCliInstalled = async () => {
14801
+ return new Promise((resolve) => {
14802
+ const process3 = spawn3("gh", ["--version"], { stdio: "pipe" });
14803
+ process3.on("close", (code) => {
14804
+ resolve(code === 0);
14805
+ });
14806
+ process3.on("error", () => {
14807
+ resolve(false);
14808
+ });
14809
+ });
14810
+ };
14811
+ var findPRTemplate = async () => {
14812
+ const possiblePaths = [
14813
+ ".github/pull_request_template.md",
14814
+ ".github/PULL_REQUEST_TEMPLATE.md",
14815
+ ".github/PULL_REQUEST_TEMPLATE/pull_request_template.md",
14816
+ "docs/pull_request_template.md",
14817
+ "docs/PULL_REQUEST_TEMPLATE.md",
14818
+ "pull_request_template.md",
14819
+ "PULL_REQUEST_TEMPLATE.md"
14820
+ ];
14821
+ for (const templatePath of possiblePaths) {
14822
+ try {
14823
+ const content = await readFile(templatePath, "utf-8");
14824
+ log.info(`Found PR template at: ${templatePath}`);
14825
+ return { path: templatePath, content: content.trim() };
14826
+ } catch {}
14827
+ }
14828
+ return null;
14829
+ };
14830
+ var createPullRequest = async (options) => {
14831
+ const { title, body, draft = false, base = "main", head } = options;
14832
+ if (!await isGitHubCliInstalled()) {
14833
+ throw new Error("GitHub CLI (gh) is not installed. Please install it to create pull requests.");
14834
+ }
14835
+ const args = ["pr", "create", "--title", title];
14836
+ args.push("--body", body || "");
14837
+ if (draft) {
14838
+ args.push("--draft");
14839
+ }
14840
+ if (base) {
14841
+ args.push("--base", base);
14842
+ }
14843
+ if (head) {
14844
+ args.push("--head", head);
14845
+ }
14846
+ return new Promise((resolve, reject) => {
14847
+ const process3 = spawn3("gh", args, { stdio: "pipe" });
14848
+ let output = "";
14849
+ let errorOutput = "";
14850
+ process3.stdout.on("data", (data) => {
14851
+ output += data.toString();
14852
+ });
14853
+ process3.stderr.on("data", (data) => {
14854
+ errorOutput += data.toString();
14855
+ });
14856
+ process3.on("close", (code) => {
14857
+ if (code === 0) {
14858
+ const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+/);
14859
+ const numberMatch = output.match(/#(\d+)/);
14860
+ if (urlMatch && numberMatch) {
14861
+ const url = urlMatch[0];
14862
+ const number = parseInt(numberMatch[1], 10);
14863
+ log.success(`${draft ? "Draft " : ""}Pull request created: ${url}`);
14864
+ resolve({ url, number });
14865
+ } else {
14866
+ log.success(`${draft ? "Draft " : ""}Pull request created successfully`);
14867
+ resolve({ url: output.trim(), number: 0 });
14868
+ }
14869
+ } else {
14870
+ const error = new Error(`Failed to create pull request: ${errorOutput || output}`);
14871
+ log.error(error.message);
14872
+ reject(error);
14873
+ }
14874
+ });
14875
+ process3.on("error", (error) => {
14876
+ reject(new Error(`Failed to execute gh command: ${error.message}`));
14877
+ });
14878
+ });
14879
+ };
14880
+ var createPRWithTemplate = async (title, branchName, options = {}) => {
14881
+ const template = await findPRTemplate();
14882
+ let body = "";
14883
+ if (template) {
14884
+ body = template.content;
14885
+ log.info("Using PR template for pull request body");
14886
+ }
14887
+ return createPullRequest({
14888
+ title,
14889
+ body,
14890
+ draft: options.draft,
14891
+ base: options.base,
14892
+ head: branchName
14893
+ });
14894
+ };
14895
+
14779
14896
  // src/commands/branch.ts
14780
14897
  var branch = async (options) => {
14781
14898
  let originalBranch = "";
@@ -14786,6 +14903,12 @@ var branch = async (options) => {
14786
14903
  if (!options.branch || !options.message || !options.owner) {
14787
14904
  throw new Error("Missing required options for branch creation");
14788
14905
  }
14906
+ if ((options.pr || options.draftPr) && !options.push) {
14907
+ throw new Error("Pull request creation requires --push option");
14908
+ }
14909
+ if (options.pr && options.draftPr) {
14910
+ throw new Error("Cannot use both --pr and --draft-pr options");
14911
+ }
14789
14912
  log.info(options.append ? "Starting branch update process..." : "Starting branch creation process...");
14790
14913
  originalBranch = await getCurrentBranch();
14791
14914
  log.info(`Currently on branch: ${originalBranch}`);
@@ -14823,6 +14946,21 @@ var branch = async (options) => {
14823
14946
  noVerify: !options.verify
14824
14947
  });
14825
14948
  }
14949
+ if ((options.pr || options.draftPr) && options.push) {
14950
+ try {
14951
+ const defaultBranch = await getDefaultBranch();
14952
+ const prResult = await createPRWithTemplate(options.message, options.branch, {
14953
+ draft: options.draftPr,
14954
+ base: defaultBranch
14955
+ });
14956
+ if (prResult) {
14957
+ log.success(`${options.draftPr ? "Draft " : ""}Pull request #${prResult.number} created: ${prResult.url}`);
14958
+ }
14959
+ } catch (prError) {
14960
+ log.error(`Failed to create pull request: ${prError}`);
14961
+ log.info("Branch was successfully created and pushed, but PR creation failed");
14962
+ }
14963
+ }
14826
14964
  log.info(`Checking out original branch "${originalBranch}"...`);
14827
14965
  await checkout(originalBranch);
14828
14966
  if (branchAlreadyExists && options.append) {
@@ -14876,6 +15014,12 @@ var multiBranch = async (options) => {
14876
15014
  if (options.ignore && options.include) {
14877
15015
  throw new Error("Cannot use both --ignore and --include options at the same time");
14878
15016
  }
15017
+ if ((options.pr || options.draftPr) && !options.push) {
15018
+ throw new Error("Pull request creation requires --push option");
15019
+ }
15020
+ if (options.pr && options.draftPr) {
15021
+ throw new Error("Cannot use both --pr and --draft-pr options");
15022
+ }
14879
15023
  log.info(options.append ? "Starting multi-branch update process..." : "Starting multi-branch creation process...");
14880
15024
  const changedFiles = await getChangedFiles();
14881
15025
  if (changedFiles.length === 0) {
@@ -14924,7 +15068,9 @@ var multiBranch = async (options) => {
14924
15068
  }
14925
15069
  const results = {
14926
15070
  success: [],
14927
- failure: []
15071
+ failure: [],
15072
+ prSuccess: [],
15073
+ prFailure: []
14928
15074
  };
14929
15075
  for (const owner of codeowners2) {
14930
15076
  try {
@@ -14943,9 +15089,14 @@ var multiBranch = async (options) => {
14943
15089
  force: options.force,
14944
15090
  keepBranchOnFailure: options.keepBranchOnFailure,
14945
15091
  isDefaultOwner: owner === options.defaultOwner,
14946
- append: options.append
15092
+ append: options.append,
15093
+ pr: options.pr,
15094
+ draftPr: options.draftPr
14947
15095
  });
14948
15096
  results.success.push(owner);
15097
+ if ((options.pr || options.draftPr) && options.push) {
15098
+ results.prSuccess.push(owner);
15099
+ }
14949
15100
  } catch (error) {
14950
15101
  log.error(`Failed to ${options.append ? "update" : "create"} branch for ${owner}: ${error}`);
14951
15102
  results.failure.push(owner);
@@ -14959,13 +15110,20 @@ var multiBranch = async (options) => {
14959
15110
  if (results.failure.length) {
14960
15111
  log.error(`Failed: ${results.failure.join(", ")}`);
14961
15112
  }
15113
+ if (options.pr || options.draftPr) {
15114
+ log.header(`${options.draftPr ? "Draft " : ""}Pull request creation summary`);
15115
+ log.info(`Successfully created ${options.draftPr ? "draft " : ""}pull requests for ${results.prSuccess.length} of ${results.success.length} successful branches`);
15116
+ if (results.prSuccess.length) {
15117
+ log.success(`${options.draftPr ? "Draft " : ""}PRs created for: ${results.prSuccess.join(", ")}`);
15118
+ }
15119
+ }
14962
15120
  } catch (err) {
14963
15121
  log.error(`Multi-branch operation failed: ${err}`);
14964
15122
  process.exit(1);
14965
15123
  }
14966
15124
  };
14967
15125
  // package.json
14968
- var version = "1.5.0";
15126
+ var version = "1.7.0";
14969
15127
 
14970
15128
  // src/commands/version.ts
14971
15129
  function getVersion() {
@@ -14974,8 +15132,8 @@ function getVersion() {
14974
15132
 
14975
15133
  // src/cli.ts
14976
15134
  var program2 = new Command;
14977
- program2.name("codeowners").description("CLI tool for grouping and managing staged files by CODEOWNERS").version(getVersion());
15135
+ program2.name("codeowners-git (cg)").description("CLI tool for grouping and managing staged files by CODEOWNERS").version(getVersion());
14978
15136
  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);
14979
- 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").option("--append", "Add commits to existing branch instead of creating a new one").action(branch);
14980
- 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/*')").option("--append", "Add commits to existing branches instead of creating new ones").action(multiBranch);
15137
+ 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").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)").action(branch);
15138
+ 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/*')").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)").action(multiBranch);
14981
15139
  program2.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeowners-git",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "module": "src/cli.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -32,13 +32,19 @@
32
32
  "build": "bun build src/cli.ts --compile --outfile bin/codeowners-git",
33
33
  "build:dist": "bun build src/cli.ts --outdir dist/ --target node",
34
34
  "test": "bun test --watch",
35
- "test:ci": "bun test --watch",
35
+ "test:unit": "bun test src/**",
36
+ "test:e2e": "bun test test/e2e",
37
+ "test:e2e:local": "TEST_REPO_URL=../cg-test bun test test/e2e",
38
+ "test:all": "bun run test:unit && bun run test:e2e",
39
+ "test:ci": "bun run test:all",
40
+ "type-check": "bun tsc",
36
41
  "format": "biome format --write ./src",
37
42
  "lint": "biome lint ./src",
38
- "prepublish": "bun run build:dist && bun test"
43
+ "prepublish": "bun run build:dist && bun run test:all"
39
44
  },
40
45
  "bin": {
41
- "codeowners-git": "dist/cli.js"
46
+ "codeowners-git": "dist/cli.js",
47
+ "cg": "dist/cli.js"
42
48
  },
43
49
  "devDependencies": {
44
50
  "@changesets/cli": "^2.27.12",