@kamaalio/codemod-kit 0.0.33 → 0.0.34
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/git/errors.d.ts +13 -13
- package/dist/git/index.d.ts +1 -1
- package/dist/git/repository.d.ts +15 -11
- package/dist/git/utils.d.ts +4 -3
- package/dist/github/utils.d.ts +2 -2
- package/dist/index.cjs +95 -47
- package/dist/index.js +96 -48
- package/dist/utils/arrays.d.ts +1 -0
- package/dist/utils/collections.d.ts +2 -1
- package/package.json +1 -1
package/dist/git/errors.d.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import type { types } from '@kamaalio/kamaal';
|
|
2
2
|
import type Repository from './repository.js';
|
|
3
|
-
export declare class GitError extends Error {
|
|
3
|
+
export declare class GitError<Tag = string> extends Error {
|
|
4
4
|
readonly cause: types.Optional<unknown>;
|
|
5
|
-
readonly repository: Repository
|
|
6
|
-
constructor(message: string, repository: Repository
|
|
5
|
+
readonly repository: Repository<Tag>;
|
|
6
|
+
constructor(message: string, repository: Repository<Tag>, options?: {
|
|
7
7
|
cause: unknown;
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
|
-
export declare class CloneError extends GitError {
|
|
11
|
-
constructor(repository: Repository);
|
|
10
|
+
export declare class CloneError<Tag = string> extends GitError<Tag> {
|
|
11
|
+
constructor(repository: Repository<Tag>);
|
|
12
12
|
}
|
|
13
|
-
export declare class CheckoutError extends GitError {
|
|
14
|
-
constructor(repository: Repository
|
|
13
|
+
export declare class CheckoutError<Tag = string> extends GitError<Tag> {
|
|
14
|
+
constructor(repository: Repository<Tag>, branchName: string);
|
|
15
15
|
}
|
|
16
|
-
export declare class GetMainBranchError extends GitError {
|
|
17
|
-
constructor(repository: Repository
|
|
16
|
+
export declare class GetMainBranchError<Tag = string> extends GitError<Tag> {
|
|
17
|
+
constructor(repository: Repository<Tag>, options?: {
|
|
18
18
|
cause: unknown;
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
export declare class PushError extends GitError {
|
|
22
|
-
constructor(repository: Repository
|
|
21
|
+
export declare class PushError<Tag = string> extends GitError<Tag> {
|
|
22
|
+
constructor(repository: Repository<Tag>, message: string, options?: {
|
|
23
23
|
cause: unknown;
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
export declare class RebaseError extends GitError {
|
|
27
|
-
constructor(repository: Repository
|
|
26
|
+
export declare class RebaseError<Tag = string> extends GitError<Tag> {
|
|
27
|
+
constructor(repository: Repository<Tag>, options?: {
|
|
28
28
|
cause: unknown;
|
|
29
29
|
});
|
|
30
30
|
}
|
package/dist/git/index.d.ts
CHANGED
package/dist/git/repository.d.ts
CHANGED
|
@@ -2,29 +2,32 @@ import { type ResultAsync, type Result } from 'neverthrow';
|
|
|
2
2
|
import { type types } from '@kamaalio/kamaal';
|
|
3
3
|
import { GitError } from './errors.js';
|
|
4
4
|
import Branch from './branch.js';
|
|
5
|
-
interface IRepository {
|
|
5
|
+
interface IRepository<Tag> {
|
|
6
6
|
name: string;
|
|
7
7
|
address: string;
|
|
8
8
|
path: string;
|
|
9
|
+
tags: Array<Tag> | Set<Tag>;
|
|
9
10
|
}
|
|
10
|
-
declare class Repository implements IRepository {
|
|
11
|
+
declare class Repository<Tag = string> implements IRepository<Tag> {
|
|
11
12
|
readonly name: string;
|
|
12
13
|
readonly address: string;
|
|
13
14
|
readonly path: string;
|
|
15
|
+
readonly tags: Array<Tag> | Set<Tag>;
|
|
14
16
|
private currentBranch;
|
|
15
17
|
private constructor();
|
|
16
|
-
|
|
18
|
+
copy: (newPath: string) => Promise<Repository<Tag>>;
|
|
19
|
+
clone: () => Promise<Result<void, GitError<Tag>>>;
|
|
17
20
|
commit: (message: string) => Promise<void>;
|
|
18
|
-
push: () => Promise<Result<void, GitError
|
|
19
|
-
resetBranch: (workingBranchName: string) => Promise<Result<void, GitError
|
|
20
|
-
updateBranchToLatestMain: (workingBranchName: string) => Promise<Result<void, GitError
|
|
21
|
+
push: () => Promise<Result<void, GitError<Tag>>>;
|
|
22
|
+
resetBranch: (workingBranchName: string) => Promise<Result<void, GitError<Tag>>>;
|
|
23
|
+
updateBranchToLatestMain: (workingBranchName: string) => Promise<Result<void, GitError<Tag>>>;
|
|
21
24
|
deleteBranch: (branchName: string) => ResultAsync<unknown, unknown>;
|
|
22
|
-
prepareForUpdate: (workingBranchName: string) => Promise<Result<Repository
|
|
23
|
-
getMainBranch: () => Promise<Result<Branch, GitError
|
|
25
|
+
prepareForUpdate: (workingBranchName: string) => Promise<Result<Repository<Tag>, GitError<Tag>>>;
|
|
26
|
+
getMainBranch: () => Promise<Result<Branch, GitError<Tag>>>;
|
|
24
27
|
getRemoteName: () => Promise<types.Optional<string>>;
|
|
25
28
|
checkoutBranch: (branchName: string, options?: {
|
|
26
29
|
forceCreateNew: boolean;
|
|
27
|
-
}) => Promise<Result<Branch, GitError
|
|
30
|
+
}) => Promise<Result<Branch, GitError<Tag>>>;
|
|
28
31
|
getSelectedBranch: () => Promise<types.Optional<Branch>>;
|
|
29
32
|
getRemoteBranches: () => Promise<Array<Branch>>;
|
|
30
33
|
getBranches: () => Promise<Array<Branch>>;
|
|
@@ -32,10 +35,11 @@ declare class Repository implements IRepository {
|
|
|
32
35
|
private getCurrentBranch;
|
|
33
36
|
private setCurrentBranch;
|
|
34
37
|
private getMainBranchName;
|
|
35
|
-
static fromAddressAndCwd: ({ address, cwd }: {
|
|
38
|
+
static fromAddressAndCwd: <Tag_1>({ address, cwd, tags, }: {
|
|
36
39
|
address: string;
|
|
37
40
|
cwd: string;
|
|
38
|
-
|
|
41
|
+
tags: Array<Tag_1> | Set<Tag_1>;
|
|
42
|
+
}) => types.Optional<Repository<Tag_1>>;
|
|
39
43
|
private static getRepoNameFromRepoAddress;
|
|
40
44
|
}
|
|
41
45
|
export default Repository;
|
package/dist/git/utils.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type types } from '@kamaalio/kamaal';
|
|
2
1
|
import Repository from './repository.js';
|
|
3
|
-
export declare function cloneRepositories
|
|
4
|
-
|
|
2
|
+
export declare function cloneRepositories<Tag = string>(repositories: Array<{
|
|
3
|
+
address: string;
|
|
4
|
+
tags: Array<Tag> | Set<Tag>;
|
|
5
|
+
}>, location: string): Promise<Array<Repository<Tag>>>;
|
package/dist/github/utils.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type { Codemod, CodemodRunnerCodemod } from '../codemods/index.js';
|
|
|
4
4
|
export declare function makePullRequestsForCodemodResults<Tag = string, C extends Codemod = Codemod>(codemods: Array<CodemodRunnerCodemod<Tag, C>>, codemodResults: Record<string, Array<Result<{
|
|
5
5
|
hasChanges: boolean;
|
|
6
6
|
content: string;
|
|
7
|
-
}, Error>>>, repositories: Array<Repository
|
|
7
|
+
}, Error>>>, repositories: Array<Repository<Tag>>): Promise<void>;
|
|
8
8
|
export declare function makePullRequestsForCodemodResult<Tag = string, C extends Codemod = Codemod>(codemod: CodemodRunnerCodemod<Tag, C>, codemodResult: Array<Result<{
|
|
9
9
|
hasChanges: boolean;
|
|
10
10
|
content: string;
|
|
11
|
-
}, Error>>, repositories: Array<Repository
|
|
11
|
+
}, Error>>, repositories: Array<Repository<Tag>>): Promise<void>;
|
package/dist/index.cjs
CHANGED
|
@@ -84,11 +84,17 @@ function getCollectionCount(collection) {
|
|
|
84
84
|
function collectionIsEmpty(collection) {
|
|
85
85
|
return 0 === getCollectionCount(collection);
|
|
86
86
|
}
|
|
87
|
-
function
|
|
88
|
-
|
|
87
|
+
function collectionContains(collection, value) {
|
|
88
|
+
if (Array.isArray(collection)) return collection.includes(value);
|
|
89
|
+
return collection.has(value);
|
|
90
|
+
}
|
|
91
|
+
function shallowCopy(array) {
|
|
92
|
+
return [
|
|
89
93
|
...array
|
|
90
94
|
];
|
|
91
|
-
|
|
95
|
+
}
|
|
96
|
+
function groupBy(array, key) {
|
|
97
|
+
return shallowCopy(array).reduce((acc, current)=>{
|
|
92
98
|
const keyValue = String(current[key]);
|
|
93
99
|
if (null == acc[keyValue]) acc[keyValue] = [
|
|
94
100
|
current
|
|
@@ -97,6 +103,13 @@ function groupBy(array, key) {
|
|
|
97
103
|
return acc;
|
|
98
104
|
}, {});
|
|
99
105
|
}
|
|
106
|
+
function groupByFlat(array, key) {
|
|
107
|
+
return shallowCopy(array).reduce((acc, current)=>{
|
|
108
|
+
const keyValue = String(current[key]);
|
|
109
|
+
acc[keyValue] = current;
|
|
110
|
+
return acc;
|
|
111
|
+
}, {});
|
|
112
|
+
}
|
|
100
113
|
const external_execa_namespaceObject = require("execa");
|
|
101
114
|
class GitError extends Error {
|
|
102
115
|
cause;
|
|
@@ -183,12 +196,24 @@ class Repository {
|
|
|
183
196
|
name;
|
|
184
197
|
address;
|
|
185
198
|
path;
|
|
199
|
+
tags;
|
|
186
200
|
currentBranch;
|
|
187
201
|
constructor(params){
|
|
188
202
|
this.name = params.name;
|
|
189
203
|
this.address = params.address;
|
|
190
204
|
this.path = params.path;
|
|
205
|
+
this.tags = params.tags;
|
|
191
206
|
}
|
|
207
|
+
copy = async (newPath)=>{
|
|
208
|
+
await (0, external_execa_namespaceObject.$)`rm -rf ${newPath}`;
|
|
209
|
+
await (0, external_execa_namespaceObject.$)`cp -R ${this.path} ${newPath}`;
|
|
210
|
+
return new Repository({
|
|
211
|
+
name: this.name,
|
|
212
|
+
address: this.address,
|
|
213
|
+
path: newPath,
|
|
214
|
+
tags: this.tags
|
|
215
|
+
});
|
|
216
|
+
};
|
|
192
217
|
clone = async ()=>{
|
|
193
218
|
const cwd = this.path.split('/').slice(0, -1).join('/');
|
|
194
219
|
const exec = (0, external_execa_namespaceObject.$)({
|
|
@@ -351,13 +376,14 @@ class Repository {
|
|
|
351
376
|
}));
|
|
352
377
|
return (0, external_neverthrow_namespaceObject.ok)(defaultBranchRef.data.defaultBranchRef.name);
|
|
353
378
|
};
|
|
354
|
-
static fromAddressAndCwd = ({ address, cwd })=>{
|
|
379
|
+
static fromAddressAndCwd = ({ address, cwd, tags })=>{
|
|
355
380
|
const name = Repository.getRepoNameFromRepoAddress(address);
|
|
356
381
|
if (null == name) return null;
|
|
357
382
|
return new Repository({
|
|
358
383
|
name,
|
|
359
384
|
address,
|
|
360
|
-
path: external_node_path_default().resolve(cwd, name)
|
|
385
|
+
path: external_node_path_default().resolve(cwd, name),
|
|
386
|
+
tags
|
|
361
387
|
});
|
|
362
388
|
};
|
|
363
389
|
static getRepoNameFromRepoAddress = (repoAddress)=>{
|
|
@@ -366,13 +392,13 @@ class Repository {
|
|
|
366
392
|
};
|
|
367
393
|
}
|
|
368
394
|
const git_repository = Repository;
|
|
369
|
-
async function cloneRepositories(
|
|
395
|
+
async function cloneRepositories(repositories, location) {
|
|
370
396
|
await (0, external_execa_namespaceObject.$)`mkdir -p ${location}`;
|
|
371
397
|
const exec = (0, external_execa_namespaceObject.$)({
|
|
372
398
|
cwd: location
|
|
373
399
|
});
|
|
374
|
-
const dedupedRepos = dedupeRepositoriesToClone(
|
|
375
|
-
const existingRepositories = await getExistingRepositories(exec,
|
|
400
|
+
const dedupedRepos = dedupeRepositoriesToClone(repositories, location);
|
|
401
|
+
const existingRepositories = await getExistingRepositories(exec, repositories);
|
|
376
402
|
const results = await Promise.all(dedupedRepos.map(cloneRepositoryInternal(existingRepositories)));
|
|
377
403
|
return kamaal_namespaceObject.arrays.compactMap(results, (result, index)=>{
|
|
378
404
|
const repo = dedupedRepos[index];
|
|
@@ -383,13 +409,14 @@ async function cloneRepositories(repoAddresses, location) {
|
|
|
383
409
|
return repo;
|
|
384
410
|
});
|
|
385
411
|
}
|
|
386
|
-
async function getExistingRepositories(exec,
|
|
412
|
+
async function getExistingRepositories(exec, repositories) {
|
|
387
413
|
const lsResult = await exec`ls`;
|
|
388
414
|
const existingNames = new Set(lsResult.stdout.split('\n'));
|
|
389
|
-
return kamaal_namespaceObject.arrays.compactMap(
|
|
415
|
+
return kamaal_namespaceObject.arrays.compactMap(repositories, (repo)=>{
|
|
390
416
|
const repository = git_repository.fromAddressAndCwd({
|
|
391
|
-
address,
|
|
392
|
-
cwd: lsResult.cwd
|
|
417
|
+
address: repo.address,
|
|
418
|
+
cwd: lsResult.cwd,
|
|
419
|
+
tags: repo.tags
|
|
393
420
|
});
|
|
394
421
|
if (null == repository) return null;
|
|
395
422
|
if (!existingNames.has(repository.name)) return null;
|
|
@@ -403,15 +430,16 @@ function cloneRepositoryInternal(existingRepositories) {
|
|
|
403
430
|
return repository.clone();
|
|
404
431
|
};
|
|
405
432
|
}
|
|
406
|
-
function dedupeRepositoriesToClone(
|
|
433
|
+
function dedupeRepositoriesToClone(repositories, cwd) {
|
|
407
434
|
const initialDedupeResult = {
|
|
408
435
|
result: [],
|
|
409
436
|
names: []
|
|
410
437
|
};
|
|
411
|
-
const dedupedRepos =
|
|
438
|
+
const dedupedRepos = repositories.reduce((acc, repo)=>{
|
|
412
439
|
const repository = git_repository.fromAddressAndCwd({
|
|
413
|
-
address:
|
|
414
|
-
cwd
|
|
440
|
+
address: repo.address,
|
|
441
|
+
cwd,
|
|
442
|
+
tags: repo.tags
|
|
415
443
|
});
|
|
416
444
|
if (null == repository) return acc;
|
|
417
445
|
if (acc.names.includes(repository.name)) return acc;
|
|
@@ -457,33 +485,22 @@ async function makePullRequest(params) {
|
|
|
457
485
|
})`gh pr create --title ${params.title} --fill`);
|
|
458
486
|
}
|
|
459
487
|
async function runCodemodsOnProjects(repositoriesToClone, codemods, options) {
|
|
460
|
-
const clonedRepositories = await cloneRepositories(repositoriesToClone
|
|
488
|
+
const clonedRepositories = await cloneRepositories(repositoriesToClone, options.workingDirectory);
|
|
461
489
|
console.log(`\u{1F5A8}\u{FE0F} cloned ${clonedRepositories.length} ${1 === clonedRepositories.length ? 'repository' : 'repositories'}`);
|
|
462
|
-
const
|
|
463
|
-
const tags = repositoriesToClone.find(({ address })=>address === repository.address)?.tags;
|
|
464
|
-
kamaal_namespaceObject.asserts.invariant(null != tags, 'Tags should be present');
|
|
465
|
-
return {
|
|
466
|
-
...acc,
|
|
467
|
-
[repository.name]: {
|
|
468
|
-
repository,
|
|
469
|
-
tags
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
}, {});
|
|
473
|
-
const codemodResults = await runCodemodRunner(codemods, clonedRepositories, mappingsByName, options.workingDirectory);
|
|
490
|
+
const codemodResults = await runCodemodRunner(codemods, clonedRepositories, options.workingDirectory);
|
|
474
491
|
if (options.pushChanges) await makePullRequestsForCodemodResults(codemods, codemodResults, clonedRepositories);
|
|
475
492
|
}
|
|
476
|
-
function utils_codemodTargetFiltering(
|
|
493
|
+
function utils_codemodTargetFiltering(repositories, failedRepositoryAddressesMappedByCodemodNames) {
|
|
477
494
|
return (filepath, codemod)=>{
|
|
478
495
|
const projectName = filepath.split('/')[0];
|
|
479
496
|
kamaal_namespaceObject.asserts.invariant(null != projectName, 'project name should be present');
|
|
480
|
-
const
|
|
481
|
-
if (null ==
|
|
497
|
+
const repository = repositories[projectName];
|
|
498
|
+
if (null == repository) return false;
|
|
482
499
|
const failedRepositoryAddressesSet = failedRepositoryAddressesMappedByCodemodNames[codemod.name];
|
|
483
500
|
if (null == failedRepositoryAddressesSet || collectionIsEmpty(failedRepositoryAddressesSet)) return true;
|
|
484
|
-
return !failedRepositoryAddressesSet.has(
|
|
501
|
+
return !failedRepositoryAddressesSet.has(repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(repository.tags) || [
|
|
485
502
|
...codemod.tags
|
|
486
|
-
].some((tag)=>
|
|
503
|
+
].some((tag)=>collectionContains(repository.tags, tag)));
|
|
487
504
|
};
|
|
488
505
|
}
|
|
489
506
|
async function codemodPreCodemodRun(repositories, codemod) {
|
|
@@ -496,19 +513,50 @@ async function codemodPreCodemodRun(repositories, codemod) {
|
|
|
496
513
|
async function codemodPostTransform(transformedContent) {
|
|
497
514
|
return transformedContent;
|
|
498
515
|
}
|
|
499
|
-
async function runCodemodRunner(codemods, repositories,
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
516
|
+
async function runCodemodRunner(codemods, repositories, workingDirectory) {
|
|
517
|
+
const codemodRepositoriesMappedByCodemodName = await prepareRepositoriesForCodemods(repositories, codemods, workingDirectory);
|
|
518
|
+
const results = Object.fromEntries(await Promise.all(codemods.map(async (codemod)=>{
|
|
519
|
+
const codemodRepositories = codemodRepositoriesMappedByCodemodName[codemod.name];
|
|
520
|
+
kamaal_namespaceObject.asserts.invariant(null != codemodRepositories, 'Codemod repositories should be present');
|
|
521
|
+
const failedRepositoryAddressesMappedByCodemodNames = {};
|
|
522
|
+
const result = await runCodemod(codemod, workingDirectory, {
|
|
523
|
+
rootPaths: codemodRepositories.map((repository)=>repository.path),
|
|
524
|
+
hooks: {
|
|
525
|
+
preCodemodRun: async (codemod)=>{
|
|
526
|
+
failedRepositoryAddressesMappedByCodemodNames[codemod.name] = await codemodPreCodemodRun(repositories, codemod);
|
|
527
|
+
},
|
|
528
|
+
targetFiltering: utils_codemodTargetFiltering(groupByFlat(codemodRepositories, 'name'), failedRepositoryAddressesMappedByCodemodNames),
|
|
529
|
+
postTransform: codemodPostTransform
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
return [
|
|
533
|
+
codemod.name,
|
|
534
|
+
result
|
|
535
|
+
];
|
|
536
|
+
})));
|
|
537
|
+
return results;
|
|
538
|
+
}
|
|
539
|
+
async function prepareRepositoriesForCodemods(repositories, codemods, workingDirectory) {
|
|
540
|
+
const reposMappedByMainBranchName = Object.fromEntries(await Promise.all(repositories.map(async (repo)=>{
|
|
541
|
+
const mainBranchResult = await repo.getMainBranch();
|
|
542
|
+
if (mainBranchResult.isErr()) throw mainBranchResult.error;
|
|
543
|
+
return [
|
|
544
|
+
mainBranchResult.value.name,
|
|
545
|
+
repo
|
|
546
|
+
];
|
|
547
|
+
})));
|
|
548
|
+
const updatedRepositories = await Promise.all(kamaal_namespaceObject.objects.toEntries(reposMappedByMainBranchName).map(async ([mainBranchName, repo])=>{
|
|
549
|
+
const prepareResult = await repo.prepareForUpdate(mainBranchName);
|
|
550
|
+
if (prepareResult.isErr()) throw prepareResult.error;
|
|
551
|
+
return prepareResult.value;
|
|
552
|
+
}));
|
|
553
|
+
return Object.fromEntries(await Promise.all(codemods.map(async (codemod)=>{
|
|
554
|
+
const codeRepositories = await Promise.all(updatedRepositories.map((repo)=>repo.copy(external_node_path_default().resolve(workingDirectory, codemod.name, repo.name))));
|
|
555
|
+
return [
|
|
556
|
+
codemod.name,
|
|
557
|
+
codeRepositories
|
|
558
|
+
];
|
|
559
|
+
})));
|
|
512
560
|
}
|
|
513
561
|
async function runCodemods(codemods, transformationPath, options) {
|
|
514
562
|
const results = {};
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import promises from "node:fs/promises";
|
|
|
3
3
|
import fast_glob from "fast-glob";
|
|
4
4
|
import { ResultAsync, err, ok } from "neverthrow";
|
|
5
5
|
import { Lang, parseAsync } from "@ast-grep/napi";
|
|
6
|
-
import { arrays, asserts } from "@kamaalio/kamaal";
|
|
6
|
+
import { arrays, asserts, objects } from "@kamaalio/kamaal";
|
|
7
7
|
import { $ } from "execa";
|
|
8
8
|
import zod from "zod";
|
|
9
9
|
const JAVASCRIPT_EXTENSIONS = [
|
|
@@ -38,11 +38,17 @@ function getCollectionCount(collection) {
|
|
|
38
38
|
function collectionIsEmpty(collection) {
|
|
39
39
|
return 0 === getCollectionCount(collection);
|
|
40
40
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
41
|
+
function collectionContains(collection, value) {
|
|
42
|
+
if (Array.isArray(collection)) return collection.includes(value);
|
|
43
|
+
return collection.has(value);
|
|
44
|
+
}
|
|
45
|
+
function shallowCopy(array) {
|
|
46
|
+
return [
|
|
43
47
|
...array
|
|
44
48
|
];
|
|
45
|
-
|
|
49
|
+
}
|
|
50
|
+
function groupBy(array, key) {
|
|
51
|
+
return shallowCopy(array).reduce((acc, current)=>{
|
|
46
52
|
const keyValue = String(current[key]);
|
|
47
53
|
if (null == acc[keyValue]) acc[keyValue] = [
|
|
48
54
|
current
|
|
@@ -51,6 +57,13 @@ function groupBy(array, key) {
|
|
|
51
57
|
return acc;
|
|
52
58
|
}, {});
|
|
53
59
|
}
|
|
60
|
+
function groupByFlat(array, key) {
|
|
61
|
+
return shallowCopy(array).reduce((acc, current)=>{
|
|
62
|
+
const keyValue = String(current[key]);
|
|
63
|
+
acc[keyValue] = current;
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
}
|
|
54
67
|
class GitError extends Error {
|
|
55
68
|
cause;
|
|
56
69
|
repository;
|
|
@@ -134,12 +147,24 @@ class Repository {
|
|
|
134
147
|
name;
|
|
135
148
|
address;
|
|
136
149
|
path;
|
|
150
|
+
tags;
|
|
137
151
|
currentBranch;
|
|
138
152
|
constructor(params){
|
|
139
153
|
this.name = params.name;
|
|
140
154
|
this.address = params.address;
|
|
141
155
|
this.path = params.path;
|
|
156
|
+
this.tags = params.tags;
|
|
142
157
|
}
|
|
158
|
+
copy = async (newPath)=>{
|
|
159
|
+
await $`rm -rf ${newPath}`;
|
|
160
|
+
await $`cp -R ${this.path} ${newPath}`;
|
|
161
|
+
return new Repository({
|
|
162
|
+
name: this.name,
|
|
163
|
+
address: this.address,
|
|
164
|
+
path: newPath,
|
|
165
|
+
tags: this.tags
|
|
166
|
+
});
|
|
167
|
+
};
|
|
143
168
|
clone = async ()=>{
|
|
144
169
|
const cwd = this.path.split('/').slice(0, -1).join('/');
|
|
145
170
|
const exec = $({
|
|
@@ -302,13 +327,14 @@ class Repository {
|
|
|
302
327
|
}));
|
|
303
328
|
return ok(defaultBranchRef.data.defaultBranchRef.name);
|
|
304
329
|
};
|
|
305
|
-
static fromAddressAndCwd = ({ address, cwd })=>{
|
|
330
|
+
static fromAddressAndCwd = ({ address, cwd, tags })=>{
|
|
306
331
|
const name = Repository.getRepoNameFromRepoAddress(address);
|
|
307
332
|
if (null == name) return null;
|
|
308
333
|
return new Repository({
|
|
309
334
|
name,
|
|
310
335
|
address,
|
|
311
|
-
path: node_path.resolve(cwd, name)
|
|
336
|
+
path: node_path.resolve(cwd, name),
|
|
337
|
+
tags
|
|
312
338
|
});
|
|
313
339
|
};
|
|
314
340
|
static getRepoNameFromRepoAddress = (repoAddress)=>{
|
|
@@ -317,13 +343,13 @@ class Repository {
|
|
|
317
343
|
};
|
|
318
344
|
}
|
|
319
345
|
const git_repository = Repository;
|
|
320
|
-
async function cloneRepositories(
|
|
346
|
+
async function cloneRepositories(repositories, location) {
|
|
321
347
|
await $`mkdir -p ${location}`;
|
|
322
348
|
const exec = $({
|
|
323
349
|
cwd: location
|
|
324
350
|
});
|
|
325
|
-
const dedupedRepos = dedupeRepositoriesToClone(
|
|
326
|
-
const existingRepositories = await getExistingRepositories(exec,
|
|
351
|
+
const dedupedRepos = dedupeRepositoriesToClone(repositories, location);
|
|
352
|
+
const existingRepositories = await getExistingRepositories(exec, repositories);
|
|
327
353
|
const results = await Promise.all(dedupedRepos.map(cloneRepositoryInternal(existingRepositories)));
|
|
328
354
|
return arrays.compactMap(results, (result, index)=>{
|
|
329
355
|
const repo = dedupedRepos[index];
|
|
@@ -334,13 +360,14 @@ async function cloneRepositories(repoAddresses, location) {
|
|
|
334
360
|
return repo;
|
|
335
361
|
});
|
|
336
362
|
}
|
|
337
|
-
async function getExistingRepositories(exec,
|
|
363
|
+
async function getExistingRepositories(exec, repositories) {
|
|
338
364
|
const lsResult = await exec`ls`;
|
|
339
365
|
const existingNames = new Set(lsResult.stdout.split('\n'));
|
|
340
|
-
return arrays.compactMap(
|
|
366
|
+
return arrays.compactMap(repositories, (repo)=>{
|
|
341
367
|
const repository = git_repository.fromAddressAndCwd({
|
|
342
|
-
address,
|
|
343
|
-
cwd: lsResult.cwd
|
|
368
|
+
address: repo.address,
|
|
369
|
+
cwd: lsResult.cwd,
|
|
370
|
+
tags: repo.tags
|
|
344
371
|
});
|
|
345
372
|
if (null == repository) return null;
|
|
346
373
|
if (!existingNames.has(repository.name)) return null;
|
|
@@ -354,15 +381,16 @@ function cloneRepositoryInternal(existingRepositories) {
|
|
|
354
381
|
return repository.clone();
|
|
355
382
|
};
|
|
356
383
|
}
|
|
357
|
-
function dedupeRepositoriesToClone(
|
|
384
|
+
function dedupeRepositoriesToClone(repositories, cwd) {
|
|
358
385
|
const initialDedupeResult = {
|
|
359
386
|
result: [],
|
|
360
387
|
names: []
|
|
361
388
|
};
|
|
362
|
-
const dedupedRepos =
|
|
389
|
+
const dedupedRepos = repositories.reduce((acc, repo)=>{
|
|
363
390
|
const repository = git_repository.fromAddressAndCwd({
|
|
364
|
-
address:
|
|
365
|
-
cwd
|
|
391
|
+
address: repo.address,
|
|
392
|
+
cwd,
|
|
393
|
+
tags: repo.tags
|
|
366
394
|
});
|
|
367
395
|
if (null == repository) return acc;
|
|
368
396
|
if (acc.names.includes(repository.name)) return acc;
|
|
@@ -408,33 +436,22 @@ async function makePullRequest(params) {
|
|
|
408
436
|
})`gh pr create --title ${params.title} --fill`);
|
|
409
437
|
}
|
|
410
438
|
async function runCodemodsOnProjects(repositoriesToClone, codemods, options) {
|
|
411
|
-
const clonedRepositories = await cloneRepositories(repositoriesToClone
|
|
439
|
+
const clonedRepositories = await cloneRepositories(repositoriesToClone, options.workingDirectory);
|
|
412
440
|
console.log(`\u{1F5A8}\u{FE0F} cloned ${clonedRepositories.length} ${1 === clonedRepositories.length ? 'repository' : 'repositories'}`);
|
|
413
|
-
const
|
|
414
|
-
const tags = repositoriesToClone.find(({ address })=>address === repository.address)?.tags;
|
|
415
|
-
asserts.invariant(null != tags, 'Tags should be present');
|
|
416
|
-
return {
|
|
417
|
-
...acc,
|
|
418
|
-
[repository.name]: {
|
|
419
|
-
repository,
|
|
420
|
-
tags
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
}, {});
|
|
424
|
-
const codemodResults = await runCodemodRunner(codemods, clonedRepositories, mappingsByName, options.workingDirectory);
|
|
441
|
+
const codemodResults = await runCodemodRunner(codemods, clonedRepositories, options.workingDirectory);
|
|
425
442
|
if (options.pushChanges) await makePullRequestsForCodemodResults(codemods, codemodResults, clonedRepositories);
|
|
426
443
|
}
|
|
427
|
-
function utils_codemodTargetFiltering(
|
|
444
|
+
function utils_codemodTargetFiltering(repositories, failedRepositoryAddressesMappedByCodemodNames) {
|
|
428
445
|
return (filepath, codemod)=>{
|
|
429
446
|
const projectName = filepath.split('/')[0];
|
|
430
447
|
asserts.invariant(null != projectName, 'project name should be present');
|
|
431
|
-
const
|
|
432
|
-
if (null ==
|
|
448
|
+
const repository = repositories[projectName];
|
|
449
|
+
if (null == repository) return false;
|
|
433
450
|
const failedRepositoryAddressesSet = failedRepositoryAddressesMappedByCodemodNames[codemod.name];
|
|
434
451
|
if (null == failedRepositoryAddressesSet || collectionIsEmpty(failedRepositoryAddressesSet)) return true;
|
|
435
|
-
return !failedRepositoryAddressesSet.has(
|
|
452
|
+
return !failedRepositoryAddressesSet.has(repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(repository.tags) || [
|
|
436
453
|
...codemod.tags
|
|
437
|
-
].some((tag)=>
|
|
454
|
+
].some((tag)=>collectionContains(repository.tags, tag)));
|
|
438
455
|
};
|
|
439
456
|
}
|
|
440
457
|
async function codemodPreCodemodRun(repositories, codemod) {
|
|
@@ -447,19 +464,50 @@ async function codemodPreCodemodRun(repositories, codemod) {
|
|
|
447
464
|
async function codemodPostTransform(transformedContent) {
|
|
448
465
|
return transformedContent;
|
|
449
466
|
}
|
|
450
|
-
async function runCodemodRunner(codemods, repositories,
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
467
|
+
async function runCodemodRunner(codemods, repositories, workingDirectory) {
|
|
468
|
+
const codemodRepositoriesMappedByCodemodName = await prepareRepositoriesForCodemods(repositories, codemods, workingDirectory);
|
|
469
|
+
const results = Object.fromEntries(await Promise.all(codemods.map(async (codemod)=>{
|
|
470
|
+
const codemodRepositories = codemodRepositoriesMappedByCodemodName[codemod.name];
|
|
471
|
+
asserts.invariant(null != codemodRepositories, 'Codemod repositories should be present');
|
|
472
|
+
const failedRepositoryAddressesMappedByCodemodNames = {};
|
|
473
|
+
const result = await runCodemod(codemod, workingDirectory, {
|
|
474
|
+
rootPaths: codemodRepositories.map((repository)=>repository.path),
|
|
475
|
+
hooks: {
|
|
476
|
+
preCodemodRun: async (codemod)=>{
|
|
477
|
+
failedRepositoryAddressesMappedByCodemodNames[codemod.name] = await codemodPreCodemodRun(repositories, codemod);
|
|
478
|
+
},
|
|
479
|
+
targetFiltering: utils_codemodTargetFiltering(groupByFlat(codemodRepositories, 'name'), failedRepositoryAddressesMappedByCodemodNames),
|
|
480
|
+
postTransform: codemodPostTransform
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return [
|
|
484
|
+
codemod.name,
|
|
485
|
+
result
|
|
486
|
+
];
|
|
487
|
+
})));
|
|
488
|
+
return results;
|
|
489
|
+
}
|
|
490
|
+
async function prepareRepositoriesForCodemods(repositories, codemods, workingDirectory) {
|
|
491
|
+
const reposMappedByMainBranchName = Object.fromEntries(await Promise.all(repositories.map(async (repo)=>{
|
|
492
|
+
const mainBranchResult = await repo.getMainBranch();
|
|
493
|
+
if (mainBranchResult.isErr()) throw mainBranchResult.error;
|
|
494
|
+
return [
|
|
495
|
+
mainBranchResult.value.name,
|
|
496
|
+
repo
|
|
497
|
+
];
|
|
498
|
+
})));
|
|
499
|
+
const updatedRepositories = await Promise.all(objects.toEntries(reposMappedByMainBranchName).map(async ([mainBranchName, repo])=>{
|
|
500
|
+
const prepareResult = await repo.prepareForUpdate(mainBranchName);
|
|
501
|
+
if (prepareResult.isErr()) throw prepareResult.error;
|
|
502
|
+
return prepareResult.value;
|
|
503
|
+
}));
|
|
504
|
+
return Object.fromEntries(await Promise.all(codemods.map(async (codemod)=>{
|
|
505
|
+
const codeRepositories = await Promise.all(updatedRepositories.map((repo)=>repo.copy(node_path.resolve(workingDirectory, codemod.name, repo.name))));
|
|
506
|
+
return [
|
|
507
|
+
codemod.name,
|
|
508
|
+
codeRepositories
|
|
509
|
+
];
|
|
510
|
+
})));
|
|
463
511
|
}
|
|
464
512
|
async function runCodemods(codemods, transformationPath, options) {
|
|
465
513
|
const results = {};
|
package/dist/utils/arrays.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
type AnyCollection = Array<
|
|
1
|
+
type AnyCollection<Value = unknown> = Array<Value> | Set<Value>;
|
|
2
2
|
export declare function collectionIsEmpty<T extends AnyCollection>(collection: T): boolean;
|
|
3
|
+
export declare function collectionContains<Value, T extends AnyCollection<Value>>(collection: T, value: Value): boolean;
|
|
3
4
|
export {};
|