codemodctl 0.1.7 → 0.1.9
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,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { analyzeCodeowners } from "./codeowner-analysis-
|
|
2
|
+
import { analyzeCodeowners } from "./codeowner-analysis-CYaliyNC.js";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { $ } from "execa";
|
|
6
|
-
import fetch from "node-fetch";
|
|
7
6
|
import { writeFile } from "node:fs/promises";
|
|
8
7
|
|
|
9
8
|
//#region src/commands/pr/create.ts
|
|
@@ -187,19 +186,24 @@ const codeownerCommand = defineCommand({
|
|
|
187
186
|
},
|
|
188
187
|
codeowners: {
|
|
189
188
|
type: "string",
|
|
190
|
-
alias: "c",
|
|
191
189
|
description: "Path to CODEOWNERS file (optional)",
|
|
192
190
|
required: false
|
|
193
191
|
},
|
|
194
|
-
|
|
192
|
+
codemodFile: {
|
|
193
|
+
type: "string",
|
|
194
|
+
alias: "c",
|
|
195
|
+
description: "Path to codemod file",
|
|
196
|
+
required: true
|
|
197
|
+
},
|
|
198
|
+
language: {
|
|
195
199
|
type: "string",
|
|
196
|
-
alias: "
|
|
197
|
-
description: "
|
|
200
|
+
alias: "l",
|
|
201
|
+
description: "Language of the codemod",
|
|
198
202
|
required: true
|
|
199
203
|
}
|
|
200
204
|
},
|
|
201
205
|
async run({ args }) {
|
|
202
|
-
const { shardSize: shardSizeStr, stateProp, codeowners: codeownersPath,
|
|
206
|
+
const { shardSize: shardSizeStr, stateProp, codeowners: codeownersPath, codemodFile: codemodFilePath, language } = args;
|
|
203
207
|
const shardSize = parseInt(shardSizeStr, 10);
|
|
204
208
|
if (isNaN(shardSize) || shardSize <= 0) {
|
|
205
209
|
console.error("Error: shard-size must be a positive number");
|
|
@@ -215,8 +219,9 @@ const codeownerCommand = defineCommand({
|
|
|
215
219
|
const analysisOptions = {
|
|
216
220
|
shardSize,
|
|
217
221
|
codeownersPath,
|
|
218
|
-
rulePath,
|
|
219
|
-
projectRoot: process.cwd()
|
|
222
|
+
rulePath: codemodFilePath,
|
|
223
|
+
projectRoot: process.cwd(),
|
|
224
|
+
language
|
|
220
225
|
};
|
|
221
226
|
const result = await analyzeCodeowners(analysisOptions);
|
|
222
227
|
const stateOutput = `${stateProp}=${JSON.stringify(result.shards)}\n`;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import path, { resolve } from "node:path";
|
|
6
|
-
import { Lang, findInFiles } from "@ast-grep/napi";
|
|
7
6
|
import Codeowners from "codeowners";
|
|
8
|
-
import yaml from "yaml";
|
|
9
7
|
|
|
10
8
|
//#region src/utils/consistent-sharding.ts
|
|
11
9
|
const HASH_RING_SIZE = 1e6;
|
|
@@ -73,25 +71,6 @@ function calculateOptimalShardCount(totalFiles, targetShardSize) {
|
|
|
73
71
|
return Math.ceil(totalFiles / targetShardSize);
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
//#endregion
|
|
77
|
-
//#region src/utils/counted-promise.ts
|
|
78
|
-
function countedPromise(func) {
|
|
79
|
-
return async (lang, t, cb) => {
|
|
80
|
-
let i = 0;
|
|
81
|
-
let fileCount = void 0;
|
|
82
|
-
let resolve$1 = () => {};
|
|
83
|
-
async function wrapped(...args) {
|
|
84
|
-
const ret = await cb(...args);
|
|
85
|
-
if (++i === fileCount) resolve$1();
|
|
86
|
-
return ret;
|
|
87
|
-
}
|
|
88
|
-
fileCount = await func(lang, t, wrapped);
|
|
89
|
-
if (fileCount > i) await new Promise((r) => resolve$1 = r);
|
|
90
|
-
return fileCount;
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
const countedFindInFiles = countedPromise(findInFiles);
|
|
94
|
-
|
|
95
74
|
//#endregion
|
|
96
75
|
//#region src/utils/codeowner-analysis.ts
|
|
97
76
|
/**
|
|
@@ -114,41 +93,41 @@ async function findCodeownersFile(projectRoot = process.cwd(), explicitPath) {
|
|
|
114
93
|
return null;
|
|
115
94
|
}
|
|
116
95
|
/**
|
|
117
|
-
* Loads and parses AST-grep rule from YAML file
|
|
118
|
-
*/
|
|
119
|
-
async function loadAstGrepRule(rulePath) {
|
|
120
|
-
if (!existsSync(rulePath)) throw new Error(`Rule file not found at: ${rulePath}`);
|
|
121
|
-
const ruleContent = await readFile(rulePath, "utf8");
|
|
122
|
-
const parsedRules = yaml.parseAllDocuments(ruleContent);
|
|
123
|
-
if (!parsedRules[0]) throw new Error("Invalid rule file: no rules found");
|
|
124
|
-
return parsedRules[0].toJSON();
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
96
|
* Normalizes owner name by removing @ prefix and converting to lowercase
|
|
128
97
|
*/
|
|
129
98
|
function normalizeOwnerName(owner) {
|
|
130
99
|
return owner.replace("@", "").toLowerCase();
|
|
131
100
|
}
|
|
132
101
|
/**
|
|
102
|
+
* Executes the codemod CLI command and returns applicable file paths
|
|
103
|
+
*/
|
|
104
|
+
async function getApplicableFiles(rulePath, language, projectRoot) {
|
|
105
|
+
try {
|
|
106
|
+
const command = `npx codemod@latest jssg list-applicable --language ${language} --target ${projectRoot} ${rulePath}`;
|
|
107
|
+
console.debug(`Executing: ${command}`);
|
|
108
|
+
const applicableFiles = execSync(command, {
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
cwd: projectRoot,
|
|
111
|
+
maxBuffer: 10 * 1024 * 1024
|
|
112
|
+
}).split("\n").filter((line) => line.startsWith("[Applicable] ")).map((line) => line.replace("[Applicable] ", "").trim()).filter((filePath) => filePath.length > 0);
|
|
113
|
+
console.debug(`Found ${applicableFiles.length} applicable files`);
|
|
114
|
+
return applicableFiles;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("Error executing codemod CLI:", error);
|
|
117
|
+
throw new Error(`Failed to execute codemod CLI: ${error}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
133
121
|
* Analyzes files and groups them by codeowner team
|
|
134
122
|
*/
|
|
135
|
-
async function analyzeFilesByOwner(codeownersPath,
|
|
123
|
+
async function analyzeFilesByOwner(codeownersPath, language, rulePath, projectRoot = process.cwd()) {
|
|
136
124
|
const codeowners = new Codeowners(codeownersPath);
|
|
137
125
|
const gitRootDir = codeowners.codeownersDirectory;
|
|
138
126
|
const filesByOwner = /* @__PURE__ */ new Map();
|
|
139
|
-
await
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}, (err, matches) => {
|
|
144
|
-
if (err) {
|
|
145
|
-
console.error("AST-grep error:", err);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (!matches || matches.length === 0) return;
|
|
149
|
-
const fileName = matches[0]?.getRoot().filename();
|
|
150
|
-
if (!fileName) return;
|
|
151
|
-
const relativePath = path.relative(gitRootDir, fileName);
|
|
127
|
+
const applicableFiles = await getApplicableFiles(rulePath, language, projectRoot);
|
|
128
|
+
for (const filePath of applicableFiles) {
|
|
129
|
+
const absolutePath = path.resolve(projectRoot, filePath);
|
|
130
|
+
const relativePath = path.relative(gitRootDir, absolutePath);
|
|
152
131
|
const owners = codeowners.getOwner(relativePath);
|
|
153
132
|
let ownerKey;
|
|
154
133
|
if (owners && owners.length > 0) {
|
|
@@ -157,30 +136,18 @@ async function analyzeFilesByOwner(codeownersPath, rule, projectRoot = process.c
|
|
|
157
136
|
} else ownerKey = "unassigned";
|
|
158
137
|
if (!filesByOwner.has(ownerKey)) filesByOwner.set(ownerKey, []);
|
|
159
138
|
filesByOwner.get(ownerKey).push(relativePath);
|
|
160
|
-
}
|
|
139
|
+
}
|
|
161
140
|
return filesByOwner;
|
|
162
141
|
}
|
|
163
142
|
/**
|
|
164
143
|
* Analyzes files without codeowner parsing - assigns all files to "unassigned"
|
|
165
144
|
*/
|
|
166
|
-
async function analyzeFilesWithoutOwner(
|
|
145
|
+
async function analyzeFilesWithoutOwner(language, rulePath, projectRoot = process.cwd()) {
|
|
167
146
|
const filesByOwner = /* @__PURE__ */ new Map();
|
|
168
|
-
|
|
169
|
-
|
|
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);
|
|
147
|
+
const unassignedFiles = (await getApplicableFiles(rulePath, language, projectRoot)).map((filePath) => {
|
|
148
|
+
return path.relative(projectRoot, path.resolve(projectRoot, filePath));
|
|
183
149
|
});
|
|
150
|
+
filesByOwner.set("unassigned", unassignedFiles);
|
|
184
151
|
return filesByOwner;
|
|
185
152
|
}
|
|
186
153
|
/**
|
|
@@ -214,20 +181,19 @@ function getTeamFileInfo(filesByOwner) {
|
|
|
214
181
|
* Main function to analyze codeowners and generate shard configuration
|
|
215
182
|
*/
|
|
216
183
|
async function analyzeCodeowners(options) {
|
|
217
|
-
const { shardSize, codeownersPath, rulePath, projectRoot = process.cwd() } = options;
|
|
184
|
+
const { shardSize, codeownersPath, rulePath, language, projectRoot = process.cwd() } = options;
|
|
218
185
|
const resolvedCodeownersPath = await findCodeownersFile(projectRoot, codeownersPath);
|
|
219
|
-
const rule = await loadAstGrepRule(rulePath);
|
|
220
186
|
let filesByOwner;
|
|
221
187
|
console.debug(`Using rule file: ${rulePath}`);
|
|
222
188
|
console.debug(`Shard size: ${shardSize}`);
|
|
223
189
|
if (resolvedCodeownersPath) {
|
|
224
190
|
console.log(`Analyzing CODEOWNERS file: ${resolvedCodeownersPath}`);
|
|
225
|
-
console.log("Analyzing files with
|
|
226
|
-
filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath,
|
|
191
|
+
console.log("Analyzing files with CLI command...");
|
|
192
|
+
filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, language, rulePath, projectRoot);
|
|
227
193
|
} else {
|
|
228
194
|
console.log("No CODEOWNERS file found, assigning all files to 'unassigned'");
|
|
229
|
-
console.log("Analyzing files with
|
|
230
|
-
filesByOwner = await analyzeFilesWithoutOwner(
|
|
195
|
+
console.log("Analyzing files with CLI command...");
|
|
196
|
+
filesByOwner = await analyzeFilesWithoutOwner(language, rulePath, projectRoot);
|
|
231
197
|
}
|
|
232
198
|
console.log("File analysis completed. Generating shards...");
|
|
233
199
|
const teams = getTeamFileInfo(filesByOwner);
|
|
@@ -240,34 +206,6 @@ async function analyzeCodeowners(options) {
|
|
|
240
206
|
totalFiles
|
|
241
207
|
};
|
|
242
208
|
}
|
|
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
|
-
}
|
|
271
209
|
|
|
272
210
|
//#endregion
|
|
273
|
-
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo,
|
|
211
|
+
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getApplicableFiles, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName };
|
package/dist/index.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ interface CodeownerAnalysisOptions {
|
|
|
68
68
|
shardSize: number;
|
|
69
69
|
codeownersPath?: string;
|
|
70
70
|
rulePath: string;
|
|
71
|
+
language: string;
|
|
71
72
|
projectRoot?: string;
|
|
72
73
|
}
|
|
73
74
|
interface CodeownerAnalysisResult {
|
|
@@ -81,22 +82,22 @@ interface CodeownerAnalysisResult {
|
|
|
81
82
|
* Returns null if no CODEOWNERS file is found
|
|
82
83
|
*/
|
|
83
84
|
declare function findCodeownersFile(projectRoot?: string, explicitPath?: string): Promise<string | null>;
|
|
84
|
-
/**
|
|
85
|
-
* Loads and parses AST-grep rule from YAML file
|
|
86
|
-
*/
|
|
87
|
-
declare function loadAstGrepRule(rulePath: string): Promise<any>;
|
|
88
85
|
/**
|
|
89
86
|
* Normalizes owner name by removing @ prefix and converting to lowercase
|
|
90
87
|
*/
|
|
91
88
|
declare function normalizeOwnerName(owner: string): string;
|
|
89
|
+
/**
|
|
90
|
+
* Executes the codemod CLI command and returns applicable file paths
|
|
91
|
+
*/
|
|
92
|
+
declare function getApplicableFiles(rulePath: string, language: string, projectRoot: string): Promise<string[]>;
|
|
92
93
|
/**
|
|
93
94
|
* Analyzes files and groups them by codeowner team
|
|
94
95
|
*/
|
|
95
|
-
declare function analyzeFilesByOwner(codeownersPath: string,
|
|
96
|
+
declare function analyzeFilesByOwner(codeownersPath: string, language: string, rulePath: string, projectRoot?: string): Promise<Map<string, string[]>>;
|
|
96
97
|
/**
|
|
97
98
|
* Analyzes files without codeowner parsing - assigns all files to "unassigned"
|
|
98
99
|
*/
|
|
99
|
-
declare function analyzeFilesWithoutOwner(
|
|
100
|
+
declare function analyzeFilesWithoutOwner(language: string, rulePath: string, projectRoot?: string): Promise<Map<string, string[]>>;
|
|
100
101
|
/**
|
|
101
102
|
* Generates shard configuration from team file analysis
|
|
102
103
|
*/
|
|
@@ -110,4 +111,4 @@ declare function getTeamFileInfo(filesByOwner: Map<string, string[]>): TeamFileI
|
|
|
110
111
|
*/
|
|
111
112
|
declare function analyzeCodeowners(options: CodeownerAnalysisOptions): Promise<CodeownerAnalysisResult>;
|
|
112
113
|
//#endregion
|
|
113
|
-
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo,
|
|
114
|
+
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getApplicableFiles, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo,
|
|
2
|
+
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getApplicableFiles, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName } from "./codeowner-analysis-CYaliyNC.js";
|
|
3
3
|
|
|
4
|
-
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo,
|
|
4
|
+
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getApplicableFiles, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codemodctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "CLI tool and utilities for workflow engine operations, file sharding, and codeowner analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -35,13 +35,10 @@
|
|
|
35
35
|
"test": "vitest"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ast-grep/napi": "^0.39.4",
|
|
39
38
|
"citty": "^0.1.6",
|
|
40
39
|
"codeowners": "^5.1.1",
|
|
41
40
|
"execa": "^9.6.0",
|
|
42
|
-
"glob": "^11.0.0"
|
|
43
|
-
"node-fetch": "^3.3.2",
|
|
44
|
-
"yaml": "^2.7.1"
|
|
41
|
+
"glob": "^11.0.0"
|
|
45
42
|
},
|
|
46
43
|
"devDependencies": {
|
|
47
44
|
"@acme/tsconfig": "workspace:*",
|