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-
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
console.
|
|
196
|
-
console.
|
|
197
|
-
|
|
198
|
-
|
|
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-
|
|
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 };
|