codemodctl 0.1.13 → 0.1.15
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 +138 -13
- package/dist/codemod-cli-DailrcEf.js +26 -0
- package/dist/{codeowner-analysis-C8hyzL4c.js → codeowner-analysis-BcFoet6s.js} +48 -34
- package/dist/{codeowner-analysis-D1oVulJ6.d.ts → codeowner-analysis-CkGR1oU6.d.ts} +44 -8
- package/dist/codeowners.d.ts +2 -2
- package/dist/codeowners.js +3 -3
- package/dist/{consistent-sharding-D9n1M6by.d.ts → consistent-sharding-B-Si8jYX.d.ts} +5 -10
- package/dist/{consistent-sharding-CcnfsY_k.js → consistent-sharding-lYO6XLIO.js} +5 -6
- package/dist/directory-analysis-6DFgAaDz.js +97 -0
- package/dist/directory.d.ts +69 -0
- package/dist/directory.js +6 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/sharding.d.ts +1 -1
- package/dist/sharding.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./
|
|
3
|
-
import { analyzeCodeowners } from "./codeowner-analysis-
|
|
2
|
+
import "./codemod-cli-DailrcEf.js";
|
|
3
|
+
import { analyzeCodeowners } from "./codeowner-analysis-BcFoet6s.js";
|
|
4
|
+
import "./consistent-sharding-lYO6XLIO.js";
|
|
5
|
+
import { analyzeDirectories } from "./directory-analysis-6DFgAaDz.js";
|
|
4
6
|
import { defineCommand, runMain } from "citty";
|
|
5
7
|
import crypto from "node:crypto";
|
|
6
8
|
import { $ } from "execa";
|
|
7
9
|
import { writeFile } from "node:fs/promises";
|
|
8
10
|
|
|
9
|
-
//#region src/commands/
|
|
10
|
-
const
|
|
11
|
+
//#region src/commands/git/create-pr.ts
|
|
12
|
+
const createPrCommand = defineCommand({
|
|
11
13
|
meta: {
|
|
12
|
-
name: "create",
|
|
14
|
+
name: "create-pr",
|
|
13
15
|
description: "Create a pull request"
|
|
14
16
|
},
|
|
15
17
|
args: {
|
|
@@ -156,17 +158,30 @@ const createCommand = defineCommand({
|
|
|
156
158
|
});
|
|
157
159
|
|
|
158
160
|
//#endregion
|
|
159
|
-
//#region src/commands/
|
|
160
|
-
const
|
|
161
|
+
//#region src/commands/git/index.ts
|
|
162
|
+
const gitCommand = defineCommand({
|
|
161
163
|
meta: {
|
|
162
|
-
name: "
|
|
163
|
-
description: "
|
|
164
|
+
name: "git",
|
|
165
|
+
description: "Git operations"
|
|
164
166
|
},
|
|
165
|
-
subCommands: {
|
|
167
|
+
subCommands: { createPr: createPrCommand }
|
|
166
168
|
});
|
|
167
169
|
|
|
168
170
|
//#endregion
|
|
169
171
|
//#region src/commands/shard/codeowner.ts
|
|
172
|
+
/**
|
|
173
|
+
* Codeowner-based sharding command
|
|
174
|
+
*
|
|
175
|
+
* Creates shards by grouping files by their CODEOWNERS team assignments.
|
|
176
|
+
* Uses simple file distribution within each team group, maintaining
|
|
177
|
+
* consistency with existing state when available.
|
|
178
|
+
*
|
|
179
|
+
* Example usage:
|
|
180
|
+
* npx codemodctl shard codeowner -l tsx -c ./codemod.ts -s 30 --stateProp shards --codeowners .github/CODEOWNERS
|
|
181
|
+
*
|
|
182
|
+
* This will analyze all applicable files, group them by CODEOWNERS team assignments, and create
|
|
183
|
+
* shards with approximately 30 files each within each team.
|
|
184
|
+
*/
|
|
170
185
|
const codeownerCommand = defineCommand({
|
|
171
186
|
meta: {
|
|
172
187
|
name: "codeowner",
|
|
@@ -217,12 +232,22 @@ const codeownerCommand = defineCommand({
|
|
|
217
232
|
}
|
|
218
233
|
try {
|
|
219
234
|
console.log(`State property: ${stateProp}`);
|
|
235
|
+
const existingStateJson = process.env.CODEMOD_STATE;
|
|
236
|
+
let existingState;
|
|
237
|
+
if (existingStateJson) try {
|
|
238
|
+
existingState = JSON.parse(existingStateJson)[stateProp];
|
|
239
|
+
console.log(`Found existing state with ${existingState.length} shards`);
|
|
240
|
+
} catch (parseError) {
|
|
241
|
+
console.warn(`Warning: Failed to parse existing state: ${parseError}`);
|
|
242
|
+
existingState = void 0;
|
|
243
|
+
}
|
|
220
244
|
const analysisOptions = {
|
|
221
245
|
shardSize,
|
|
222
246
|
codeownersPath,
|
|
223
247
|
rulePath: codemodFilePath,
|
|
224
248
|
projectRoot: process.cwd(),
|
|
225
|
-
language
|
|
249
|
+
language,
|
|
250
|
+
existingState
|
|
226
251
|
};
|
|
227
252
|
const result = await analyzeCodeowners(analysisOptions);
|
|
228
253
|
const stateOutput = `${stateProp}=${JSON.stringify(result.shards)}\n`;
|
|
@@ -238,6 +263,103 @@ const codeownerCommand = defineCommand({
|
|
|
238
263
|
}
|
|
239
264
|
});
|
|
240
265
|
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/commands/shard/directory.ts
|
|
268
|
+
/**
|
|
269
|
+
* Directory-based sharding command
|
|
270
|
+
*
|
|
271
|
+
* Creates shards by grouping files within subdirectories of a target directory.
|
|
272
|
+
* Uses consistent hashing to distribute files within each directory group, maintaining
|
|
273
|
+
* consistency with existing state when available.
|
|
274
|
+
*
|
|
275
|
+
* Example usage:
|
|
276
|
+
* npx codemodctl shard directory -l tsx -c ./codemod.ts -s 30 --stateProp shards --target packages/
|
|
277
|
+
*
|
|
278
|
+
* This will analyze all applicable files within subdirectories of 'packages/' and create
|
|
279
|
+
* shards with approximately 30 files each, grouped by directory.
|
|
280
|
+
*/
|
|
281
|
+
const directoryCommand = defineCommand({
|
|
282
|
+
meta: {
|
|
283
|
+
name: "directory",
|
|
284
|
+
description: "Create directory-based sharding output"
|
|
285
|
+
},
|
|
286
|
+
args: {
|
|
287
|
+
shardSize: {
|
|
288
|
+
type: "string",
|
|
289
|
+
alias: "s",
|
|
290
|
+
description: "Number of files per shard",
|
|
291
|
+
required: true
|
|
292
|
+
},
|
|
293
|
+
stateProp: {
|
|
294
|
+
type: "string",
|
|
295
|
+
alias: "p",
|
|
296
|
+
description: "Property name for state output",
|
|
297
|
+
required: true
|
|
298
|
+
},
|
|
299
|
+
target: {
|
|
300
|
+
type: "string",
|
|
301
|
+
description: "Target directory to shard by subdirectories",
|
|
302
|
+
required: true
|
|
303
|
+
},
|
|
304
|
+
codemodFile: {
|
|
305
|
+
type: "string",
|
|
306
|
+
alias: "c",
|
|
307
|
+
description: "Path to codemod file",
|
|
308
|
+
required: true
|
|
309
|
+
},
|
|
310
|
+
language: {
|
|
311
|
+
type: "string",
|
|
312
|
+
alias: "l",
|
|
313
|
+
description: "Language of the codemod",
|
|
314
|
+
required: true
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
async run({ args }) {
|
|
318
|
+
const { shardSize: shardSizeStr, stateProp, target, codemodFile: codemodFilePath, language } = args;
|
|
319
|
+
const shardSize = parseInt(shardSizeStr, 10);
|
|
320
|
+
if (isNaN(shardSize) || shardSize <= 0) {
|
|
321
|
+
console.error("Error: shard-size must be a positive number");
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
const stateOutputsPath = process.env.STATE_OUTPUTS;
|
|
325
|
+
if (!stateOutputsPath) {
|
|
326
|
+
console.error("Error: STATE_OUTPUTS environment variable is required");
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
console.log(`State property: ${stateProp}`);
|
|
331
|
+
console.log(`Target directory: ${target}`);
|
|
332
|
+
const existingStateJson = process.env.CODEMOD_STATE;
|
|
333
|
+
let existingState;
|
|
334
|
+
if (existingStateJson) try {
|
|
335
|
+
existingState = JSON.parse(existingStateJson)[stateProp];
|
|
336
|
+
console.log(`Found existing state with ${existingState.length} shards`);
|
|
337
|
+
} catch (parseError) {
|
|
338
|
+
console.warn(`Warning: Failed to parse existing state: ${parseError}`);
|
|
339
|
+
existingState = void 0;
|
|
340
|
+
}
|
|
341
|
+
const analysisOptions = {
|
|
342
|
+
shardSize,
|
|
343
|
+
target,
|
|
344
|
+
rulePath: codemodFilePath,
|
|
345
|
+
projectRoot: process.cwd(),
|
|
346
|
+
language,
|
|
347
|
+
existingState
|
|
348
|
+
};
|
|
349
|
+
const result = await analyzeDirectories(analysisOptions);
|
|
350
|
+
const stateOutput = `${stateProp}=${JSON.stringify(result.shards)}\n`;
|
|
351
|
+
console.log(`Writing state output to: ${stateOutputsPath}`);
|
|
352
|
+
await writeFile(stateOutputsPath, stateOutput, { flag: "a" });
|
|
353
|
+
console.log("✅ Directory-based sharding completed successfully!");
|
|
354
|
+
console.log("Generated shards:", JSON.stringify(result.shards, null, 2));
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error("❌ Failed to process directory sharding:");
|
|
357
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
241
363
|
//#endregion
|
|
242
364
|
//#region src/commands/shard/index.ts
|
|
243
365
|
const shardCommand = defineCommand({
|
|
@@ -245,7 +367,10 @@ const shardCommand = defineCommand({
|
|
|
245
367
|
name: "shard",
|
|
246
368
|
description: "Sharding operations for distributing work"
|
|
247
369
|
},
|
|
248
|
-
subCommands: {
|
|
370
|
+
subCommands: {
|
|
371
|
+
codeowner: codeownerCommand,
|
|
372
|
+
directory: directoryCommand
|
|
373
|
+
}
|
|
249
374
|
});
|
|
250
375
|
|
|
251
376
|
//#endregion
|
|
@@ -257,7 +382,7 @@ const main = defineCommand({
|
|
|
257
382
|
description: "CLI tool for workflow engine operations"
|
|
258
383
|
},
|
|
259
384
|
subCommands: {
|
|
260
|
-
|
|
385
|
+
git: gitCommand,
|
|
261
386
|
shard: shardCommand
|
|
262
387
|
}
|
|
263
388
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/codemod-cli.ts
|
|
5
|
+
/**
|
|
6
|
+
* Executes the codemod CLI command and returns applicable file paths
|
|
7
|
+
*/
|
|
8
|
+
async function getApplicableFiles(rulePath, language, projectRoot) {
|
|
9
|
+
try {
|
|
10
|
+
const command = `npx -y codemod@latest jssg list-applicable --language ${language} --target ${projectRoot} ${rulePath}`;
|
|
11
|
+
console.debug(`Executing: ${command}`);
|
|
12
|
+
const applicableFiles = execSync(command, {
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
cwd: projectRoot,
|
|
15
|
+
maxBuffer: 10 * 1024 * 1024
|
|
16
|
+
}).split("\n").filter((line) => line.startsWith("[Applicable] ")).map((line) => line.replace("[Applicable] ", "").trim()).filter((filePath) => filePath.length > 0);
|
|
17
|
+
console.debug(`Found ${applicableFiles.length} applicable files`);
|
|
18
|
+
return applicableFiles;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Error executing codemod CLI:", error);
|
|
21
|
+
throw new Error(`Failed to execute codemod CLI: ${error}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { getApplicableFiles };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
2
|
+
import { getApplicableFiles } from "./codemod-cli-DailrcEf.js";
|
|
4
3
|
import { existsSync } from "node:fs";
|
|
5
4
|
import path, { resolve } from "node:path";
|
|
6
5
|
import Codeowners from "codeowners";
|
|
@@ -32,25 +31,6 @@ function normalizeOwnerName(owner) {
|
|
|
32
31
|
return owner.replace("@", "").toLowerCase();
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
35
|
-
* Executes the codemod CLI command and returns applicable file paths
|
|
36
|
-
*/
|
|
37
|
-
async function getApplicableFiles(rulePath, language, projectRoot) {
|
|
38
|
-
try {
|
|
39
|
-
const command = `npx -y codemod@latest jssg list-applicable --language ${language} --target ${projectRoot} ${rulePath}`;
|
|
40
|
-
console.debug(`Executing: ${command}`);
|
|
41
|
-
const applicableFiles = execSync(command, {
|
|
42
|
-
encoding: "utf8",
|
|
43
|
-
cwd: projectRoot,
|
|
44
|
-
maxBuffer: 10 * 1024 * 1024
|
|
45
|
-
}).split("\n").filter((line) => line.startsWith("[Applicable] ")).map((line) => line.replace("[Applicable] ", "").trim()).filter((filePath) => filePath.length > 0);
|
|
46
|
-
console.debug(`Found ${applicableFiles.length} applicable files`);
|
|
47
|
-
return applicableFiles;
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error("Error executing codemod CLI:", error);
|
|
50
|
-
throw new Error(`Failed to execute codemod CLI: ${error}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
34
|
* Analyzes files and groups them by codeowner team
|
|
55
35
|
*/
|
|
56
36
|
async function analyzeFilesByOwner(codeownersPath, language, rulePath, projectRoot = process.cwd()) {
|
|
@@ -84,19 +64,48 @@ async function analyzeFilesWithoutOwner(language, rulePath, projectRoot = proces
|
|
|
84
64
|
return filesByOwner;
|
|
85
65
|
}
|
|
86
66
|
/**
|
|
87
|
-
*
|
|
67
|
+
* Calculate optimal number of shards based on target shard size
|
|
68
|
+
*
|
|
69
|
+
* @param totalFiles - Total number of files
|
|
70
|
+
* @param targetShardSize - Desired number of files per shard
|
|
71
|
+
* @returns Number of shards needed
|
|
88
72
|
*/
|
|
89
|
-
function
|
|
73
|
+
function calculateOptimalShardCount(totalFiles, targetShardSize) {
|
|
74
|
+
return Math.ceil(totalFiles / targetShardSize);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Generates shard configuration from team file analysis with actual file distribution.
|
|
78
|
+
* Maintains consistency with existing state when provided.
|
|
79
|
+
*
|
|
80
|
+
* @param filesByOwner - Map of team names to their file arrays
|
|
81
|
+
* @param shardSize - Target number of files per shard
|
|
82
|
+
* @param existingState - Optional existing state for consistency
|
|
83
|
+
* @returns Array of team-based shards with file assignments
|
|
84
|
+
*/
|
|
85
|
+
function generateShards(filesByOwner, shardSize, existingState) {
|
|
90
86
|
const allShards = [];
|
|
87
|
+
const existingByTeam = /* @__PURE__ */ new Map();
|
|
88
|
+
if (existingState) for (const shard of existingState) {
|
|
89
|
+
if (!existingByTeam.has(shard.team)) existingByTeam.set(shard.team, []);
|
|
90
|
+
existingByTeam.get(shard.team).push(shard);
|
|
91
|
+
}
|
|
91
92
|
for (const [team, files] of filesByOwner.entries()) {
|
|
92
93
|
const fileCount = files.length;
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
const optimalShardCount = calculateOptimalShardCount(fileCount, shardSize);
|
|
95
|
+
const existingShardCount = (existingByTeam.get(team) || []).length;
|
|
96
|
+
const numShards = existingShardCount > 0 ? existingShardCount : optimalShardCount;
|
|
97
|
+
console.log(`Team "${team}" owns ${fileCount} files, ${existingShardCount > 0 ? `maintaining ${numShards} existing shards` : `creating ${numShards} new shards`}`);
|
|
98
|
+
const sortedFiles = [...files].sort();
|
|
99
|
+
for (let i = 1; i <= numShards; i++) {
|
|
100
|
+
const shardFiles = [];
|
|
101
|
+
for (let fileIndex = i - 1; fileIndex < sortedFiles.length; fileIndex += numShards) shardFiles.push(sortedFiles[fileIndex] ?? "");
|
|
102
|
+
allShards.push({
|
|
103
|
+
team,
|
|
104
|
+
shard: `${i}/${numShards}`,
|
|
105
|
+
shardId: `${team} ${i}/${numShards}`,
|
|
106
|
+
files: shardFiles
|
|
107
|
+
});
|
|
108
|
+
}
|
|
100
109
|
}
|
|
101
110
|
return allShards;
|
|
102
111
|
}
|
|
@@ -111,10 +120,14 @@ function getTeamFileInfo(filesByOwner) {
|
|
|
111
120
|
}));
|
|
112
121
|
}
|
|
113
122
|
/**
|
|
114
|
-
* Main function to analyze codeowners and generate shard configuration
|
|
123
|
+
* Main function to analyze codeowners and generate shard configuration.
|
|
124
|
+
* Maintains consistency with existing state when provided.
|
|
125
|
+
*
|
|
126
|
+
* @param options - Configuration options for codeowner analysis
|
|
127
|
+
* @returns Promise resolving to codeowner analysis result
|
|
115
128
|
*/
|
|
116
129
|
async function analyzeCodeowners(options) {
|
|
117
|
-
const { shardSize, codeownersPath, rulePath, language, projectRoot = process.cwd() } = options;
|
|
130
|
+
const { shardSize, codeownersPath, rulePath, language, projectRoot = process.cwd(), existingState } = options;
|
|
118
131
|
const resolvedCodeownersPath = await findCodeownersFile(projectRoot, codeownersPath);
|
|
119
132
|
let filesByOwner;
|
|
120
133
|
console.debug(`Using rule file: ${rulePath}`);
|
|
@@ -129,8 +142,9 @@ async function analyzeCodeowners(options) {
|
|
|
129
142
|
filesByOwner = await analyzeFilesWithoutOwner(language, rulePath, projectRoot);
|
|
130
143
|
}
|
|
131
144
|
console.log("File analysis completed. Generating shards...");
|
|
145
|
+
if (existingState) console.debug(`Using existing state with ${existingState.length} shards`);
|
|
132
146
|
const teams = getTeamFileInfo(filesByOwner);
|
|
133
|
-
const shards = generateShards(filesByOwner, shardSize);
|
|
147
|
+
const shards = generateShards(filesByOwner, shardSize, existingState);
|
|
134
148
|
const totalFiles = Array.from(filesByOwner.values()).reduce((sum, files) => sum + files.length, 0);
|
|
135
149
|
console.log(`Generated ${shards.length} total shards for ${totalFiles} files`);
|
|
136
150
|
return {
|
|
@@ -141,4 +155,4 @@ async function analyzeCodeowners(options) {
|
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
//#endregion
|
|
144
|
-
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
158
|
+
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName };
|
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
//#region src/utils/codeowner-analysis.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Result for a single team-based shard
|
|
4
|
+
*/
|
|
2
5
|
interface ShardResult {
|
|
6
|
+
/** The team that owns these files */
|
|
3
7
|
team: string;
|
|
8
|
+
/** The shard identifier string (e.g., "1/3") */
|
|
4
9
|
shard: string;
|
|
10
|
+
/** The combined shard ID (e.g., "team-name 1/3") */
|
|
5
11
|
shardId: string;
|
|
12
|
+
/** Array of file paths in this shard */
|
|
13
|
+
files: string[];
|
|
6
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Information about a team and their files
|
|
17
|
+
*/
|
|
7
18
|
interface TeamFileInfo {
|
|
19
|
+
/** Team name */
|
|
8
20
|
team: string;
|
|
21
|
+
/** Number of files owned by this team */
|
|
9
22
|
fileCount: number;
|
|
23
|
+
/** Array of file paths owned by this team */
|
|
10
24
|
files: string[];
|
|
11
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for codeowner-based analysis
|
|
28
|
+
*/
|
|
12
29
|
interface CodeownerAnalysisOptions {
|
|
30
|
+
/** Target number of files per shard */
|
|
13
31
|
shardSize: number;
|
|
32
|
+
/** Optional path to CODEOWNERS file */
|
|
14
33
|
codeownersPath?: string;
|
|
34
|
+
/** Path to the codemod rule file */
|
|
15
35
|
rulePath: string;
|
|
36
|
+
/** Programming language for the codemod */
|
|
16
37
|
language: string;
|
|
38
|
+
/** Project root directory (defaults to process.cwd()) */
|
|
17
39
|
projectRoot?: string;
|
|
40
|
+
/** Existing state for consistency (optional) */
|
|
41
|
+
existingState?: ShardResult[];
|
|
18
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Result of codeowner-based analysis
|
|
45
|
+
*/
|
|
19
46
|
interface CodeownerAnalysisResult {
|
|
47
|
+
/** Array of team information */
|
|
20
48
|
teams: TeamFileInfo[];
|
|
49
|
+
/** Array of team-based shards with file assignments */
|
|
21
50
|
shards: ShardResult[];
|
|
51
|
+
/** Total number of files processed */
|
|
22
52
|
totalFiles: number;
|
|
23
53
|
}
|
|
24
54
|
/**
|
|
@@ -31,10 +61,6 @@ declare function findCodeownersFile(projectRoot?: string, explicitPath?: string)
|
|
|
31
61
|
* Normalizes owner name by removing @ prefix and converting to lowercase
|
|
32
62
|
*/
|
|
33
63
|
declare function normalizeOwnerName(owner: string): string;
|
|
34
|
-
/**
|
|
35
|
-
* Executes the codemod CLI command and returns applicable file paths
|
|
36
|
-
*/
|
|
37
|
-
declare function getApplicableFiles(rulePath: string, language: string, projectRoot: string): Promise<string[]>;
|
|
38
64
|
/**
|
|
39
65
|
* Analyzes files and groups them by codeowner team
|
|
40
66
|
*/
|
|
@@ -44,16 +70,26 @@ declare function analyzeFilesByOwner(codeownersPath: string, language: string, r
|
|
|
44
70
|
*/
|
|
45
71
|
declare function analyzeFilesWithoutOwner(language: string, rulePath: string, projectRoot?: string): Promise<Map<string, string[]>>;
|
|
46
72
|
/**
|
|
47
|
-
* Generates shard configuration from team file analysis
|
|
73
|
+
* Generates shard configuration from team file analysis with actual file distribution.
|
|
74
|
+
* Maintains consistency with existing state when provided.
|
|
75
|
+
*
|
|
76
|
+
* @param filesByOwner - Map of team names to their file arrays
|
|
77
|
+
* @param shardSize - Target number of files per shard
|
|
78
|
+
* @param existingState - Optional existing state for consistency
|
|
79
|
+
* @returns Array of team-based shards with file assignments
|
|
48
80
|
*/
|
|
49
|
-
declare function generateShards(filesByOwner: Map<string, string[]>, shardSize: number): ShardResult[];
|
|
81
|
+
declare function generateShards(filesByOwner: Map<string, string[]>, shardSize: number, existingState?: ShardResult[]): ShardResult[];
|
|
50
82
|
/**
|
|
51
83
|
* Converts file ownership map to team info array
|
|
52
84
|
*/
|
|
53
85
|
declare function getTeamFileInfo(filesByOwner: Map<string, string[]>): TeamFileInfo[];
|
|
54
86
|
/**
|
|
55
|
-
* Main function to analyze codeowners and generate shard configuration
|
|
87
|
+
* Main function to analyze codeowners and generate shard configuration.
|
|
88
|
+
* Maintains consistency with existing state when provided.
|
|
89
|
+
*
|
|
90
|
+
* @param options - Configuration options for codeowner analysis
|
|
91
|
+
* @returns Promise resolving to codeowner analysis result
|
|
56
92
|
*/
|
|
57
93
|
declare function analyzeCodeowners(options: CodeownerAnalysisOptions): Promise<CodeownerAnalysisResult>;
|
|
58
94
|
//#endregion
|
|
59
|
-
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
95
|
+
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName };
|
package/dist/codeowners.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
2
|
-
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
1
|
+
import { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName } from "./codeowner-analysis-CkGR1oU6.js";
|
|
2
|
+
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName };
|
package/dist/codeowners.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./
|
|
3
|
-
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
2
|
+
import "./codemod-cli-DailrcEf.js";
|
|
3
|
+
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName } from "./codeowner-analysis-BcFoet6s.js";
|
|
4
4
|
|
|
5
|
-
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
5
|
+
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName };
|
|
@@ -23,19 +23,14 @@ declare function getShardForFilename(filename: string, {
|
|
|
23
23
|
shardCount: number;
|
|
24
24
|
}): number;
|
|
25
25
|
/**
|
|
26
|
-
* Checks if a file belongs to a specific shard
|
|
26
|
+
* Checks if a file belongs to a specific shard by simply checking if it's in the shard's files list
|
|
27
27
|
*
|
|
28
28
|
* @param filename - The file path to check
|
|
29
|
-
* @param
|
|
30
|
-
* @
|
|
31
|
-
* @returns True if file belongs to the specified shard
|
|
29
|
+
* @param shard - Shard object containing files array
|
|
30
|
+
* @returns True if file is in the shard's files list
|
|
32
31
|
*/
|
|
33
|
-
declare function fitsInShard(filename: string, {
|
|
34
|
-
|
|
35
|
-
shardIndex
|
|
36
|
-
}: {
|
|
37
|
-
shardCount: number;
|
|
38
|
-
shardIndex: number;
|
|
32
|
+
declare function fitsInShard(filename: string, shard: {
|
|
33
|
+
files: string[];
|
|
39
34
|
}): boolean;
|
|
40
35
|
/**
|
|
41
36
|
* Distributes files across shards using deterministic hashing
|
|
@@ -46,15 +46,14 @@ function getShardForFilename(filename, { shardCount }) {
|
|
|
46
46
|
return shardInfo[0].index;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Checks if a file belongs to a specific shard
|
|
49
|
+
* Checks if a file belongs to a specific shard by simply checking if it's in the shard's files list
|
|
50
50
|
*
|
|
51
51
|
* @param filename - The file path to check
|
|
52
|
-
* @param
|
|
53
|
-
* @
|
|
54
|
-
* @returns True if file belongs to the specified shard
|
|
52
|
+
* @param shard - Shard object containing files array
|
|
53
|
+
* @returns True if file is in the shard's files list
|
|
55
54
|
*/
|
|
56
|
-
function fitsInShard(filename,
|
|
57
|
-
return
|
|
55
|
+
function fitsInShard(filename, shard) {
|
|
56
|
+
return shard.files.includes(filename);
|
|
58
57
|
}
|
|
59
58
|
/**
|
|
60
59
|
* Distributes files across shards using deterministic hashing
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getApplicableFiles } from "./codemod-cli-DailrcEf.js";
|
|
3
|
+
import { calculateOptimalShardCount, distributeFilesAcrossShards } from "./consistent-sharding-lYO6XLIO.js";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/utils/directory-analysis.ts
|
|
7
|
+
/**
|
|
8
|
+
* Groups files by their immediate subdirectory within the target directory
|
|
9
|
+
*
|
|
10
|
+
* @param files - Array of file paths to group
|
|
11
|
+
* @param target - Target directory to analyze subdirectories within
|
|
12
|
+
* @returns Map of subdirectory paths to their file lists
|
|
13
|
+
*/
|
|
14
|
+
function groupFilesByDirectory(files, target) {
|
|
15
|
+
const normalizedTarget = path.normalize(target);
|
|
16
|
+
const filesByDirectory = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const filePath of files) {
|
|
18
|
+
const normalizedFile = path.normalize(filePath);
|
|
19
|
+
if (!normalizedFile.startsWith(normalizedTarget)) continue;
|
|
20
|
+
const relativePath = path.relative(normalizedTarget, normalizedFile);
|
|
21
|
+
if (!relativePath.includes(path.sep)) continue;
|
|
22
|
+
const firstDir = relativePath.split(path.sep)[0];
|
|
23
|
+
if (!firstDir) continue;
|
|
24
|
+
const subdirectory = path.join(normalizedTarget, firstDir);
|
|
25
|
+
if (!filesByDirectory.has(subdirectory)) filesByDirectory.set(subdirectory, []);
|
|
26
|
+
filesByDirectory.get(subdirectory).push(normalizedFile);
|
|
27
|
+
}
|
|
28
|
+
return filesByDirectory;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates directory-based shards using consistent hashing within each directory group.
|
|
32
|
+
* Maintains consistency with existing state when provided.
|
|
33
|
+
*
|
|
34
|
+
* @param filesByDirectory - Map of directory paths to their file lists
|
|
35
|
+
* @param shardSize - Target number of files per shard
|
|
36
|
+
* @param existingState - Optional existing state for consistency
|
|
37
|
+
* @returns Array of directory-based shards
|
|
38
|
+
*/
|
|
39
|
+
function createDirectoryShards(filesByDirectory, shardSize, existingState) {
|
|
40
|
+
const allShards = [];
|
|
41
|
+
const existingByDirectory = /* @__PURE__ */ new Map();
|
|
42
|
+
if (existingState) for (const shard of existingState) {
|
|
43
|
+
if (!existingByDirectory.has(shard.directory)) existingByDirectory.set(shard.directory, []);
|
|
44
|
+
existingByDirectory.get(shard.directory).push(shard);
|
|
45
|
+
}
|
|
46
|
+
for (const [directory, files] of filesByDirectory.entries()) {
|
|
47
|
+
const fileCount = files.length;
|
|
48
|
+
const optimalShardCount = calculateOptimalShardCount(fileCount, shardSize);
|
|
49
|
+
const existingShards = existingByDirectory.get(directory) || [];
|
|
50
|
+
const existingShardCount = existingShards.length > 0 ? existingShards[0]?.shardCount ?? 0 : 0;
|
|
51
|
+
const shardCount = existingShardCount > 0 ? existingShardCount : optimalShardCount;
|
|
52
|
+
console.log(`Directory "${directory}" contains ${fileCount} files, ${existingShardCount > 0 ? `maintaining ${shardCount} existing shards` : `creating ${shardCount} new shards`}`);
|
|
53
|
+
const shardMap = distributeFilesAcrossShards(files, shardCount);
|
|
54
|
+
for (let shardIndex = 0; shardIndex < shardCount; shardIndex++) {
|
|
55
|
+
const shardFiles = shardMap.get(shardIndex) || [];
|
|
56
|
+
allShards.push({
|
|
57
|
+
directory,
|
|
58
|
+
shard: shardIndex + 1,
|
|
59
|
+
shardCount,
|
|
60
|
+
files: shardFiles.sort()
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return allShards;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Main function to analyze directories and generate shard configuration.
|
|
68
|
+
* Maintains consistency with existing state when provided.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Configuration options for directory analysis
|
|
71
|
+
* @returns Promise resolving to directory analysis result
|
|
72
|
+
* @throws Error if no files found in target subdirectories
|
|
73
|
+
*/
|
|
74
|
+
async function analyzeDirectories(options) {
|
|
75
|
+
const { shardSize, target, rulePath, language, projectRoot = process.cwd(), existingState } = options;
|
|
76
|
+
console.debug(`Using rule file: ${rulePath}`);
|
|
77
|
+
console.debug(`Target directory: ${target}`);
|
|
78
|
+
console.debug(`Shard size: ${shardSize}`);
|
|
79
|
+
if (existingState) console.debug(`Using existing state with ${existingState.length} shards`);
|
|
80
|
+
console.log("Analyzing files with CLI command...");
|
|
81
|
+
const applicableFiles = await getApplicableFiles(rulePath, language, projectRoot);
|
|
82
|
+
console.log("Grouping files by directory...");
|
|
83
|
+
const filesByDirectory = groupFilesByDirectory(applicableFiles, target);
|
|
84
|
+
if (filesByDirectory.size === 0) throw new Error(`No files found in subdirectories of target: ${target}`);
|
|
85
|
+
console.log(`Found ${filesByDirectory.size} subdirectories in target`);
|
|
86
|
+
console.log("Generating directory-based shards...");
|
|
87
|
+
const shards = createDirectoryShards(filesByDirectory, shardSize, existingState);
|
|
88
|
+
const totalFiles = Array.from(filesByDirectory.values()).reduce((sum, files) => sum + files.length, 0);
|
|
89
|
+
console.log(`Generated ${shards.length} total shards for ${totalFiles} files across ${filesByDirectory.size} directories`);
|
|
90
|
+
return {
|
|
91
|
+
shards,
|
|
92
|
+
totalFiles
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { analyzeDirectories, createDirectoryShards, groupFilesByDirectory };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/utils/directory-analysis.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Result for a single directory-based shard
|
|
4
|
+
*/
|
|
5
|
+
interface DirectoryShardResult {
|
|
6
|
+
/** The directory path this shard belongs to */
|
|
7
|
+
directory: string;
|
|
8
|
+
/** The shard number (1-based) within this directory */
|
|
9
|
+
shard: number;
|
|
10
|
+
/** Total number of shards for this directory */
|
|
11
|
+
shardCount: number;
|
|
12
|
+
/** Array of file paths in this shard */
|
|
13
|
+
files: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Options for directory-based analysis
|
|
17
|
+
*/
|
|
18
|
+
interface DirectoryAnalysisOptions {
|
|
19
|
+
/** Target number of files per shard */
|
|
20
|
+
shardSize: number;
|
|
21
|
+
/** Target directory to analyze subdirectories within */
|
|
22
|
+
target: string;
|
|
23
|
+
/** Path to the codemod rule file */
|
|
24
|
+
rulePath: string;
|
|
25
|
+
/** Programming language for the codemod */
|
|
26
|
+
language: string;
|
|
27
|
+
/** Project root directory (defaults to process.cwd()) */
|
|
28
|
+
projectRoot?: string;
|
|
29
|
+
/** Existing state for consistency (optional) */
|
|
30
|
+
existingState?: DirectoryShardResult[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Result of directory-based analysis
|
|
34
|
+
*/
|
|
35
|
+
interface DirectoryAnalysisResult {
|
|
36
|
+
/** Array of directory-based shards */
|
|
37
|
+
shards: DirectoryShardResult[];
|
|
38
|
+
/** Total number of files processed */
|
|
39
|
+
totalFiles: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Groups files by their immediate subdirectory within the target directory
|
|
43
|
+
*
|
|
44
|
+
* @param files - Array of file paths to group
|
|
45
|
+
* @param target - Target directory to analyze subdirectories within
|
|
46
|
+
* @returns Map of subdirectory paths to their file lists
|
|
47
|
+
*/
|
|
48
|
+
declare function groupFilesByDirectory(files: string[], target: string): Map<string, string[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Creates directory-based shards using consistent hashing within each directory group.
|
|
51
|
+
* Maintains consistency with existing state when provided.
|
|
52
|
+
*
|
|
53
|
+
* @param filesByDirectory - Map of directory paths to their file lists
|
|
54
|
+
* @param shardSize - Target number of files per shard
|
|
55
|
+
* @param existingState - Optional existing state for consistency
|
|
56
|
+
* @returns Array of directory-based shards
|
|
57
|
+
*/
|
|
58
|
+
declare function createDirectoryShards(filesByDirectory: Map<string, string[]>, shardSize: number, existingState?: DirectoryShardResult[]): DirectoryShardResult[];
|
|
59
|
+
/**
|
|
60
|
+
* Main function to analyze directories and generate shard configuration.
|
|
61
|
+
* Maintains consistency with existing state when provided.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Configuration options for directory analysis
|
|
64
|
+
* @returns Promise resolving to directory analysis result
|
|
65
|
+
* @throws Error if no files found in target subdirectories
|
|
66
|
+
*/
|
|
67
|
+
declare function analyzeDirectories(options: DirectoryAnalysisOptions): Promise<DirectoryAnalysisResult>;
|
|
68
|
+
//#endregion
|
|
69
|
+
export { DirectoryAnalysisOptions, DirectoryAnalysisResult, DirectoryShardResult, analyzeDirectories, createDirectoryShards, groupFilesByDirectory };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./codemod-cli-DailrcEf.js";
|
|
3
|
+
import "./consistent-sharding-lYO6XLIO.js";
|
|
4
|
+
import { analyzeDirectories, createDirectoryShards, groupFilesByDirectory } from "./directory-analysis-6DFgAaDz.js";
|
|
5
|
+
|
|
6
|
+
export { analyzeDirectories, createDirectoryShards, groupFilesByDirectory };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-
|
|
2
|
-
import { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
3
|
-
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards,
|
|
1
|
+
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-B-Si8jYX.js";
|
|
2
|
+
import { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName } from "./codeowner-analysis-CkGR1oU6.js";
|
|
3
|
+
export { CodeownerAnalysisOptions, CodeownerAnalysisResult, ShardResult, TeamFileInfo, analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards,
|
|
2
|
+
import "./codemod-cli-DailrcEf.js";
|
|
3
|
+
import { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, findCodeownersFile, generateShards, getTeamFileInfo, normalizeOwnerName } from "./codeowner-analysis-BcFoet6s.js";
|
|
4
|
+
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-lYO6XLIO.js";
|
|
4
5
|
|
|
5
|
-
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards,
|
|
6
|
+
export { analyzeCodeowners, analyzeFilesByOwner, analyzeFilesWithoutOwner, analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, findCodeownersFile, fitsInShard, generateShards, getFileHashPosition, getNumericFileNameSha1, getShardForFilename, getTeamFileInfo, normalizeOwnerName };
|
package/dist/sharding.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-
|
|
1
|
+
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-B-Si8jYX.js";
|
|
2
2
|
export { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename };
|
package/dist/sharding.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-
|
|
2
|
+
import { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename } from "./consistent-sharding-lYO6XLIO.js";
|
|
3
3
|
|
|
4
4
|
export { analyzeShardScaling, calculateOptimalShardCount, distributeFilesAcrossShards, fitsInShard, getFileHashPosition, getNumericFileNameSha1, getShardForFilename };
|