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-BNVB1xK_.js";
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
- rule: {
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: "r",
197
- description: "Path to rule file",
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, rule: rulePath } = args;
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 { readFile } from "node:fs/promises";
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, rule, projectRoot = process.cwd()) {
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 countedFindInFiles(rule.language || Lang.TypeScript, {
140
- matcher: rule,
141
- paths: [projectRoot],
142
- languageGlobs: generateLanguageGlobsByLanguage(rule.language)
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(rule, projectRoot = process.cwd()) {
145
+ async function analyzeFilesWithoutOwner(language, rulePath, projectRoot = process.cwd()) {
167
146
  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);
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 AST-grep...");
226
- filesByOwner = await analyzeFilesByOwner(resolvedCodeownersPath, rule, projectRoot);
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 AST-grep...");
230
- filesByOwner = await analyzeFilesWithoutOwner(rule, projectRoot);
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, loadAstGrepRule, normalizeOwnerName };
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, rule: any, projectRoot?: string): Promise<Map<string, 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(rule: any, projectRoot?: string): Promise<Map<string, string[]>>;
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, loadAstGrepRule, normalizeOwnerName };
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, loadAstGrepRule, normalizeOwnerName } from "./codeowner-analysis-BNVB1xK_.js";
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, loadAstGrepRule, normalizeOwnerName };
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.7",
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:*",