diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 2b612642f..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,18 +0,0 @@ -cache: - paths: - - node_modules/ - -stages: - - test - -job_lint_and_test: - image: node - stage: test - before_script: - - yarn - - yarn build - script: - - ./bin/run --version - - ./bin/run --help - - yarn lint - - yarn test --coverage diff --git a/renovate.json b/.renovaterc similarity index 63% rename from renovate.json rename to .renovaterc index 949330f8e..132158ec6 100644 --- a/renovate.json +++ b/.renovaterc @@ -1,8 +1,8 @@ { "extends": [ - "config:base" + "config:base", + ":preserveSemverRanges" ], - "rangeStrategy": "replace", "automerge": true, "major": { "automerge": false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3989aa272..c09b03418 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,55 +1,66 @@ # Contributing to DX Scanner -It seems you want to participate on DX Scanner - that's great! We welcome contributions to our [open source project on GitHub](http://github.com/DXHeroes/dx-scanner). +It seems like you want to participate on our DX Scanner - that's great! We welcome contributions to our [open source project on GitHub](http://github.com/DXHeroes/dx-scanner). ## Introduction -We're glad you're interested in DX Scanner in the way of contributing. We value the pro-community developers as you are. + +We are glad that you are interested in DX Scanner in the way of contributing. We value the pro-community developers as you are. ## Help the community -1) Repport an Error or Bug πŸ› + +1) Report an Error or a Bug πŸ› 2) Request a Feature πŸ†• -3) Contribute Code πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’» -4) Contribute Documentation πŸ“ +3) Contribute to the Code πŸ‘¨β€πŸ’»πŸ‘©β€πŸ’» +4) Contribute to the Documentation πŸ“ 5) Provide Support on Issues ℹ️ -## Need a help? +## Need help? -If you have any question about this project, how to use it, or just need clarification about anything open an Issue at https://github.com/DXHeroes/dx-scanner/issues . +If you have any question about this project (for example, how to use it) or if you just need some clarification about anything, please open an Issue at [Issues](https://github.com/DXHeroes/dx-scanner/issues). ## Contributing -1. **Fork & Clone** the repository -2. **Setup** the DX Scanner - - Install Yarn with `npm i -g yarn` if not yet installed - - Install packages with `yarn install` - - Run with `yarn start` - - Build with `yarn build` - - Run tests with `yarn test` - - Lint code with `yarn lint` or `yarn lint:fix` with autofixer -3. **Commit** changes to your own branch -4. **Push** your work back up to your fork -5. Submit a **Pull Request** so that we can review your changes +Follow these steps: + +1. **Fork & Clone** the repository +2. **Setup** the DX Scanner + - Install Yarn with `npm i -g yarn` if not yet installed + - Install packages with `yarn install` + - Run with `yarn start` + - Build with `yarn build` + - Run tests with `yarn test` + - Lint code with `yarn lint` or `yarn lint:fix` with autofixer +3. **Commit** changes to your own branch +4. **Push** your work back up to your fork +5. Submit a **Pull Request** so that we can review your changes -NOTE: Be sure to merge the latest from "upstream" before making a pull request. +**NOTE: Be sure to merge the latest from "upstream" before making a pull request.** ## Contribute Code + ### Practices -Practices check your repository for using tooling, which improves product development. + +Check your repository for using the correct tooling which improves product development. #### JavaScript/TypeScript -We have more than 10 JS/TS practices for now! If you want to add more, you're more than welcome. Check [JavaScript Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices/JavaScript) to see, what's the pattern of implementation of your own practice. + +We have more than 10 JS/TS practices for now! If you want to add more, you are more than welcome. Check [JavaScript Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices/JavaScript) to see what is the pattern of implementation of your own practice. #### Other Languages -Other languages are not supported yet. If you want to contribute, check [Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices). Get inspired by [Javascript Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices/JavaScript) implementation. + +Other languages are not supported yet. If you want to contribute, see [Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices). Get inspired by [Javascript Practices](https://github.com/DXHeroes/dx-scanner/tree/master/src/practices/JavaScript) implementation. ### Inspectors -Inspectors indirectly works with Git code hosting providers APIs. They use common interfaces provided by services so you don't have to which API to use. + +Inspectors indirectly work with Git code hosting providers APIs. They use common interfaces provided by services so you do not have to know which API to use. ### Services + There is a [File System Service](https://github.com/DXHeroes/dx-scanner/tree/master/src/services) working with files. #### Git -Services directly use Git code hosting providers APIs while checking the rate limits. They convert API responses to the own interface, so Inspectors can use them. Only the GitHub Service is implemented for now. If you need e.g. GitLab Service, you can contribute! Get inspired by [GitHub Service](https://github.com/DXHeroes/dx-scanner/blob/master/src/services/git/GitHubService.ts) + +Services directly use Git code hosting providers APIs while checking the rate limits. They convert API responses to their own interface so Inspectors can use them. Only the GitHub Service is implemented so far. If you need for example, GitLab Service, you can contribute! Get inspired by [GitHub Service](https://github.com/DXHeroes/dx-scanner/blob/master/src/services/git/GitHubService.ts) ## Copyright and Licensing diff --git a/README.md b/README.md index 95941d598..422197670 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Version](https://img.shields.io/npm/v/dx-scanner.svg)](https://npmjs.org/package/dx-scanner) [![Travis (.org)](https://img.shields.io/travis/DXHeroes/dx-scanner)](https://travis-ci.org/DXHeroes/dx-scanner) ![Codecov](https://img.shields.io/codecov/c/github/DXHeroes/dx-scanner) -![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/DXHeroes/dx-scanner) ![last commit](https://img.shields.io/github/last-commit/DXHeroes/dx-scanner) ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/DXHeroes/dx-scanner) [![Downloads/week](https://img.shields.io/npm/dw/dx-scanner.svg)](https://npmjs.org/package/dx-scanner) @@ -14,9 +13,10 @@ # DX Scanner -DX Scanner is an open-source library that allows you to β€œmeasure” Developer Experience directly based on your source code and recommend practices to adopt that will help you to improve your product development. +DX Scanner is an open source library that allows you to β€œmeasure” Developer Experience directly based on your source code. DX Scanner recommends practices that can help you with improving your product development. + +## What language is supported? -### Which languages are supported? Language | Supported ------------ | ------------- JavaScript/TypeScript | βœ… @@ -64,10 +64,10 @@ dxs [path] ``` ## Configuration βš™οΈ -Add ```dxscannerrc.*``` config file to change default configuration. It can be a ```.json```, ```.yml``` even dotfile! +Add ```dxscannerrc.*``` config file to change default configuration. It can be a ```.json```, ```.yml```, and even a dotfile! **Practices** -You can switch off practices you don't want to scan or change its impact. Use the id of the practice. +You can switch off practices you do not want to scan or change its impact. Use the id of the practice. Possible impact: ``` @@ -93,7 +93,7 @@ Example : ``` ## Contributing πŸ‘©β€πŸ’» πŸ‘¨β€πŸ’» -Feel free to contribute to the DX Scanner. If you want to contribute, please follow our [Contribution Guide](CONTRIBUTING.md). +Feel free to contribute to our DX Scanner. Please follow the [Contribution Guide](CONTRIBUTING.md). ## License πŸ“ @@ -101,7 +101,7 @@ The DX Scanner open source project is licensed under the [Attribution-NonCommerc ## Contributors ✨ -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): +Many thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -120,4 +120,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Any kind of contributions are welcome! diff --git a/package.json b/package.json index d24d57b32..5754cdf09 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@types/glob": "^7.1.1", "@types/jest": "^24.0.18", "@types/lodash": "^4.14.137", - "@types/nock": "^10.0.3", "@types/node": "^12", "@types/rimraf": "^2.0.2", "@types/semver": "^6.0.1", @@ -70,7 +69,7 @@ "eslint-config-prettier": "^6.3.0", "eslint-plugin-prettier": "^3.1.0", "jest": "^24.9.0", - "nock": "^10.0.6", + "nock": "^11.0.0", "prettier": "^1.18.2", "rimraf": "^3.0.0", "semantic-release": "^15.13.21", diff --git a/src/detectors/JavaScript/JavaScriptComponentDetector.spec.ts b/src/detectors/JavaScript/JavaScriptComponentDetector.spec.ts index e0c906806..dc3b061a0 100644 --- a/src/detectors/JavaScript/JavaScriptComponentDetector.spec.ts +++ b/src/detectors/JavaScript/JavaScriptComponentDetector.spec.ts @@ -41,7 +41,7 @@ describe('JavaScriptComponentDetector', () => { mockJsPackageInspector.packages = [mockPackage('webpack')]; mockJsPackageInspector.hasOneOfPackages = (packages: string[]) => { - const files = packages.filter((file) => file === 'express'); + const files = packages.filter((file) => file === 'webpack'); if (files.length > 0) { return true; } @@ -64,6 +64,6 @@ describe('JavaScriptComponentDetector', () => { detector = new JavaScriptComponentDetector(mockJsPackageInspector); const components = await detector.detectComponent({ language: ProgrammingLanguage.JavaScript, path: './src' }); - expect(components.length).toEqual(0); + expect(components[0].platform).toEqual(ProjectComponentPlatform.UNKNOWN); }); }); diff --git a/src/detectors/JavaScript/JavaScriptComponentDetector.ts b/src/detectors/JavaScript/JavaScriptComponentDetector.ts index ef1341db4..d5d6c00f6 100644 --- a/src/detectors/JavaScript/JavaScriptComponentDetector.ts +++ b/src/detectors/JavaScript/JavaScriptComponentDetector.ts @@ -30,32 +30,24 @@ export class JavaScriptComponentDetector implements IProjectComponentDetector { const frontendPackages = ['webpack', 'jquery', 'gulp', 'grunt', 'browserify', 'babel']; + let frontendOrBackend; if (this.packageInspector.hasOneOfPackages(backendPackages)) { - return [ - { - framework: ProjectComponentFramework.UNKNOWN, - language: langAtPath.language, - path: langAtPath.path, - platform: ProjectComponentPlatform.BackEnd, - repositoryPath: undefined, - type: ProjectComponentType.Application, - }, - ]; + frontendOrBackend = ProjectComponentPlatform.BackEnd; } if (this.packageInspector.hasOneOfPackages(frontendPackages)) { - return [ - { - framework: ProjectComponentFramework.UNKNOWN, - language: langAtPath.language, - path: langAtPath.path, - platform: ProjectComponentPlatform.FrontEnd, - repositoryPath: undefined, - type: ProjectComponentType.Application, - }, - ]; + frontendOrBackend = ProjectComponentPlatform.FrontEnd; } - return []; + return [ + { + framework: ProjectComponentFramework.UNKNOWN, + language: langAtPath.language, + path: langAtPath.path, + platform: frontendOrBackend ? frontendOrBackend : ProjectComponentPlatform.UNKNOWN, + repositoryPath: undefined, + type: ProjectComponentType.Application, + }, + ]; } } diff --git a/src/inspectors/IPackageInspector.ts b/src/inspectors/IPackageInspector.ts index 41a8aca84..62dec24c2 100644 --- a/src/inspectors/IPackageInspector.ts +++ b/src/inspectors/IPackageInspector.ts @@ -3,7 +3,7 @@ export interface IPackageInspector { init(): Promise; hasPackageManagement(): boolean; findPackages(searchTerm: string | RegExp): Package[]; - hasPackage(name: string, options?: PackageOptions): boolean; + hasPackage(name: string | RegExp, options?: PackageOptions): boolean; findPackage(name: string, options?: PackageOptions): Package | undefined; hasOneOfPackages(packages: string[]): boolean; hasLockfile(): boolean | undefined; diff --git a/src/inspectors/package/PackageInspectorBase.ts b/src/inspectors/package/PackageInspectorBase.ts index c0bf1eb30..cdccf245d 100644 --- a/src/inspectors/package/PackageInspectorBase.ts +++ b/src/inspectors/package/PackageInspectorBase.ts @@ -25,14 +25,19 @@ export abstract class PackageInspectorBase implements IPackageInspector, IInitia throw new Error('Method not implemented.'); } - hasPackage(name: string, options?: PackageOptions | undefined): boolean { + hasPackage(name: string | RegExp, options?: PackageOptions | undefined): boolean { if (!this.packages) { return false; } for (const pkg of this.packages) { - const nameRegExp = new RegExp(name); - if (nameRegExp.test(pkg.name.toLowerCase())) { - return true; + if (typeof name === 'string') { + if (pkg.name.toLowerCase() === name.toLowerCase()) { + return true; + } + } else { + if (name.test(pkg.name.toLowerCase())) { + return true; + } } } return false; diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 4306588cc..afcf0a03a 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -27,6 +27,7 @@ export const createRootContainer = (args: ArgumentsProvider): Container => { bindScanningStrategyDetectors(container); bindScanningContext(container); args.json ? container.bind(Types.IReporter).to(JSONReporter) : container.bind(Types.IReporter).to(CLIReporter); + container.bind(Types.JSONReporter).to(JSONReporter); container.bind(Types.ArgumentsProvider).toConstantValue(args); container.bind(Scanner).toSelf(); container.bind(FileSystemService).toSelf(); diff --git a/src/lib/assertNever.ts b/src/lib/assertNever.ts new file mode 100644 index 000000000..9dd93eddd --- /dev/null +++ b/src/lib/assertNever.ts @@ -0,0 +1,5 @@ +import { ErrorFactory } from "./errors"; + +export const assertNever = (x: never): never => { + throw ErrorFactory.newInternalError("Unexpected object: " + x); +} diff --git a/src/practices/JavaScript/ESLintUsedPractice.ts b/src/practices/JavaScript/ESLintUsedPractice.ts index 9331c3ad7..7857c9188 100644 --- a/src/practices/JavaScript/ESLintUsedPractice.ts +++ b/src/practices/JavaScript/ESLintUsedPractice.ts @@ -20,7 +20,8 @@ export class ESLintUsedPractice implements IPractice { async evaluate(ctx: PracticeContext): Promise { if (ctx.packageInspector) { - if (ctx.packageInspector.hasPackage('eslint')) { + const eslintRegex = new RegExp('eslint'); + if (ctx.packageInspector.hasPackage(eslintRegex)) { return PracticeEvaluationResult.practicing; } else { return PracticeEvaluationResult.notPracticing; diff --git a/src/reporters/CLIReporter.ts b/src/reporters/CLIReporter.ts index 2021901b2..43d3f5797 100644 --- a/src/reporters/CLIReporter.ts +++ b/src/reporters/CLIReporter.ts @@ -1,28 +1,25 @@ -import { Color, blue, bold, green, grey, italic, red, reset, yellow, underline } from 'colors'; -import { PracticeAndComponent, PracticeImpact } from '../model'; -import { GitHubUrlParser } from '../services/git/GitHubUrlParser'; -import { IReporter } from './IReporter'; -import { injectable } from 'inversify'; -import { uniq, compact } from 'lodash'; +import { blue, bold, Color, green, grey, italic, red, reset, underline, yellow } from 'colors'; +import { inject, injectable } from 'inversify'; +import { PracticeAndComponent, PracticeImpact, PracticeMetadata } from '../model'; import { IPracticeWithMetadata } from '../practices/DxPracticeDecorator'; +import { Types } from '../types'; +import { ComponentReport, IReporter } from './IReporter'; +import { JSONReporter } from './JSONReporter'; +import { sharedSubpath } from '../detectors/utils'; +import { GitServiceUtils } from '../services/git/GitServiceUtils'; @injectable() export class CLIReporter implements IReporter { + private readonly JSONReporter: JSONReporter; + + constructor(@inject(Types.JSONReporter) JSONReporter: JSONReporter) { + this.JSONReporter = JSONReporter; + } + report(practicesAndComponents: PracticeAndComponent[], practicesOff: IPracticeWithMetadata[]): string { const lines: string[] = []; - const repoNames = uniq( - compact( - practicesAndComponents.map((pac): string | undefined => { - if (pac.component.repositoryPath) { - const git = GitHubUrlParser.getOwnerAndRepoName(pac.component.repositoryPath); - return `${git.owner}/${git.repoName}`; - } - - return pac.component.path; - }), - ), - ); + const report = this.JSONReporter.report(practicesAndComponents, practicesOff); lines.push(bold(blue('----------------------------'))); lines.push(bold(blue('| |'))); @@ -30,27 +27,39 @@ export class CLIReporter implements IReporter { lines.push(bold(blue('| |'))); lines.push(bold(blue('----------------------------'))); - lines.push(bold(blue('Developer Experience Report for:'))); - repoNames.forEach((repoName) => { + let repoName; + const componentsSharedSubpath = sharedSubpath(report.components.map((c) => c.path)); + + for (const component of report.components) { + lines.push('\n----------------------------\n'); + lines.push(bold(blue('Developer Experience Report for:'))); + + if (component.repositoryPath) { + repoName = GitServiceUtils.getUrlToRepo(component.repositoryPath, component.path.replace(componentsSharedSubpath, '')); + } else { + repoName = component.path; + } lines.push(repoName); - }); + lines.push('\n----------------------------'); - lines.push('\n----------------------------'); - for (const key in PracticeImpact) { - const impact = PracticeImpact[key]; + for (const key in PracticeImpact) { + const impact = PracticeImpact[key]; - const impactLine = this.emitImpactSegment(practicesAndComponents, impact); - impactLine && lines.push(impactLine); + const impactLine = this.emitImpactSegment(component, impact); + impactLine && lines.push(impactLine); + } } lines.push('----------------------------'); lines.push(''); + practicesOff.length === 0 - ? lines.push(bold(yellow('No practice was switched off.'))) + ? lines.push(bold(yellow('No practices were switched off.'))) : lines.push(bold(red('You switched off these practices:'))); for (const practice of practicesOff) { lines.push(red(`- ${italic(practice.getMetadata().name)}`)); } + lines.push(''); lines.push('----------------------------'); lines.push(''); @@ -61,14 +70,17 @@ export class CLIReporter implements IReporter { return lines.join('\n'); } - private emitImpactSegment(practicesAndComponents: PracticeAndComponent[], impact: PracticeImpact): string | undefined { + private emitImpactSegment(component: ComponentReport, impact: PracticeImpact): string | undefined { const lines: string[] = []; - practicesAndComponents = practicesAndComponents.filter((pac) => pac.practice.impact === impact); - if (practicesAndComponents.length === 0) { + + const practices = component.practices.filter((practice) => practice.impact === impact); + if (practices.length === 0) { return undefined; } + lines.push(reset('')); let color = blue; + if (impact === PracticeImpact.high) { color = red; lines.push(bold(color('Improvements with highest impact:\n'))); @@ -82,39 +94,36 @@ export class CLIReporter implements IReporter { color = grey; lines.push(bold(color('Also consider:'))); } - for (const pac of practicesAndComponents) { - lines.push(this.linesForPractice(pac, color, practicesAndComponents.length > 1)); - if (pac.practice.defaultImpact !== pac.practice.impact) { - lines.push(bold(this.changedImpact(pac, (color = grey)))); + + for (const practice of practices) { + lines.push(this.linesForPractice(practice, color)); + + if (practice.defaultImpact !== practice.impact) { + lines.push(bold(this.changedImpact(practice, (color = grey)))); } lines.push(bold('')); } + + lines.push(bold('')); return lines.join('\n'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars - private linesForPractice(pac: PracticeAndComponent, color: Color, _includeFindingPath: boolean): string { + private linesForPractice(practice: PracticeMetadata, color: Color): string { const findingPath = ''; - // if (includeFindingPath) { - // const ownerAndRepo = GitHubUrlParser.getOwnerAndRepoName(pac.component.githubUrl!); - // findingPath = `at: ${ownerAndRepo.owner}/${ownerAndRepo.repoName}`; - // } - - const practiceLineTexts = [reset(color(`- ${bold(pac.practice.name)} - ${italic(pac.practice.suggestion)}`))]; - if (pac.practice.url) { - practiceLineTexts.push(color(italic(`${findingPath}(${pac.practice.url})`))); + const practiceLineTexts = [reset(color(`- ${bold(practice.name)} - ${italic(practice.suggestion)}`))]; + if (practice.url) { + practiceLineTexts.push(color(italic(`${findingPath}(${practice.url})`))); } return practiceLineTexts.join(' '); } - private changedImpact(pac: PracticeAndComponent, color: Color) { + private changedImpact(practice: PracticeMetadata, color: Color) { const practiceLineTexts = [ reset( color( - `You changed impact of ${bold(pac.practice.name)} from ${underline(pac.practice.defaultImpact)} to ${underline( - pac.practice.impact, - )}`, + `You changed impact of ${bold(practice.name)} from ${underline(practice.defaultImpact)} to ${underline(practice.impact)}`, ), ), ]; diff --git a/src/services/git/Git.ts b/src/services/git/Git.ts index d2bfac1b4..4760684d4 100644 --- a/src/services/git/Git.ts +++ b/src/services/git/Git.ts @@ -1,5 +1,4 @@ import { Repository } from '../../model'; -import { GitHubUrlParser } from './GitHubUrlParser'; import { isArray } from 'util'; import { inject, injectable } from 'inversify'; import { ErrorFactory } from '../../lib/errors/ErrorFactory'; @@ -9,6 +8,7 @@ import { Metadata, MetadataType, IProjectFilesBrowserService } from '../model'; import { Types } from '../../types'; import { ProjectIssueBrowserService as ContentRepositoryBrowserService } from '../../model'; import { Directory, File, Symlink } from './model'; +import { GitServiceUtils } from './GitServiceUtils'; @injectable() export class Git implements IProjectFilesBrowserService { @@ -109,12 +109,12 @@ export class Git implements IProjectFilesBrowserService { } async getContributorCount(): Promise { - const params = GitHubUrlParser.getOwnerAndRepoName(this.repository.url); + const params = GitServiceUtils.getOwnerAndRepoName(this.repository.url); return this.service.getContributors(params.owner, params.repoName).then((r) => r.totalCount); } async getPullRequestCount(): Promise { - const params = GitHubUrlParser.getOwnerAndRepoName(this.repository.url); + const params = GitServiceUtils.getOwnerAndRepoName(this.repository.url); return this.service.getPullRequests(params.owner, params.repoName, { filter: { state: GitHubPullRequestState.all } }).then((r) => { if (!r) { throw ErrorFactory.newInternalError('Could not get pull requests'); @@ -124,7 +124,7 @@ export class Git implements IProjectFilesBrowserService { } private getRepoContent(path: string): Promise { - const params = GitHubUrlParser.getOwnerAndRepoName(this.repository.url); + const params = GitServiceUtils.getOwnerAndRepoName(this.repository.url); return this.service.getRepoContent(params.owner, params.repoName, path); } diff --git a/src/services/git/GitHubUrlParser.ts b/src/services/git/GitHubUrlParser.ts deleted file mode 100644 index 2624c2ee7..000000000 --- a/src/services/git/GitHubUrlParser.ts +++ /dev/null @@ -1,12 +0,0 @@ -import gitUrlParse from 'git-url-parse'; - -export class GitHubUrlParser { - static getOwnerAndRepoName(url: string): { owner: string; repoName: string } { - const parsedUrl = gitUrlParse(url); - - return { - owner: parsedUrl.owner, - repoName: parsedUrl.name, - }; - } -} diff --git a/src/services/git/GitServiceUtils.ts b/src/services/git/GitServiceUtils.ts new file mode 100644 index 000000000..89358a6d0 --- /dev/null +++ b/src/services/git/GitServiceUtils.ts @@ -0,0 +1,36 @@ +import gitUrlParse from 'git-url-parse'; +import { GitService } from './model'; +import { assertNever } from '../../lib/assertNever'; + +export class GitServiceUtils { + static getUrlToRepo = (url: string, path?: string | undefined, branch = 'master') => { + const parsedUrl = gitUrlParse(url); + + let completeUrl = parsedUrl.toString('https'); + + if (path) { + completeUrl += GitServiceUtils.getPath(parsedUrl.source, path, branch || parsedUrl.ref); + } + + return completeUrl; + }; + + static getOwnerAndRepoName = (url: string) => { + const parsedUrl = gitUrlParse(url); + + return { + owner: parsedUrl.owner, + repoName: parsedUrl.name, + }; + }; + + static getPath = (service: GitService, path: string, branch = 'master') => { + switch (service) { + case GitService.github: + return `/tree/${branch}${path}`; + + default: + return assertNever(service); + } + }; +} diff --git a/src/services/git/model.ts b/src/services/git/model.ts index bd2364b73..186049006 100644 --- a/src/services/git/model.ts +++ b/src/services/git/model.ts @@ -1,3 +1,7 @@ +export enum GitService { + github = 'github.com', +} + export interface UserInfo { login: string; id: number; diff --git a/src/types.ts b/src/types.ts index d45482859..b32d64963 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,7 @@ export const Types = { Practice: Symbol('Practice'), IReporter: Symbol('IReporter'), ConfigProvider: Symbol('ConfigProvider'), + JSONReporter: Symbol('JSONReporter'), }; export type GitFactory = (repository: Repository) => Git; diff --git a/test/helpers/gitHubNock.ts b/test/helpers/gitHubNock.ts index 7f8185dce..fcbe89063 100644 --- a/test/helpers/gitHubNock.ts +++ b/test/helpers/gitHubNock.ts @@ -77,7 +77,7 @@ export class GitHubNock { persist = true, ): T { const url = this.repository.pulls_url.replace('{/number}', number !== undefined ? `/${number}` : ''); - const params: nock.POJO = {}; + const params: nock.DataMatcherMap = {}; if (state !== undefined) { params.state = state; } @@ -96,7 +96,7 @@ export class GitHubNock { getIssues(number?: number, persist = true): nock.Interceptor { const url = this.repository.issues_url.replace('{/number}', number !== undefined ? `/${number}` : ''); - const params: nock.POJO = {}; + const params: nock.DataMatcherMap = {}; return GitHubNock.get(url, params, persist); } @@ -105,7 +105,7 @@ export class GitHubNock { return GitHubNock.get(this.repository.url + suffix, {}, persist); } - private static get(url: string, params: nock.POJO, persist = true): nock.Interceptor { + private static get(url: string, params: nock.DataMatcherMap, persist = true): nock.Interceptor { const urlObj = new URL(url); const scope = nock(urlObj.origin); diff --git a/tsconfig.json b/tsconfig.json index c9488f76f..2521bd46b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -60,6 +60,6 @@ "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, "preserveConstEnums": true }, - "include": ["src/**/*", "typings/**/*", "test/helpers", "test/**/*"], + "include": ["src/**/*", "typings/**/*", "test/helpers/**/*", "test/**/*"], "exclude": ["node_modules", "lib"] } diff --git a/yarn.lock b/yarn.lock index 874c848b4..ba68f15ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -741,13 +741,6 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/nock@^10.0.3": - version "10.0.3" - resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c" - integrity sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow== - dependencies: - "@types/node" "*" - "@types/node@*": version "12.0.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031" @@ -2274,11 +2267,6 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-equal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -5211,7 +5199,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.5.1: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.2, lodash@^4.2.0, lodash@^4.5.1: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -5700,20 +5688,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nock@^10.0.6: - version "10.0.6" - resolved "https://registry.yarnpkg.com/nock/-/nock-10.0.6.tgz#e6d90ee7a68b8cfc2ab7f6127e7d99aa7d13d111" - integrity sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w== +nock@^11.0.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/nock/-/nock-11.3.5.tgz#f2c7b4b672a04c35342593b6bfd821497881199c" + integrity sha512-6WGeZcWc3RExkBcMSYSrUm/5YukDo52m/jhwniQyrnuiCnKRljBwwje9vTwJyEi4J6m2bq0Aj6C1vzuM6iuaeg== dependencies: chai "^4.1.2" debug "^4.1.0" - deep-equal "^1.0.0" json-stringify-safe "^5.0.1" - lodash "^4.17.5" + lodash "^4.17.13" mkdirp "^0.5.0" - propagate "^1.0.0" - qs "^6.5.1" - semver "^5.5.0" + propagate "^2.0.0" node-alias@^1.0.4: version "1.0.4" @@ -6765,10 +6750,10 @@ promzard@^0.3.0: dependencies: read "1" -propagate@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-1.0.0.tgz#00c2daeedda20e87e3782b344adba1cddd6ad709" - integrity sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk= +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== proto-list@~1.2.1: version "1.2.4" @@ -6847,11 +6832,6 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@^6.5.1: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"