codemodctl 0.1.4 → 0.1.6
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,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { analyzeCodeowners } from "./codeowner-analysis-
|
|
2
|
+
import { analyzeCodeowners } from "./codeowner-analysis-CcSOX0Ve.js";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
|
-
import { exec } from "node:child_process";
|
|
5
4
|
import crypto from "node:crypto";
|
|
5
|
+
import { $ } from "execa";
|
|
6
6
|
import fetch from "node-fetch";
|
|
7
7
|
import { writeFile } from "node:fs/promises";
|
|
8
8
|
|
|
@@ -71,52 +71,62 @@ const createCommand = defineCommand({
|
|
|
71
71
|
console.error("Error: BUTTERFLOW_API_AUTH_TOKEN environment variable is required");
|
|
72
72
|
process.exit(1);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
console.error("No changes detected, skipping pull request creation.");
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
const taskIdSignature = crypto.createHash("sha256").update(taskId).digest("hex").slice(0, 8);
|
|
79
|
-
const codemodBranchName = branchName ? branchName : `codemod-${taskIdSignature}`;
|
|
80
|
-
const remoteBaseBranchExec = exec("git remote show origin | sed -n '/HEAD branch/s/.*: //p'");
|
|
81
|
-
if (remoteBaseBranchExec.stderr?.read().toString().trim()) {
|
|
82
|
-
console.error("Error: Failed to get remote base branch");
|
|
83
|
-
console.error(remoteBaseBranchExec.stderr?.read().toString().trim());
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const remoteBaseBranch = remoteBaseBranchExec.stdout?.read().toString().trim();
|
|
87
|
-
console.debug(`Remote base branch: ${remoteBaseBranch}`);
|
|
74
|
+
const prData = { title };
|
|
88
75
|
if (push) {
|
|
89
|
-
|
|
90
|
-
|
|
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}`);
|
|
97
|
+
try {
|
|
98
|
+
await $`git checkout -b ${codemodBranchName}`;
|
|
99
|
+
} catch (error) {
|
|
91
100
|
console.error("Error: Failed to checkout branch");
|
|
92
|
-
console.error(
|
|
101
|
+
console.error(error);
|
|
93
102
|
process.exit(1);
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
try {
|
|
105
|
+
await $`git add .`;
|
|
106
|
+
} catch (error) {
|
|
97
107
|
console.error("Error: Failed to add changes");
|
|
98
|
-
console.error(
|
|
108
|
+
console.error(error);
|
|
99
109
|
process.exit(1);
|
|
100
110
|
}
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
try {
|
|
112
|
+
await $`git commit -m ${commitMessage}`;
|
|
113
|
+
} catch (error) {
|
|
103
114
|
console.error("Error: Failed to commit changes");
|
|
104
|
-
console.error(
|
|
115
|
+
console.error(error);
|
|
105
116
|
process.exit(1);
|
|
106
117
|
}
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
try {
|
|
119
|
+
await $`git push origin ${codemodBranchName} --force`;
|
|
120
|
+
console.log(`Pushed branch to origin: ${codemodBranchName}`);
|
|
121
|
+
} catch (error) {
|
|
109
122
|
console.error("Error: Failed to push changes");
|
|
110
|
-
console.error(
|
|
123
|
+
console.error(error);
|
|
111
124
|
process.exit(1);
|
|
112
125
|
}
|
|
113
126
|
}
|
|
114
|
-
const prData = { title };
|
|
115
127
|
if (body) prData.body = body;
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
if (base) prData.base = base;
|
|
119
|
-
else if (remoteBaseBranch) prData.base = remoteBaseBranch;
|
|
128
|
+
if (head && !prData.head) prData.head = head;
|
|
129
|
+
if (base && !prData.base) prData.base = base;
|
|
120
130
|
try {
|
|
121
131
|
console.debug("Creating pull request...");
|
|
122
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
|
-
|
|
114
|
+
return null;
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
116
117
|
* Loads and parses AST-grep rule from YAML file
|
|
@@ -159,6 +160,28 @@ async function analyzeFilesByOwner(codeownersPath, rule, projectRoot = process.c
|
|
|
159
160
|
return filesByOwner;
|
|
160
161
|
}
|
|
161
162
|
/**
|
|
163
|
+
* Analyzes files without codeowner parsing - assigns all files to "unassigned"
|
|
164
|
+
*/
|
|
165
|
+
async function analyzeFilesWithoutOwner(rule, projectRoot = process.cwd()) {
|
|
166
|
+
const filesByOwner = /* @__PURE__ */ new Map();
|
|
167
|
+
filesByOwner.set("unassigned", []);
|
|
168
|
+
await countedFindInFiles(Lang.TypeScript, {
|
|
169
|
+
matcher: rule,
|
|
170
|
+
paths: [projectRoot]
|
|
171
|
+
}, (err, matches) => {
|
|
172
|
+
if (err) {
|
|
173
|
+
console.error("AST-grep error:", err);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!matches || matches.length === 0) return;
|
|
177
|
+
const fileName = matches[0]?.getRoot().filename();
|
|
178
|
+
if (!fileName) return;
|
|
179
|
+
const relativePath = path.relative(projectRoot, fileName);
|
|
180
|
+
filesByOwner.get("unassigned").push(relativePath);
|
|
181
|
+
});
|
|
182
|
+
return filesByOwner;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
162
185
|
* Generates shard configuration from team file analysis
|
|
163
186
|
*/
|
|
164
187
|
function generateShards(filesByOwner, shardSize) {
|
|
@@ -169,7 +192,8 @@ function generateShards(filesByOwner, shardSize) {
|
|
|
169
192
|
console.log(`Team "${team}" owns ${fileCount} files, creating ${numShards} shards`);
|
|
170
193
|
for (let i = 1; i <= numShards; i++) allShards.push({
|
|
171
194
|
team,
|
|
172
|
-
shard: `${i}/${numShards}
|
|
195
|
+
shard: `${i}/${numShards}`,
|
|
196
|
+
shardId: `${team} ${i}/${numShards}`
|
|
173
197
|
});
|
|
174
198
|
}
|
|
175
199
|
return allShards;
|
|
@@ -191,11 +215,18 @@ async function analyzeCodeowners(options) {
|
|
|
191
215
|
const { shardSize, codeownersPath, rulePath, projectRoot = process.cwd() } = options;
|
|
192
216
|
const resolvedCodeownersPath = await findCodeownersFile(projectRoot, codeownersPath);
|
|
193
217
|
const rule = await loadAstGrepRule(rulePath);
|
|
194
|
-
|
|
195
|
-
console.
|
|
196
|
-
console.
|
|
197
|
-
|
|
198
|
-
|
|
218
|
+
let filesByOwner;
|
|
219
|
+
console.debug(`Using rule file: ${rulePath}`);
|
|
220
|
+
console.debug(`Shard size: ${shardSize}`);
|
|
221
|
+
if (resolvedCodeownersPath) {
|
|
222
|
+
console.log(`Analyzing CODEOWNERS file: ${resolvedCodeownersPath}`);
|
|
223
|
+
console.log("Analyzing files with AST-grep...");
|
|
224
|
+
filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, rule, projectRoot);
|
|
225
|
+
} else {
|
|
226
|
+
console.log("No CODEOWNERS file found, assigning all files to 'unassigned'");
|
|
227
|
+
console.log("Analyzing files with AST-grep...");
|
|
228
|
+
filesByOwner = await analyzeFilesWithoutOwner(rule, projectRoot);
|
|
229
|
+
}
|
|
199
230
|
console.log("File analysis completed. Generating shards...");
|
|
200
231
|
const teams = getTeamFileInfo(filesByOwner);
|
|
201
232
|
const shards = generateShards(filesByOwner, shardSize);
|
|
@@ -209,4 +240,4 @@ async function analyzeCodeowners(options) {
|
|
|
209
240
|
}
|
|
210
241
|
|
|
211
242
|
//#endregion
|
|
212
|
-
export { analyzeCodeowners, analyzeFilesByOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName };
|
|
243
|
+
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-
|
|
2
|
+
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, loadAstGrepRule, normalizeOwnerName } from "./codeowner-analysis-CcSOX0Ve.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 };
|