From 24d689f33da8094c81b9db70d8a43e10a1d68c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Fri, 4 Feb 2022 23:37:41 +0100 Subject: [PATCH] WIP --- .prettierrc | 1 + package.json | 39 +-- src/entrypoint.cli.e2e.private.test.ts | 5 +- src/entrypoint.module.ts | 6 +- src/options/cliArgs.ts | 1 + src/options/config/config.ts | 9 +- src/options/config/getOptionsFromGit.ts | 6 - src/options/options.test.ts | 70 ++++- src/options/options.ts | 71 +++-- src/services/git.integration.test.ts | 48 +++- src/services/git.test.ts | 26 +- src/services/git.ts | 95 +++---- src/services/github/v3/createPullRequest.ts | 4 +- src/services/github/v3/createStatusComment.ts | 4 +- .../fetchCommits/allFetchers.private.test.ts | 30 ++- .../fetchPullRequestBySearchQuery.ts | 1 + .../getOptionsFromGithub.ts | 60 +++-- .../v4/getRepoOwnerAndName.private.test.ts | 44 +++ src/services/github/v4/getRepoOwnerAndName.ts | 74 +++++ src/services/logger.ts | 2 +- src/test/mocks.ts | 21 ++ src/test/setupFiles/automatic-mocks.ts | 21 +- src/ui/maybeSetupRepo.test.ts | 202 ++++++++++++-- src/ui/maybeSetupRepo.ts | 40 ++- src/utils/getPackageVersion.ts | 5 - src/utils/maybe.ts | 3 + src/utils/packageVersion.ts | 1 + tsconfig.json | 1 - yarn.lock | 255 ++++++++---------- 29 files changed, 756 insertions(+), 389 deletions(-) delete mode 100644 src/options/config/getOptionsFromGit.ts create mode 100644 src/services/github/v4/getRepoOwnerAndName.private.test.ts create mode 100644 src/services/github/v4/getRepoOwnerAndName.ts create mode 100644 src/test/mocks.ts delete mode 100644 src/utils/getPackageVersion.ts create mode 100644 src/utils/maybe.ts create mode 100644 src/utils/packageVersion.ts diff --git a/.prettierrc b/.prettierrc index 937375d2..463d6d6c 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "parser": "typescript", "semi": true, "singleQuote": true } diff --git a/package.json b/package.json index 38b0bff4..8c2cebba 100644 --- a/package.json +++ b/package.json @@ -22,15 +22,18 @@ }, "license": "MIT", "scripts": { - "lint": "tsc --project ./tsconfig.test.json && eslint './**/*.{ts,js}'", - "prepare": "husky install", "postinstall": "test -f ./dist/scripts/runPostinstall.js && node ./dist/scripts/runPostinstall.js || echo 'Dist folder missing'", - "prepublishOnly": "tsc --project ./tsconfig.prod.json", - "test": "jest", - "test-private": "jest --config ./jest.config.private.js", - "test-mutation": "jest --config ./jest.config.mutation.js", + "postversion": "yarn extractPackageVersion && yarn build", + "prepare": "husky install", + "prepublishOnly": "yarn build", + "build": "tsc --project ./tsconfig.prod.json", + "extractPackageVersion": "node -p \"'export const PACKAGE_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" | prettier > src/utils/packageVersion.ts", + "lint": "tsc --project ./tsconfig.test.json && eslint './**/*.{ts,js}'", + "start": "ts-node --transpile-only ./src/entrypoint.cli.ts", "test-all": "yarn lint && jest --config ./jest.config.all.js", - "start": "ts-node --transpile-only ./src/entrypoint.cli.ts" + "test-mutation": "jest --config ./jest.config.mutation.js", + "test-private": "jest --config ./jest.config.private.js", + "test": "jest" }, "lint-staged": { "*.ts": [ @@ -60,7 +63,7 @@ }, "dependencies": { "@octokit/rest": "^18.12.0", - "axios": "^0.24.0", + "axios": "^0.25.0", "dedent": "^0.7.0", "del": "^6.0.0", "find-up": "^5.0.0", @@ -72,38 +75,38 @@ "strip-json-comments": "^3.1.1", "terminal-link": "^2.1.1", "utility-types": "^3.10.0", - "winston": "^3.4.0", + "winston": "^3.5.1", "yargs": "^17.3.1" }, "devDependencies": { "@types/core-js": "^2.5.5", "@types/dedent": "^0.7.0", - "@types/inquirer": "^8.1.3", + "@types/inquirer": "^8.2.0", "@types/jest": "^27.4.0", "@types/lodash": "^4.14.178", "@types/node": "^17.0.8", "@types/safe-json-stringify": "^1.1.2", "@types/yargs": "^17.0.8", "@types/yargs-parser": "^20.2.1", - "@typescript-eslint/eslint-plugin": "^5.9.1", - "@typescript-eslint/parser": "^5.9.1", - "eslint": "^8.7.0", + "@typescript-eslint/eslint-plugin": "^5.10.2", + "@typescript-eslint/parser": "^5.10.2", + "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-jest": "^25.7.0", + "eslint-plugin-jest": "^26.0.0", "eslint-plugin-prettier": "^4.0.0", - "graphql": "^16.2.0", + "graphql": "^16.3.0", "graphql-config": "^4.1.0", "graphql-tag": "^2.12.6", "husky": "^7.0.4", "jest": "^27.4.7", "jest-snapshot-serializer-ansi": "^1.0.0", - "lint-staged": "^12.1.7", - "nock": "^13.2.2", + "lint-staged": "^12.3.3", + "nock": "^13.2.4", "prettier": "^2.5.1", "strip-ansi": "^6.0.1", "ts-jest": "^27.1.3", "ts-node": "^10.4.0", - "typescript": "^4.5.4" + "typescript": "^4.5.5" } } diff --git a/src/entrypoint.cli.e2e.private.test.ts b/src/entrypoint.cli.e2e.private.test.ts index b8b8aced..50fcb99d 100644 --- a/src/entrypoint.cli.e2e.private.test.ts +++ b/src/entrypoint.cli.e2e.private.test.ts @@ -40,7 +40,7 @@ describe('inquirer cli', () => { --ci Disable interactive prompts [boolean] --cherrypickRef Append commit message with \\"(cherry picked from commit...) [boolean] - --configFile Path to project config [string] + --configFile, --config Path to project config [string] --since ISO-8601 date for filtering commits [string] --until ISO-8601 date for filtering commits [string] --dir Location where the temporary repository will be stored @@ -111,9 +111,6 @@ describe('inquirer cli', () => { devAccessToken, ]); - // eslint-disable-next-line no-console - console.log('res', JSON.stringify(res)); - const lineCount = res.split('\n').length; expect(lineCount).toBeGreaterThan(10); diff --git a/src/entrypoint.module.ts b/src/entrypoint.module.ts index 48cc4506..1ef4adf3 100644 --- a/src/entrypoint.module.ts +++ b/src/entrypoint.module.ts @@ -46,14 +46,10 @@ export async function getCommits(options: { sourceBranch?: string; dateUntil?: string; dateSince?: string; - cwd?: string; }) { initLogger({ ci: true, accessToken: options.accessToken }); - const optionsFromGithub = await getOptionsFromGithub({ - cwd: process.cwd(), - ...options, - }); + const optionsFromGithub = await getOptionsFromGithub(options); if (options.pullNumber) { return [ diff --git a/src/options/cliArgs.ts b/src/options/cliArgs.ts index edd2905d..4c46518d 100644 --- a/src/options/cliArgs.ts +++ b/src/options/cliArgs.ts @@ -67,6 +67,7 @@ export function getOptionsFromCliArgs( }) .option('configFile', { + alias: 'config', description: 'Path to project config', type: 'string', }) diff --git a/src/options/config/config.ts b/src/options/config/config.ts index e4fcf341..906f530f 100644 --- a/src/options/config/config.ts +++ b/src/options/config/config.ts @@ -1,6 +1,5 @@ import { ConfigFileOptions } from '../ConfigOptions'; import { OptionsFromCliArgs } from '../cliArgs'; -import { getOptionsFromGit } from './getOptionsFromGit'; import { getGlobalConfig } from './globalConfig'; import { getProjectConfig } from './projectConfig'; @@ -22,18 +21,14 @@ export async function getOptionsFromConfigFiles({ // ci: cli and module only flag const configFile = - optionsFromCliArgs.configFile ?? - optionsFromModule.configFile ?? - defaultConfigOptions.configFile; + optionsFromCliArgs.configFile ?? optionsFromModule.configFile; - const [gitConfig, projectConfig, globalConfig] = await Promise.all([ - getOptionsFromGit({ cwd: process.cwd() }), + const [projectConfig, globalConfig] = await Promise.all([ getProjectConfig({ configFile }), ci ? undefined : getGlobalConfig(), ]); return { - ...gitConfig, ...globalConfig, ...projectConfig, ...optionsFromModule, diff --git a/src/options/config/getOptionsFromGit.ts b/src/options/config/getOptionsFromGit.ts deleted file mode 100644 index eccc3e46..00000000 --- a/src/options/config/getOptionsFromGit.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { getRepoOwnerAndNameFromGitRemotes } from '../../services/git'; - -export async function getOptionsFromGit({ cwd }: { cwd: string }) { - const remotes = await getRepoOwnerAndNameFromGitRemotes({ cwd }); - return remotes[0]; -} diff --git a/src/options/options.test.ts b/src/options/options.test.ts index c3b2dd7b..da018b0d 100644 --- a/src/options/options.test.ts +++ b/src/options/options.test.ts @@ -1,7 +1,9 @@ import fs from 'fs/promises'; import os from 'os'; import nock from 'nock'; +import * as git from '../services/git'; import { GithubConfigOptionsResponse } from '../services/github/v4/getOptionsFromGithub/query'; +import { RepoOwnerAndNameResponse } from '../services/github/v4/getRepoOwnerAndName'; import * as logger from '../services/logger'; import { mockConfigFiles } from '../test/mockConfigFiles'; import { mockGqlRequest } from '../test/nockHelpers'; @@ -67,26 +69,38 @@ describe('getOptions', () => { `); }); - it('when repoName is missing', async () => { - mockProjectConfig({ repoName: '' }); + describe('when repoName and repoOwner are missing', () => { + beforeEach(() => { + mockProjectConfig({ repoName: undefined, repoOwner: undefined }); + }); - await expect(() => getOptions([], {})).rejects - .toThrowErrorMatchingInlineSnapshot(` + it('should throw if there are no remotes', async () => { + jest.spyOn(git, 'getRepoInfoFromGitRemotes').mockResolvedValue([]); + + await expect(() => getOptions([], {})).rejects + .toThrowErrorMatchingInlineSnapshot(` "Please specify a repo name: \\"--repo-name kibana\\". Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" `); - }); + }); - it('when repoOwner is missing', async () => { - mockProjectConfig({ repoOwner: '' }); + it('should get repoName from the remote', async () => { + mockRepoOwnerAndName({ + childRepoOwner: 'sqren', + parentRepoOwner: 'elastic', + repoName: 'kibana', + }); - await expect(() => getOptions([], {})).rejects - .toThrowErrorMatchingInlineSnapshot(` - "Please specify a repo owner: \\"--repo-owner elastic\\". + jest + .spyOn(git, 'getRepoInfoFromGitRemotes') + .mockResolvedValue([{ repoName: 'kibana', repoOwner: 'sqren' }]); - Read more: https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson" - `); + const options = await getOptions([], {}); + + expect(options.repoName).toBe('kibana'); + expect(options.repoOwner).toBe('elastic'); + }); }); }); @@ -146,6 +160,7 @@ describe('getOptions', () => { details: false, editor: 'code', fork: true, + gitHostname: 'github.com', githubApiBaseUrlV4: 'http://localhost/graphql', historicalBranchLabelMappings: [ { @@ -380,3 +395,34 @@ function mockGithubConfigOptions({ }, }); } + +function mockRepoOwnerAndName({ + repoName, + parentRepoOwner, + childRepoOwner, +}: { + repoName: string; + parentRepoOwner: string; + childRepoOwner: string; +}) { + return mockGqlRequest({ + name: 'RepoOwnerAndName', + statusCode: 200, + body: { + data: { + repository: { + isFork: true, + name: repoName, + owner: { + login: childRepoOwner, + }, + parent: { + owner: { + login: parentRepoOwner, + }, + }, + }, + }, + }, + }); +} diff --git a/src/options/options.ts b/src/options/options.ts index c697af5d..a796a02a 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -2,6 +2,7 @@ import { isEmpty } from 'lodash'; import { HandledError } from '../services/HandledError'; import { getGlobalConfigPath } from '../services/env'; import { getOptionsFromGithub } from '../services/github/v4/getOptionsFromGithub/getOptionsFromGithub'; +import { getRepoOwnerAndName } from '../services/github/v4/getRepoOwnerAndName'; import { updateLogger } from './../services/logger'; import { ConfigFileOptions, TargetBranchChoiceOrString } from './ConfigOptions'; import { getOptionsFromCliArgs, OptionsFromCliArgs } from './cliArgs'; @@ -10,7 +11,7 @@ import { OptionsFromConfigFiles, } from './config/config'; -const PROJECT_CONFIG_DOCS_LINK = +export const PROJECT_CONFIG_DOCS_LINK = 'https://github.com/sqren/backport/blob/main/docs/configuration.md#project-config-backportrcjson'; const GLOBAL_CONFIG_DOCS_LINK = @@ -34,6 +35,7 @@ export const defaultConfigOptions = { dateUntil: null, details: false, fork: true, + gitHostname: 'github.com', maxNumber: 10, multipleBranches: true, multipleCommits: false, @@ -65,18 +67,17 @@ export async function getOptions( optionsFromCliArgs, }); - // update logger - updateLogger(combined); + const { accessToken, repoName, repoOwner } = await getRequiredOptions( + combined + ); - // required - const accessToken = requireAccessToken(combined); - const repoName = requireRepoName(combined); - const repoOwner = requireRepoOwner(combined); + // update logger + updateLogger({ accessToken, verbose: combined.verbose }); const optionsFromGithub = await getOptionsFromGithub({ ...combined, - // required props + // required options accessToken, repoName, repoOwner, @@ -109,6 +110,42 @@ export async function getOptions( return res; } +async function getRequiredOptions(combined: CombinedOptions) { + const accessToken = requireAccessToken(combined); + + if (combined.repoName && combined.repoOwner) { + return { + accessToken, + repoName: combined.repoName, + repoOwner: combined.repoOwner, + }; + } + + const { repoName, repoOwner } = await getRepoOwnerAndName({ + cwd: combined.cwd, + githubApiBaseUrlV4: combined.githubApiBaseUrlV4, + accessToken, + }); + + if (!repoName) { + throw new HandledError( + `Please specify a repo name: "--repo-name kibana".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` + ); + } + + if (!repoOwner) { + throw new HandledError( + `Please specify a repo owner: "--repo-owner elastic".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` + ); + } + + return { + accessToken, + repoName, + repoOwner, + }; +} + type CombinedOptions = ReturnType; function getCombinedOptions({ optionsFromConfigFiles, @@ -135,24 +172,6 @@ function requireAccessToken(combinedOptions: CombinedOptions): string { return combinedOptions.accessToken; } -function requireRepoName(combinedOptions: CombinedOptions): string { - if (!combinedOptions.repoName) { - throw new HandledError( - `Please specify a repo name: "--repo-name kibana".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` - ); - } - return combinedOptions.repoName; -} - -function requireRepoOwner(combinedOptions: CombinedOptions): string { - if (!combinedOptions.repoOwner) { - throw new HandledError( - `Please specify a repo owner: "--repo-owner elastic".\n\nRead more: ${PROJECT_CONFIG_DOCS_LINK}` - ); - } - return combinedOptions.repoOwner; -} - function requireTargetBranch(config: { targetBranches: CombinedOptions['targetBranches']; targetBranchChoices: CombinedOptions['targetBranchChoices']; diff --git a/src/services/git.integration.test.ts b/src/services/git.integration.test.ts index 333527a5..1831ae4e 100644 --- a/src/services/git.integration.test.ts +++ b/src/services/git.integration.test.ts @@ -2,6 +2,7 @@ import { resolve } from 'path'; import del from 'del'; import makeDir from 'make-dir'; import { ValidConfigOptions } from '../options/options'; +import { mockGqlRequest } from '../test/nockHelpers'; import { SpyHelper } from '../types/SpyHelper'; import * as childProcess from './child-process-promisified'; import { @@ -11,6 +12,7 @@ import { getSourceRepoPath, } from './git'; import { getShortSha } from './github/commitFormatters'; +import { RepoOwnerAndNameResponse } from './github/v4/getRepoOwnerAndName'; jest.unmock('make-dir'); jest.unmock('del'); @@ -279,6 +281,12 @@ describe('git.integration', () => { `git remote add origin git@github.com:elastic/kibana.git`, execOpts ); + + mockRepoOwnerAndName({ + childRepoOwner: 'sqren', + parentRepoOwner: 'elastic', + repoName: 'kibana', + }); }); it('returns local source repo, when one remote matches', async () => { @@ -287,6 +295,7 @@ describe('git.integration', () => { repoName: 'kibana', repoOwner: 'elastic', cwd: sourceRepo, + githubApiBaseUrlV4: 'http://localhost/graphql', // required to mock the response } as ValidConfigOptions; const sourcePath = await getSourceRepoPath(options); expect(sourcePath).toBe(sourceRepo); @@ -296,13 +305,48 @@ describe('git.integration', () => { const options = { accessToken: 'verysecret', repoName: 'kibana', - repoOwner: 'sqren', + repoOwner: 'no-a-match', cwd: sourceRepo, + githubApiBaseUrlV4: 'http://localhost/graphql', // required to mock the response } as ValidConfigOptions; const sourcePath = await getSourceRepoPath(options); expect(sourcePath).toBe( - 'https://x-access-token:verysecret@github.com/sqren/kibana.git' + 'https://x-access-token:verysecret@github.com/no-a-match/kibana.git' ); }); }); }); + +function mockRepoOwnerAndName({ + repoName, + parentRepoOwner, + childRepoOwner, +}: { + repoName: string; + childRepoOwner: string; + parentRepoOwner?: string; +}) { + return mockGqlRequest({ + name: 'RepoOwnerAndName', + statusCode: 200, + body: { + data: { + // @ts-expect-error + repository: { + isFork: !!parentRepoOwner, + name: repoName, + owner: { + login: childRepoOwner, + }, + parent: parentRepoOwner + ? { + owner: { + login: parentRepoOwner, + }, + } + : null, + }, + }, + }, + }); +} diff --git a/src/services/git.test.ts b/src/services/git.test.ts index de35cf80..853ae00e 100644 --- a/src/services/git.test.ts +++ b/src/services/git.test.ts @@ -15,7 +15,7 @@ import { getLocalConfigFileCommitDate, isLocalConfigFileUntracked, isLocalConfigFileModified, - getRepoOwnerAndNameFromGitRemotes, + getRepoInfoFromGitRemotes, } from './git'; import { Commit } from './sourceCommit/parseSourceCommit'; @@ -160,7 +160,7 @@ describe('isLocalConfigFileModified', () => { }); }); -describe('getRepoOwnerAndNameFromGitRemotes', () => { +describe('getRepoInfoFromGitRemotes', () => { it('returns repoName and repoOwner for every remote', async () => { const res = { stdout: @@ -175,22 +175,18 @@ describe('getRepoOwnerAndNameFromGitRemotes', () => { stderr: '', }; jest.spyOn(childProcess, 'exec').mockResolvedValue(res); - expect(await getRepoOwnerAndNameFromGitRemotes({ cwd: 'foo/bar' })).toEqual( - [ - { repoName: 'kibana', repoOwner: 'john.doe' }, - { repoName: 'kibana', repoOwner: 'elastic' }, - { repoName: 'kibana', repoOwner: 'peter' }, - { repoName: 'kibana', repoOwner: 'sqren' }, - ] - ); + expect(await getRepoInfoFromGitRemotes({ cwd: 'foo/bar' })).toEqual([ + { repoName: 'kibana', repoOwner: 'john.doe' }, + { repoName: 'kibana', repoOwner: 'elastic' }, + { repoName: 'kibana', repoOwner: 'peter' }, + { repoName: 'kibana', repoOwner: 'sqren' }, + ]); }); it('returns undefined when no remotes exist', async () => { const res = { stdout: '', stderr: '' }; jest.spyOn(childProcess, 'exec').mockResolvedValue(res); - expect(await getRepoOwnerAndNameFromGitRemotes({ cwd: 'foo/bar' })).toEqual( - [] - ); + expect(await getRepoInfoFromGitRemotes({ cwd: 'foo/bar' })).toEqual([]); }); it('handles errors', async () => { @@ -204,9 +200,7 @@ describe('getRepoOwnerAndNameFromGitRemotes', () => { 'fatal: not a git repository (or any of the parent directories): .git\n', }; jest.spyOn(childProcess, 'exec').mockRejectedValueOnce(err); - expect(await getRepoOwnerAndNameFromGitRemotes({ cwd: 'foo/bar' })).toEqual( - [] - ); + expect(await getRepoInfoFromGitRemotes({ cwd: 'foo/bar' })).toEqual([]); }); }); diff --git a/src/services/git.ts b/src/services/git.ts index d1eb126f..d4e00de2 100644 --- a/src/services/git.ts +++ b/src/services/git.ts @@ -1,7 +1,4 @@ -import { ExecException } from 'child_process'; -import { stat } from 'fs/promises'; import { resolve as pathResolve } from 'path'; -import del from 'del'; import { uniq, isEmpty } from 'lodash'; import ora from 'ora'; import { ValidConfigOptions } from '../options/options'; @@ -10,32 +7,11 @@ import { HandledError } from './HandledError'; import { execAsCallback, exec } from './child-process-promisified'; import { getRepoPath } from './env'; import { getShortSha } from './github/commitFormatters'; +import { getRepoOwnerAndName } from './github/v4/getRepoOwnerAndName'; import { logger } from './logger'; import { ExpectedTargetPullRequest } from './sourceCommit/getExpectedTargetPullRequests'; import { Commit } from './sourceCommit/parseSourceCommit'; -async function folderExists(path: string): Promise { - try { - const stats = await stat(path); - return stats.isDirectory(); - } catch (e) { - if (e.code === 'ENOENT') { - return false; - } - - throw e; - } -} - -export function repoExists(options: ValidConfigOptions): Promise { - return folderExists(getRepoPath(options)); -} - -export function deleteRepo(options: ValidConfigOptions) { - const repoPath = getRepoPath(options); - return del(repoPath); -} - export function getRemoteUrl( { repoName, accessToken, gitHostname = 'github.com' }: ValidConfigOptions, repoOwner: string @@ -45,29 +21,46 @@ export function getRemoteUrl( export async function cloneRepo( { sourcePath, targetPath }: { sourcePath: string; targetPath: string }, - callback: (progress: string) => void + onProgress: (progress: number) => void ) { - // where to store the repo (always local on disc) - return new Promise((resolve, reject) => { - const cb = (error: ExecException | null) => { - return error ? reject(error) : resolve(); - }; - const execProcess = execAsCallback( `git clone ${sourcePath} ${targetPath} --progress`, { maxBuffer: 100 * 1024 * 1024 }, - cb + (error) => { + return error ? reject(error) : resolve(); + } ); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const progress = { + fileUpdate: 0, + objectReceive: 0, + }; + if (execProcess.stderr) { execProcess.stderr.on('data', (data) => { - const regex = /^Receiving objects:\s+(\d+)%/; - const [, progress]: RegExpMatchArray = - data.toString().match(regex) || []; - if (progress) { - callback(progress); + logger.verbose(data); + const [, objectReceiveProgress]: RegExpMatchArray = + data.toString().match(/^Receiving objects:\s+(\d+)%/) || []; + + if (objectReceiveProgress) { + progress.objectReceive = parseInt(objectReceiveProgress, 10); + } + + const [, fileUpdateProgress]: RegExpMatchArray = + data.toString().match(/^Updating files:\s+(\d+)%/) || []; + + if (fileUpdateProgress) { + progress.objectReceive = 100; + progress.fileUpdate = parseInt(fileUpdateProgress, 10); + } + + const progressSum = Math.round( + progress.fileUpdate * 0.1 + progress.objectReceive * 0.9 + ); + + if (progressSum > 0) { + onProgress(progressSum); } }); } @@ -117,11 +110,7 @@ export async function isLocalConfigFileModified({ cwd }: { cwd: string }) { } } -export async function getRepoOwnerAndNameFromGitRemotes({ - cwd, -}: { - cwd: string; -}) { +export async function getRepoInfoFromGitRemotes({ cwd }: { cwd: string }) { try { const { stdout } = await exec('git remote --verbose', { cwd }); const remotes = stdout @@ -156,9 +145,8 @@ export async function getIsCommitInBranch( commitSha: string ) { try { - const cwd = getRepoPath(options); await exec(`git merge-base --is-ancestor ${commitSha} HEAD`, { - cwd, + cwd: getRepoPath(options), }); return true; } catch (e) { @@ -472,18 +460,15 @@ export async function pushBackportBranch({ } export async function getSourceRepoPath(options: ValidConfigOptions) { - const gitRemotes = await getRepoOwnerAndNameFromGitRemotes(options); - const isProjectMatch = gitRemotes.some( - (remote) => - remote.repoName === options.repoName && - remote.repoOwner === options.repoOwner - ); + const gitRemote = await getRepoOwnerAndName(options); // where to fetch the repo from (either remotely from Github or from a local path) const remoteUrl = getRemoteUrl(options, options.repoOwner); - const sourcePath = isProjectMatch - ? (await getGitProjectRoot(options)) ?? remoteUrl - : remoteUrl; + const sourcePath = + options.repoName === gitRemote.repoName && + options.repoOwner === gitRemote.repoOwner + ? (await getGitProjectRoot(options)) ?? remoteUrl + : remoteUrl; return sourcePath; } diff --git a/src/services/github/v3/createPullRequest.ts b/src/services/github/v3/createPullRequest.ts index 19a3ad80..da0662fb 100644 --- a/src/services/github/v3/createPullRequest.ts +++ b/src/services/github/v3/createPullRequest.ts @@ -1,7 +1,7 @@ import { Octokit } from '@octokit/rest'; import ora from 'ora'; import { ValidConfigOptions } from '../../../options/options'; -import { getPackageVersion } from '../../../utils/getPackageVersion'; +import { PACKAGE_VERSION } from '../../../utils/packageVersion'; import { HandledError } from '../../HandledError'; import { logger } from '../../logger'; import { Commit } from '../../sourceCommit/parseSourceCommit'; @@ -105,7 +105,7 @@ export function getPullRequestBody({ This is an automatic backport to \`${targetBranch}\` of: ${commitMessages} - + ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport)`; diff --git a/src/services/github/v3/createStatusComment.ts b/src/services/github/v3/createStatusComment.ts index 46d8d5dd..1af1dfa8 100644 --- a/src/services/github/v3/createStatusComment.ts +++ b/src/services/github/v3/createStatusComment.ts @@ -1,7 +1,7 @@ import { Octokit } from '@octokit/rest'; import { BackportResponse } from '../../../backportRun'; import { ValidConfigOptions } from '../../../options/options'; -import { getPackageVersion } from '../../../utils/getPackageVersion'; +import { PACKAGE_VERSION } from '../../../utils/packageVersion'; import { redact } from '../../../utils/redact'; import { HandledError } from '../../HandledError'; import { logger } from '../../logger'; @@ -92,7 +92,7 @@ export function getCommentBody({ } } - const packageVersionSection = `\n`; + const packageVersionSection = `\n`; const backportPRCommand = `\n### Manual backport\nTo create the backport manually run:\n\`\`\`\n${options.backportBinary} --pr ${pullNumber}\n\`\`\``; const supportSection = '\n\n### Questions ?\nPlease refer to the [Backport tool documentation](https://github.com/sqren/backport)'; diff --git a/src/services/github/v4/fetchCommits/allFetchers.private.test.ts b/src/services/github/v4/fetchCommits/allFetchers.private.test.ts index 09475741..d6306ce5 100644 --- a/src/services/github/v4/fetchCommits/allFetchers.private.test.ts +++ b/src/services/github/v4/fetchCommits/allFetchers.private.test.ts @@ -1,5 +1,6 @@ import { ValidConfigOptions } from '../../../../options/options'; import { getDevAccessToken } from '../../../../test/private/getDevAccessToken'; +import { Commit } from '../../../sourceCommit/parseSourceCommit'; import { fetchCommitByPullNumber } from './fetchCommitByPullNumber'; import { fetchCommitBySha } from './fetchCommitBySha'; import { fetchCommitsByAuthor } from './fetchCommitsByAuthor'; @@ -7,12 +8,11 @@ import { fetchPullRequestBySearchQuery } from './fetchPullRequestBySearchQuery'; describe('allFetchers', () => { let devAccessToken: string; + let commitByAuthor: Commit; beforeEach(async () => { devAccessToken = await getDevAccessToken(); - }); - it('all fetchers return the same commit', async () => { const commitsByAuthor = await fetchCommitsByAuthor({ accessToken: devAccessToken, author: 'sqren', @@ -26,10 +26,12 @@ describe('allFetchers', () => { commitPaths: [] as Array, }); - const commitByAuthor = commitsByAuthor[0]; + commitByAuthor = commitsByAuthor[0]; + }); + it('matches commitByAuthor with commitByPullNumber', async () => { if (!commitByAuthor.pullNumber) { - throw new Error('Missing pullnumber!'); + throw new Error('Missing pull number!'); } const commitByPullNumber = await fetchCommitByPullNumber({ @@ -41,6 +43,10 @@ describe('allFetchers', () => { historicalBranchLabelMappings: [], }); + expect(commitByAuthor).toEqual(commitByPullNumber); + }); + + it('matches commitByAuthor with commitBySha', async () => { const commitBySha = await fetchCommitBySha({ repoOwner: 'elastic', repoName: 'kibana', @@ -50,18 +56,26 @@ describe('allFetchers', () => { historicalBranchLabelMappings: [], }); + expect(commitByAuthor).toEqual(commitBySha); + }); + + it('matches commitByAuthor with commitBySearchQuery', async () => { const commitsBySearchQuery = await fetchPullRequestBySearchQuery({ repoOwner: 'elastic', repoName: 'kibana', accessToken: devAccessToken, maxNumber: 1, - prFilter: `[APM] Add note about synthtrace to APM docs`, + prFilter: `created:2021-12-20..2021-12-20`, sourceBranch: 'main', + author: 'sqren', } as ValidConfigOptions); - expect(commitByAuthor).toEqual(commitByPullNumber); - expect(commitByAuthor).toEqual(commitBySha); - expect(commitByAuthor).toEqual(commitsBySearchQuery[0]); + const commitBySearchQuery = commitsBySearchQuery[0]; + + expect(commitByAuthor).toEqual(commitBySearchQuery); + }); + + it('returns correct response for commitByAuthor', async () => { expect(commitByAuthor).toEqual({ committedDate: '2021-12-20T14:20:16Z', expectedTargetPullRequests: [ diff --git a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts index 20993a53..f3112790 100644 --- a/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts +++ b/src/services/github/v4/fetchCommits/fetchPullRequestBySearchQuery.ts @@ -41,6 +41,7 @@ export async function fetchPullRequestBySearchQuery( const authorFilter = author ? ` author:${author}` : ''; const searchQuery = `type:pr is:merged sort:updated-desc repo:${repoOwner}/${repoName}${authorFilter} ${prFilter} base:${sourceBranch}`; + const variables = { query: searchQuery, maxNumber: maxNumber, diff --git a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts b/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts index da4a701e..248437b6 100644 --- a/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts +++ b/src/services/github/v4/getOptionsFromGithub/getOptionsFromGithub.ts @@ -31,7 +31,7 @@ export async function getOptionsFromGithub(options: { repoName: string; repoOwner: string; skipRemoteConfig?: boolean; - cwd: string; + cwd?: string; }) { const { accessToken, githubApiBaseUrlV4, repoName, repoOwner } = options; @@ -88,7 +88,7 @@ export async function getOptionsFromGithub(options: { } async function getSkipRemoteConfigFile( - cwd: string, + cwd?: string, skipRemoteConfig?: boolean, remoteConfig?: RemoteConfig ) { @@ -104,33 +104,35 @@ async function getSkipRemoteConfigFile( return true; } - const [isLocalConfigModified, isLocalConfigUntracked, localCommitDate] = - await Promise.all([ - isLocalConfigFileModified({ cwd }), - isLocalConfigFileUntracked({ cwd }), - getLocalConfigFileCommitDate({ cwd }), - ]); - - if (isLocalConfigUntracked) { - logger.info('Skipping remote config: local config is new'); - return true; - } - - if (isLocalConfigModified) { - logger.info('Skipping remote config: local config is modified'); - return true; - } - - if ( - localCommitDate && - localCommitDate > Date.parse(remoteConfig.committedDate) - ) { - logger.info( - `Skipping remote config: local config is newer: ${new Date( - localCommitDate - ).toISOString()} > ${remoteConfig.committedDate}` - ); - return true; + if (cwd) { + const [isLocalConfigModified, isLocalConfigUntracked, localCommitDate] = + await Promise.all([ + isLocalConfigFileModified({ cwd }), + isLocalConfigFileUntracked({ cwd }), + getLocalConfigFileCommitDate({ cwd }), + ]); + + if (isLocalConfigUntracked) { + logger.info('Skipping remote config: local config is new'); + return true; + } + + if (isLocalConfigModified) { + logger.info('Skipping remote config: local config is modified'); + return true; + } + + if ( + localCommitDate && + localCommitDate > Date.parse(remoteConfig.committedDate) + ) { + logger.info( + `Skipping remote config: local config is newer: ${new Date( + localCommitDate + ).toISOString()} > ${remoteConfig.committedDate}` + ); + return true; + } } return false; diff --git a/src/services/github/v4/getRepoOwnerAndName.private.test.ts b/src/services/github/v4/getRepoOwnerAndName.private.test.ts new file mode 100644 index 00000000..590ed57a --- /dev/null +++ b/src/services/github/v4/getRepoOwnerAndName.private.test.ts @@ -0,0 +1,44 @@ +import { resolve } from 'path'; +import del from 'del'; +import makeDir from 'make-dir'; +import * as childProcess from '../../../services/child-process-promisified'; +import { getDevAccessToken } from '../../../test/private/getDevAccessToken'; +import { getRepoOwnerAndName } from './getRepoOwnerAndName'; + +describe('fetchRemoteProjectConfig', () => { + let devAccessToken: string; + + async function resetSandbox() { + const GIT_SANDBOX_DIR_PATH = resolve( + `${__dirname}/_tmp_sandbox_/getRepoOwnerAndName.private.test` + ); + + await del(GIT_SANDBOX_DIR_PATH); + await makeDir(GIT_SANDBOX_DIR_PATH); + + return GIT_SANDBOX_DIR_PATH; + } + + beforeEach(async () => { + devAccessToken = await getDevAccessToken(); + }); + + describe('when there are at least one git remote', () => { + it('calls github to get original owner', async () => { + const sandboxPath = await resetSandbox(); + const execOpts = { cwd: sandboxPath }; + await childProcess.exec(`git init`, execOpts); + await childProcess.exec( + `git remote add origin git@github.com:sqren/kibana.git`, + execOpts + ); + + const { repoName, repoOwner } = await getRepoOwnerAndName({ + accessToken: devAccessToken, + cwd: sandboxPath, + }); + + expect({ repoName, repoOwner }).toEqual({}); + }); + }); +}); diff --git a/src/services/github/v4/getRepoOwnerAndName.ts b/src/services/github/v4/getRepoOwnerAndName.ts new file mode 100644 index 00000000..32aaa3dc --- /dev/null +++ b/src/services/github/v4/getRepoOwnerAndName.ts @@ -0,0 +1,74 @@ +import { maybe } from '../../../utils/maybe'; +import { getRepoInfoFromGitRemotes } from '../../git'; +import { apiRequestV4 } from './apiRequestV4'; + +export async function getRepoOwnerAndName({ + accessToken, + githubApiBaseUrlV4, + cwd, +}: { + accessToken: string; + githubApiBaseUrlV4?: string; + cwd: string; +}): Promise<{ repoOwner?: string; repoName?: string }> { + const remotes = await getRepoInfoFromGitRemotes({ cwd }); + const firstRemote = maybe(remotes[0]); + + if (!firstRemote) { + return {}; + } + + const res = await apiRequestV4({ + githubApiBaseUrlV4, + accessToken, + query, + variables: { + repoOwner: firstRemote.repoOwner, + repoName: firstRemote.repoName, + }, + handleError: false, + }); + + return { + repoName: res.repository.name, + // get the original owner (not the fork owner) + repoOwner: res.repository.isFork + ? res.repository.parent.owner.login + : res.repository.owner.login, + }; +} + +export interface RepoOwnerAndNameResponse { + repository: + | { + isFork: true; + name: string; + owner: { login: string }; + parent: { + owner: { login: string }; + }; + } + | { + isFork: false; + name: string; + owner: { login: string }; + parent: null; + }; +} + +export const query = /* GraphQL */ ` + query RepoOwnerAndName($repoOwner: String!, $repoName: String!) { + repository(owner: $repoOwner, name: $repoName) { + isFork + name + owner { + login + } + parent { + owner { + login + } + } + } + } +`; diff --git a/src/services/logger.ts b/src/services/logger.ts index a9f0b7ae..de4882e4 100644 --- a/src/services/logger.ts +++ b/src/services/logger.ts @@ -42,7 +42,7 @@ export function updateLogger({ accessToken, verbose, }: { - accessToken?: string; + accessToken: string; verbose?: boolean; }) { // set access token diff --git a/src/test/mocks.ts b/src/test/mocks.ts new file mode 100644 index 00000000..e4e5582f --- /dev/null +++ b/src/test/mocks.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +const oraMock = { + start: () => oraMock, + succeed: () => jest.fn(), + stop: () => jest.fn(), + fail: () => jest.fn(), + stopAndPersist: () => jest.fn(), + set text(value: string) {}, +}; + +export function mockOra() { + const ora = jest.fn(() => oraMock); + //@ts-expect-error + ora.oraMock = oraMock; + jest.mock('ora', () => ora); +} + +export function getOraMock() { + return oraMock; +} diff --git a/src/test/setupFiles/automatic-mocks.ts b/src/test/setupFiles/automatic-mocks.ts index 8dd2aac8..7ed4ba60 100644 --- a/src/test/setupFiles/automatic-mocks.ts +++ b/src/test/setupFiles/automatic-mocks.ts @@ -3,7 +3,8 @@ * It will be run once per test file */ -import * as getPackageVersionModule from '../../utils/getPackageVersion'; +import * as packageVersionModule from '../../utils/packageVersion'; +import { mockOra } from '../mocks'; /* eslint-disable @typescript-eslint/no-empty-function */ @@ -11,9 +12,9 @@ jest.mock('find-up', () => { return jest.fn(async () => '/path/to/project/config'); }); -jest - .spyOn(getPackageVersionModule, 'getPackageVersion') - .mockReturnValue('1.2.3'); +// @ts-expect-error +// eslint-disable-next-line no-import-assign +packageVersionModule.PACKAGE_VERSION = '1.2.3'; jest.mock('make-dir', () => { return jest.fn(() => Promise.resolve('/some/path')); @@ -23,17 +24,7 @@ jest.mock('del', () => { return jest.fn(async (path) => `Attempted to delete ${path}`); }); -jest.mock('ora', () => { - const ora = { - succeed: () => {}, - start: () => ora, - stop: () => {}, - fail: () => {}, - stopAndPersist: () => {}, - }; - - return jest.fn(() => ora); -}); +mockOra(); // silence logger jest.mock('../../services/logger', () => { diff --git a/src/ui/maybeSetupRepo.test.ts b/src/ui/maybeSetupRepo.test.ts index d95da3ad..b4dcd14b 100644 --- a/src/ui/maybeSetupRepo.test.ts +++ b/src/ui/maybeSetupRepo.test.ts @@ -1,57 +1,147 @@ -import fs from 'fs/promises'; +import { stat } from 'fs/promises'; import os from 'os'; import del from 'del'; import { ValidConfigOptions } from '../options/options'; import * as childProcess from '../services/child-process-promisified'; +import * as git from '../services/git'; +import { getOraMock } from '../test/mocks'; import { maybeSetupRepo } from './maybeSetupRepo'; +const fs = { stat }; describe('maybeSetupRepo', () => { let execSpy: jest.SpyInstance; + beforeEach(() => { jest.spyOn(os, 'homedir').mockReturnValue('/myHomeDir'); + execSpy = jest .spyOn(childProcess, 'exec') .mockResolvedValue({ stderr: '', stdout: '' }); }); - it('should delete repo if an error occurs', async () => { - expect.assertions(2); + afterEach(() => { + jest.restoreAllMocks(); + }); - execSpy = jest - .spyOn(childProcess, 'execAsCallback') - .mockImplementation((cmd) => { - if (cmd.startsWith('git clone')) { - throw new Error('Simulated git clone failure'); - } + describe('if an error occurs while cloning', () => { + it('should delete repo', async () => { + expect.assertions(2); - throw new Error('unknown error'); - }); + execSpy = jest + .spyOn(childProcess, 'execAsCallback') + .mockImplementation((cmd) => { + if (cmd.startsWith('git clone')) { + throw new Error('Simulated git clone failure'); + } - await expect( - maybeSetupRepo({ - accessToken: 'myAccessToken', - gitHostname: 'github.com', + throw new Error('unknown error'); + }); + + await expect( + maybeSetupRepo({ + repoName: 'kibana', + repoOwner: 'elastic', + } as ValidConfigOptions) + ).rejects.toThrowError('Simulated git clone failure'); + + expect(del).toHaveBeenCalledWith( + '/myHomeDir/.backport/repositories/elastic/kibana' + ); + }); + }); + + describe('while cloning the repo', () => { + it('updates the progress', async () => { + let onCloneComplete: () => void; + let dataHandler: (chunk: any) => void; + + const oraMock = getOraMock(); + const spinnerTextSpy = jest.spyOn(oraMock, 'text', 'set'); + const spinnerSuccessSpy = jest.spyOn(oraMock, 'succeed'); + + jest + .spyOn(git, 'getSourceRepoPath') + .mockResolvedValue('/path/to/source/repo'); + + jest + .spyOn(childProcess, 'execAsCallback') + //@ts-expect-error + .mockImplementation((cmdString, cmdOptions, onComplete) => { + // callback should be called to finalize the operation + if (onComplete) { + //@ts-expect-error + onCloneComplete = onComplete; + } + + return { + stderr: { + on: (name, handler) => { + dataHandler = handler; + }, + }, + }; + }); + + setTimeout(() => { + dataHandler('Receiving objects: 1%'); + dataHandler('Receiving objects: 10%'); + dataHandler('Receiving objects: 20%'); + dataHandler('Receiving objects: 100%'); + dataHandler('Updating files: 1%'); + dataHandler('Updating files: 10%'); + dataHandler('Updating files: 20%'); + dataHandler('Updating files: 100%'); + onCloneComplete(); + }, 50); + + await maybeSetupRepo({ repoName: 'kibana', repoOwner: 'elastic', - } as ValidConfigOptions) - ).rejects.toThrowError('Simulated git clone failure'); + } as ValidConfigOptions); - expect(del).toHaveBeenCalledWith( - '/myHomeDir/.backport/repositories/elastic/kibana' - ); + expect(spinnerTextSpy.mock.calls.map((call) => call[0])) + .toMatchInlineSnapshot(` + Array [ + "0% Cloning repository from /path/to/source/repo (one-time operation)", + "1% Cloning repository from /path/to/source/repo (one-time operation)", + "9% Cloning repository from /path/to/source/repo (one-time operation)", + "18% Cloning repository from /path/to/source/repo (one-time operation)", + "90% Cloning repository from /path/to/source/repo (one-time operation)", + "90% Cloning repository from /path/to/source/repo (one-time operation)", + "91% Cloning repository from /path/to/source/repo (one-time operation)", + "92% Cloning repository from /path/to/source/repo (one-time operation)", + "100% Cloning repository from /path/to/source/repo (one-time operation)", + ] + `); + + expect(spinnerSuccessSpy).toHaveBeenCalledWith( + '100% Cloning repository from /path/to/source/repo (one-time operation)' + ); + }); }); describe('if repo already exists', () => { beforeEach(() => { // @ts-expect-error jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); + + jest + .spyOn(childProcess, 'execAsCallback') + //@ts-expect-error + .mockImplementation((cmdString, cmdOptions, callback) => { + //@ts-expect-error + callback(); + + return { + stderr: { on: () => null }, + }; + }); }); it('should re-create remotes for both source repo and fork', async () => { await maybeSetupRepo({ accessToken: 'myAccessToken', authenticatedUsername: 'sqren_authenticated', - gitHostname: 'github.com', repoName: 'kibana', repoOwner: 'elastic', } as ValidConfigOptions); @@ -66,4 +156,74 @@ describe('maybeSetupRepo', () => { ]); }); }); + + describe('if repo does not exists locally', () => { + beforeEach(() => { + // @ts-expect-error + jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); + + jest + .spyOn(childProcess, 'execAsCallback') + //@ts-expect-error + .mockImplementation((cmdString, cmdOptions, callback) => { + //@ts-expect-error + callback(); + + return { + stderr: { on: () => null }, + }; + }); + }); + + it('should clone it from github.com', async () => { + await maybeSetupRepo({ + accessToken: 'myAccessToken', + gitHostname: 'github.com', + repoName: 'kibana', + repoOwner: 'elastic', + } as ValidConfigOptions); + + expect(childProcess.execAsCallback).toHaveBeenCalledWith( + 'git clone https://x-access-token:myAccessToken@github.com/elastic/kibana.git /myHomeDir/.backport/repositories/elastic/kibana --progress', + expect.any(Object), + expect.any(Function) + ); + }); + }); + + describe('if repo does exist locally', () => { + beforeEach(() => { + // @ts-expect-error + jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true }); + + jest + .spyOn(git, 'getSourceRepoPath') + .mockResolvedValue('/path/to/source/repo'); + + jest + .spyOn(childProcess, 'execAsCallback') + //@ts-expect-error + .mockImplementation((cmdString, cmdOptions, callback) => { + //@ts-expect-error + callback(); + + return { + stderr: { on: () => null }, + }; + }); + }); + + it('should clone it from local folder', async () => { + await maybeSetupRepo({ + repoName: 'kibana', + repoOwner: 'elastic', + } as ValidConfigOptions); + + expect(childProcess.execAsCallback).toHaveBeenCalledWith( + 'git clone /path/to/source/repo /myHomeDir/.backport/repositories/elastic/kibana --progress', + expect.any(Object), + expect.any(Function) + ); + }); + }); }); diff --git a/src/ui/maybeSetupRepo.ts b/src/ui/maybeSetupRepo.ts index 95de1622..ae378e9b 100644 --- a/src/ui/maybeSetupRepo.ts +++ b/src/ui/maybeSetupRepo.ts @@ -1,3 +1,5 @@ +import { stat } from 'fs/promises'; +import del = require('del'); import ora = require('ora'); import { ValidConfigOptions } from '../options/options'; import { getRepoPath } from '../services/env'; @@ -5,32 +7,37 @@ import { addRemote, cloneRepo, deleteRemote, - deleteRepo, getSourceRepoPath, - repoExists, } from '../services/git'; export async function maybeSetupRepo(options: ValidConfigOptions) { - const isAlreadyCloned = await repoExists(options); + const repoPath = getRepoPath(options); + const isAlreadyCloned = await getIsRepoCloned(repoPath); // clone repo if folder does not already exists if (!isAlreadyCloned) { const spinner = ora().start(); try { const sourcePath = await getSourceRepoPath(options); - const targetPath = getRepoPath(options); - const spinnerCloneText = `Cloning repository from ${sourcePath} (one-time operation)`; + const sourcePathHumanReadable = sourcePath.includes(options.gitHostname) + ? options.gitHostname + : sourcePath; + + const spinnerCloneText = `Cloning repository from ${sourcePathHumanReadable} (one-time operation)`; spinner.text = `0% ${spinnerCloneText}`; - await cloneRepo({ sourcePath, targetPath }, (progress: string) => { - spinner.text = `${progress}% ${spinnerCloneText}`; - }); + await cloneRepo( + { sourcePath, targetPath: repoPath }, + (progress: number) => { + spinner.text = `${progress}% ${spinnerCloneText}`; + } + ); spinner.succeed(`100% ${spinnerCloneText}`); } catch (e) { spinner.fail(); - await deleteRepo(options); + await del(repoPath); throw e; } } @@ -42,9 +49,22 @@ export async function maybeSetupRepo(options: ValidConfigOptions) { await deleteRemote(options, options.authenticatedUsername); await addRemote(options, options.authenticatedUsername); - // update remote for origin (if the above is a fork) + // add remote for non-fork repo (if the above is a fork) if (options.authenticatedUsername !== options.repoOwner) { await deleteRemote(options, options.repoOwner); await addRemote(options, options.repoOwner); } } + +async function getIsRepoCloned(path: string): Promise { + try { + const stats = await stat(path); + return stats.isDirectory(); + } catch (e) { + if (e.code === 'ENOENT') { + return false; + } + + throw e; + } +} diff --git a/src/utils/getPackageVersion.ts b/src/utils/getPackageVersion.ts deleted file mode 100644 index 3a125ae1..00000000 --- a/src/utils/getPackageVersion.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { version as packageVersion } from '../../package.json'; - -export function getPackageVersion() { - return packageVersion; -} diff --git a/src/utils/maybe.ts b/src/utils/maybe.ts new file mode 100644 index 00000000..6fe9602e --- /dev/null +++ b/src/utils/maybe.ts @@ -0,0 +1,3 @@ +export function maybe(value: T): T | undefined { + return value; +} diff --git a/src/utils/packageVersion.ts b/src/utils/packageVersion.ts new file mode 100644 index 00000000..eb5bcc95 --- /dev/null +++ b/src/utils/packageVersion.ts @@ -0,0 +1 @@ +export const PACKAGE_VERSION = '6.2.1'; diff --git a/tsconfig.json b/tsconfig.json index 87c2717a..39a39891 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "declaration": true, "target": "ES2021", - "resolveJsonModule": true, "lib": ["ES2021"], "module": "CommonJS", "outDir": "./dist", diff --git a/yarn.lock b/yarn.lock index 1e937ce5..70e1a5c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1069,10 +1069,10 @@ dependencies: "@types/node" "*" -"@types/inquirer@^8.1.3": - version "8.1.3" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.1.3.tgz#dfda4c97cdbe304e4dceb378a80f79448ea5c8fe" - integrity sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ== +"@types/inquirer@^8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.0.tgz#b9566d048f5ff65159f2ed97aff45fe0f00b35ec" + integrity sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ== dependencies: "@types/through" "*" rxjs "^7.2.0" @@ -1189,14 +1189,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.1.tgz#e5a86d7e1f9dc0b3df1e6d94feaf20dd838d066c" - integrity sha512-Xv9tkFlyD4MQGpJgTo6wqDqGvHIRmRgah/2Sjz1PUnJTawjHWIwBivUE9x0QtU2WVii9baYgavo/bHjrZJkqTw== +"@typescript-eslint/eslint-plugin@^5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz#f8c1d59fc37bd6d9d11c97267fdfe722c4777152" + integrity sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q== dependencies: - "@typescript-eslint/experimental-utils" "5.9.1" - "@typescript-eslint/scope-manager" "5.9.1" - "@typescript-eslint/type-utils" "5.9.1" + "@typescript-eslint/scope-manager" "5.10.2" + "@typescript-eslint/type-utils" "5.10.2" + "@typescript-eslint/utils" "5.10.2" debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" @@ -1204,115 +1204,69 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.1.tgz#8c407c4dd5ffe522329df6e4c9c2b52206d5f7f1" - integrity sha512-cb1Njyss0mLL9kLXgS/eEY53SZQ9sT519wpX3i+U457l2UXRDuo87hgKfgRazmu9/tQb0x2sr3Y0yrU+Zz0y+w== +"@typescript-eslint/parser@^5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.2.tgz#b6076d27cc5499ce3f2c625f5ccde946ecb7db9a" + integrity sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg== dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.9.1" - "@typescript-eslint/types" "5.9.1" - "@typescript-eslint/typescript-estree" "5.9.1" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/experimental-utils@^5.0.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.6.0.tgz#f3a5960f2004abdcac7bb81412bafc1560841c23" - integrity sha512-VDoRf3Qj7+W3sS/ZBXZh3LBzp0snDLEgvp6qj0vOAIiAPM07bd5ojQ3CTzF/QFl5AKh7Bh1ycgj6lFBJHUt/DA== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.6.0" - "@typescript-eslint/types" "5.6.0" - "@typescript-eslint/typescript-estree" "5.6.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@^5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.9.1.tgz#b114011010a87e17b3265ca715e16c76a9834cef" - integrity sha512-PLYO0AmwD6s6n0ZQB5kqPgfvh73p0+VqopQQLuNfi7Lm0EpfKyDalchpVwkE+81k5HeiRrTV/9w1aNHzjD7C4g== - dependencies: - "@typescript-eslint/scope-manager" "5.9.1" - "@typescript-eslint/types" "5.9.1" - "@typescript-eslint/typescript-estree" "5.9.1" + "@typescript-eslint/scope-manager" "5.10.2" + "@typescript-eslint/types" "5.10.2" + "@typescript-eslint/typescript-estree" "5.10.2" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.6.0.tgz#9dd7f007dc8f3a34cdff6f79f5eaab27ae05157e" - integrity sha512-1U1G77Hw2jsGWVsO2w6eVCbOg0HZ5WxL/cozVSTfqnL/eB9muhb8THsP0G3w+BB5xAHv9KptwdfYFAUfzcIh4A== - dependencies: - "@typescript-eslint/types" "5.6.0" - "@typescript-eslint/visitor-keys" "5.6.0" - -"@typescript-eslint/scope-manager@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.9.1.tgz#6c27be89f1a9409f284d95dfa08ee3400166fe69" - integrity sha512-8BwvWkho3B/UOtzRyW07ffJXPaLSUKFBjpq8aqsRvu6HdEuzCY57+ffT7QoV4QXJXWSU1+7g3wE4AlgImmQ9pQ== +"@typescript-eslint/scope-manager@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz#92c0bc935ec00f3d8638cdffb3d0e70c9b879639" + integrity sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw== dependencies: - "@typescript-eslint/types" "5.9.1" - "@typescript-eslint/visitor-keys" "5.9.1" + "@typescript-eslint/types" "5.10.2" + "@typescript-eslint/visitor-keys" "5.10.2" -"@typescript-eslint/type-utils@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.9.1.tgz#c6832ffe655b9b1fec642d36db1a262d721193de" - integrity sha512-tRSpdBnPRssjlUh35rE9ug5HrUvaB9ntREy7gPXXKwmIx61TNN7+l5YKgi1hMKxo5NvqZCfYhA5FvyuJG6X6vg== +"@typescript-eslint/type-utils@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.10.2.tgz#ad5acdf98a7d2ab030bea81f17da457519101ceb" + integrity sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw== dependencies: - "@typescript-eslint/experimental-utils" "5.9.1" + "@typescript-eslint/utils" "5.10.2" debug "^4.3.2" tsutils "^3.21.0" -"@typescript-eslint/types@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.6.0.tgz#745cb1b59daadcc1f32f7be95f0f68accf38afdd" - integrity sha512-OIZffked7mXv4mXzWU5MgAEbCf9ecNJBKi+Si6/I9PpTaj+cf2x58h2oHW5/P/yTnPkKaayfjhLvx+crnl5ubA== - -"@typescript-eslint/types@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.9.1.tgz#1bef8f238a2fb32ebc6ff6d75020d9f47a1593c6" - integrity sha512-SsWegWudWpkZCwwYcKoDwuAjoZXnM1y2EbEerTHho19Hmm+bQ56QG4L4jrtCu0bI5STaRTvRTZmjprWlTw/5NQ== - -"@typescript-eslint/typescript-estree@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.6.0.tgz#dfbb19c9307fdd81bd9c650c67e8397821d7faf0" - integrity sha512-92vK5tQaE81rK7fOmuWMrSQtK1IMonESR+RJR2Tlc7w4o0MeEdjgidY/uO2Gobh7z4Q1hhS94Cr7r021fMVEeA== - dependencies: - "@typescript-eslint/types" "5.6.0" - "@typescript-eslint/visitor-keys" "5.6.0" - debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" +"@typescript-eslint/types@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.2.tgz#604d15d795c4601fffba6ecb4587ff9fdec68ce8" + integrity sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w== -"@typescript-eslint/typescript-estree@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.1.tgz#d5b996f49476495070d2b8dd354861cf33c005d6" - integrity sha512-gL1sP6A/KG0HwrahVXI9fZyeVTxEYV//6PmcOn1tD0rw8VhUWYeZeuWHwwhnewnvEMcHjhnJLOBhA9rK4vmb8A== +"@typescript-eslint/typescript-estree@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz#810906056cd3ddcb35aa333fdbbef3713b0fe4a7" + integrity sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ== dependencies: - "@typescript-eslint/types" "5.9.1" - "@typescript-eslint/visitor-keys" "5.9.1" + "@typescript-eslint/types" "5.10.2" + "@typescript-eslint/visitor-keys" "5.10.2" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.6.0.tgz#3e36509e103fe9713d8f035ac977235fd63cb6e6" - integrity sha512-1p7hDp5cpRFUyE3+lvA74egs+RWSgumrBpzBCDzfTFv0aQ7lIeay80yU0hIxgAhwQ6PcasW35kaOCyDOv6O/Ng== +"@typescript-eslint/utils@5.10.2", "@typescript-eslint/utils@^5.10.0": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.2.tgz#1fcd37547c32c648ab11aea7173ec30060ee87a8" + integrity sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg== dependencies: - "@typescript-eslint/types" "5.6.0" - eslint-visitor-keys "^3.0.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.10.2" + "@typescript-eslint/types" "5.10.2" + "@typescript-eslint/typescript-estree" "5.10.2" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.1.tgz#f52206f38128dd4f675cf28070a41596eee985b7" - integrity sha512-Xh37pNz9e9ryW4TVdwiFzmr4hloty8cFj8GTWMXh3Z8swGwyQWeCcNgF0hm6t09iZd6eiZmIf4zHedQVP6TVtg== +"@typescript-eslint/visitor-keys@5.10.2": + version "5.10.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz#fdbf272d8e61c045d865bd6c8b41bea73d222f3d" + integrity sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q== dependencies: - "@typescript-eslint/types" "5.9.1" + "@typescript-eslint/types" "5.10.2" eslint-visitor-keys "^3.0.0" abab@^2.0.3, abab@^2.0.5: @@ -1501,12 +1455,12 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -axios@^0.24.0: - version "0.24.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" - integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== dependencies: - follow-redirects "^1.14.4" + follow-redirects "^1.14.7" babel-jest@^27.4.6: version "27.4.6" @@ -2205,12 +2159,12 @@ eslint-plugin-import@^2.25.4: resolve "^1.20.0" tsconfig-paths "^3.12.0" -eslint-plugin-jest@^25.7.0: - version "25.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" - integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== +eslint-plugin-jest@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.0.0.tgz#f83a25a23ab90ce5b375b1d44389b8c391be5ce8" + integrity sha512-Fvs0YgJ/nw9FTrnqTuMGVrkozkd07jkQzWm0ajqyHlfcsdkxGfAuv30fgfWHOnHiCr9+1YQ365CcDX7vrNhqQg== dependencies: - "@typescript-eslint/experimental-utils" "^5.0.0" + "@typescript-eslint/utils" "^5.10.0" eslint-plugin-prettier@^4.0.0: version "4.0.0" @@ -2257,10 +2211,10 @@ eslint-visitor-keys@^3.2.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== -eslint@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.7.0.tgz#22e036842ee5b7cf87b03fe237731675b4d3633c" - integrity sha512-ifHYzkBGrzS2iDU7KjhCAVMGCvF6M3Xfs8X8b37cgrUlDt6bWRTpRh6T/gtSXv1HJ/BUGgmjvNvOEGu85Iif7w== +eslint@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" + integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== dependencies: "@eslint/eslintrc" "^1.0.5" "@humanwhocodes/config-array" "^0.9.2" @@ -2516,7 +2470,7 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.14.4: +follow-redirects@^1.14.7: version "1.14.7" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== @@ -2689,10 +2643,10 @@ graphql-ws@^5.4.1: resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-5.5.5.tgz#f375486d3f196e2a2527b503644693ae3a8670a9" integrity sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw== -graphql@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.2.0.tgz#de3150e80f1fc009590b92a9d16ab1b46e12b656" - integrity sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA== +graphql@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" + integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== has-ansi@^3.0.0: version "3.0.0" @@ -3643,10 +3597,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -lint-staged@^12.1.7: - version "12.1.7" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.1.7.tgz#fe9137992ac18a456422bb8484dd30be0140629f" - integrity sha512-bltv/ejiLWtowExpjU+s5z8j1Byjg9AlmaAjMmqNbIicY69u6sYIwXGg0dCn0TlkrrY2CphtHIXAkbZ+1VoWQQ== +lint-staged@^12.3.3: + version "12.3.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.3.tgz#0a465962fe53baa2b4b9da50801ead49a910e03b" + integrity sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag== dependencies: cli-truncate "^3.1.0" colorette "^2.0.16" @@ -3654,25 +3608,25 @@ lint-staged@^12.1.7: debug "^4.3.3" execa "^5.1.1" lilconfig "2.0.4" - listr2 "^3.13.5" + listr2 "^4.0.1" micromatch "^4.0.4" normalize-path "^3.0.0" - object-inspect "^1.11.1" + object-inspect "^1.12.0" string-argv "^0.3.1" supports-color "^9.2.1" yaml "^1.10.2" -listr2@^3.13.5: - version "3.13.5" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.5.tgz#105a813f2eb2329c4aae27373a281d610ee4985f" - integrity sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA== +listr2@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.2.tgz#04d66f8c8694a14920d7df08ebe01568948fb500" + integrity sha512-YcgwfCWpvPbj9FLUGqvdFvd3hrFWKpOeuXznRgfWEJ7RNr8b/IKKIKZABHx3aU+4CWN/iSAFFSReziQG6vTeIA== dependencies: cli-truncate "^2.1.0" colorette "^2.0.16" log-update "^4.0.0" p-map "^4.0.0" rfdc "^1.3.0" - rxjs "^7.4.0" + rxjs "^7.5.2" through "^2.3.8" wrap-ansi "^7.0.0" @@ -3855,10 +3809,10 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -nock@^13.2.2: - version "13.2.2" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.2.tgz#29a6942250278209c2b3e7a38310f703581b21fa" - integrity sha512-PcBHuvl9i6zfaJ50A7LS55oU+nFLv8htXIhffJO+FxyfibdZ4jEvd9kTuvkrJireBFIGMZ+oUIRpMK5gU9h//g== +nock@^13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1" + integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -3916,7 +3870,7 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.1.tgz#d4bd7d7de54b9a75599f59a00bd698c1f1c6549b" integrity sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA== -object-inspect@^1.11.1: +object-inspect@^1.12.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== @@ -4308,13 +4262,20 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.2.0, rxjs@^7.4.0: +rxjs@^7.2.0: version "7.4.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== dependencies: tslib "~2.1.0" +rxjs@^7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.2.tgz#11e4a3a1dfad85dbf7fb6e33cbba17668497490b" + integrity sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== + dependencies: + tslib "^2.1.0" + safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -4335,6 +4296,11 @@ safe-stable-stringify@^1.1.0: resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz#c8a220ab525cd94e60ebf47ddc404d610dc5d84a" integrity sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw== +safe-stable-stringify@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" + integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4830,10 +4796,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@^4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" + integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== unbox-primitive@^1.0.1: version "1.0.1" @@ -5012,10 +4978,10 @@ winston-transport@^4.4.2: readable-stream "^3.4.0" triple-beam "^1.2.0" -winston@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.4.0.tgz#7080f24b02a0684f8a37f9d5c6afb1ac23e95b84" - integrity sha512-FqilVj+5HKwCfIHQzMxrrd5tBIH10JTS3koFGbLVWBODjiIYq7zir08rFyBT4rrTYG/eaTqDcfSIbcjSM78YSw== +winston@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.5.1.tgz#b25cc899d015836dbf8c583dec8c4c4483a0da2e" + integrity sha512-tbRtVy+vsSSCLcZq/8nXZaOie/S2tPXPFt4be/Q3vI/WtYwm7rrwidxVw2GRa38FIXcJ1kUM6MOZ9Jmnk3F3UA== dependencies: "@dabh/diagnostics" "^2.0.2" async "^3.2.3" @@ -5023,6 +4989,7 @@ winston@^3.4.0: logform "^2.3.2" one-time "^1.0.0" readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" stack-trace "0.0.x" triple-beam "^1.3.0" winston-transport "^4.4.2"