-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(asyn/await): parallelize commands
- Loading branch information
Showing
3 changed files
with
186 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,10 @@ | ||
id: 2018.08.15-eslintrc-yml | ||
title: Rename all .eslintrc files to .eslintrc.yml | ||
id: 2024.10.06-test-migration | ||
title: | | ||
feat: test migration | ||
adapter: | ||
type: github | ||
search_query: repo:NerdWalletOSS/shepherd-demo path:/ filename:.eslintrc | ||
search_query: repo:NerdWalletOSS/shepherd path:/ | ||
hooks: | ||
should_migrate: | ||
- ls .eslintrc | ||
apply: | ||
- mv .eslintrc .eslintrc.yml | ||
pr_message: | ||
- echo "Hey! This PR renames `.eslintrc` to `.eslintrc.yml`" | ||
apply: 'touch $SHEPHERD_REPO_DIR/testfile.js && echo "some content" > $SHEPHERD_REPO_DIR/testfile.js' | ||
pr_message: | | ||
echo "e2e test suite" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,145 @@ | ||
import fs from 'fs-extra'; | ||
|
||
import IRepoAdapter, { IRepo } from '../adapters/base.js'; | ||
import { IMigrationContext } from '../migration-context.js'; | ||
import executeSteps from '../util/execute-steps.js'; | ||
import forEachRepo from '../util/for-each-repo.js'; | ||
import { updateRepoList } from '../util/persisted-data.js'; | ||
import forEachRepo from '../util/for-each-repo.js'; | ||
import chalk from 'chalk'; | ||
|
||
const removeRepoDirectories = async (adapter: IRepoAdapter, repo: IRepo) => { | ||
await fs.remove(adapter.getRepoDir(repo)); | ||
await fs.remove(adapter.getDataDir(repo)); | ||
}; | ||
|
||
export default async (context: IMigrationContext) => { | ||
const loadRepos = async ( | ||
context: IMigrationContext, | ||
onRetry: (numSeconds: number) => void | ||
): Promise<IRepo[]> => { | ||
const { | ||
migration: { selectedRepos }, | ||
adapter, | ||
logger, | ||
} = context; | ||
function onRetry(numSeconds: number) { | ||
logger.info(`Hit rate limit; waiting ${numSeconds} seconds and retrying.`); | ||
} | ||
|
||
let repos; | ||
|
||
if (selectedRepos) { | ||
logger.info(`Using ${selectedRepos.length} selected repos`); | ||
repos = selectedRepos; | ||
return selectedRepos; | ||
} else { | ||
const spinner = logger.spinner('Loading candidate repos'); | ||
repos = (await adapter.getCandidateRepos(onRetry)) || []; | ||
const repos = (await adapter.getCandidateRepos(onRetry)) || []; | ||
spinner.succeed(`Loaded ${repos.length} repos`); | ||
return repos; | ||
} | ||
}; | ||
|
||
/** | ||
* Handles the checkout process for a given repository. | ||
* | ||
* @param context - The migration context containing the adapter and logger. | ||
* @param repo - The repository to be checked out. | ||
* @param checkedOutRepos - An array to store successfully checked out repositories. | ||
* @param discardedRepos - An array to store repositories that were discarded during the process. | ||
* @param repoLogs - An array to store log messages related to the checkout process. | ||
* | ||
* @returns A promise that resolves when the checkout process is complete. | ||
* | ||
* @throws Will log an error and skip the repository if the checkout process fails. | ||
* | ||
* The function performs the following steps: | ||
* 1. Attempts to check out the repository using the adapter. | ||
* 2. Creates necessary directories for the repository. | ||
* 3. Runs the 'should_migrate' steps and discards the repository if they fail. | ||
* 4. Runs the 'post_checkout' steps and discards the repository if they fail. | ||
* 5. Adds the repository to the checkedOutRepos array if all steps succeed. | ||
*/ | ||
const handleRepoCheckout = async ( | ||
context: IMigrationContext, | ||
repo: IRepo, | ||
checkedOutRepos: IRepo[], | ||
discardedRepos: IRepo[], | ||
repoLogs: string[] | ||
) => { | ||
const { adapter, logger } = context; | ||
try { | ||
await adapter.checkoutRepo(repo); | ||
repoLogs.push('Checked out repo'); | ||
} catch (e: any) { | ||
logger.error(e); | ||
repoLogs.push('Failed to check out repo; skipping'); | ||
return; | ||
} | ||
context.migration.repos = repos; | ||
const checkedOutRepos: IRepo[] = []; | ||
const discardedRepos: IRepo[] = []; | ||
|
||
const options = { warnMissingDirectory: false }; | ||
|
||
await forEachRepo(context, options, async (repo) => { | ||
const spinner = logger.spinner('Checking out repo'); | ||
try { | ||
await adapter.checkoutRepo(repo); | ||
spinner.succeed('Checked out repo'); | ||
} catch (e: any) { | ||
logger.error(e); | ||
spinner.fail('Failed to check out repo; skipping'); | ||
return; | ||
} | ||
// We need to create the data directory before running should_migrate | ||
await fs.mkdirs(adapter.getDataDir(repo)); | ||
|
||
logger.info('> Running should_migrate steps'); | ||
const stepsResults = await executeSteps(context, repo, 'should_migrate'); | ||
if (!stepsResults.succeeded) { | ||
discardedRepos.push(repo); | ||
await removeRepoDirectories(adapter, repo); | ||
logger.failIcon('Error running should_migrate steps; skipping'); | ||
} else { | ||
logger.succeedIcon('Completed all should_migrate steps successfully'); | ||
|
||
logger.info('> Running post_checkout steps'); | ||
const postCheckoutStepsResults = await executeSteps(context, repo, 'post_checkout'); | ||
if (!postCheckoutStepsResults.succeeded) { | ||
discardedRepos.push(repo); | ||
await removeRepoDirectories(adapter, repo); | ||
logger.failIcon('Error running post_checkout steps; skipping'); | ||
} else { | ||
logger.succeedIcon('Completed all post_checkout steps successfully'); | ||
checkedOutRepos.push(repo); | ||
} | ||
} | ||
await fs.mkdirs(adapter.getDataDir(repo)); | ||
|
||
repoLogs.push('> Running should_migrate steps'); | ||
const shouldMigrateResults = await executeSteps(context, repo, 'should_migrate'); | ||
if (!shouldMigrateResults.succeeded) { | ||
discardedRepos.push(repo); | ||
await removeRepoDirectories(adapter, repo); | ||
repoLogs.push('Error running should_migrate steps; skipping'); | ||
return; | ||
} | ||
|
||
repoLogs.push('Completed all should_migrate steps successfully'); | ||
repoLogs.push('> Running post_checkout steps'); | ||
const postCheckoutResults = await executeSteps(context, repo, 'post_checkout'); | ||
if (!postCheckoutResults.succeeded) { | ||
discardedRepos.push(repo); | ||
await removeRepoDirectories(adapter, repo); | ||
repoLogs.push('Error running post_checkout steps; skipping'); | ||
} else { | ||
repoLogs.push('Completed all post_checkout steps successfully'); | ||
checkedOutRepos.push(repo); | ||
} | ||
}; | ||
|
||
const logRepoInfo = ( | ||
repo: IRepo, | ||
count: number, | ||
total: number, | ||
adapter: IRepoAdapter, | ||
repoLogs: string[] | ||
): void => { | ||
const indexString = chalk.dim(`${count}/${total}`); | ||
repoLogs.push(chalk.bold(`\n[${adapter.stringifyRepo(repo)}] ${indexString}`)); | ||
}; | ||
|
||
const checkoutRepos = async ( | ||
context: IMigrationContext, | ||
repos: IRepo[], | ||
checkedOutRepos: IRepo[], | ||
discardedRepos: IRepo[] | ||
) => { | ||
const { adapter, logger } = context; | ||
let count = 1; | ||
|
||
logger.info('Checking out repos'); | ||
await forEachRepo(context, { warnMissingDirectory: false }, async (repo) => { | ||
const repoLogs: string[] = []; | ||
logRepoInfo(repo, count++, repos.length, adapter, repoLogs); | ||
await handleRepoCheckout(context, repo, checkedOutRepos, discardedRepos, repoLogs); | ||
repoLogs.forEach((log) => logger.info(log)); | ||
}); | ||
|
||
logger.info(''); | ||
logger.info(`Checked out ${checkedOutRepos.length} out of ${repos.length} repos`); | ||
}; | ||
|
||
const mappedCheckedOutRepos: IRepo[] = []; | ||
for (const repo of checkedOutRepos) { | ||
mappedCheckedOutRepos.push(await adapter.mapRepoAfterCheckout(repo)); | ||
} | ||
export default async (context: IMigrationContext) => { | ||
const { adapter, logger } = context; | ||
const onRetry = (numSeconds: number) => | ||
logger.info(`Hit rate limit; waiting ${numSeconds} seconds and retrying.`); | ||
|
||
const repos = await loadRepos(context, onRetry); | ||
context.migration.repos = repos; | ||
|
||
const checkedOutRepos: IRepo[] = []; | ||
const discardedRepos: IRepo[] = []; | ||
|
||
await checkoutRepos(context, repos, checkedOutRepos, discardedRepos); | ||
|
||
const mappedCheckedOutRepos = await Promise.all( | ||
checkedOutRepos.map((repo) => adapter.mapRepoAfterCheckout(repo)) | ||
); | ||
|
||
// We'll persist this list of repos for use in future steps | ||
await updateRepoList(context, mappedCheckedOutRepos, discardedRepos); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters