codemodctl 0.1.6 → 0.1.8
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-CYaliyNC.js";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { $ } from "execa";
|
|
@@ -187,19 +187,24 @@ const codeownerCommand = defineCommand({
|
|
|
187
187
|
},
|
|
188
188
|
codeowners: {
|
|
189
189
|
type: "string",
|
|
190
|
-
alias: "c",
|
|
191
190
|
description: "Path to CODEOWNERS file (optional)",
|
|
192
191
|
required: false
|
|
193
192
|
},
|
|
194
|
-
|
|
193
|
+
codemodFile: {
|
|
194
|
+
type: "string",
|
|
195
|
+
alias: "c",
|
|
196
|
+
description: "Path to codemod file",
|
|
197
|
+
required: true
|
|
198
|
+
},
|
|
199
|
+
language: {
|
|
195
200
|
type: "string",
|
|
196
|
-
alias: "
|
|
197
|
-
description: "
|
|
201
|
+
alias: "l",
|
|
202
|
+
description: "Language of the codemod",
|
|
198
203
|
required: true
|
|
199
204
|
}
|
|
200
205
|
},
|
|
201
206
|
async run({ args }) {
|
|
202
|
-
const { shardSize: shardSizeStr, stateProp, codeowners: codeownersPath,
|
|
207
|
+
const { shardSize: shardSizeStr, stateProp, codeowners: codeownersPath, codemodFile: codemodFilePath, language } = args;
|
|
203
208
|
const shardSize = parseInt(shardSizeStr, 10);
|
|
204
209
|
if (isNaN(shardSize) || shardSize <= 0) {
|
|
205
210
|
console.error("Error: shard-size must be a positive number");
|
|
@@ -215,8 +220,9 @@ const codeownerCommand = defineCommand({
|
|
|
215
220
|
const analysisOptions = {
|
|
216
221
|
shardSize,
|
|
217
222
|
codeownersPath,
|
|
218
|
-
rulePath,
|
|
219
|
-
projectRoot: process.cwd()
|
|
223
|
+
rulePath: codemodFilePath,
|
|
224
|
+
projectRoot: process.cwd(),
|
|
225
|
+
language
|
|
220
226
|
};
|
|
221
227
|
const result = await analyzeCodeowners(analysisOptions);
|
|
222
228
|
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,40 +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
|
-
if (err) {
|
|
144
|
-
console.error("AST-grep error:", err);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (!matches || matches.length === 0) return;
|
|
148
|
-
const fileName = matches[0]?.getRoot().filename();
|
|
149
|
-
if (!fileName) return;
|
|
150
|
-
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);
|
|
151
131
|
const owners = codeowners.getOwner(relativePath);
|
|
152
132
|
let ownerKey;
|
|
153
133
|
if (owners && owners.length > 0) {
|
|
@@ -156,29 +136,18 @@ async function analyzeFilesByOwner(codeownersPath, rule, projectRoot = process.c
|
|
|
156
136
|
} else ownerKey = "unassigned";
|
|
157
137
|
if (!filesByOwner.has(ownerKey)) filesByOwner.set(ownerKey, []);
|
|
158
138
|
filesByOwner.get(ownerKey).push(relativePath);
|
|
159
|
-
}
|
|
139
|
+
}
|
|
160
140
|
return filesByOwner;
|
|
161
141
|
}
|
|
162
142
|
/**
|
|
163
143
|
* Analyzes files without codeowner parsing - assigns all files to "unassigned"
|
|
164
144
|
*/
|
|
165
|
-
async function analyzeFilesWithoutOwner(
|
|
145
|
+
async function analyzeFilesWithoutOwner(language, rulePath, projectRoot = process.cwd()) {
|
|
166
146
|
const filesByOwner = /* @__PURE__ */ new Map();
|
|
167
|
-
|
|
168
|
-
|
|
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);
|
|
147
|
+
const unassignedFiles = (await getApplicableFiles(rulePath, language, projectRoot)).map((filePath) => {
|
|
148
|
+
return path.relative(projectRoot, path.resolve(projectRoot, filePath));
|
|
181
149
|
});
|
|
150
|
+
filesByOwner.set("unassigned", unassignedFiles);
|
|
182
151
|
return filesByOwner;
|
|
183
152
|
}
|
|
184
153
|
/**
|
|
@@ -212,20 +181,19 @@ function getTeamFileInfo(filesByOwner) {
|
|
|
212
181
|
* Main function to analyze codeowners and generate shard configuration
|
|
213
182
|
*/
|
|
214
183
|
async function analyzeCodeowners(options) {
|
|
215
|
-
const { shardSize, codeownersPath, rulePath, projectRoot = process.cwd() } = options;
|
|
184
|
+
const { shardSize, codeownersPath, rulePath, language, projectRoot = process.cwd() } = options;
|
|
216
185
|
const resolvedCodeownersPath = await findCodeownersFile(projectRoot, codeownersPath);
|
|
217
|
-
const rule = await loadAstGrepRule(rulePath);
|
|
218
186
|
let filesByOwner;
|
|
219
187
|
console.debug(`Using rule file: ${rulePath}`);
|
|
220
188
|
console.debug(`Shard size: ${shardSize}`);
|
|
221
189
|
if (resolvedCodeownersPath) {
|
|
222
190
|
console.log(`Analyzing CODEOWNERS file: ${resolvedCodeownersPath}`);
|
|
223
|
-
console.log("Analyzing files with
|
|
224
|
-
filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath,
|
|
191
|
+
console.log("Analyzing files with CLI command...");
|
|
192
|
+
filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, language, rulePath, projectRoot);
|
|
225
193
|
} else {
|
|
226
194
|
console.log("No CODEOWNERS file found, assigning all files to 'unassigned'");
|
|
227
|
-
console.log("Analyzing files with
|
|
228
|
-
filesByOwner = await analyzeFilesWithoutOwner(
|
|
195
|
+
console.log("Analyzing files with CLI command...");
|
|
196
|
+
filesByOwner = await analyzeFilesWithoutOwner(language, rulePath, projectRoot);
|
|
229
197
|
}
|
|
230
198
|
console.log("File analysis completed. Generating shards...");
|
|
231
199
|
const teams = getTeamFileInfo(filesByOwner);
|
|
@@ -240,4 +208,4 @@ async function analyzeCodeowners(options) {
|
|
|
240
208
|
}
|
|
241
209
|
|
|
242
210
|
//#endregion
|
|
243
|
-
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.8",
|
|
4
4
|
"description": "CLI tool and utilities for workflow engine operations, file sharding, and codeowner analysis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -35,7 +35,6 @@
|
|
|
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",
|