@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.
@@ -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, options?: {
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, branchName: string);
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, options?: {
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, message: string, options?: {
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, options?: {
26
+ export declare class RebaseError<Tag = string> extends GitError<Tag> {
27
+ constructor(repository: Repository<Tag>, options?: {
28
28
  cause: unknown;
29
29
  });
30
30
  }
@@ -1,3 +1,3 @@
1
- export { cloneRepositories, cloneRepository } from './utils.js';
1
+ export { cloneRepositories } from './utils.js';
2
2
  export { GitError } from './errors.js';
3
3
  export { default as Repository } from './repository.js';
@@ -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
- clone: () => Promise<Result<void, GitError>>;
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, GitError>>;
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
- }) => types.Optional<Repository>;
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;
@@ -1,4 +1,5 @@
1
- import { type types } from '@kamaalio/kamaal';
2
1
  import Repository from './repository.js';
3
- export declare function cloneRepositories(repoAddresses: Array<string>, location: string): Promise<Array<Repository>>;
4
- export declare function cloneRepository(repoAddress: string, location: string): Promise<types.Optional<Repository>>;
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>>>;
@@ -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>): Promise<void>;
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>): Promise<void>;
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 groupBy(array, key) {
88
- const arrayCopy = [
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
- return arrayCopy.reduce((acc, current)=>{
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(repoAddresses, location) {
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(repoAddresses, location);
375
- const existingRepositories = await getExistingRepositories(exec, repoAddresses);
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, repoAddresses) {
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(repoAddresses, (address)=>{
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(repoAddresses, cwd) {
433
+ function dedupeRepositoriesToClone(repositories, cwd) {
407
434
  const initialDedupeResult = {
408
435
  result: [],
409
436
  names: []
410
437
  };
411
- const dedupedRepos = repoAddresses.reduce((acc, repoAddress)=>{
438
+ const dedupedRepos = repositories.reduce((acc, repo)=>{
412
439
  const repository = git_repository.fromAddressAndCwd({
413
- address: repoAddress,
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.map((repo)=>repo.address), options.workingDirectory);
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 mappingsByName = clonedRepositories.reduce((acc, repository)=>{
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(mappingsByName, failedRepositoryAddressesMappedByCodemodNames) {
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 mapping = mappingsByName[projectName];
481
- if (null == mapping) return false;
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(mapping.repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(mapping.tags) || [
501
+ return !failedRepositoryAddressesSet.has(repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(repository.tags) || [
485
502
  ...codemod.tags
486
- ].some((tag)=>mapping.tags.has(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, mappingsByName, workingDirectory) {
500
- const rootPaths = repositories.map((repository)=>repository.path);
501
- const failedRepositoryAddressesMappedByCodemodNames = {};
502
- return runCodemods(codemods, workingDirectory, {
503
- rootPaths,
504
- hooks: {
505
- preCodemodRun: async (codemod)=>{
506
- failedRepositoryAddressesMappedByCodemodNames[codemod.name] = await codemodPreCodemodRun(repositories, codemod);
507
- },
508
- targetFiltering: utils_codemodTargetFiltering(mappingsByName, failedRepositoryAddressesMappedByCodemodNames),
509
- postTransform: codemodPostTransform
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 groupBy(array, key) {
42
- const arrayCopy = [
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
- return arrayCopy.reduce((acc, current)=>{
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(repoAddresses, location) {
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(repoAddresses, location);
326
- const existingRepositories = await getExistingRepositories(exec, repoAddresses);
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, repoAddresses) {
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(repoAddresses, (address)=>{
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(repoAddresses, cwd) {
384
+ function dedupeRepositoriesToClone(repositories, cwd) {
358
385
  const initialDedupeResult = {
359
386
  result: [],
360
387
  names: []
361
388
  };
362
- const dedupedRepos = repoAddresses.reduce((acc, repoAddress)=>{
389
+ const dedupedRepos = repositories.reduce((acc, repo)=>{
363
390
  const repository = git_repository.fromAddressAndCwd({
364
- address: repoAddress,
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.map((repo)=>repo.address), options.workingDirectory);
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 mappingsByName = clonedRepositories.reduce((acc, repository)=>{
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(mappingsByName, failedRepositoryAddressesMappedByCodemodNames) {
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 mapping = mappingsByName[projectName];
432
- if (null == mapping) return false;
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(mapping.repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(mapping.tags) || [
452
+ return !failedRepositoryAddressesSet.has(repository.address) && (collectionIsEmpty(codemod.tags) || collectionIsEmpty(repository.tags) || [
436
453
  ...codemod.tags
437
- ].some((tag)=>mapping.tags.has(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, mappingsByName, workingDirectory) {
451
- const rootPaths = repositories.map((repository)=>repository.path);
452
- const failedRepositoryAddressesMappedByCodemodNames = {};
453
- return runCodemods(codemods, workingDirectory, {
454
- rootPaths,
455
- hooks: {
456
- preCodemodRun: async (codemod)=>{
457
- failedRepositoryAddressesMappedByCodemodNames[codemod.name] = await codemodPreCodemodRun(repositories, codemod);
458
- },
459
- targetFiltering: utils_codemodTargetFiltering(mappingsByName, failedRepositoryAddressesMappedByCodemodNames),
460
- postTransform: codemodPostTransform
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 = {};
@@ -1 +1,2 @@
1
1
  export declare function groupBy<T, K extends keyof T>(array: Array<T>, key: K): Record<string, Array<T>>;
2
+ export declare function groupByFlat<T, K extends keyof T>(array: Array<T>, key: K): Record<string, T>;
@@ -1,3 +1,4 @@
1
- type AnyCollection = Array<unknown> | Set<unknown>;
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 {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamaalio/codemod-kit",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "type": "module",
5
5
  "author": "Kamaal Farah",
6
6
  "license": "MIT",