codemodctl 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { analyzeCodeowners } from "./codeowner-analysis-CeRIGuLu.js";
2
+ import { analyzeCodeowners } from "./codeowner-analysis-BNVB1xK_.js";
3
3
  import { defineCommand, runMain } from "citty";
4
4
  import crypto from "node:crypto";
5
5
  import { $ } from "execa";
@@ -71,29 +71,31 @@ const createCommand = defineCommand({
71
71
  console.error("Error: BUTTERFLOW_API_AUTH_TOKEN environment variable is required");
72
72
  process.exit(1);
73
73
  }
74
- try {
75
- await $`git diff --quiet`;
76
- await $`git diff --cached --quiet`;
77
- console.error("No changes detected, skipping pull request creation.");
78
- process.exit(0);
79
- } catch {
80
- console.log("Changes detected, proceeding with pull request creation...");
81
- }
82
- const taskIdSignature = crypto.createHash("sha256").update(taskId).digest("hex").slice(0, 8);
83
- const codemodBranchName = branchName ? branchName : `codemod-${taskIdSignature}`;
84
- let remoteBaseBranch;
85
- try {
86
- remoteBaseBranch = (await $`git remote show origin`).stdout.match(/HEAD branch: (.+)/)?.[1]?.trim() || "main";
87
- } catch (error) {
88
- console.error("Error: Failed to get remote base branch");
89
- console.error(error);
90
- process.exit(1);
91
- }
92
- console.debug(`Remote base branch: ${remoteBaseBranch}`);
74
+ const prData = { title };
93
75
  if (push) {
76
+ try {
77
+ await $`git diff --quiet`;
78
+ await $`git diff --cached --quiet`;
79
+ console.error("No changes detected, skipping pull request creation.");
80
+ process.exit(0);
81
+ } catch {
82
+ console.log("Changes detected, proceeding with pull request creation...");
83
+ }
84
+ const taskIdSignature = crypto.createHash("sha256").update(taskId).digest("hex").slice(0, 8);
85
+ const codemodBranchName = branchName ? branchName : `codemod-${taskIdSignature}`;
86
+ let remoteBaseBranch;
87
+ try {
88
+ remoteBaseBranch = (await $`git remote show origin`).stdout.match(/HEAD branch: (.+)/)?.[1]?.trim() || "main";
89
+ } catch (error) {
90
+ console.error("Error: Failed to get remote base branch");
91
+ console.error(error);
92
+ process.exit(1);
93
+ }
94
+ if (codemodBranchName) prData.head = codemodBranchName;
95
+ if (remoteBaseBranch) prData.base = remoteBaseBranch;
96
+ console.debug(`Remote base branch: ${remoteBaseBranch}`);
94
97
  try {
95
98
  await $`git checkout -b ${codemodBranchName}`;
96
- console.log(`✅ Created and checked out branch: ${codemodBranchName}`);
97
99
  } catch (error) {
98
100
  console.error("Error: Failed to checkout branch");
99
101
  console.error(error);
@@ -101,7 +103,6 @@ const createCommand = defineCommand({
101
103
  }
102
104
  try {
103
105
  await $`git add .`;
104
- console.log("✅ Added changes to staging");
105
106
  } catch (error) {
106
107
  console.error("Error: Failed to add changes");
107
108
  console.error(error);
@@ -109,7 +110,6 @@ const createCommand = defineCommand({
109
110
  }
110
111
  try {
111
112
  await $`git commit -m ${commitMessage}`;
112
- console.log("✅ Committed changes");
113
113
  } catch (error) {
114
114
  console.error("Error: Failed to commit changes");
115
115
  console.error(error);
@@ -117,19 +117,16 @@ const createCommand = defineCommand({
117
117
  }
118
118
  try {
119
119
  await $`git push origin ${codemodBranchName} --force`;
120
- console.log(`✅ Pushed branch to origin: ${codemodBranchName}`);
120
+ console.log(`Pushed branch to origin: ${codemodBranchName}`);
121
121
  } catch (error) {
122
122
  console.error("Error: Failed to push changes");
123
123
  console.error(error);
124
124
  process.exit(1);
125
125
  }
126
126
  }
127
- const prData = { title };
128
127
  if (body) prData.body = body;
129
- if (codemodBranchName && push) prData.head = codemodBranchName;
130
- else if (head) prData.head = head;
131
- if (base) prData.base = base;
132
- else if (remoteBaseBranch) prData.base = remoteBaseBranch;
128
+ if (head && !prData.head) prData.head = head;
129
+ if (base && !prData.base) prData.base = base;
133
130
  try {
134
131
  console.debug("Creating pull request...");
135
132
  console.debug(`Title: ${title}`);
@@ -97,6 +97,7 @@ const countedFindInFiles = countedPromise(findInFiles);
97
97
  /**
98
98
  * Finds and resolves the CODEOWNERS file path
99
99
  * Searches in common locations: root, .github/, docs/
100
+ * Returns null if no CODEOWNERS file is found
100
101
  */
101
102
  async function findCodeownersFile(projectRoot = process.cwd(), explicitPath) {
102
103
  if (explicitPath) {
@@ -110,7 +111,7 @@ async function findCodeownersFile(projectRoot = process.cwd(), explicitPath) {
110
111
  resolve(projectRoot, "docs", "CODEOWNERS")
111
112
  ];
112
113
  for (const searchPath of searchPaths) if (existsSync(searchPath)) return searchPath;
113
- throw new Error("CODEOWNERS file not found. Please specify path explicitly or ensure CODEOWNERS file exists in project root, .github/, or docs/ folder.");
114
+ return null;
114
115
  }
115
116
  /**
116
117
  * Loads and parses AST-grep rule from YAML file
@@ -135,9 +136,10 @@ async function analyzeFilesByOwner(codeownersPath, rule, projectRoot = process.c
135
136
  const codeowners = new Codeowners(codeownersPath);
136
137
  const gitRootDir = codeowners.codeownersDirectory;
137
138
  const filesByOwner = /* @__PURE__ */ new Map();
138
- await countedFindInFiles(Lang.TypeScript, {
139
+ await countedFindInFiles(rule.language || Lang.TypeScript, {
139
140
  matcher: rule,
140
- paths: [projectRoot]
141
+ paths: [projectRoot],
142
+ languageGlobs: generateLanguageGlobsByLanguage(rule.language)
141
143
  }, (err, matches) => {
142
144
  if (err) {
143
145
  console.error("AST-grep error:", err);
@@ -159,6 +161,29 @@ async function analyzeFilesByOwner(codeownersPath, rule, projectRoot = process.c
159
161
  return filesByOwner;
160
162
  }
161
163
  /**
164
+ * Analyzes files without codeowner parsing - assigns all files to "unassigned"
165
+ */
166
+ async function analyzeFilesWithoutOwner(rule, projectRoot = process.cwd()) {
167
+ const filesByOwner = /* @__PURE__ */ new Map();
168
+ filesByOwner.set("unassigned", []);
169
+ await countedFindInFiles(rule.language || Lang.TypeScript, {
170
+ matcher: rule,
171
+ paths: [projectRoot],
172
+ languageGlobs: generateLanguageGlobsByLanguage(rule.language)
173
+ }, (err, matches) => {
174
+ if (err) {
175
+ console.error("AST-grep error:", err);
176
+ return;
177
+ }
178
+ if (!matches || matches.length === 0) return;
179
+ const fileName = matches[0]?.getRoot().filename();
180
+ if (!fileName) return;
181
+ const relativePath = path.relative(projectRoot, fileName);
182
+ filesByOwner.get("unassigned").push(relativePath);
183
+ });
184
+ return filesByOwner;
185
+ }
186
+ /**
162
187
  * Generates shard configuration from team file analysis
163
188
  */
164
189
  function generateShards(filesByOwner, shardSize) {
@@ -169,7 +194,8 @@ function generateShards(filesByOwner, shardSize) {
169
194
  console.log(`Team "${team}" owns ${fileCount} files, creating ${numShards} shards`);
170
195
  for (let i = 1; i <= numShards; i++) allShards.push({
171
196
  team,
172
- shard: `${i}/${numShards}`
197
+ shard: `${i}/${numShards}`,
198
+ shardId: `${team} ${i}/${numShards}`
173
199
  });
174
200
  }
175
201
  return allShards;
@@ -191,11 +217,18 @@ async function analyzeCodeowners(options) {
191
217
  const { shardSize, codeownersPath, rulePath, projectRoot = process.cwd() } = options;
192
218
  const resolvedCodeownersPath = await findCodeownersFile(projectRoot, codeownersPath);
193
219
  const rule = await loadAstGrepRule(rulePath);
194
- console.log(`Analyzing CODEOWNERS file: ${resolvedCodeownersPath}`);
195
- console.log(`Using rule file: ${rulePath}`);
196
- console.log(`Shard size: ${shardSize}`);
197
- console.log("Analyzing files with AST-grep...");
198
- const filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, rule, projectRoot);
220
+ let filesByOwner;
221
+ console.debug(`Using rule file: ${rulePath}`);
222
+ console.debug(`Shard size: ${shardSize}`);
223
+ if (resolvedCodeownersPath) {
224
+ console.log(`Analyzing CODEOWNERS file: ${resolvedCodeownersPath}`);
225
+ console.log("Analyzing files with AST-grep...");
226
+ filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, rule, projectRoot);
227
+ } else {
228
+ console.log("No CODEOWNERS file found, assigning all files to 'unassigned'");
229
+ console.log("Analyzing files with AST-grep...");
230
+ filesByOwner = await analyzeFilesWithoutOwner(rule, projectRoot);
231
+ }
199
232
  console.log("File analysis completed. Generating shards...");
200
233
  const teams = getTeamFileInfo(filesByOwner);
201
234
  const shards = generateShards(filesByOwner, shardSize);
@@ -207,6 +240,34 @@ async function analyzeCodeowners(options) {
207
240
  totalFiles
208
241
  };
209
242
  }
243
+ function generateExtensionsByLanguage(language) {
244
+ switch (language) {
245
+ case "typescript": return [
246
+ "ts",
247
+ "mts",
248
+ "cts"
249
+ ];
250
+ case "tsx": return [
251
+ "tsx",
252
+ "ts",
253
+ "mts",
254
+ "cts"
255
+ ];
256
+ case "javascript":
257
+ case "jsx":
258
+ case "js": return [
259
+ "js",
260
+ "mjs",
261
+ "cjs"
262
+ ];
263
+ default: return;
264
+ }
265
+ }
266
+ function generateLanguageGlobsByLanguage(language) {
267
+ const extensions = generateExtensionsByLanguage(language);
268
+ if (!extensions) return;
269
+ return extensions.map((extension) => `**/*.${extension}`);
270
+ }
210
271
 
211
272
  //#endregion
212
- export { analyzeCodeowners, analyzeFilesByOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
273
+ export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
package/dist/index.d.ts CHANGED
@@ -57,6 +57,7 @@ declare function calculateOptimalShardCount(totalFiles: number, targetShardSize:
57
57
  interface ShardResult {
58
58
  team: string;
59
59
  shard: string;
60
+ shardId: string;
60
61
  }
61
62
  interface TeamFileInfo {
62
63
  team: string;
@@ -77,8 +78,9 @@ interface CodeownerAnalysisResult {
77
78
  /**
78
79
  * Finds and resolves the CODEOWNERS file path
79
80
  * Searches in common locations: root, .github/, docs/
81
+ * Returns null if no CODEOWNERS file is found
80
82
  */
81
- declare function findCodeownersFile(projectRoot?: string, explicitPath?: string): Promise<string>;
83
+ declare function findCodeownersFile(projectRoot?: string, explicitPath?: string): Promise<string | null>;
82
84
  /**
83
85
  * Loads and parses AST-grep rule from YAML file
84
86
  */
@@ -91,6 +93,10 @@ declare function normalizeOwnerName(owner: string): string;
91
93
  * Analyzes files and groups them by codeowner team
92
94
  */
93
95
  declare function analyzeFilesByOwner(codeownersPath: string, rule: any, projectRoot?: string): Promise<Map<string, string[]>>;
96
+ /**
97
+ * Analyzes files without codeowner parsing - assigns all files to "unassigned"
98
+ */
99
+ declare function analyzeFilesWithoutOwner(rule: any, projectRoot?: string): Promise<Map<string, string[]>>;
94
100
  /**
95
101
  * Generates shard configuration from team file analysis
96
102
  */
@@ -104,4 +110,4 @@ declare function getTeamFileInfo(filesByOwner: Map<string, string[]>): TeamFileI
104
110
  */
105
111
  declare function analyzeCodeowners(options: CodeownerAnalysisOptions): Promise<CodeownerAnalysisResult>;
106
112
  //#endregion
107
- export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
113
+ export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { analyzeCodeowners, analyzeFilesByOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName } from "./codeowner-analysis-CeRIGuLu.js";
2
+ import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName } from "./codeowner-analysis-BNVB1xK_.js";
3
3
 
4
- export { analyzeCodeowners, analyzeFilesByOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
4
+ export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codemodctl",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "CLI tool and utilities for workflow engine operations, file sharding, and codeowner analysis",
5
5
  "type": "module",
6
6
  "exports": {