├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── test-cli-mock-project │ ├── CODEOWNERS │ └── dir1 │ │ └── CODEOWNERS └── workflows │ ├── build-cli.yml │ ├── lint.yml │ ├── release.yml │ ├── semantic-pull-request.yml │ ├── test-cli.yml │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .nojekyll ├── .nvmrc ├── .whitesource ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __mocks__ ├── CODEOWNERS1 ├── CODEOWNERS2 ├── CODEOWNERS3 ├── CODEOWNERS5 ├── CODEOWNERS_POPULATED_OUTPUT ├── CODEOWNERS_POPULATED_OUTPUT_2 ├── cosmiconfig.ts ├── gitIgnore1 ├── ora.ts ├── package1.json ├── package2.json ├── package3.json ├── package4.json └── package5.json ├── __tests__ ├── codeowners.spec.ts ├── generate.spec.ts └── helpers.ts ├── action.yml ├── images └── logo.png ├── index.html ├── index.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── bin │ └── cli.ts ├── commands │ ├── generate.ts │ └── index.ts ├── index.ts └── utils │ ├── codeowners.ts │ ├── constants.ts │ ├── debug.ts │ ├── getCustomConfiguration.ts │ ├── getGlobalOptions.ts │ ├── getPatternsFromIgnoreFiles.ts │ ├── readContent.ts │ └── templates.ts ├── tsconfig.eslint.json ├── tsconfig.json └── tsconfig.node.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE]' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/test-cli-mock-project/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @foo 2 | 3 | 4 | #################################### Generated content - do not edit! #################################### 5 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 6 | # Don't worry, the content outside this block will be kept. 7 | 8 | # Rule extracted from dir1/CODEOWNERS 9 | /dir1/**/* @bar 10 | 11 | #################################### Generated content - do not edit! #################################### -------------------------------------------------------------------------------- /.github/test-cli-mock-project/dir1/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bar 2 | -------------------------------------------------------------------------------- /.github/workflows/build-cli.yml: -------------------------------------------------------------------------------- 1 | name: build-cli 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | getNodeVersion: 11 | uses: gagoar/ts-node-template/.github/workflows/get_node_version.yml@main 12 | 13 | build: 14 | name: build-cli 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js 12.x 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 12.x 22 | - name: Install dependencies 23 | run: npm install 24 | - name: Build 25 | run: npm run build-cli 26 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Setting node version 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version-file: '.nvmrc' 18 | - run: npm install 19 | - run: npm run lint 20 | - run: npm run prettier 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release-please: 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Release Please 16 | uses: google-github-actions/release-please-action@v3 17 | id: release 18 | with: 19 | release-type: node 20 | package-name: codeowners-generator 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | if: ${{ steps.release.outputs.release_created }} 24 | - name: Set up Node.js 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version-file: '.nvmrc' 28 | registry-url: 'https://registry.npmjs.org' 29 | if: ${{ steps.release.outputs.release_created }} 30 | - name: Install dependencies 31 | run: npm ci 32 | if: ${{ steps.release.outputs.release_created }} 33 | - name: Build package 34 | run: | 35 | npm run build 36 | npm run build-cli 37 | if: ${{ steps.release.outputs.release_created }} 38 | - name: Publish package 39 | run: npm publish 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | if: ${{ steps.release.outputs.release_created }} 43 | -------------------------------------------------------------------------------- /.github/workflows/semantic-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Semantic Pull Request 2 | 3 | on: 4 | pull_request_target: 5 | types: ['opened', 'edited', 'synchronize'] 6 | 7 | permissions: 8 | pull-requests: read 9 | 10 | jobs: 11 | semantic-pull-request: 12 | name: Semantic Pull Request 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Validate PR title 16 | uses: amannn/action-semantic-pull-request@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/test-cli.yml: -------------------------------------------------------------------------------- 1 | name: Test CLI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version-file: '.nvmrc' 20 | - name: Install npm dependencies 21 | run: npm ci 22 | - name: Build and package project 23 | run: | 24 | npm run build 25 | npm run build-cli 26 | npm pack 27 | - name: Upload project artifacts 28 | uses: actions/upload-artifact@v3 29 | with: 30 | name: built-project 31 | path: codeowners-generator-*.tgz 32 | retention-days: 5 33 | test: 34 | name: Test 35 | needs: build 36 | runs-on: ${{ matrix.os }} 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | os: 41 | - macos-latest 42 | - ubuntu-latest 43 | - windows-latest 44 | node-version: 45 | - 16 46 | - 18 47 | - 20 48 | - 21 49 | steps: 50 | - name: Set git to use LF 51 | if: ${{ matrix.os == 'windows-latest' }} 52 | run: | 53 | git config --global core.autocrlf false 54 | git config --global core.eol lf 55 | - name: Download project artifacts 56 | uses: actions/download-artifact@v3 57 | with: 58 | name: built-project 59 | path: . 60 | - name: Set up Node.js 61 | uses: actions/setup-node@v3 62 | with: 63 | node-version: ${{ matrix.node-version }} 64 | - name: Install project 65 | shell: bash 66 | run: npm i -g codeowners-generator-*.tgz 67 | - name: Checkout repository 68 | uses: actions/checkout@v3 69 | - name: Test CLI 70 | shell: bash 71 | working-directory: ./.github/test-cli-mock-project 72 | run: | 73 | echo "::group::Show CODEOWNERS content" 74 | cat CODEOWNERS 75 | echo "" 76 | echo "::endgroup::" 77 | echo "::group::Get Version" 78 | codeowners-generator --version 79 | echo "::endgroup::" 80 | echo "::group::Check CODEOWNERS content" 81 | codeowners-generator generate --check 82 | echo "::endgroup::" 83 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: 15 | - macos-latest 16 | - ubuntu-latest 17 | - windows-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Setting node version 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version-file: '.nvmrc' 24 | - run: npm install 25 | - run: npm run test 26 | - name: Send coverage to codecov 27 | if: matrix.os == 'ubuntu-latest' 28 | uses: codecov/codecov-action@v3 29 | with: 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | flags: unittests 32 | - name: Coveralls 33 | if: matrix.os == 'ubuntu-latest' 34 | uses: coverallsapp/github-action@v1.2.5 35 | with: 36 | github-token: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode/ 3 | # ignore codecoverage output 4 | coverage/ 5 | # ignore build/ output 6 | build/ 7 | # ignore dist/ output 8 | dist/ 9 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm exec lint-staged 5 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gagoar/codeowners-generator/653b87f7008e7ee43e5bc86480bce7a47a570924/.nojekyll -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.20.2 2 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## [2.5.0](https://github.com/gagoar/codeowners-generator/compare/v2.4.1...v2.5.0) (2025-01-12) 5 | 6 | 7 | ### Features 8 | 9 | * hidden directories scan ([#405](https://github.com/gagoar/codeowners-generator/issues/405)) ([80d3507](https://github.com/gagoar/codeowners-generator/commit/80d3507591e0da0426384e8cacbfc554f4dc7297)) 10 | 11 | ## [2.4.1](https://github.com/gagoar/codeowners-generator/compare/v2.4.0...v2.4.1) (2024-01-16) 12 | 13 | 14 | ### ⚠ BREAKING CHANGES 15 | 16 | * **action:** Effectively change default value for preserve-block-position in github actions 17 | 18 | ### Bug Fixes 19 | 20 | * **action:** Fix preserve-block-position default ([#393](https://github.com/gagoar/codeowners-generator/issues/393)) ([f4ae6ee](https://github.com/gagoar/codeowners-generator/commit/f4ae6eeb1f4adfc32f4ab478053292b4ee284757)) 21 | * only include relevant files in published package ([#399](https://github.com/gagoar/codeowners-generator/issues/399)) ([3bff598](https://github.com/gagoar/codeowners-generator/commit/3bff598d60289ca9df82dccfdd66a0058f5d1c83)) 22 | 23 | 24 | ### Miscellaneous Chores 25 | 26 | * release 2.4.1 ([#401](https://github.com/gagoar/codeowners-generator/issues/401)) ([10be326](https://github.com/gagoar/codeowners-generator/commit/10be3261f5c4d77e7d072aaad676b9dce92e515b)) 27 | 28 | ## [2.4.0](https://github.com/gagoar/codeowners-generator/compare/v2.3.1...v2.4.0) (2024-01-12) 29 | 30 | 31 | ### Features 32 | 33 | * Expose includes variable to actions ([#392](https://github.com/gagoar/codeowners-generator/issues/392)) ([670d636](https://github.com/gagoar/codeowners-generator/commit/670d636f7b9d3d61e9df7455d5f6561928d573b6)) 34 | * Read rest of options from custom configuration file ([#391](https://github.com/gagoar/codeowners-generator/issues/391)) ([80756fe](https://github.com/gagoar/codeowners-generator/commit/80756feb11314c9e08a12c2a14911589a4903992)) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **deps:** update dependency cosmiconfig to v7.1.0 ([#368](https://github.com/gagoar/codeowners-generator/issues/368)) ([91bab21](https://github.com/gagoar/codeowners-generator/commit/91bab21a3ab45f625f1504900c7c03b56a31e6be)) 40 | 41 | ## [2.3.1](https://github.com/gagoar/codeowners-generator/compare/v2.3.0...v2.3.1) 42 | 43 | - Update jest monorepo to v29 (major) [`#379`](https://github.com/gagoar/codeowners-generator/pull/379) 44 | - Fix path/posix usage not compatible with Node 12 and 14 [`#381`](https://github.com/gagoar/codeowners-generator/pull/381) 45 | - Run prettier in CI [`#382`](https://github.com/gagoar/codeowners-generator/pull/382) 46 | - Update typescript-eslint monorepo to v6 (major) [`#377`](https://github.com/gagoar/codeowners-generator/pull/377) 47 | - Update dependency eslint-config-prettier to v9 [`#378`](https://github.com/gagoar/codeowners-generator/pull/378) 48 | - Update dependency node to v16.20.2 [`#365`](https://github.com/gagoar/codeowners-generator/pull/365) 49 | - Update dependency @types/debug to v4.1.12 [`#362`](https://github.com/gagoar/codeowners-generator/pull/362) 50 | - Fix it to work on Windows as well and add tests [`#374`](https://github.com/gagoar/codeowners-generator/pull/374) 51 | - Update dependency eslint-config-prettier to v8.10.0 [`#373`](https://github.com/gagoar/codeowners-generator/pull/373) 52 | - Update dependency eslint to v8.53.0 [`#370`](https://github.com/gagoar/codeowners-generator/pull/370) 53 | - 2.3.0 [`#359`](https://github.com/gagoar/codeowners-generator/pull/359) 54 | 55 | ## [2.3.0](https://github.com/gagoar/codeowners-generator/compare/v2.2.0...v2.3.0) 56 | 57 | > 19 April 2023 58 | 59 | - Allowing exclusions per CODEOWNERS added feature [`#358`](https://github.com/gagoar/codeowners-generator/pull/358) 60 | - Remove extraneous quote. [`#356`](https://github.com/gagoar/codeowners-generator/pull/356) 61 | - missing index + configuration clarification [`#354`](https://github.com/gagoar/codeowners-generator/pull/354) 62 | - Add action support for preserve-block-position [`#351`](https://github.com/gagoar/codeowners-generator/pull/351) 63 | - 2.2.0 [`#352`](https://github.com/gagoar/codeowners-generator/pull/352) 64 | - Allowing exclusions per CODEOWNERS added feature (#358) [`#357`](https://github.com/gagoar/codeowners-generator/issues/357) 65 | - Add action support for preserve-block-position (#351) [`#350`](https://github.com/gagoar/codeowners-generator/issues/350) 66 | 67 | ## [2.2.0](https://github.com/gagoar/codeowners-generator/compare/v2.1.5...v2.2.0) 68 | 69 | > 22 March 2023 70 | 71 | - fixing release workflow [`#353`](https://github.com/gagoar/codeowners-generator/pull/353) 72 | - Preserve block position [`#347`](https://github.com/gagoar/codeowners-generator/pull/347) 73 | - ChatGPT rewrote it [`#349`](https://github.com/gagoar/codeowners-generator/pull/349) 74 | - Using swc instead of ts-jest [`#348`](https://github.com/gagoar/codeowners-generator/pull/348) 75 | - Update Node.js to v16.19.1 [`#341`](https://github.com/gagoar/codeowners-generator/pull/341) 76 | - Update dependency husky to v8.0.3 [`#339`](https://github.com/gagoar/codeowners-generator/pull/339) 77 | - adding composite action to use codeowners-generator in a workflow [`#337`](https://github.com/gagoar/codeowners-generator/pull/337) 78 | - 2.1.5 [`#346`](https://github.com/gagoar/codeowners-generator/pull/346) 79 | - Preserve block position (#347) [`#343`](https://github.com/gagoar/codeowners-generator/issues/343) 80 | - adding composite action to use codeowners-generator in a workflow (#337) [`#335`](https://github.com/gagoar/codeowners-generator/issues/335) 81 | 82 | ## [2.1.5](https://github.com/gagoar/codeowners-generator/compare/v2.1.4...v2.1.5) 83 | 84 | > 18 March 2023 85 | 86 | - Making `--use-root-maintainers` available via CLI [`#345`](https://github.com/gagoar/codeowners-generator/pull/345) 87 | - Update dependency ignore to v5.2.4 [`#340`](https://github.com/gagoar/codeowners-generator/pull/340) 88 | - adding Check functionality [`#342`](https://github.com/gagoar/codeowners-generator/pull/342) 89 | - Adding SourceGraph to the Family [`#336`](https://github.com/gagoar/codeowners-generator/pull/336) 90 | - Update dependency @types/jest to v28.1.8 [`#332`](https://github.com/gagoar/codeowners-generator/pull/332) 91 | - Update dependency ts-jest to v28.0.8 [`#333`](https://github.com/gagoar/codeowners-generator/pull/333) 92 | - Update dependency fast-glob to v3.2.12 [`#338`](https://github.com/gagoar/codeowners-generator/pull/338) 93 | - 2.1.4 [`#331`](https://github.com/gagoar/codeowners-generator/pull/331) 94 | - Bump json5 from 2.2.1 to 2.2.3 [`#334`](https://github.com/gagoar/codeowners-generator/pull/334) 95 | - Making `--use-root-maintainers` available via CLI (#345) [`#344`](https://github.com/gagoar/codeowners-generator/issues/344) 96 | 97 | ## [2.1.4](https://github.com/gagoar/codeowners-generator/compare/v2.1.3...v2.1.4) 98 | 99 | > 27 February 2023 100 | 101 | - Handle maintainers in root package.json files [`#330`](https://github.com/gagoar/codeowners-generator/pull/330) 102 | - Update dependency esbuild to v0.14.53 [`#328`](https://github.com/gagoar/codeowners-generator/pull/328) 103 | - Update dependency @types/node to v16.11.47 [`#327`](https://github.com/gagoar/codeowners-generator/pull/327) 104 | - Update references to codeowners-generator to v2 [`#326`](https://github.com/gagoar/codeowners-generator/pull/326) 105 | - Remove duplicate Update [`#325`](https://github.com/gagoar/codeowners-generator/pull/325) 106 | - Use renovate to update package reference in readme [`#324`](https://github.com/gagoar/codeowners-generator/pull/324) 107 | - Remove unused dependency jest-mock-process [`#323`](https://github.com/gagoar/codeowners-generator/pull/323) 108 | - Update renovate config to reduce noise [`#320`](https://github.com/gagoar/codeowners-generator/pull/320) 109 | - Remove unused dependencies [`#321`](https://github.com/gagoar/codeowners-generator/pull/321) 110 | - Update dependency esbuild to v0.14.49 [`#316`](https://github.com/gagoar/codeowners-generator/pull/316) 111 | - Update Node.js to v16.16.0 [`#318`](https://github.com/gagoar/codeowners-generator/pull/318) 112 | - Update dependency prettier-eslint-cli to v6 [`#304`](https://github.com/gagoar/codeowners-generator/pull/304) 113 | - Update dependency prettier to v2.7.1 [`#319`](https://github.com/gagoar/codeowners-generator/pull/319) 114 | - Update dependency typescript to v4.7.4 [`#314`](https://github.com/gagoar/codeowners-generator/pull/314) 115 | - Update dependency ts-node to v10.9.1 [`#289`](https://github.com/gagoar/codeowners-generator/pull/289) 116 | - Update dependency lint-staged to v13 [`#317`](https://github.com/gagoar/codeowners-generator/pull/317) 117 | - Update typescript-eslint monorepo to v5.30.6 [`#315`](https://github.com/gagoar/codeowners-generator/pull/315) 118 | - Update dependency husky to v8 [`#301`](https://github.com/gagoar/codeowners-generator/pull/301) 119 | - Update dependency eslint to v8.19.0 [`#300`](https://github.com/gagoar/codeowners-generator/pull/300) 120 | - Update jest monorepo to v28 (major) [`#299`](https://github.com/gagoar/codeowners-generator/pull/299) 121 | - removing mocked function from `ts-jest` [`#312`](https://github.com/gagoar/codeowners-generator/pull/312) 122 | - Update dependency prettier-eslint to v14.1.0 [`#308`](https://github.com/gagoar/codeowners-generator/pull/308) 123 | - Update dependency esbuild to v0.14.43 [`#307`](https://github.com/gagoar/codeowners-generator/pull/307) 124 | - Update Node.js to v16.15.1 [`#306`](https://github.com/gagoar/codeowners-generator/pull/306) 125 | - Update dependency ts-jest to v27.1.4 [`#288`](https://github.com/gagoar/codeowners-generator/pull/288) 126 | - Update typescript-eslint monorepo to v5.23.0 [`#251`](https://github.com/gagoar/codeowners-generator/pull/251) 127 | - Update dependency @types/jest to v27.5.1 [`#305`](https://github.com/gagoar/codeowners-generator/pull/305) 128 | - Update dependency prettier-eslint to v14.0.3 [`#303`](https://github.com/gagoar/codeowners-generator/pull/303) 129 | - Update dependency @types/node to v16.11.34 [`#302`](https://github.com/gagoar/codeowners-generator/pull/302) 130 | - Update dependency eslint-config-prettier to v8.5.0 [`#286`](https://github.com/gagoar/codeowners-generator/pull/286) 131 | - Update dependency ignore to v5.2.0 [`#287`](https://github.com/gagoar/codeowners-generator/pull/287) 132 | - Update Node.js to v16.15.0 [`#285`](https://github.com/gagoar/codeowners-generator/pull/285) 133 | - Update dependency tslib to v2.4.0 [`#290`](https://github.com/gagoar/codeowners-generator/pull/290) 134 | - Update dependency prettier-eslint to v14 [`#298`](https://github.com/gagoar/codeowners-generator/pull/298) 135 | - changing workflows to take node upgrades into account [`#296`](https://github.com/gagoar/codeowners-generator/pull/296) 136 | - Update dependency typescript to v4.6.4 [`#291`](https://github.com/gagoar/codeowners-generator/pull/291) 137 | - Update codecov/codecov-action action to v3 [`#295`](https://github.com/gagoar/codeowners-generator/pull/295) 138 | - Update jest monorepo [`#292`](https://github.com/gagoar/codeowners-generator/pull/292) 139 | - Update actions/setup-node action to v3 [`#294`](https://github.com/gagoar/codeowners-generator/pull/294) 140 | - Update actions/checkout action to v3 [`#293`](https://github.com/gagoar/codeowners-generator/pull/293) 141 | - 2.1.3 [`#284`](https://github.com/gagoar/codeowners-generator/pull/284) 142 | 143 | ## [2.1.3](https://github.com/gagoar/codeowners-generator/compare/v2.1.2...v2.1.3) 144 | 145 | > 5 May 2022 146 | 147 | - updating browserlist [`#283`](https://github.com/gagoar/codeowners-generator/pull/283) 148 | - Update dependency debug to v4.3.4 [`#277`](https://github.com/gagoar/codeowners-generator/pull/277) 149 | - Update dependency @types/lodash.groupby to v4.6.7 [`#280`](https://github.com/gagoar/codeowners-generator/pull/280) 150 | - Update dependency esbuild to v0.14.38 [`#281`](https://github.com/gagoar/codeowners-generator/pull/281) 151 | - Update dependency prettier to v2.6.2 [`#249`](https://github.com/gagoar/codeowners-generator/pull/249) 152 | - Update dependency cosmiconfig to v7.0.1 [`#261`](https://github.com/gagoar/codeowners-generator/pull/261) 153 | - Update dependency lint-staged to v12 [`#272`](https://github.com/gagoar/codeowners-generator/pull/272) 154 | - Update dependency eslint to v8 [`#265`](https://github.com/gagoar/codeowners-generator/pull/265) 155 | - Update typescript-eslint monorepo to v5 (major) [`#270`](https://github.com/gagoar/codeowners-generator/pull/270) 156 | - Update dependency husky to v7 [`#252`](https://github.com/gagoar/codeowners-generator/pull/252) 157 | - Stop using parse-glob [`#279`](https://github.com/gagoar/codeowners-generator/pull/279) 158 | - Update dependency common-tags to v1.8.2 [`#271`](https://github.com/gagoar/codeowners-generator/pull/271) 159 | - Update dependency auto-changelog to v2.4.0 [`#243`](https://github.com/gagoar/codeowners-generator/pull/243) 160 | - Update dependency tslib to v2.3.1 [`#234`](https://github.com/gagoar/codeowners-generator/pull/234) 161 | - Update dependency fast-glob to v3.2.11 [`#250`](https://github.com/gagoar/codeowners-generator/pull/250) 162 | - Update dependency esbuild to v0.14.25 [`#240`](https://github.com/gagoar/codeowners-generator/pull/240) 163 | - Update dependency ts-node to v10 [`#242`](https://github.com/gagoar/codeowners-generator/pull/242) 164 | - Bump node-fetch from 2.6.1 to 2.6.7 [`#275`](https://github.com/gagoar/codeowners-generator/pull/275) 165 | - Update Node.js to v16 [`#268`](https://github.com/gagoar/codeowners-generator/pull/268) 166 | - Update dependency prettier-eslint to v13 [`#267`](https://github.com/gagoar/codeowners-generator/pull/267) 167 | - Update codecov/codecov-action action to v2 [`#263`](https://github.com/gagoar/codeowners-generator/pull/263) 168 | - Bump lodash from 4.17.19 to 4.17.21 [`#274`](https://github.com/gagoar/codeowners-generator/pull/274) 169 | - Bump handlebars from 4.7.6 to 4.7.7 [`#273`](https://github.com/gagoar/codeowners-generator/pull/273) 170 | - Update jest monorepo [`#244`](https://github.com/gagoar/codeowners-generator/pull/244) 171 | - Update dependency ignore to v5.1.9 [`#269`](https://github.com/gagoar/codeowners-generator/pull/269) 172 | - Update dependency debug to v4.3.2 [`#253`](https://github.com/gagoar/codeowners-generator/pull/253) 173 | - Update dependency eslint to v7.32.0 [`#254`](https://github.com/gagoar/codeowners-generator/pull/254) 174 | - Update dependency typescript to v4.4.3 [`#246`](https://github.com/gagoar/codeowners-generator/pull/246) 175 | - Update dependency ora to v5.4.1 [`#247`](https://github.com/gagoar/codeowners-generator/pull/247) 176 | - Update dependency jest-mock-process to v1.4.1 [`#255`](https://github.com/gagoar/codeowners-generator/pull/255) 177 | - Update dependency @types/common-tags to v1.8.1 [`#256`](https://github.com/gagoar/codeowners-generator/pull/256) 178 | - Update dependency @types/jest to v26.0.24 [`#258`](https://github.com/gagoar/codeowners-generator/pull/258) 179 | - Update coverallsapp/github-action action to v1.1.3 [`#257`](https://github.com/gagoar/codeowners-generator/pull/257) 180 | - Update dependency lint-staged to v11 [`#239`](https://github.com/gagoar/codeowners-generator/pull/239) 181 | - Update dependency eslint to v7.29.0 [`#226`](https://github.com/gagoar/codeowners-generator/pull/226) 182 | - Update typescript-eslint monorepo to v4.28.0 [`#227`](https://github.com/gagoar/codeowners-generator/pull/227) 183 | - Update dependency prettier to v2.3.1 [`#241`](https://github.com/gagoar/codeowners-generator/pull/241) 184 | - Update dependency eslint-config-prettier to v8 [`#219`](https://github.com/gagoar/codeowners-generator/pull/219) 185 | - Update dependency @types/jest to v26.0.23 [`#230`](https://github.com/gagoar/codeowners-generator/pull/230) 186 | - Update dependency esbuild to v0.11.19 [`#222`](https://github.com/gagoar/codeowners-generator/pull/222) 187 | - Update dependency prettier-eslint-cli to v5.0.1 [`#228`](https://github.com/gagoar/codeowners-generator/pull/228) 188 | - Update dependency typescript to v4.2.4 [`#235`](https://github.com/gagoar/codeowners-generator/pull/235) 189 | - making sure the husky is updated to 6.0.0 [`#236`](https://github.com/gagoar/codeowners-generator/pull/236) 190 | - Update dependency y18n to 4.0.1 [SECURITY] [`#233`](https://github.com/gagoar/codeowners-generator/pull/233) 191 | - Update dependency commander to v7.2.0 [`#231`](https://github.com/gagoar/codeowners-generator/pull/231) 192 | - Update dependency typescript to v4.2.3 [`#225`](https://github.com/gagoar/codeowners-generator/pull/225) 193 | - Update dependency ora to v5.4.0 [`#229`](https://github.com/gagoar/codeowners-generator/pull/229) 194 | - Update dependency commander to v7.1.0 [`#217`](https://github.com/gagoar/codeowners-generator/pull/217) 195 | - Update dependency esbuild to v0.8.50 [`#215`](https://github.com/gagoar/codeowners-generator/pull/215) 196 | - Update typescript-eslint monorepo to v4.15.2 [`#221`](https://github.com/gagoar/codeowners-generator/pull/221) 197 | - Update dependency eslint to v7.20.0 [`#214`](https://github.com/gagoar/codeowners-generator/pull/214) 198 | - Update dependency @types/node to v12.20.4 [`#216`](https://github.com/gagoar/codeowners-generator/pull/216) 199 | - Update typescript-eslint monorepo to v4.15.1 [`#218`](https://github.com/gagoar/codeowners-generator/pull/218) 200 | - 2.1.2 [`#213`](https://github.com/gagoar/codeowners-generator/pull/213) 201 | 202 | ## [2.1.2](https://github.com/gagoar/codeowners-generator/compare/v2.1.1...v2.1.2) 203 | 204 | > 12 February 2021 205 | 206 | - Update Node.js to v12.20.2 [`#211`](https://github.com/gagoar/codeowners-generator/pull/211) 207 | - Update dependency esbuild to v0.8.44 [`#203`](https://github.com/gagoar/codeowners-generator/pull/203) 208 | - Update dependency typescript to v4.1.5 [`#209`](https://github.com/gagoar/codeowners-generator/pull/209) 209 | - Limit blank lines between original and generated [`#210`](https://github.com/gagoar/codeowners-generator/pull/210) 210 | - Update dependency ts-jest to v26.5.1 [`#208`](https://github.com/gagoar/codeowners-generator/pull/208) 211 | - Update typescript-eslint monorepo to v4.15.0 [`#206`](https://github.com/gagoar/codeowners-generator/pull/206) 212 | - Update dependency lint-staged to v10.5.4 [`#205`](https://github.com/gagoar/codeowners-generator/pull/205) 213 | - Update dependency @types/node to v12.19.16 [`#204`](https://github.com/gagoar/codeowners-generator/pull/204) 214 | - Update dependency esbuild to v0.8.39 [`#200`](https://github.com/gagoar/codeowners-generator/pull/200) 215 | - Update dependency @typescript-eslint/parser to v4.14.2 [`#202`](https://github.com/gagoar/codeowners-generator/pull/202) 216 | - Update dependency @typescript-eslint/eslint-plugin to v4.14.2 [`#201`](https://github.com/gagoar/codeowners-generator/pull/201) 217 | - Create docsify site [`#199`](https://github.com/gagoar/codeowners-generator/pull/199) 218 | - Update dependency esbuild to v0.8.38 [`#197`](https://github.com/gagoar/codeowners-generator/pull/197) 219 | - Update dependency eslint to v7.19.0 [`#198`](https://github.com/gagoar/codeowners-generator/pull/198) 220 | - 2.1.1 [`#196`](https://github.com/gagoar/codeowners-generator/pull/196) 221 | 222 | ## [2.1.1](https://github.com/gagoar/codeowners-generator/compare/v2.1.0...v2.1.1) 223 | 224 | > 29 January 2021 225 | 226 | - Update dependency ts-jest to v26.5.0 [`#195`](https://github.com/gagoar/codeowners-generator/pull/195) 227 | - ignoring package.json when is along side a CODEOWNERS file [`#194`](https://github.com/gagoar/codeowners-generator/pull/194) 228 | - 2.1.0 [`#192`](https://github.com/gagoar/codeowners-generator/pull/192) 229 | 230 | ## [2.1.0](https://github.com/gagoar/codeowners-generator/compare/v2.0.0...v2.1.0) 231 | 232 | > 28 January 2021 233 | 234 | - Add customRegenerationCommand to specify the printed re-generate command [`#189`](https://github.com/gagoar/codeowners-generator/pull/189) 235 | - 2.0.0 [`#191`](https://github.com/gagoar/codeowners-generator/pull/191) 236 | 237 | ## [2.0.0](https://github.com/gagoar/codeowners-generator/compare/v1.4.0...v2.0.0) 238 | 239 | > 26 January 2021 240 | 241 | - Update dependency esbuild to v0.8.36 [`#190`](https://github.com/gagoar/codeowners-generator/pull/190) 242 | - add leading slash to file patterns [`#188`](https://github.com/gagoar/codeowners-generator/pull/188) 243 | - Update typescript-eslint monorepo to v4.14.1 [`#187`](https://github.com/gagoar/codeowners-generator/pull/187) 244 | - moving to esbuild [`#185`](https://github.com/gagoar/codeowners-generator/pull/185) 245 | - translate glob patterns when generating the codeowners file [`#161`](https://github.com/gagoar/codeowners-generator/pull/161) 246 | - 1.4.0 [`#183`](https://github.com/gagoar/codeowners-generator/pull/183) 247 | 248 | ## [1.4.0](https://github.com/gagoar/codeowners-generator/compare/v1.3.0...v1.4.0) 249 | 250 | > 24 January 2021 251 | 252 | - removing warn from spec [`#182`](https://github.com/gagoar/codeowners-generator/pull/182) 253 | - Clean up package.json: remove unused dependencies, remove unused script, etc [`#181`](https://github.com/gagoar/codeowners-generator/pull/181) 254 | - moving codeowners.spect.ts to **tests** [`#180`](https://github.com/gagoar/codeowners-generator/pull/180) 255 | - Fixing type issue introduced by commander fix [`#179`](https://github.com/gagoar/codeowners-generator/pull/179) 256 | - adding groupSourceComments [`#168`](https://github.com/gagoar/codeowners-generator/pull/168) 257 | - fix compatibility with commander v7 [`#177`](https://github.com/gagoar/codeowners-generator/pull/177) 258 | - Fix jest hanging after running [`#176`](https://github.com/gagoar/codeowners-generator/pull/176) 259 | - fix typos of `--use-maintainers` flag in readme [`#171`](https://github.com/gagoar/codeowners-generator/pull/171) 260 | - Pin dependency @types/parse-glob to 3.0.29 [`#174`](https://github.com/gagoar/codeowners-generator/pull/174) 261 | - add isValidCodeownersGlob function [`#170`](https://github.com/gagoar/codeowners-generator/pull/170) 262 | - exclude more places by default [`#167`](https://github.com/gagoar/codeowners-generator/pull/167) 263 | - Update dependency ora to v5.3.0 [`#166`](https://github.com/gagoar/codeowners-generator/pull/166) 264 | - Update typescript-eslint monorepo to v4.14.0 [`#133`](https://github.com/gagoar/codeowners-generator/pull/133) 265 | - Update dependency typescript to v4.1.3 [`#148`](https://github.com/gagoar/codeowners-generator/pull/148) 266 | - Update dependency ts-node to v9.1.1 [`#145`](https://github.com/gagoar/codeowners-generator/pull/145) 267 | - Update dependency bufferutil to v4.0.3 [`#155`](https://github.com/gagoar/codeowners-generator/pull/155) 268 | - Update dependency eslint to v7.18.0 [`#141`](https://github.com/gagoar/codeowners-generator/pull/141) 269 | - Update dependency utf-8-validate to v5.0.4 [`#156`](https://github.com/gagoar/codeowners-generator/pull/156) 270 | - Update dependency @types/jest to v26.0.20 [`#146`](https://github.com/gagoar/codeowners-generator/pull/146) 271 | - Update dependency ajv-keywords to v4 [`#151`](https://github.com/gagoar/codeowners-generator/pull/151) 272 | - Update dependency prettier-eslint to v12 [`#132`](https://github.com/gagoar/codeowners-generator/pull/132) 273 | - Update Node.js to v12.20.1 [`#157`](https://github.com/gagoar/codeowners-generator/pull/157) 274 | - Update dependency husky to v4.3.8 [`#144`](https://github.com/gagoar/codeowners-generator/pull/144) 275 | - Update dependency @types/node to v13.13.40 [`#147`](https://github.com/gagoar/codeowners-generator/pull/147) 276 | - Update dependency ora to v5.2.0 [`#154`](https://github.com/gagoar/codeowners-generator/pull/154) 277 | - Update dependency tslib to v2.1.0 [`#158`](https://github.com/gagoar/codeowners-generator/pull/158) 278 | - Update dependency commander to v7 [`#162`](https://github.com/gagoar/codeowners-generator/pull/162) 279 | - Add output to the readme [`#159`](https://github.com/gagoar/codeowners-generator/pull/159) 280 | - Bump ini from 1.3.5 to 1.3.8 [`#149`](https://github.com/gagoar/codeowners-generator/pull/149) 281 | - Update actions/setup-node action to v2 [`#152`](https://github.com/gagoar/codeowners-generator/pull/152) 282 | - Update dependency eslint-config-prettier to v7 [`#143`](https://github.com/gagoar/codeowners-generator/pull/143) 283 | - Update dependency fast-glob to v3.2.5 [`#163`](https://github.com/gagoar/codeowners-generator/pull/163) 284 | - Bump node-notifier from 8.0.0 to 8.0.1 [`#153`](https://github.com/gagoar/codeowners-generator/pull/153) 285 | - Update dependency @types/jest to v26.0.16 [`#138`](https://github.com/gagoar/codeowners-generator/pull/138) 286 | - Update dependency ts-node to v9.1.0 [`#139`](https://github.com/gagoar/codeowners-generator/pull/139) 287 | - Update dependency prettier to v2.2.1 [`#136`](https://github.com/gagoar/codeowners-generator/pull/136) 288 | - Update dependency @types/node to v13.13.34 [`#137`](https://github.com/gagoar/codeowners-generator/pull/137) 289 | - Update dependency lint-staged to v10.5.3 [`#140`](https://github.com/gagoar/codeowners-generator/pull/140) 290 | - Update dependency lint-staged to v10.5.2 [`#134`](https://github.com/gagoar/codeowners-generator/pull/134) 291 | - Update dependency prettier to v2.2.0 [`#130`](https://github.com/gagoar/codeowners-generator/pull/130) 292 | - Update dependency typescript to v4.1.2 [`#129`](https://github.com/gagoar/codeowners-generator/pull/129) 293 | - Update dependency eslint to v7.14.0 [`#131`](https://github.com/gagoar/codeowners-generator/pull/131) 294 | - Update dependency @types/node to v13.13.33 [`#127`](https://github.com/gagoar/codeowners-generator/pull/127) 295 | - Update dependency debug to v4.3.1 [`#128`](https://github.com/gagoar/codeowners-generator/pull/128) 296 | - Update Node.js to v12.20.0 [`#135`](https://github.com/gagoar/codeowners-generator/pull/135) 297 | - Update typescript-eslint monorepo to v4.8.1 [`#126`](https://github.com/gagoar/codeowners-generator/pull/126) 298 | - Update typescript-eslint monorepo to v4.8.0 [`#125`](https://github.com/gagoar/codeowners-generator/pull/125) 299 | - Update Node.js to v12.19.1 [`#124`](https://github.com/gagoar/codeowners-generator/pull/124) 300 | - Update dependency eslint to v7.13.0 [`#121`](https://github.com/gagoar/codeowners-generator/pull/121) 301 | - Update dependency ts-jest to v26.4.4 [`#122`](https://github.com/gagoar/codeowners-generator/pull/122) 302 | - Update dependency jest to v26.6.3 [`#120`](https://github.com/gagoar/codeowners-generator/pull/120) 303 | - Update typescript-eslint monorepo to v4.7.0 [`#123`](https://github.com/gagoar/codeowners-generator/pull/123) 304 | - Update dependency jest to v26.6.2 [`#116`](https://github.com/gagoar/codeowners-generator/pull/116) 305 | - Update dependency utf-8-validate to v5.0.3 [`#115`](https://github.com/gagoar/codeowners-generator/pull/115) 306 | - Update dependency bufferutil to v4.0.2 [`#114`](https://github.com/gagoar/codeowners-generator/pull/114) 307 | - Update dependency lint-staged to v10.5.1 [`#113`](https://github.com/gagoar/codeowners-generator/pull/113) 308 | - Update typescript-eslint monorepo to v4.6.1 [`#117`](https://github.com/gagoar/codeowners-generator/pull/117) 309 | - Add .whitesource configuration file [`#118`](https://github.com/gagoar/codeowners-generator/pull/118) 310 | - Update typescript-eslint monorepo to v4.6.0 [`#104`](https://github.com/gagoar/codeowners-generator/pull/104) 311 | - Update dependency lint-staged to v10.5.0 [`#105`](https://github.com/gagoar/codeowners-generator/pull/105) 312 | - Update dependency ts-jest to v26.4.3 [`#106`](https://github.com/gagoar/codeowners-generator/pull/106) 313 | - Update dependency @types/node to v13.13.30 [`#107`](https://github.com/gagoar/codeowners-generator/pull/107) 314 | - Update dependency typescript to v4.0.5 [`#108`](https://github.com/gagoar/codeowners-generator/pull/108) 315 | - Update dependency eslint-config-prettier to v6.15.0 [`#110`](https://github.com/gagoar/codeowners-generator/pull/110) 316 | - Update dependency eslint to v7.12.1 [`#109`](https://github.com/gagoar/codeowners-generator/pull/109) 317 | - 1.3.0 [`#112`](https://github.com/gagoar/codeowners-generator/pull/112) 318 | - adding groupSourceComments (#168) [`#164`](https://github.com/gagoar/codeowners-generator/issues/164) 319 | - exclude more places by default (#167) [`#165`](https://github.com/gagoar/codeowners-generator/issues/165) 320 | 321 | ## [1.3.0](https://github.com/gagoar/codeowners-generator/compare/v1.2.5...v1.3.0) 322 | 323 | > 28 October 2020 324 | 325 | - adding npm build to generate a build directory with the transpile code to be used programatically [`#111`](https://github.com/gagoar/codeowners-generator/pull/111) 326 | - Update dependency commander to v6.2.0 [`#103`](https://github.com/gagoar/codeowners-generator/pull/103) 327 | - Update dependency @types/node to v13.13.28 [`#99`](https://github.com/gagoar/codeowners-generator/pull/99) 328 | - Update dependency jest to v26.6.1 [`#100`](https://github.com/gagoar/codeowners-generator/pull/100) 329 | - Update dependency ts-jest to v26.4.2 [`#101`](https://github.com/gagoar/codeowners-generator/pull/101) 330 | - Update dependency eslint to v7.12.0 [`#102`](https://github.com/gagoar/codeowners-generator/pull/102) 331 | - Update dependency @types/node to v13.13.27 [`#97`](https://github.com/gagoar/codeowners-generator/pull/97) 332 | - Update dependency @types/jest to v26.0.15 [`#96`](https://github.com/gagoar/codeowners-generator/pull/96) 333 | - Update typescript-eslint monorepo to v4.5.0 [`#95`](https://github.com/gagoar/codeowners-generator/pull/95) 334 | - Update dependency jest to v26.6.0 [`#94`](https://github.com/gagoar/codeowners-generator/pull/94) 335 | - Update dependency eslint-config-prettier to v6.14.0 [`#98`](https://github.com/gagoar/codeowners-generator/pull/98) 336 | - Update dependency lint-staged to v10.4.2 [`#91`](https://github.com/gagoar/codeowners-generator/pull/91) 337 | - Update dependency eslint-config-prettier to v6.13.0 [`#92`](https://github.com/gagoar/codeowners-generator/pull/92) 338 | - Update dependency @types/node to v13.13.26 [`#93`](https://github.com/gagoar/codeowners-generator/pull/93) 339 | - Update typescript-eslint monorepo to v4.4.1 [`#81`](https://github.com/gagoar/codeowners-generator/pull/81) 340 | - Update dependency jest to v26.5.3 [`#88`](https://github.com/gagoar/codeowners-generator/pull/88) 341 | - Update dependency pretty-quick to v3.1.0 [`#89`](https://github.com/gagoar/codeowners-generator/pull/89) 342 | - Update Readme to include the mention about matching package.json syntax for authors/contributors [`#90`](https://github.com/gagoar/codeowners-generator/pull/90) 343 | - Update dependency jest to v26.5.2 [`#80`](https://github.com/gagoar/codeowners-generator/pull/80) 344 | - Update dependency tslib to v2.0.3 [`#83`](https://github.com/gagoar/codeowners-generator/pull/83) 345 | - Update dependency @types/node to v13.13.25 [`#82`](https://github.com/gagoar/codeowners-generator/pull/82) 346 | - Update dependency eslint to v7.11.0 [`#87`](https://github.com/gagoar/codeowners-generator/pull/87) 347 | - Update Node.js to v12.19.0 [`#84`](https://github.com/gagoar/codeowners-generator/pull/84) 348 | - Adding the pull_request [`#86`](https://github.com/gagoar/codeowners-generator/pull/86) 349 | - adding coverall [`#85`](https://github.com/gagoar/codeowners-generator/pull/85) 350 | - [BUG] Leave default output unchanged when no output is provided [`#79`](https://github.com/gagoar/codeowners-generator/pull/79) 351 | 352 | ## [1.2.5](https://github.com/gagoar/codeowners-generator/compare/v1.2.4...v1.2.5) 353 | 354 | > 2 October 2020 355 | 356 | - removing path.join on output [`3a6cb2d`](https://github.com/gagoar/codeowners-generator/commit/3a6cb2d323933c2397d9533b1850970f819871ed) 357 | 358 | ## [1.2.4](https://github.com/gagoar/codeowners-generator/compare/v1.2.3...v1.2.4) 359 | 360 | > 2 October 2020 361 | 362 | - removing default from cli [`#78`](https://github.com/gagoar/codeowners-generator/pull/78) 363 | 364 | ## [1.2.3](https://github.com/gagoar/codeowners-generator/compare/v1.2.2...v1.2.3) 365 | 366 | > 1 October 2020 367 | 368 | - Bump v1.2.3 [`#77`](https://github.com/gagoar/codeowners-generator/pull/77) 369 | - fixing output from customConfig [`#76`](https://github.com/gagoar/codeowners-generator/pull/76) 370 | - Update dependency ts-jest to v26.4.1 [`#75`](https://github.com/gagoar/codeowners-generator/pull/75) 371 | - Update typescript-eslint monorepo to v4.3.0 [`#74`](https://github.com/gagoar/codeowners-generator/pull/74) 372 | - Update dependency eslint to v7.10.0 [`#73`](https://github.com/gagoar/codeowners-generator/pull/73) 373 | - Update dependency eslint-config-prettier to v6.12.0 [`#72`](https://github.com/gagoar/codeowners-generator/pull/72) 374 | - Update typescript-eslint monorepo to v4.2.0 [`#71`](https://github.com/gagoar/codeowners-generator/pull/71) 375 | - Update dependency ts-jest to v26.4.0 [`#70`](https://github.com/gagoar/codeowners-generator/pull/70) 376 | - Update dependency auto-changelog to v2.2.1 [`#68`](https://github.com/gagoar/codeowners-generator/pull/68) 377 | - Update dependency @types/node to v13.13.21 [`#66`](https://github.com/gagoar/codeowners-generator/pull/66) 378 | - Update dependency typescript to v4.0.3 [`#67`](https://github.com/gagoar/codeowners-generator/pull/67) 379 | - Update dependency debug to v4.2.0 [`#69`](https://github.com/gagoar/codeowners-generator/pull/69) 380 | - Update typescript-eslint monorepo to v4.1.1 [`#57`](https://github.com/gagoar/codeowners-generator/pull/57) 381 | - Update Node.js to v12.18.4 [`#62`](https://github.com/gagoar/codeowners-generator/pull/62) 382 | - Update dependency @types/node to v13.13.20 [`#58`](https://github.com/gagoar/codeowners-generator/pull/58) 383 | - Update dependency eslint to v7.9.0 [`#60`](https://github.com/gagoar/codeowners-generator/pull/60) 384 | - Update dependency pretty-quick to v3.0.2 [`#59`](https://github.com/gagoar/codeowners-generator/pull/59) 385 | - Update dependency husky to v4.3.0 [`#56`](https://github.com/gagoar/codeowners-generator/pull/56) 386 | - Update dependency ora to v5.1.0 [`#55`](https://github.com/gagoar/codeowners-generator/pull/55) 387 | - Update dependency prettier to v2.1.2 [`#63`](https://github.com/gagoar/codeowners-generator/pull/63) 388 | - Update dependency lint-staged to v10.4.0 [`#64`](https://github.com/gagoar/codeowners-generator/pull/64) 389 | - Update dependency @types/jest to v26.0.14 [`#65`](https://github.com/gagoar/codeowners-generator/pull/65) 390 | - Bump node-fetch from 2.6.0 to 2.6.1 [`#61`](https://github.com/gagoar/codeowners-generator/pull/61) 391 | - Update dependency @types/jest to v26.0.13 [`#52`](https://github.com/gagoar/codeowners-generator/pull/52) 392 | - Update dependency eslint to v7.8.1 [`#51`](https://github.com/gagoar/codeowners-generator/pull/51) 393 | - Update dependency @types/node to v13.13.16 [`#53`](https://github.com/gagoar/codeowners-generator/pull/53) 394 | - Update dependency lint-staged to v10.3.0 [`#54`](https://github.com/gagoar/codeowners-generator/pull/54) 395 | - Update typescript-eslint monorepo to v4 [`#48`](https://github.com/gagoar/codeowners-generator/pull/48) 396 | - Update dependency @types/jest to v26.0.12 [`#49`](https://github.com/gagoar/codeowners-generator/pull/49) 397 | - Update dependency eslint to v7.8.0 [`#50`](https://github.com/gagoar/codeowners-generator/pull/50) 398 | - Bumping v.1.2.2 [`#47`](https://github.com/gagoar/codeowners-generator/pull/47) 399 | 400 | ## [1.2.2](https://github.com/gagoar/codeowners-generator/compare/v1.2.1...v1.2.2) 401 | 402 | > 29 August 2020 403 | 404 | - fixing release process [`#46`](https://github.com/gagoar/codeowners-generator/pull/46) 405 | - Update dependency commander to v6.1.0 [`#45`](https://github.com/gagoar/codeowners-generator/pull/45) 406 | - Update dependency lint-staged to v10.2.13 [`#41`](https://github.com/gagoar/codeowners-generator/pull/41) 407 | - Update typescript-eslint monorepo to v3.10.1 [`#40`](https://github.com/gagoar/codeowners-generator/pull/40) 408 | - Update dependency prettier to v2.1.1 [`#42`](https://github.com/gagoar/codeowners-generator/pull/42) 409 | - Update dependency ts-jest to v26.3.0 [`#43`](https://github.com/gagoar/codeowners-generator/pull/43) 410 | - version 1.2.1 bump [`#39`](https://github.com/gagoar/codeowners-generator/pull/39) 411 | 412 | ## [1.2.1](https://github.com/gagoar/codeowners-generator/compare/v1.2.0...v1.2.1) 413 | 414 | > 24 August 2020 415 | 416 | - adding logo [`#38`](https://github.com/gagoar/codeowners-generator/pull/38) 417 | - Take includes when provided via CustomConfiguration [`#37`](https://github.com/gagoar/codeowners-generator/pull/37) 418 | - Update dependency prettier to v2.1.0 [`#34`](https://github.com/gagoar/codeowners-generator/pull/34) 419 | - Update typescript-eslint monorepo to v3.10.0 [`#35`](https://github.com/gagoar/codeowners-generator/pull/35) 420 | - fixing release + version bump + documentation update [`#33`](https://github.com/gagoar/codeowners-generator/pull/33) 421 | - Take includes when provided via CustomConfiguration (#37) [`#36`](https://github.com/gagoar/codeowners-generator/issues/36) 422 | 423 | ## [1.2.0](https://github.com/gagoar/codeowners-generator/compare/v1.1.1...v1.2.0) 424 | 425 | > 24 August 2020 426 | 427 | - fixes #21 [`#32`](https://github.com/gagoar/codeowners-generator/pull/32) 428 | - Update dependency jest to v26.4.2 [`#31`](https://github.com/gagoar/codeowners-generator/pull/31) 429 | - Update dependency ts-node to v9 [`#30`](https://github.com/gagoar/codeowners-generator/pull/30) 430 | - Update dependency commander to v6 [`#17`](https://github.com/gagoar/codeowners-generator/pull/17) 431 | - Update dependency pretty-quick to v3 [`#29`](https://github.com/gagoar/codeowners-generator/pull/29) 432 | - Update dependency typescript to v4 [`#27`](https://github.com/gagoar/codeowners-generator/pull/27) 433 | - Update dependency jest to v26.4.1 [`#23`](https://github.com/gagoar/codeowners-generator/pull/23) 434 | - Update typescript-eslint monorepo to v3.9.1 [`#16`](https://github.com/gagoar/codeowners-generator/pull/16) 435 | - Update dependency eslint to v7.7.0 [`#25`](https://github.com/gagoar/codeowners-generator/pull/25) 436 | - Update dependency ts-jest to v26.2.0 [`#24`](https://github.com/gagoar/codeowners-generator/pull/24) 437 | - Update dependency typescript to v3.9.7 [`#15`](https://github.com/gagoar/codeowners-generator/pull/15) 438 | - Update dependency @types/jest to v26.0.10 [`#26`](https://github.com/gagoar/codeowners-generator/pull/26) 439 | - Making release workflow work [`#22`](https://github.com/gagoar/codeowners-generator/pull/22) 440 | - fixes #21 (#32) [`#21`](https://github.com/gagoar/codeowners-generator/issues/21) 441 | - Update issue templates [`41031da`](https://github.com/gagoar/codeowners-generator/commit/41031daa340b83d87374e5cff4e8e406906f78ea) 442 | 443 | ## [1.1.1](https://github.com/gagoar/codeowners-generator/compare/v1.1.0...v1.1.1) 444 | 445 | > 7 August 2020 446 | 447 | - Lowering engine requirements [`#20`](https://github.com/gagoar/codeowners-generator/pull/20) 448 | - Update dependency eslint to v7.6.0 [`#10`](https://github.com/gagoar/codeowners-generator/pull/10) 449 | - Update dependency jest to v26.2.2 [`#11`](https://github.com/gagoar/codeowners-generator/pull/11) 450 | - Update dependency ajv-keywords to v3.5.2 [`#9`](https://github.com/gagoar/codeowners-generator/pull/9) 451 | - Update dependency tslib to v2.0.1 [`#14`](https://github.com/gagoar/codeowners-generator/pull/14) 452 | - Update dependency @types/jest to v26.0.9 [`#7`](https://github.com/gagoar/codeowners-generator/pull/7) 453 | - Update dependency ts-jest to v26.1.4 [`#13`](https://github.com/gagoar/codeowners-generator/pull/13) 454 | - Update Node.js to v12.18.3 [`#6`](https://github.com/gagoar/codeowners-generator/pull/6) 455 | - Update dependency @types/node to v13.13.15 [`#8`](https://github.com/gagoar/codeowners-generator/pull/8) 456 | - Update dependency cosmiconfig to v7 [`#18`](https://github.com/gagoar/codeowners-generator/pull/18) 457 | - Update dependency ora to v5 [`#19`](https://github.com/gagoar/codeowners-generator/pull/19) 458 | - Add renovate.json [`#5`](https://github.com/gagoar/codeowners-generator/pull/5) 459 | 460 | ## [1.1.0](https://github.com/gagoar/codeowners-generator/compare/v1.0.0...v1.1.0) 461 | 462 | > 3 August 2020 463 | 464 | - bumping v1.1.0 [`#4`](https://github.com/gagoar/codeowners-generator/pull/4) 465 | - Changing copy based on yarn.lock [`#3`](https://github.com/gagoar/codeowners-generator/pull/3) 466 | - 100% tested [`#2`](https://github.com/gagoar/codeowners-generator/pull/2) 467 | 468 | ## 1.0.0 469 | 470 | > 1 August 2020 471 | 472 | - basic functionality + tests [`#1`](https://github.com/gagoar/codeowners-generator/pull/1) 473 | - Initial commit [`b23784e`](https://github.com/gagoar/codeowners-generator/commit/b23784ec9aee2faef91b502c70adf1dd79675ebf) 474 | 475 | 476 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | NPM Version 5 | 6 | 7 | Workflow 8 | 9 | 10 | codecov 11 | 12 | 13 | MIT license 14 | 15 |

16 | 17 |

18 | 19 |

codeowners-generator

20 |

21 | 22 | Logo 23 | 24 |

25 | 26 |

27 | this project is sponsored by:
28 | 29 | SourceGraph 30 | 31 |

32 | 33 |

34 | ✨ use codeowners anywhere in your monorepo 🛠️ 35 |
36 | Explore the docs » 37 |
38 | Report Bug 39 | · 40 | Request Feature 41 |

42 |

43 | 44 | 45 | 46 | ## Table of Contents 47 | 48 | - [About the Project](#about-the-project) 49 | - [Built With](#built-with) 50 | - [Installation](#installation) 51 | - [Configuration](#configuration) 52 | - [Usage](#usage) 53 | - [Action](#action) 54 | - [Contributing](#contributing) 55 | - [License](#license) 56 | 57 | 58 | 59 | ## About The Project 60 | 61 | [CODEOWNERS](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners) are automatically requested for review when someone opens a pull request that modifies code that they own. This is a great feature, but when working on monorepos ownership is shared between teams and it becomes difficult to maintain. 62 | 63 | `codeowners-generator` allows you to position CODEOWNERS files anywhere in your project tree and it will take care of compiling all the files into a single generated file, that Github can understand. It also can read the maintainers fields (`contributors`, `author` and alternatively `maintainers`) in `package.json` (`--use-maintainers` option in the cli ) making easy to keep CODEOWNERS and package.json in sync. Make sure the `author`/`contributors` syntax matches with `package.json` expected syntax from the [documentation](https://docs.npmjs.com/files/package.json#people-fields-author-contributors). 64 | 65 | ### Built With 66 | 67 | - [ora](https://github.com/sindresorhus/ora) 68 | - [commander](https://github.com/tj/commander.js/) 69 | - [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) 70 | 71 | 72 | 73 | ### Installation 74 | 75 | If you wish to use `codeowners-generator` as a standalone utility: 76 | 77 | ```sh 78 | npm -g install codeowners-generator 79 | ``` 80 | 81 | This will make the `codeowners-generator` command available in your terminal. 82 | 83 | ```sh 84 | codeowners-generator --help 85 | ``` 86 | 87 | If instead you would like to add it to a package: 88 | 89 | ```sh 90 | npm install --only=dev codeowners-generator 91 | ``` 92 | 93 | 94 | 95 | ## Usage 96 | 97 | Every command accepts several options through command line or custom configuration [see configuration for more](#configuration) 98 | 99 | ### Generate CODEOWNERS file 100 | 101 | ```sh 102 | codeowners-generator generate 103 | ``` 104 | 105 | ### Generate CODEOWNERS file (using `maintainers` field from `package.json`) 106 | 107 | ```sh 108 | codeowners-generator generate --use-maintainers 109 | ``` 110 | 111 | ### Specify CODEOWNERS (in case the CODEOWNERS files are named differently) 112 | 113 | ```sh 114 | codeowners-generator generate --includes '**/CODEOWNERS' 115 | ``` 116 | 117 | ## Action 118 | 119 | Now you can use `codeowners-generator` to validate if the CODEOWNERS file has been updated during a Pull Request. 120 | 121 | ```yml 122 | name: Lint CODEOWNERS 123 | 124 | on: 125 | pull_request: 126 | 127 | jobs: 128 | codeowners: 129 | runs-on: ubuntu-latest 130 | steps: 131 | - uses: actions/checkout@v2 # to checkout the code of the repo you want to check the CODEOWNERS from. 132 | - name: check codeowners 133 | uses: gagoar/codeowners-generator@master 134 | with: 135 | use-maintainers: true 136 | check: true 137 | ``` 138 | 139 | You can also use it to update the Pull Request. For that, you will need a GitHub App or Personal Token with the necessary permissions (code content). The code for that will look roughly like this: 140 | 141 | ```yml 142 | name: update CODEOWNERS 143 | 144 | on: 145 | pull_request: 146 | 147 | jobs: 148 | build: 149 | runs-on: ubuntu-latest 150 | steps: 151 | - uses: actions/checkout@v3 152 | - uses: gagoar/codeowners-generator@master 153 | with: 154 | use-maintainers: true 155 | - run: | 156 | STATUS=$(git diff --quiet && echo clean || echo modified) 157 | echo "status=$(echo $STATUS)" >> $GITHUB_OUTPUT 158 | id: gitStatus 159 | - run: | 160 | echo ${{ steps.gitStatus.outputs.status }} 161 | echo ${{ contains(steps.gitStatus.outputs.status, 'modified') }} 162 | - name: Commit CODEOWNERS 163 | if: contains(steps.gitStatus.outputs.status, 'modified') 164 | run: | 165 | set -x 166 | git config --local user.email "action@github.com" 167 | git config --local user.name "GitHub Action" 168 | git add CODEOWNERS 169 | git commit -m "update CODEOWNERS" 170 | - id: auth 171 | if: contains(steps.gitStatus.outputs.status, 'modified') 172 | uses: jnwng/github-app-installation-token-action@v2 173 | with: 174 | appId: ${{ secrets.YOUR_APP_ID }} 175 | installationId: ${{ secrets.YOUR_APP_INSTALLATION_ID }} 176 | privateKey: ${{ secrets.YOUR_APP_PRIVATE_KEY }} 177 | - name: Push changes 178 | if: contains(steps.gitStatus.outputs.status, 'modified') 179 | uses: ad-m/github-push-action@master 180 | with: 181 | github_token: ${{ steps.auth.outputs.token }} 182 | branch: ${{github.head_ref}} 183 | ``` 184 | 185 | 186 | 187 | Remember that you can always create a configuration file in your project that will be picked up by the tool running on the action. For examples in how to configure take a look at the [configuration section below](#configuration). 188 | 189 | ## Configuration 190 | 191 | You can configure `codeowners-generator` from several places: 192 | 193 | ### CLI options 194 | 195 | - **includes** (`--includes`): The glob used to find CODEOWNERS files in the repo `default: ['**/CODEOWNERS', '!CODEOWNERS', '!.github/CODEOWNERS', '!docs/CODEOWNERS', '!node_modules']` 196 | 197 | - **output** (`--output`): The output path and name of the file `default: CODEOWNERS` 198 | 199 | - **useMaintainers** (`--use-maintainers`): It will use `maintainers` field from package.json to generate codeowners, by default it will use `**/package.json` 200 | 201 | - **useRootMaintainers** (`--use-root-maintainers`): It will use `maintainers` field from the package.json in the root to generate default codeowners. Works only in conjunction with `useMaintainers`. `default: false` 202 | 203 | - **groupSourceComments** (`--group-source-comments`): Instead of generating one comment per rule, enabling this flag will group them, reducing comments to one per source file. Useful if your codeowners file gets too noisy. 204 | 205 | - **preserveBlockPosition** (`--preserve-block-position`): It will keep the generated block in the same position it was found in the CODEOWNERS file (if present). Useful for when you make manual additions. 206 | 207 | - **customRegenerationCommand** (`--custom-regeneration-command`): Specify a custom regeneration command to be printed in the generated CODEOWNERS file, it should be mapped to run codeowners-generator (e.g. "npm run codeowners"). 208 | 209 | - **check** (`--check`): It will fail if the CODEOWNERS generated doesn't match the current (or missing) CODEOWNERS . Useful for validating that the CODEOWNERS file is not out of date during CI. 210 | 211 | - **hidden-directories** (`--hidden-directories`): Also include searching in hidden (dot) directories. 212 | 213 | For more details you can invoke: 214 | 215 | ```sh 216 | codeowners-generator --help 217 | ``` 218 | 219 | ### Custom Configuration 220 | 221 | You can also define custom configuration in your package: 222 | 223 | ```json 224 | { 225 | "name": "my-package", 226 | "codeowners-generator": { 227 | "includes": ["**/CODEOWNERS"], 228 | "output": ".github/CODEOWNERS", 229 | "useMaintainers": true, 230 | "useRootMaintainers": true, 231 | "groupSourceComments": true, 232 | "customRegenerationCommand": "npm run codeowners" 233 | }, 234 | "scripts": { 235 | "codeowners": " codeowners-generator generate" 236 | }, 237 | "devDependencies": { 238 | "codeowners-generator": "^2.0.0" 239 | } 240 | } 241 | ``` 242 | 243 | When the command is invoked it will look for the `codeowners-generator` configuration block. 244 | 245 | ```bash 246 | (my-package)$ npm run codeowners 247 | ``` 248 | 249 | If you create any files matching the following patterns, `codeowners-generator` will pick them up: 250 | 251 | - a `codeowners-generator` property in package.json 252 | - a `.codeowners-generatorrc` file in JSON or YAML format 253 | - a `.codeowners-generator.json`, `.codeowners-generator.yaml`, `.codeowners-generator.yml`, `.codeowners-generator.js`, or `.codeowners-generator.cjs` file 254 | - a `codeowners-generatorrc`, `codeowners-generator.json`, `codeowners-generatorrc.yaml`, `codeowners-generatorrc.yml`, `codeowners-generator.js` or `codeowners-generator.cjs` file inside a .config subdirectory 255 | - a `codeowners-generator.config.js` or `codeowners-generator.config.cjs` CommonJS module exporting an object 256 | 257 | For more insight into the custom configuration and where it can be defined check [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) 258 | 259 | 260 | 261 | ## Roadmap 262 | 263 | See the [open issues](https://github.com/gagoar/codeowners-generator/issues) for a list of proposed features (and known issues). 264 | 265 | 266 | 267 | ## Contributing 268 | 269 | Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated **greatly appreciated**. 270 | 271 | 1. Fork the Project 272 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 273 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 274 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 275 | 5. Open a Pull Request 276 | 277 | 278 | 279 | ## License 280 | 281 | Distributed under the MIT License. See `LICENSE` for more information. 282 | 283 | 284 | 285 |

286 | 287 | follow on Twitter 288 | 289 | 290 | follow on Twitter 291 | 292 |

293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS1: -------------------------------------------------------------------------------- 1 | # some comment 2 | *.ts @eeny @meeny 3 | /*.ts @miny 4 | /*.md 5 | # the line below refers to any file named README.md 6 | README.md @miny 7 | # the line below refers to a file in the root 8 | /README.md @moe 9 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS2: -------------------------------------------------------------------------------- 1 | # a comment 2 | *.ts @moe 3 | # another comment 4 | dir3/*.ts @miny 5 | **/*.md @meeny 6 | # the line below refers to any directory named dir4/ 7 | dir4/ @eeny 8 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS3: -------------------------------------------------------------------------------- 1 | # a comment 2 | *.ts @miny 3 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS5: -------------------------------------------------------------------------------- 1 | *.ts @randomDudeFromDifferentRepo 2 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS_POPULATED_OUTPUT: -------------------------------------------------------------------------------- 1 | # We are already using CODEOWNERS and we don't want to lose the content of this file. 2 | scripts/ @myOrg/infraTeam 3 | # We might wanna keep an eye on something else, like yml files and workflows. 4 | .github/workflows/ @myOrg/infraTeam 5 | 6 | #################################### Generated content - do not edit! #################################### 7 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator/README.md) 8 | # To re-generate, run npm run codeowners-generator generate. Don't worry, the content outside this block will be kept. 9 | # Rule extracted from dir1/CODEOWNERS 10 | dir1/*.ts @eeny @meeny 11 | #################################### Generated content - do not edit! #################################### 12 | 13 | # Another line here that should be moved to after the generated block without preserve-block-position option enabled 14 | dir2/ @otherTeam 15 | -------------------------------------------------------------------------------- /__mocks__/CODEOWNERS_POPULATED_OUTPUT_2: -------------------------------------------------------------------------------- 1 | # We are already using CODEOWNERS and we don't want to lose the content of this file. 2 | scripts/ @myOrg/infraTeam 3 | # We might wanna keep an eye on something else, like yml files and workflows. 4 | .github/workflows/ @myOrg/infraTeam 5 | 6 | #################################### Generated content - do not edit! #################################### 7 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 8 | # To re-generate, run `yarn codeowners-generator generate`. Don't worry, the content outside this block will be kept. 9 | 10 | # Rule extracted from dir1/CODEOWNERS 11 | /dir1/**/*.ts @eeny @meeny 12 | # Rule extracted from dir1/CODEOWNERS 13 | /dir1/*.ts @miny 14 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 15 | /dir1/*.md 16 | # Rule extracted from dir1/CODEOWNERS 17 | /dir1/**/README.md @miny 18 | # Rule extracted from dir1/CODEOWNERS 19 | /dir1/README.md @moe 20 | # Rule extracted from dir2/CODEOWNERS 21 | /dir2/**/*.ts @moe 22 | # Rule extracted from dir2/CODEOWNERS 23 | /dir2/dir3/*.ts @miny 24 | # Rule extracted from dir2/CODEOWNERS 25 | /dir2/**/*.md @meeny 26 | # Rule extracted from dir2/CODEOWNERS 27 | /dir2/**/dir4/ @eeny 28 | # Rule extracted from dir2/dir3/CODEOWNERS 29 | /dir2/dir3/**/*.ts @miny 30 | 31 | #################################### Generated content - do not edit! #################################### 32 | -------------------------------------------------------------------------------- /__mocks__/cosmiconfig.ts: -------------------------------------------------------------------------------- 1 | export const search = jest.fn(); 2 | export const cosmiconfig = (): { search: jest.Mock } => ({ search }); 3 | -------------------------------------------------------------------------------- /__mocks__/gitIgnore1: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /__mocks__/ora.ts: -------------------------------------------------------------------------------- 1 | export const stopAndPersist = jest.fn(); 2 | export const fail = jest.fn(); 3 | 4 | const ora = { 5 | start: jest.fn(() => ({ 6 | stopAndPersist, 7 | fail, 8 | })), 9 | }; 10 | export default (): typeof ora => ora; 11 | -------------------------------------------------------------------------------- /__mocks__/package1.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "randomPackage", 3 | "maintainers": [ 4 | "Your Friend (http://friends-website.com)", 5 | "Other Friend (http://other-website.com)", 6 | "Friend without an email (http://other-website.com)" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /__mocks__/package2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otherPackage", 3 | "maintainers": [ 4 | { "email": "friend@example.com" }, 5 | { "name": "Other Friend", "email": "other@example.com", "url": "http://other-website.com)" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /__mocks__/package3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otherPackage", 3 | "maintainers": [{ "name": "Other Friend", "url": "http://other-website.com)" }] 4 | } 5 | -------------------------------------------------------------------------------- /__mocks__/package4.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "without-maintainers" 3 | } 4 | -------------------------------------------------------------------------------- /__mocks__/package5.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "otherPackage", 3 | "contributors": [{ "name": "Other Friend", "email": "other.friend@domain.com" }], 4 | "author": { "name": "George the Builder", "email": "gbuilder@builder.com" } 5 | } 6 | -------------------------------------------------------------------------------- /__tests__/codeowners.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadCodeOwnerFiles } from '../src/utils/codeowners'; 2 | import { readContent } from '../src/utils/readContent'; 3 | 4 | jest.mock('../src/utils/readContent'); 5 | 6 | const readContentMock = jest.mocked(readContent); 7 | type ValidRulesCases = [shouldStatement: string, pattern: string, resultingPath: string, owners?: string]; 8 | describe('Codeowners', () => { 9 | describe('loadCodeOwnerFiles', () => { 10 | const invalidRuleCases = [ 11 | ['should throw if a rule is missing a file pattern', ' @eeny', ' @eeny in dir1/CODEOWNERS can not be parsed'], 12 | [ 13 | 'should throw if a rule is using ! to negate a pattern', 14 | '!*.ts @eeny', 15 | '!*.ts @eeny in dir1/CODEOWNERS can not be parsed', 16 | ], 17 | [ 18 | 'should throw if a pattern is using [ ] to define a character range', 19 | '[a-z].ts @meeny', 20 | '[a-z].ts @meeny in dir1/CODEOWNERS can not be parsed', 21 | ], 22 | [ 23 | 'should throw if a pattern is using braces for brace expansion or brace sets', 24 | '*.{txt,md} @miny', 25 | '*.{txt,md} @miny in dir1/CODEOWNERS can not be parsed', 26 | ], 27 | [ 28 | 'should throw if a pattern is escaping a pattern starting with # using \\ so it is treated as a pattern and not a comment', 29 | '\\#fileName @moe', 30 | '\\#fileName @moe in dir1/CODEOWNERS can not be parsed', 31 | ], 32 | ]; 33 | 34 | it.each(invalidRuleCases)('%s (%s)', async (_name, rule, expectedError) => { 35 | readContentMock.mockResolvedValueOnce(rule); 36 | 37 | await expect(loadCodeOwnerFiles('/root', ['/root/dir1/CODEOWNERS'])).rejects.toThrowError(expectedError); 38 | }); 39 | 40 | const validRuleCases: ValidRulesCases[] = [ 41 | ['should be considered a rule with an exclusion if a rule is missing owners', '/*.ts', '/dir1/*.ts', ''], 42 | ['should match * to all file under the given directory and its subdirectories', '*', '/dir1/**/*'], 43 | [ 44 | 'should only match /* to all file in the root of the given directory and not its subdirectories', 45 | '/*', 46 | '/dir1/*', 47 | ], 48 | ['should match *.ts to all ts file under the given directory and its subdirectories', '*.ts', '/dir1/**/*.ts'], 49 | ['should only match /*.ts to ts file in the given directory and not its subdirectories', '/*.ts', '/dir1/*.ts'], 50 | [ 51 | 'should match apps/ to any directories named apps in the given directory and its subdirectories', 52 | 'apps/', 53 | '/dir1/**/apps/', 54 | ], 55 | [ 56 | 'should match docs/* to files under directly inside a directory named docs in the given directory', 57 | 'docs/*', 58 | '/dir1/docs/*', 59 | ], 60 | [ 61 | 'should match filenames to files in the given directory and its subdirectories', 62 | 'README.md', 63 | '/dir1/**/README.md', 64 | ], 65 | ['should append globstar patterns to the given directory', '**/something.ts', '/dir1/**/something.ts'], 66 | ['should append globstar/glob patterns to the given directory', '**/*.ts', '/dir1/**/*.ts'], 67 | ['should append glob patterns with fixed base to the given directory', 'dir2/*.ts', '/dir1/dir2/*.ts'], 68 | ['should append static file patterns to the given directory', 'dir2/something.ts', '/dir1/dir2/something.ts'], 69 | ['should append static directory patterns (dir2/dir3/) to the given directory', 'dir2/dir3/', '/dir1/dir2/dir3/'], 70 | ['should append patterns starting with a slash (/) to the given directory', '/', '/dir1/'], 71 | [ 72 | 'should append file patterns starting with a slash (/asd/asd.ts) to the given directory', 73 | '/asd/asd.ts', 74 | '/dir1/asd/asd.ts', 75 | ], 76 | [ 77 | 'should append glob patterns starting with a slash (/**/asd.ts) to the given directory', 78 | '/**/asd.ts', 79 | '/dir1/**/asd.ts', 80 | ], 81 | ]; 82 | 83 | it.each(validRuleCases)('%s (%s)', async (_name, pattern, expectedGlob, owners = '@eeny') => { 84 | readContentMock.mockResolvedValueOnce(`${pattern} ${owners}`); 85 | 86 | await expect(loadCodeOwnerFiles('/root', ['/root/dir1/CODEOWNERS'])).resolves.toEqual([ 87 | { filePath: 'dir1/CODEOWNERS', glob: expectedGlob, owners: [owners] }, 88 | ]); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /__tests__/generate.spec.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as fg from 'fast-glob'; 3 | import { mockProcessExit } from 'jest-mock-process'; 4 | import { posix as path } from 'path'; 5 | import { generateCommand } from '../'; 6 | import { generate } from '../src/commands/generate'; 7 | import { fail, stopAndPersist } from '../__mocks__/ora'; 8 | import { search } from '../__mocks__/cosmiconfig'; 9 | import { mockConsole, unMockConsole } from './helpers'; 10 | 11 | jest.mock('fs'); 12 | jest.mock('fast-glob'); 13 | jest.mock('cosmiconfig'); 14 | 15 | const { readFileSync } = jest.requireActual('fs'); 16 | const sync = jest.mocked(fg.sync); 17 | const readFile = jest.mocked(fs.readFile); 18 | const writeFile = jest.mocked(fs.writeFileSync); 19 | const existsSync = jest.mocked(fs.existsSync); 20 | 21 | const files = { 22 | 'dir1/CODEOWNERS': '../__mocks__/CODEOWNERS1', 23 | 'dir2/CODEOWNERS': '../__mocks__/CODEOWNERS2', 24 | 'dir2/dir3/CODEOWNERS': '../__mocks__/CODEOWNERS3', 25 | 'node_modules/dir1/CODEOWNERS': '../__mocks__/CODEOWNERS5', 26 | }; 27 | 28 | const withGitIgnore = { ...files, '.gitignore': '../__mocks__/gitIgnore1' }; 29 | 30 | describe('Generate', () => { 31 | let consoleWarnMock: jest.Mock; 32 | beforeAll(() => { 33 | consoleWarnMock = mockConsole('warn'); 34 | }); 35 | afterAll(() => { 36 | unMockConsole('warn'); 37 | }); 38 | 39 | beforeEach(() => { 40 | sync.mockRestore(); 41 | stopAndPersist.mockClear(); 42 | writeFile.mockClear(); 43 | existsSync.mockRestore(); 44 | search.mockRestore(); 45 | fail.mockReset(); 46 | consoleWarnMock.mockReset(); 47 | }); 48 | 49 | it('should generate a CODEOWNERS file (re-using codeowners content) and not fail when using --check option', async () => { 50 | mockProcessExit(false); 51 | sync.mockReturnValueOnce(Object.keys(files)); 52 | 53 | sync.mockReturnValueOnce(['.gitignore']); 54 | 55 | const withPopulatedCodeownersFile = { 56 | ...withGitIgnore, 57 | CODEOWNERS: '../__mocks__/CODEOWNERS_POPULATED_OUTPUT_2', 58 | }; 59 | existsSync.mockReturnValue(true); 60 | readFile.mockImplementation((file, callback): void => { 61 | const fullPath = path.join( 62 | __dirname, 63 | withPopulatedCodeownersFile[file as keyof typeof withPopulatedCodeownersFile] 64 | ); 65 | const content = readFileSync(fullPath); 66 | callback(null, content); 67 | }); 68 | 69 | await generateCommand( 70 | { 71 | output: 'CODEOWNERS', 72 | customRegenerationCommand: 'yarn codeowners-generator generate', 73 | check: true, 74 | preserveBlockPosition: true, 75 | }, 76 | { parent: {} } 77 | ); 78 | }); 79 | it('should generate a CODEOWNERS file (re-using codeowners content) and fail when using --check option', async () => { 80 | const mockExit = mockProcessExit(false); 81 | 82 | sync.mockReturnValueOnce(Object.keys(files)); 83 | 84 | sync.mockReturnValueOnce(['.gitignore']); 85 | 86 | const withPopulatedCodeownersFile = { 87 | ...withGitIgnore, 88 | CODEOWNERS: '../__mocks__/CODEOWNERS_POPULATED_OUTPUT', 89 | }; 90 | existsSync.mockReturnValue(true); 91 | readFile.mockImplementation((file, callback): void => { 92 | const fullPath = path.join( 93 | __dirname, 94 | withPopulatedCodeownersFile[file as keyof typeof withPopulatedCodeownersFile] 95 | ); 96 | const content = readFileSync(fullPath); 97 | callback(null, content); 98 | }); 99 | 100 | await generateCommand( 101 | { output: 'CODEOWNERS', customRegenerationCommand: 'yarn codeowners-generator generate', check: true }, 102 | { parent: {} } 103 | ); 104 | 105 | expect(mockExit).toBeCalledWith(1); 106 | }); 107 | 108 | it('should generate a CODEOWNERS file (re-using codeowners content)', async () => { 109 | sync.mockReturnValueOnce(Object.keys(files)); 110 | 111 | sync.mockReturnValueOnce(['.gitignore']); 112 | 113 | const withPopulatedCodeownersFile = { 114 | ...withGitIgnore, 115 | CODEOWNERS: '../__mocks__/CODEOWNERS_POPULATED_OUTPUT', 116 | }; 117 | existsSync.mockReturnValue(true); 118 | readFile.mockImplementation((file, callback): void => { 119 | const fullPath = path.join( 120 | __dirname, 121 | withPopulatedCodeownersFile[file as keyof typeof withPopulatedCodeownersFile] 122 | ); 123 | const content = readFileSync(fullPath); 124 | callback(null, content); 125 | }); 126 | 127 | await generateCommand( 128 | { output: 'CODEOWNERS', customRegenerationCommand: 'yarn codeowners-generator generate' }, 129 | { parent: {} } 130 | ); 131 | expect(writeFile.mock.calls[0]).toMatchInlineSnapshot(` 132 | [ 133 | "CODEOWNERS", 134 | "# We are already using CODEOWNERS and we don't want to lose the content of this file. 135 | scripts/ @myOrg/infraTeam 136 | # We might wanna keep an eye on something else, like yml files and workflows. 137 | .github/workflows/ @myOrg/infraTeam 138 | 139 | 140 | # Another line here that should be moved to after the generated block without preserve-block-position option enabled 141 | dir2/ @otherTeam 142 | 143 | #################################### Generated content - do not edit! #################################### 144 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 145 | # To re-generate, run \`yarn codeowners-generator generate\`. Don't worry, the content outside this block will be kept. 146 | 147 | # Rule extracted from dir1/CODEOWNERS 148 | /dir1/**/*.ts @eeny @meeny 149 | # Rule extracted from dir1/CODEOWNERS 150 | /dir1/*.ts @miny 151 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 152 | /dir1/*.md 153 | # Rule extracted from dir1/CODEOWNERS 154 | /dir1/**/README.md @miny 155 | # Rule extracted from dir1/CODEOWNERS 156 | /dir1/README.md @moe 157 | # Rule extracted from dir2/CODEOWNERS 158 | /dir2/**/*.ts @moe 159 | # Rule extracted from dir2/CODEOWNERS 160 | /dir2/dir3/*.ts @miny 161 | # Rule extracted from dir2/CODEOWNERS 162 | /dir2/**/*.md @meeny 163 | # Rule extracted from dir2/CODEOWNERS 164 | /dir2/**/dir4/ @eeny 165 | # Rule extracted from dir2/dir3/CODEOWNERS 166 | /dir2/dir3/**/*.ts @miny 167 | 168 | #################################### Generated content - do not edit! ####################################", 169 | ] 170 | `); 171 | }); 172 | 173 | it('should generate a CODEOWNERS file (re-using codeowners content, with preserve-block-position enabled)', async () => { 174 | sync.mockReturnValueOnce(Object.keys(files)); 175 | 176 | sync.mockReturnValueOnce(['.gitignore']); 177 | 178 | const withPopulatedCodeownersFile = { 179 | ...withGitIgnore, 180 | CODEOWNERS: '../__mocks__/CODEOWNERS_POPULATED_OUTPUT', 181 | }; 182 | existsSync.mockReturnValue(true); 183 | readFile.mockImplementation((file, callback): void => { 184 | const fullPath = path.join( 185 | __dirname, 186 | withPopulatedCodeownersFile[file as keyof typeof withPopulatedCodeownersFile] 187 | ); 188 | const content = readFileSync(fullPath); 189 | callback(null, content); 190 | }); 191 | 192 | await generateCommand( 193 | { 194 | output: 'CODEOWNERS', 195 | customRegenerationCommand: 'yarn codeowners-generator generate', 196 | preserveBlockPosition: true, 197 | }, 198 | { parent: {} } 199 | ); 200 | expect(writeFile.mock.calls[0]).toMatchInlineSnapshot(` 201 | [ 202 | "CODEOWNERS", 203 | "# We are already using CODEOWNERS and we don't want to lose the content of this file. 204 | scripts/ @myOrg/infraTeam 205 | # We might wanna keep an eye on something else, like yml files and workflows. 206 | .github/workflows/ @myOrg/infraTeam 207 | 208 | #################################### Generated content - do not edit! #################################### 209 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 210 | # To re-generate, run \`yarn codeowners-generator generate\`. Don't worry, the content outside this block will be kept. 211 | 212 | # Rule extracted from dir1/CODEOWNERS 213 | /dir1/**/*.ts @eeny @meeny 214 | # Rule extracted from dir1/CODEOWNERS 215 | /dir1/*.ts @miny 216 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 217 | /dir1/*.md 218 | # Rule extracted from dir1/CODEOWNERS 219 | /dir1/**/README.md @miny 220 | # Rule extracted from dir1/CODEOWNERS 221 | /dir1/README.md @moe 222 | # Rule extracted from dir2/CODEOWNERS 223 | /dir2/**/*.ts @moe 224 | # Rule extracted from dir2/CODEOWNERS 225 | /dir2/dir3/*.ts @miny 226 | # Rule extracted from dir2/CODEOWNERS 227 | /dir2/**/*.md @meeny 228 | # Rule extracted from dir2/CODEOWNERS 229 | /dir2/**/dir4/ @eeny 230 | # Rule extracted from dir2/dir3/CODEOWNERS 231 | /dir2/dir3/**/*.ts @miny 232 | 233 | #################################### Generated content - do not edit! #################################### 234 | 235 | # Another line here that should be moved to after the generated block without preserve-block-position option enabled 236 | dir2/ @otherTeam", 237 | ] 238 | `); 239 | }); 240 | it('should generate a CODEOWNERS FILE with groupSourceComments and customRegenerationCommand', async () => { 241 | sync.mockReturnValueOnce(Object.keys(files)); 242 | 243 | sync.mockReturnValueOnce(['.gitignore']); 244 | 245 | readFile.mockImplementation((file, callback) => { 246 | const content = readFileSync(path.join(__dirname, withGitIgnore[file as keyof typeof withGitIgnore])); 247 | callback(null, content); 248 | }); 249 | 250 | await generateCommand( 251 | { 252 | output: 'CODEOWNERS', 253 | groupSourceComments: true, 254 | customRegenerationCommand: 'npm run codeowners-generator generate', 255 | }, 256 | { parent: {} } 257 | ); 258 | expect(writeFile.mock.calls).toMatchInlineSnapshot(` 259 | [ 260 | [ 261 | "CODEOWNERS", 262 | "#################################### Generated content - do not edit! #################################### 263 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 264 | # To re-generate, run \`npm run codeowners-generator generate\`. Don't worry, the content outside this block will be kept. 265 | 266 | # Rules extracted from dir1/CODEOWNERS 267 | /dir1/**/*.ts @eeny @meeny 268 | /dir1/*.ts @miny 269 | /dir1/*.md 270 | /dir1/**/README.md @miny 271 | /dir1/README.md @moe 272 | # Rules extracted from dir2/CODEOWNERS 273 | /dir2/**/*.ts @moe 274 | /dir2/dir3/*.ts @miny 275 | /dir2/**/*.md @meeny 276 | /dir2/**/dir4/ @eeny 277 | # Rule extracted from dir2/dir3/CODEOWNERS 278 | /dir2/dir3/**/*.ts @miny 279 | 280 | #################################### Generated content - do not edit! ####################################", 281 | ], 282 | ] 283 | `); 284 | }); 285 | it('should generate a CODEOWNERS FILE', async () => { 286 | sync.mockReturnValueOnce(Object.keys(files)); 287 | 288 | sync.mockReturnValueOnce(['.gitignore']); 289 | 290 | readFile.mockImplementation((file, callback) => { 291 | const content = readFileSync(path.join(__dirname, withGitIgnore[file as keyof typeof withGitIgnore])); 292 | callback(null, content); 293 | }); 294 | 295 | await generateCommand({ output: 'CODEOWNERS' }, { parent: {} }); 296 | expect(writeFile.mock.calls).toMatchInlineSnapshot(` 297 | [ 298 | [ 299 | "CODEOWNERS", 300 | "#################################### Generated content - do not edit! #################################### 301 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 302 | # Don't worry, the content outside this block will be kept. 303 | 304 | # Rule extracted from dir1/CODEOWNERS 305 | /dir1/**/*.ts @eeny @meeny 306 | # Rule extracted from dir1/CODEOWNERS 307 | /dir1/*.ts @miny 308 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 309 | /dir1/*.md 310 | # Rule extracted from dir1/CODEOWNERS 311 | /dir1/**/README.md @miny 312 | # Rule extracted from dir1/CODEOWNERS 313 | /dir1/README.md @moe 314 | # Rule extracted from dir2/CODEOWNERS 315 | /dir2/**/*.ts @moe 316 | # Rule extracted from dir2/CODEOWNERS 317 | /dir2/dir3/*.ts @miny 318 | # Rule extracted from dir2/CODEOWNERS 319 | /dir2/**/*.md @meeny 320 | # Rule extracted from dir2/CODEOWNERS 321 | /dir2/**/dir4/ @eeny 322 | # Rule extracted from dir2/dir3/CODEOWNERS 323 | /dir2/dir3/**/*.ts @miny 324 | 325 | #################################### Generated content - do not edit! ####################################", 326 | ], 327 | ] 328 | `); 329 | }); 330 | 331 | it('should generate a CODEOWNERS FILE with package.contributors and package.author field and removing package.json when a CODEOWNERS file exist at the same level', async () => { 332 | search.mockImplementationOnce(() => 333 | Promise.resolve({ 334 | isEmpty: false, 335 | filepath: '/some/package.json', 336 | config: { 337 | output: 'CODEOWNERS', 338 | useMaintainers: true, 339 | }, 340 | }) 341 | ); 342 | 343 | const packageFiles = { 344 | ...files, 345 | 'dir1/package.json': '../__mocks__/package1.json', 346 | 'dir2/dir1/package.json': '../__mocks__/package2.json', 347 | 'dir6/package.json': '../__mocks__/package3.json', 348 | 'dir7/package.json': '../__mocks__/package4.json', 349 | 'dir8/package.json': '../__mocks__/package5.json', 350 | }; 351 | 352 | sync.mockReturnValueOnce(Object.keys(packageFiles)); 353 | 354 | sync.mockReturnValueOnce(['.gitignore']); 355 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 356 | readFile.mockImplementation((file, callback) => { 357 | const content = readFileSync( 358 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 359 | ); 360 | callback(null, content); 361 | }); 362 | 363 | await generateCommand({}, { parent: {} }); 364 | expect(search).toHaveBeenCalled(); 365 | 366 | expect(consoleWarnMock).toHaveBeenCalled(); 367 | expect(consoleWarnMock.mock.calls).toMatchInlineSnapshot(` 368 | [ 369 | [ 370 | "We will ignore the package.json dir1/package.json, given that we have encountered a CODEOWNERS file at the same dir level", 371 | ], 372 | ] 373 | `); 374 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 375 | "#################################### Generated content - do not edit! #################################### 376 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 377 | # Don't worry, the content outside this block will be kept. 378 | 379 | # Rule extracted from dir2/dir1/package.json 380 | /dir2/dir1/ friend@example.com other@example.com 381 | # Rule extracted from dir8/package.json 382 | /dir8/ gbuilder@builder.com other.friend@domain.com 383 | # Rule extracted from dir1/CODEOWNERS 384 | /dir1/**/*.ts @eeny @meeny 385 | # Rule extracted from dir1/CODEOWNERS 386 | /dir1/*.ts @miny 387 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 388 | /dir1/*.md 389 | # Rule extracted from dir1/CODEOWNERS 390 | /dir1/**/README.md @miny 391 | # Rule extracted from dir1/CODEOWNERS 392 | /dir1/README.md @moe 393 | # Rule extracted from dir2/CODEOWNERS 394 | /dir2/**/*.ts @moe 395 | # Rule extracted from dir2/CODEOWNERS 396 | /dir2/dir3/*.ts @miny 397 | # Rule extracted from dir2/CODEOWNERS 398 | /dir2/**/*.md @meeny 399 | # Rule extracted from dir2/CODEOWNERS 400 | /dir2/**/dir4/ @eeny 401 | # Rule extracted from dir2/dir3/CODEOWNERS 402 | /dir2/dir3/**/*.ts @miny 403 | 404 | #################################### Generated content - do not edit! ####################################" 405 | `); 406 | }); 407 | it('should generate a CODEOWNERS FILE with package.contributors and package.author field using cosmiconfig', async () => { 408 | search.mockImplementationOnce(() => 409 | Promise.resolve({ 410 | isEmpty: false, 411 | filepath: '/some/package.json', 412 | config: { 413 | output: 'CODEOWNERS', 414 | useMaintainers: true, 415 | }, 416 | }) 417 | ); 418 | 419 | const packageFiles = { 420 | ...files, 421 | 'dir5/package.json': '../__mocks__/package1.json', 422 | 'dir2/dir1/package.json': '../__mocks__/package2.json', 423 | 'dir6/package.json': '../__mocks__/package3.json', 424 | 'dir7/package.json': '../__mocks__/package4.json', 425 | 'dir8/package.json': '../__mocks__/package5.json', 426 | }; 427 | 428 | sync.mockReturnValueOnce(Object.keys(packageFiles)); 429 | 430 | sync.mockReturnValueOnce(['.gitignore']); 431 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 432 | readFile.mockImplementation((file, callback) => { 433 | const content = readFileSync( 434 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 435 | ); 436 | callback(null, content); 437 | }); 438 | 439 | await generateCommand({}, { parent: {} }); 440 | expect(search).toHaveBeenCalled(); 441 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 442 | "#################################### Generated content - do not edit! #################################### 443 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 444 | # Don't worry, the content outside this block will be kept. 445 | 446 | # Rule extracted from dir5/package.json 447 | /dir5/ friend@example.com other@example.com 448 | # Rule extracted from dir2/dir1/package.json 449 | /dir2/dir1/ friend@example.com other@example.com 450 | # Rule extracted from dir8/package.json 451 | /dir8/ gbuilder@builder.com other.friend@domain.com 452 | # Rule extracted from dir1/CODEOWNERS 453 | /dir1/**/*.ts @eeny @meeny 454 | # Rule extracted from dir1/CODEOWNERS 455 | /dir1/*.ts @miny 456 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 457 | /dir1/*.md 458 | # Rule extracted from dir1/CODEOWNERS 459 | /dir1/**/README.md @miny 460 | # Rule extracted from dir1/CODEOWNERS 461 | /dir1/README.md @moe 462 | # Rule extracted from dir2/CODEOWNERS 463 | /dir2/**/*.ts @moe 464 | # Rule extracted from dir2/CODEOWNERS 465 | /dir2/dir3/*.ts @miny 466 | # Rule extracted from dir2/CODEOWNERS 467 | /dir2/**/*.md @meeny 468 | # Rule extracted from dir2/CODEOWNERS 469 | /dir2/**/dir4/ @eeny 470 | # Rule extracted from dir2/dir3/CODEOWNERS 471 | /dir2/dir3/**/*.ts @miny 472 | 473 | #################################### Generated content - do not edit! ####################################" 474 | `); 475 | }); 476 | it('should generate a CODEOWNERS FILE with package.maintainers field and groupSourceComments using cosmiconfig', async () => { 477 | search.mockImplementationOnce(() => 478 | Promise.resolve({ 479 | isEmpty: false, 480 | filepath: '/some/package.json', 481 | config: { 482 | output: '.github/CODEOWNERS', 483 | useMaintainers: true, 484 | groupSourceComments: true, 485 | includes: ['dir1/*', 'dir2/*', 'dir5/*', 'dir6/*', 'dir7/*'], 486 | }, 487 | }) 488 | ); 489 | 490 | const packageFiles = { 491 | ...files, 492 | 'dir5/package.json': '../__mocks__/package1.json', 493 | 'dir2/dir1/package.json': '../__mocks__/package2.json', 494 | 'dir6/package.json': '../__mocks__/package3.json', 495 | 'dir7/package.json': '../__mocks__/package4.json', 496 | }; 497 | 498 | sync.mockReturnValueOnce(Object.keys(packageFiles)); 499 | 500 | sync.mockReturnValueOnce(['.gitignore']); 501 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 502 | readFile.mockImplementation((file, callback) => { 503 | const content = readFileSync( 504 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 505 | ); 506 | callback(null, content); 507 | }); 508 | 509 | await generateCommand({}, { parent: {} }); 510 | expect(search).toHaveBeenCalled(); 511 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 512 | "#################################### Generated content - do not edit! #################################### 513 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 514 | # Don't worry, the content outside this block will be kept. 515 | 516 | # Rule extracted from dir5/package.json 517 | /dir5/ friend@example.com other@example.com 518 | # Rule extracted from dir2/dir1/package.json 519 | /dir2/dir1/ friend@example.com other@example.com 520 | # Rules extracted from dir1/CODEOWNERS 521 | /dir1/**/*.ts @eeny @meeny 522 | /dir1/*.ts @miny 523 | /dir1/*.md 524 | /dir1/**/README.md @miny 525 | /dir1/README.md @moe 526 | # Rules extracted from dir2/CODEOWNERS 527 | /dir2/**/*.ts @moe 528 | /dir2/dir3/*.ts @miny 529 | /dir2/**/*.md @meeny 530 | /dir2/**/dir4/ @eeny 531 | # Rule extracted from dir2/dir3/CODEOWNERS 532 | /dir2/dir3/**/*.ts @miny 533 | 534 | #################################### Generated content - do not edit! ####################################" 535 | `); 536 | }); 537 | it('should generate a CODEOWNERS FILE with package.maintainers field and customRegenerationCommand using cosmiconfig', async () => { 538 | search.mockImplementationOnce(() => 539 | Promise.resolve({ 540 | isEmpty: false, 541 | filepath: '/some/package.json', 542 | config: { 543 | output: '.github/CODEOWNERS', 544 | useMaintainers: true, 545 | groupSourceComments: true, 546 | includes: ['dir1/*', 'dir2/*', 'dir5/*', 'dir6/*', 'dir7/*'], 547 | }, 548 | }) 549 | ); 550 | 551 | const packageFiles = { 552 | ...files, 553 | 'dir5/package.json': '../__mocks__/package1.json', 554 | 'dir2/dir1/package.json': '../__mocks__/package2.json', 555 | 'dir6/package.json': '../__mocks__/package3.json', 556 | 'dir7/package.json': '../__mocks__/package4.json', 557 | }; 558 | 559 | sync.mockReturnValueOnce(Object.keys(packageFiles)); 560 | 561 | sync.mockReturnValueOnce(['.gitignore']); 562 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 563 | readFile.mockImplementation((file, callback) => { 564 | const content = readFileSync( 565 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 566 | ); 567 | callback(null, content); 568 | }); 569 | 570 | await generateCommand({ customRegenerationCommand: 'npx codeowners-generator generate' }, { parent: {} }); 571 | expect(search).toHaveBeenCalled(); 572 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 573 | "#################################### Generated content - do not edit! #################################### 574 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 575 | # To re-generate, run \`npx codeowners-generator generate\`. Don't worry, the content outside this block will be kept. 576 | 577 | # Rule extracted from dir5/package.json 578 | /dir5/ friend@example.com other@example.com 579 | # Rule extracted from dir2/dir1/package.json 580 | /dir2/dir1/ friend@example.com other@example.com 581 | # Rules extracted from dir1/CODEOWNERS 582 | /dir1/**/*.ts @eeny @meeny 583 | /dir1/*.ts @miny 584 | /dir1/*.md 585 | /dir1/**/README.md @miny 586 | /dir1/README.md @moe 587 | # Rules extracted from dir2/CODEOWNERS 588 | /dir2/**/*.ts @moe 589 | /dir2/dir3/*.ts @miny 590 | /dir2/**/*.md @meeny 591 | /dir2/**/dir4/ @eeny 592 | # Rule extracted from dir2/dir3/CODEOWNERS 593 | /dir2/dir3/**/*.ts @miny 594 | 595 | #################################### Generated content - do not edit! ####################################" 596 | `); 597 | }); 598 | it('should generate a CODEOWNERS FILE with package.maintainers field', async () => { 599 | const packageFiles = { 600 | ...files, 601 | 'dir5/package.json': '../__mocks__/package1.json', 602 | 'dir2/dir1/package.json': '../__mocks__/package2.json', 603 | 'dir6/package.json': '../__mocks__/package3.json', 604 | 'dir7/package.json': '../__mocks__/package4.json', 605 | }; 606 | 607 | sync.mockReturnValueOnce([ 608 | ...Object.keys(packageFiles), 609 | 'dir5/package.json', 610 | 'dir2/dir1/package.json', 611 | 'dir6/package.json', 612 | 'dir7/package.json', 613 | ]); 614 | 615 | sync.mockReturnValueOnce(['.gitignore']); 616 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 617 | readFile.mockImplementation((file, callback) => { 618 | const content = readFileSync( 619 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 620 | ); 621 | callback(null, content); 622 | }); 623 | 624 | await generateCommand({ output: 'CODEOWNERS', useMaintainers: true }, { parent: {} }); 625 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 626 | "#################################### Generated content - do not edit! #################################### 627 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 628 | # Don't worry, the content outside this block will be kept. 629 | 630 | # Rule extracted from dir5/package.json 631 | /dir5/ friend@example.com other@example.com 632 | # Rule extracted from dir2/dir1/package.json 633 | /dir2/dir1/ friend@example.com other@example.com 634 | # Rule extracted from dir5/package.json 635 | /dir5/ friend@example.com other@example.com 636 | # Rule extracted from dir2/dir1/package.json 637 | /dir2/dir1/ friend@example.com other@example.com 638 | # Rule extracted from dir1/CODEOWNERS 639 | /dir1/**/*.ts @eeny @meeny 640 | # Rule extracted from dir1/CODEOWNERS 641 | /dir1/*.ts @miny 642 | # Rule extracted from dir1/CODEOWNERS (containing path exclusions) 643 | /dir1/*.md 644 | # Rule extracted from dir1/CODEOWNERS 645 | /dir1/**/README.md @miny 646 | # Rule extracted from dir1/CODEOWNERS 647 | /dir1/README.md @moe 648 | # Rule extracted from dir2/CODEOWNERS 649 | /dir2/**/*.ts @moe 650 | # Rule extracted from dir2/CODEOWNERS 651 | /dir2/dir3/*.ts @miny 652 | # Rule extracted from dir2/CODEOWNERS 653 | /dir2/**/*.md @meeny 654 | # Rule extracted from dir2/CODEOWNERS 655 | /dir2/**/dir4/ @eeny 656 | # Rule extracted from dir2/dir3/CODEOWNERS 657 | /dir2/dir3/**/*.ts @miny 658 | 659 | #################################### Generated content - do not edit! ####################################" 660 | `); 661 | }); 662 | it('should generate a CODEOWNERS FILE with package.maintainers field for root package.json', async () => { 663 | const packageFiles = { 664 | 'package.json': '../__mocks__/package1.json', 665 | 'dir2/package.json': '../__mocks__/package2.json', 666 | }; 667 | 668 | sync.mockReturnValueOnce(['package.json', 'dir2/package.json']); 669 | 670 | sync.mockReturnValueOnce(['.gitignore']); 671 | const withAddedPackageFiles = { ...packageFiles, ...withGitIgnore }; 672 | readFile.mockImplementation((file, callback) => { 673 | const content = readFileSync( 674 | path.join(__dirname, withAddedPackageFiles[file as keyof typeof withAddedPackageFiles]) 675 | ); 676 | callback(null, content); 677 | }); 678 | 679 | await generateCommand({ output: 'CODEOWNERS', useMaintainers: true, useRootMaintainers: true }, { parent: {} }); 680 | expect(writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` 681 | "#################################### Generated content - do not edit! #################################### 682 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 683 | # Don't worry, the content outside this block will be kept. 684 | 685 | # Rule extracted from package.json 686 | * friend@example.com other@example.com 687 | # Rule extracted from dir2/package.json 688 | /dir2/ friend@example.com other@example.com 689 | 690 | #################################### Generated content - do not edit! ####################################" 691 | `); 692 | }); 693 | it('should return rules', async () => { 694 | sync.mockReturnValueOnce(Object.keys(files)); 695 | sync.mockReturnValueOnce(['.gitignore']); 696 | 697 | const filesWithIgnore = { 698 | ...files, 699 | ...withGitIgnore, 700 | }; 701 | readFile.mockImplementation((file, callback) => { 702 | const content = readFileSync(path.join(__dirname, filesWithIgnore[file as keyof typeof filesWithIgnore])); 703 | callback(null, content); 704 | }); 705 | 706 | const response = await generate({ rootDir: __dirname }); 707 | expect(response).toMatchInlineSnapshot(` 708 | [ 709 | { 710 | "filePath": "dir1/CODEOWNERS", 711 | "glob": "/dir1/**/*.ts", 712 | "owners": [ 713 | "@eeny", 714 | "@meeny", 715 | ], 716 | }, 717 | { 718 | "filePath": "dir1/CODEOWNERS", 719 | "glob": "/dir1/*.ts", 720 | "owners": [ 721 | "@miny", 722 | ], 723 | }, 724 | { 725 | "filePath": "dir1/CODEOWNERS", 726 | "glob": "/dir1/*.md", 727 | "owners": [], 728 | }, 729 | { 730 | "filePath": "dir1/CODEOWNERS", 731 | "glob": "/dir1/**/README.md", 732 | "owners": [ 733 | "@miny", 734 | ], 735 | }, 736 | { 737 | "filePath": "dir1/CODEOWNERS", 738 | "glob": "/dir1/README.md", 739 | "owners": [ 740 | "@moe", 741 | ], 742 | }, 743 | { 744 | "filePath": "dir2/CODEOWNERS", 745 | "glob": "/dir2/**/*.ts", 746 | "owners": [ 747 | "@moe", 748 | ], 749 | }, 750 | { 751 | "filePath": "dir2/CODEOWNERS", 752 | "glob": "/dir2/dir3/*.ts", 753 | "owners": [ 754 | "@miny", 755 | ], 756 | }, 757 | { 758 | "filePath": "dir2/CODEOWNERS", 759 | "glob": "/dir2/**/*.md", 760 | "owners": [ 761 | "@meeny", 762 | "", 763 | ], 764 | }, 765 | { 766 | "filePath": "dir2/CODEOWNERS", 767 | "glob": "/dir2/**/dir4/", 768 | "owners": [ 769 | "@eeny", 770 | ], 771 | }, 772 | { 773 | "filePath": "dir2/dir3/CODEOWNERS", 774 | "glob": "/dir2/dir3/**/*.ts", 775 | "owners": [ 776 | "@miny", 777 | ], 778 | }, 779 | ] 780 | `); 781 | }); 782 | 783 | it('should not find any rules', async () => { 784 | sync.mockReturnValue([]); 785 | await generateCommand( 786 | {}, 787 | { 788 | parent: { 789 | includes: ['**/NOT_STANDARD_CODEOWNERS'], 790 | }, 791 | } 792 | ); 793 | expect(stopAndPersist.mock.calls).toMatchInlineSnapshot(` 794 | [ 795 | [ 796 | { 797 | "symbol": "💫", 798 | "text": "No custom configuration found", 799 | }, 800 | ], 801 | [ 802 | { 803 | "symbol": "¯\\_(ツ)_/¯", 804 | "text": "We couldn't find any codeowners under **/NOT_STANDARD_CODEOWNERS", 805 | }, 806 | ], 807 | ] 808 | `); 809 | }); 810 | 811 | it('should show customConfiguration is malformed', async () => { 812 | sync.mockReturnValueOnce(Object.keys(files)); 813 | sync.mockReturnValueOnce(['.gitignore']); 814 | 815 | const filesWithIgnore = { 816 | ...files, 817 | ...withGitIgnore, 818 | }; 819 | readFile.mockImplementation((file, callback) => { 820 | const content = readFileSync(path.join(__dirname, filesWithIgnore[file as keyof typeof filesWithIgnore])); 821 | callback(null, content); 822 | }); 823 | search.mockImplementationOnce(() => Promise.reject(new Error('some malformed configuration'))); 824 | 825 | await generateCommand({}, { parent: {} }); 826 | expect(fail.mock.calls).toMatchInlineSnapshot(` 827 | [ 828 | [ 829 | "We found an error looking for custom configuration, we will use cli options if provided", 830 | ], 831 | ] 832 | `); 833 | }); 834 | 835 | it('should fail trying to read .gitignore', async () => { 836 | sync.mockReturnValueOnce(Object.keys(files)); 837 | sync.mockReturnValueOnce(['.gitignore']); 838 | 839 | readFile.mockImplementation((file, callback) => { 840 | const content = readFileSync(path.join(__dirname, files[file as keyof typeof files])); 841 | callback(null, content); 842 | }); 843 | 844 | await generateCommand({}, { parent: {} }); 845 | expect(writeFile).toHaveBeenCalled(); 846 | }); 847 | 848 | it('should call sync with dot flag when hidden flag is set', async () => { 849 | await generateCommand( 850 | { 851 | hiddenDirectories: true, 852 | }, 853 | { parent: {} } 854 | ); 855 | 856 | expect(sync.mock.calls[0][1]?.dot).toBeTruthy; 857 | }); 858 | 859 | it('should call sync without dot flag when hidden flag is not set', async () => { 860 | await generateCommand( 861 | { 862 | hiddenDirectories: false, 863 | }, 864 | { parent: {} } 865 | ); 866 | 867 | expect(sync.mock.calls[0][1]?.dot).toBeFalsy; 868 | }); 869 | }); 870 | -------------------------------------------------------------------------------- /__tests__/helpers.ts: -------------------------------------------------------------------------------- 1 | const ORIGINALS = { 2 | log: global.console.log, 3 | error: global.console.error, 4 | info: global.console.info, 5 | warn: global.console.warn, 6 | }; 7 | 8 | type METHODS = 'log' | 'error' | 'info' | 'warn'; 9 | export const mockConsole = (method: METHODS): jest.Mock => { 10 | const handler = jest.fn(); 11 | global.console[method] = handler; 12 | return handler; 13 | }; 14 | export const unMockConsole = (method: METHODS): void => { 15 | global.console[method] = ORIGINALS[method]; 16 | }; 17 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'codeowners-generator' 2 | description: 'CODEOWNERS generator for mono-repos. This action will run codeowners-generator on your project and apply changes' 3 | inputs: 4 | use-maintainers: 5 | description: 'For every package.json found, generate a CODEOWNERS entry using the maintainers field' 6 | required: false 7 | default: 'false' 8 | group-source-comments: 9 | description: 'Instead of generating one comment per rule, enabling this flag will group them, reducing comments to one per source file. Useful if your codeowners file gets too noisy' 10 | required: false 11 | default: 'false' 12 | custom-regeneration-command: 13 | description: 'Specify a custom regeneration command to be printed in the generated CODEOWNERS file, it should be mapped to run codeowners-generator' 14 | required: false 15 | default: 'false' 16 | check: 17 | description: It will fail if the CODEOWNERS generated does not match the current (or missing) CODEOWNERS. Useful for validating that the CODEOWNERS file is up to date during CI.' 18 | required: false 19 | default: 'false' 20 | output: 21 | description: 'The output path and name of the file, (default: CODEOWNERS)' 22 | required: false 23 | default: 'CODEOWNERS' 24 | preserve-block-position: 25 | description: Keep the generated block in the same position it was found in the CODEOWNERS file (if present). Useful for when you make manual additions. 26 | required: false 27 | default: 'false' 28 | includes: 29 | description: The glob used to find CODEOWNERS files in the repo. Defaults to the value in src/utils/constants.ts 30 | required: false 31 | default: '' 32 | version: 33 | description: codeowners-generator version. It will default to the latest in npm otherwise' 34 | required: false 35 | hidden-directories: 36 | description: Also include searching in hidden (dot) directories. 37 | required: false 38 | default: 'false' 39 | 40 | runs: 41 | using: 'composite' 42 | steps: 43 | - id: get-input 44 | shell: bash 45 | run: | 46 | ARGS_INPUT=("--output ${{inputs.output}}") 47 | VERSION='latest' 48 | 49 | if [ "${{inputs.use-maintainers}}" = "true" ]; then 50 | ARGS_INPUT+=("--use-maintainers") 51 | fi 52 | 53 | if [ "${{inputs.use-maintainers}}" = "true" ]; then 54 | ARGS_INPUT+=("--use-root-maintainers") 55 | fi 56 | 57 | if [ "${{inputs.group-source-comments}}" = "true" ]; then 58 | ARGS_INPUT+=("--group-source-comments") 59 | fi 60 | 61 | if [ "${{inputs.custom-regeneration-command}}" = "true" ]; then 62 | ARGS_INPUT+=("--custom-regeneration-command") 63 | fi 64 | 65 | if [ "${{inputs.check}}" = "true" ]; then 66 | ARGS_INPUT+=("--check") 67 | fi 68 | 69 | if [ "${{inputs.preserve-block-position}}" = "true" ]; then 70 | ARGS_INPUT+=("--preserve-block-position") 71 | fi 72 | 73 | if [ "${{inputs.includes}}" ]; then 74 | ARGS_INPUT+=("--includes ${{inputs.includes}}") 75 | fi 76 | 77 | if [ "${{inputs.hidden-directories}}" ]; then 78 | ARGS_INPUT+=("--hidden-directories") 79 | fi 80 | 81 | if [ ! -z "${{inputs.version}}" ]; then 82 | VERSION="${{inputs.version}}" 83 | fi 84 | 85 | echo "Arguments we will use: ${ARGS_INPUT[@]}" 86 | 87 | echo "version=$VERSION" >> $GITHUB_OUTPUT 88 | echo "args-input=${ARGS_INPUT[@]}" >> $GITHUB_OUTPUT 89 | 90 | - shell: bash 91 | run: | 92 | npx codeowners-generator@${{steps.get-input.outputs.version}} generate ${{steps.get-input.outputs.args-input}} 93 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gagoar/codeowners-generator/653b87f7008e7ee43e5bc86480bce7a47a570924/images/logo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Documentation | codeowners-generator 6 | 7 | 11 | 12 | 13 | 14 | 15 |
16 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src'; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | process.env.TZ = 'UTC'; 2 | 3 | module.exports = { 4 | verbose: true, 5 | transform: { 6 | '^.+\\.(t|j)sx?$': '@swc/jest', 7 | }, 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | testEnvironmentOptions: { 10 | url: 'http://localhost', 11 | }, 12 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$'], 13 | testMatch: ['**/*.(spec|test).{ts,tsx}'], 14 | collectCoverage: true, 15 | collectCoverageFrom: ['config/**/*.{ts,tsx}', 'src/**/*.{ts,tsx}', '!src/utils/guards.ts', '!src/bin/cli.ts'], 16 | coverageDirectory: './coverage/', 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeowners-generator", 3 | "repository": "git@github.com:gagoar/codeowners-generator.git", 4 | "license": "MIT", 5 | "version": "2.5.0", 6 | "description": "CODEOWNERS generator for mono-repos", 7 | "files": [ 8 | "build", 9 | "dist" 10 | ], 11 | "main": "build/index.js", 12 | "bin": { 13 | "codeowners-generator": "dist/cli.js" 14 | }, 15 | "scripts": { 16 | "test": "jest", 17 | "build-cli": "esbuild --bundle src/bin/cli.ts --platform=node --target=node12 --main-fields=main --outdir=dist/", 18 | "build": "esbuild --bundle index.ts --platform=node --target=node12 --main-fields=main --outdir=build/", 19 | "lint": "eslint src/* --ext .ts", 20 | "execute": "tsx src/bin/cli.ts", 21 | "prepare": "husky install", 22 | "prettier": "prettier --ignore-path .gitignore --check ." 23 | }, 24 | "engines": { 25 | "node": ">10.0.0" 26 | }, 27 | "keywords": [ 28 | "cli", 29 | "CODEOWNERS", 30 | "monorepo" 31 | ], 32 | "lint-staged": { 33 | "*": [ 34 | "prettier --ignore-unknown --write" 35 | ], 36 | "*.ts": [ 37 | "eslint --fix", 38 | "bash -c \"npm run build\"" 39 | ] 40 | }, 41 | "prettier": { 42 | "singleQuote": true, 43 | "semi": true, 44 | "printWidth": 120 45 | }, 46 | "eslintConfig": { 47 | "extends": [ 48 | "plugin:@typescript-eslint/recommended", 49 | "prettier" 50 | ], 51 | "parser": "@typescript-eslint/parser", 52 | "parserOptions": { 53 | "project": "./tsconfig.eslint.json" 54 | }, 55 | "rules": { 56 | "no-debugger": "error", 57 | "no-process-env": "off", 58 | "import/prefer-default-export": "off", 59 | "@typescript-eslint/explicit-function-return-type": "off", 60 | "@typescript-eslint/no-unused-vars": [ 61 | "error", 62 | { 63 | "vars": "all", 64 | "args": "after-used", 65 | "ignoreRestSiblings": true 66 | } 67 | ], 68 | "new-cap": [ 69 | "error", 70 | { 71 | "capIsNewExceptions": [ 72 | "Injectable", 73 | "Inject" 74 | ] 75 | } 76 | ], 77 | "prefer-destructuring": [ 78 | "error", 79 | { 80 | "VariableDeclarator": { 81 | "array": false, 82 | "object": true 83 | }, 84 | "AssignmentExpression": { 85 | "array": true, 86 | "object": false 87 | } 88 | }, 89 | { 90 | "enforceForRenamedProperties": false 91 | } 92 | ] 93 | } 94 | }, 95 | "author": "Gago ", 96 | "dependencies": { 97 | "commander": "7.2.0", 98 | "common-tags": "1.8.2", 99 | "cosmiconfig": "7.1.0", 100 | "debug": "4.3.4", 101 | "fast-glob": "3.2.12", 102 | "ignore": "5.2.4", 103 | "is-glob": "4.0.3", 104 | "lodash.groupby": "4.6.0", 105 | "ora": "5.4.1" 106 | }, 107 | "devDependencies": { 108 | "@swc/core": "^1.3.41", 109 | "@swc/jest": "^0.2.24", 110 | "@types/common-tags": "1.8.4", 111 | "@types/debug": "4.1.12", 112 | "@types/is-glob": "4.0.2", 113 | "@types/jest": "29.5.8", 114 | "@types/lodash.groupby": "4.6.7", 115 | "@types/node": "16.18.61", 116 | "@typescript-eslint/eslint-plugin": "6.10.0", 117 | "@typescript-eslint/parser": "6.10.0", 118 | "esbuild": "0.14.53", 119 | "eslint": "8.53.0", 120 | "eslint-config-prettier": "9.0.0", 121 | "husky": "8.0.3", 122 | "jest": "^29.0.0", 123 | "jest-mock-process": "^2.0.0", 124 | "lint-staged": "13.0.3", 125 | "prettier": "2.7.1", 126 | "tslib": "2.4.0", 127 | "tsx": "^3.12.5", 128 | "typescript": "4.7.4" 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", ":semanticCommits", "schedule:monthly"], 4 | "packageRules": [ 5 | { 6 | "description": "Require dashboard approval for major updates", 7 | "matchUpdateTypes": ["major"], 8 | "dependencyDashboardApproval": true 9 | }, 10 | { 11 | "description": "Update codeowners-generator references in Markdown files", 12 | "matchPackageNames": ["codeowners-generator"], 13 | "matchPaths": [".md"], 14 | "commitMessageTopic": "references to {{{depName}}}", 15 | "additionalBranchPrefix": "docs-", 16 | "semanticCommitType": "docs" 17 | }, 18 | { 19 | "description": "Use ci commit type for Github Actions", 20 | "matchManagers": ["github-actions"], 21 | "semanticCommitType": "ci" 22 | } 23 | ], 24 | "regexManagers": [ 25 | { 26 | "description": "Update codeowners-generator references in Markdown files", 27 | "fileMatch": ["\\.md$"], 28 | "matchStrings": ["\"(?codeowners-generator)\": \"(?.*?)\""], 29 | "datasourceTemplate": "npm" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/bin/cli.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import program from 'commander'; 4 | import { generateCommand } from '../commands'; 5 | import packageJSON from '../../package.json'; 6 | import { INCLUDES } from '../utils/constants'; 7 | 8 | program 9 | .name(packageJSON.name) 10 | .version(packageJSON.version) 11 | .description(packageJSON.description) 12 | .option('--includes [glob patterns...]', `The patterns that match CODEOWNERS files, by default ${INCLUDES}`); 13 | 14 | program 15 | .command('generate') 16 | .description('Generates a topLevel file containing the paths of all the nested CODEOWNERS') 17 | .option( 18 | '--use-maintainers', 19 | 'For every package.json found, generate a CODEOWNERS entry using the maintainers field', 20 | false 21 | ) 22 | .option( 23 | '--use-root-maintainers', 24 | 'It will use `maintainers` field from the package.json in the root to generate default codeowners. Works only in conjunction with `useMaintainers`', 25 | false 26 | ) 27 | .option( 28 | '--group-source-comments', 29 | 'Instead of generating one comment per rule, enabling this flag will group them, reducing comments to one per source file. Useful if your codeowners file gets too noisy', 30 | false 31 | ) 32 | .option( 33 | '--preserve-block-position', 34 | 'It will keep the generated block in the same position it was found in the CODEOWNERS file (if present). Useful for when you make manual additions', 35 | false 36 | ) 37 | .option( 38 | '--custom-regeneration-command', 39 | 'Specify a custom regeneration command to be printed in the generated CODEOWNERS file, it should be mapped to run codeowners-generator' 40 | ) 41 | .option('--output [output file]', 'The output path and name of the file, (default: CODEOWNERS)') 42 | .option( 43 | '--check', 44 | 'It will fail if the CODEOWNERS generated does not match the current (or missing) CODEOWNERS. Useful for validating that the CODEOWNERS file is up to date date during CI' 45 | ) 46 | .option('--hidden-directories', 'Includes hidden directories when searching for CODEOWNERS files', false) 47 | .action(generateCommand); 48 | 49 | program.parse(process.argv); 50 | -------------------------------------------------------------------------------- /src/commands/generate.ts: -------------------------------------------------------------------------------- 1 | import ora from 'ora'; 2 | import { posix as path } from 'path'; 3 | import fs from 'fs'; 4 | import { sync } from 'fast-glob'; 5 | import { Command, GlobalOptions, getGlobalOptions } from '../utils/getGlobalOptions'; 6 | import { 7 | OUTPUT, 8 | INCLUDES, 9 | SUCCESS_SYMBOL, 10 | SHRUG_SYMBOL, 11 | PACKAGE_JSON_PATTERN, 12 | IGNORE_ROOT_PACKAGE_JSON_PATTERN, 13 | } from '../utils/constants'; 14 | import { ownerRule, loadCodeOwnerFiles, loadOwnersFromPackage, generateOwnersFile } from '../utils/codeowners'; 15 | import { logger } from '../utils/debug'; 16 | import groupBy from 'lodash.groupby'; 17 | import ignore from 'ignore'; 18 | import { getPatternsFromIgnoreFiles } from '../utils/getPatternsFromIgnoreFiles'; 19 | 20 | const debug = logger('generate'); 21 | type Generate = (options: GenerateInput) => Promise; 22 | type GenerateInput = { 23 | rootDir: string; 24 | useMaintainers?: boolean; 25 | useRootMaintainers?: boolean; 26 | includes?: string[]; 27 | hiddenDirectories: boolean; 28 | }; 29 | 30 | const { basename, dirname } = path; 31 | 32 | export const generate: Generate = async ({ 33 | rootDir, 34 | includes, 35 | useMaintainers = false, 36 | useRootMaintainers = false, 37 | hiddenDirectories = false, 38 | }) => { 39 | debug('input:', rootDir, includes, useMaintainers, useRootMaintainers); 40 | 41 | const includePatterns = includes && includes.length ? includes : INCLUDES; 42 | 43 | debug('includePatterns:', includePatterns); 44 | 45 | const globs = [...includePatterns]; 46 | 47 | if (useMaintainers) { 48 | globs.push(...PACKAGE_JSON_PATTERN); 49 | 50 | if (!useRootMaintainers) { 51 | globs.push(...IGNORE_ROOT_PACKAGE_JSON_PATTERN); 52 | } 53 | } 54 | 55 | debug('provided globs:', globs); 56 | 57 | const matches = sync(globs, { 58 | dot: hiddenDirectories, 59 | onlyFiles: true, 60 | }); 61 | 62 | debug('files found:', matches); 63 | 64 | const ig = ignore().add(await getPatternsFromIgnoreFiles()); 65 | 66 | let files = matches.filter(ig.createFilter()); 67 | 68 | debug('matches after filtering ignore patterns:', files); 69 | 70 | let codeOwners = [] as ownerRule[]; 71 | if (matches.length) { 72 | if (useMaintainers) { 73 | const groups = groupBy(files, (filePath) => (basename(filePath) === 'package.json' ? 'json' : 'txt')) as Partial< 74 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 75 | Record<'json' | 'txt', any[]> 76 | >; 77 | 78 | if (groups.json?.length) { 79 | const codeownersDirNames = new Set(groups.txt?.map((path: string) => dirname(path))); 80 | const filteredJSONs: string[] = groups.json.filter((packageJsonFile: string) => { 81 | if (codeownersDirNames.has(dirname(packageJsonFile))) { 82 | console.warn( 83 | `We will ignore the package.json ${packageJsonFile}, given that we have encountered a CODEOWNERS file at the same dir level` 84 | ); 85 | return false; 86 | } 87 | return true; 88 | }); 89 | 90 | codeOwners = [...codeOwners, ...(await loadOwnersFromPackage(rootDir, filteredJSONs))]; 91 | } 92 | 93 | files = groups.txt ?? []; 94 | } 95 | 96 | if (files.length) { 97 | codeOwners = [...codeOwners, ...(await loadCodeOwnerFiles(rootDir, files))]; 98 | } 99 | 100 | // TODO: use Intl.Collator to naturally sort the file paths. https://stackoverflow.com/questions/57257395/how-to-get-a-sorted-file-path-list-using-node-js 101 | return codeOwners; 102 | } else { 103 | return []; 104 | } 105 | }; 106 | 107 | interface Options extends GlobalOptions { 108 | check?: boolean; 109 | hiddenDirectories?: boolean; 110 | } 111 | 112 | export const command = async (options: Options, command: Command): Promise => { 113 | const globalOptions = await getGlobalOptions(command); 114 | 115 | const { check } = options; 116 | 117 | const output = options.output || globalOptions.output || OUTPUT; 118 | 119 | const loader = ora('generating codeowners...').start(); 120 | 121 | const useMaintainers = globalOptions.useMaintainers || options.useMaintainers; 122 | const useRootMaintainers = globalOptions.useRootMaintainers || options.useRootMaintainers; 123 | const groupSourceComments = globalOptions.groupSourceComments || options.groupSourceComments; 124 | const preserveBlockPosition = globalOptions.preserveBlockPosition || options.preserveBlockPosition; 125 | const customRegenerationCommand = globalOptions.customRegenerationCommand || options.customRegenerationCommand; 126 | const { hiddenDirectories } = options; 127 | 128 | debug('Options:', { 129 | ...globalOptions, 130 | output, 131 | useMaintainers, 132 | useRootMaintainers, 133 | groupSourceComments, 134 | preserveBlockPosition, 135 | customRegenerationCommand, 136 | hiddenDirectories, 137 | }); 138 | 139 | try { 140 | const ownerRules = await generate({ 141 | rootDir: __dirname, 142 | useMaintainers, 143 | useRootMaintainers, 144 | hiddenDirectories, 145 | ...globalOptions, 146 | }); 147 | 148 | if (ownerRules.length) { 149 | const [originalContent, newContent] = await generateOwnersFile( 150 | output, 151 | ownerRules, 152 | groupSourceComments, 153 | preserveBlockPosition, 154 | customRegenerationCommand 155 | ); 156 | 157 | if (check) { 158 | if (originalContent.trimEnd() !== newContent) { 159 | throw new Error( 160 | 'We found differences between the existing codeowners file and the generated rules. Remove --check option to fix this.' 161 | ); 162 | } 163 | } else { 164 | fs.writeFileSync(output, newContent); 165 | } 166 | const message = check ? `up to date` : 'updated with the generated rules'; 167 | loader.stopAndPersist({ text: `CODEOWNERS file ${message}! location: ${output}`, symbol: SUCCESS_SYMBOL }); 168 | } else { 169 | const includes = globalOptions.includes?.length ? globalOptions.includes : INCLUDES; 170 | loader.stopAndPersist({ 171 | text: `We couldn't find any codeowners under ${includes.join(', ')}`, 172 | symbol: SHRUG_SYMBOL, 173 | }); 174 | } 175 | } catch (e) { 176 | const error = e as Error; 177 | debug(`We encountered an error: ${error.message}`); 178 | loader.fail(`We encountered an error: ${error.message}`); 179 | process.exit(1); 180 | } 181 | }; 182 | -------------------------------------------------------------------------------- /src/commands/index.ts: -------------------------------------------------------------------------------- 1 | export { command as generateCommand } from './generate'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../src/commands'; 2 | -------------------------------------------------------------------------------- /src/utils/codeowners.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import isGlob from 'is-glob'; 3 | import { MAINTAINERS_EMAIL_PATTERN, CONTENT_MARK, CHARACTER_RANGE_PATTERN, LINE_ENDING_PATTERN } from './constants'; 4 | import { posix as path } from 'path'; 5 | import { readContent } from './readContent'; 6 | import { logger } from '../utils/debug'; 7 | import groupBy from 'lodash.groupby'; 8 | import { generatedContentTemplate, rulesBlockTemplate } from './templates'; 9 | 10 | const debug = logger('utils/codeowners'); 11 | 12 | const { dirname, join } = path; 13 | 14 | const isString = (x: unknown): x is string => { 15 | return typeof x === 'string'; 16 | }; 17 | 18 | const isObject = (x: unknown): x is Record => x !== null && typeof x === 'object'; 19 | 20 | export type ownerRule = { 21 | filePath: string; 22 | owners: string[]; 23 | glob: string; 24 | }; 25 | 26 | const filterGeneratedContent = (content: string): [withoutGeneratedCode: string[], blockPosition: number] => { 27 | const lines = content.split(LINE_ENDING_PATTERN); 28 | 29 | let skip = false; 30 | let generatedBlockPosition = -1; 31 | 32 | const withoutGeneratedCode = lines.reduce((memo, line, index) => { 33 | if (line === CONTENT_MARK) { 34 | skip = !skip; 35 | if (generatedBlockPosition === -1) { 36 | generatedBlockPosition = index; 37 | } 38 | return memo; 39 | } 40 | 41 | return skip ? memo : [...memo, line]; 42 | }, [] as string[]); 43 | 44 | return [withoutGeneratedCode, generatedBlockPosition]; 45 | }; 46 | 47 | type createOwnersFileResponse = [originalContent: string, newContent: string]; 48 | export const generateOwnersFile = async ( 49 | outputFile: string, 50 | ownerRules: ownerRule[], 51 | groupSourceComments = false, 52 | preserveBlockPosition = false, 53 | customRegenerationCommand?: string 54 | ): Promise => { 55 | let originalContent = ''; 56 | 57 | if (fs.existsSync(outputFile)) { 58 | debug(`output file ${outputFile} exists, extracting content before overwriting`); 59 | originalContent = await readContent(outputFile); 60 | } 61 | 62 | let content = [] as string[]; 63 | 64 | if (groupSourceComments) { 65 | const groupedRules = groupBy(ownerRules, (rule) => rule.filePath); 66 | 67 | content = Object.keys(groupedRules).reduce((memo, filePath) => { 68 | const rules = groupedRules[filePath].map((rule) => `${rule.glob} ${rule.owners.join(' ')}`); 69 | return [...memo, rulesBlockTemplate(filePath, rules)]; 70 | }, [] as string[]); 71 | } else { 72 | content = ownerRules.map((rule) => rulesBlockTemplate(rule.filePath, [`${rule.glob} ${rule.owners.join(' ')}`])); 73 | } 74 | const [withoutGeneratedCode, blockPosition] = filterGeneratedContent(originalContent); 75 | 76 | let normalizedContent = ''; 77 | 78 | const generatedContent = generatedContentTemplate(content.join('\n'), customRegenerationCommand) + '\n'; 79 | 80 | if (originalContent) { 81 | normalizedContent = withoutGeneratedCode.reduce((memo, line, index) => { 82 | if (preserveBlockPosition && index === blockPosition) { 83 | memo += generatedContent; 84 | } 85 | memo += line + '\n'; 86 | 87 | return memo; 88 | }, ''); 89 | } 90 | 91 | if (!preserveBlockPosition) { 92 | normalizedContent = normalizedContent + generatedContent; 93 | } 94 | 95 | return [originalContent, normalizedContent.trimEnd()]; 96 | }; 97 | 98 | const parseCodeOwner = (filePath: string, codeOwnerContent: string): ownerRule[] => { 99 | const content = codeOwnerContent.split(LINE_ENDING_PATTERN); 100 | 101 | // TODO: include comments optionally. 102 | const filteredRules = content.filter((line) => line && !line.startsWith('#')); 103 | 104 | return filteredRules.map((rule) => ({ filePath, ...createMatcherCodeownersRule(filePath, rule) })); 105 | }; 106 | 107 | const isValidCodeownersGlob = (glob: string) => { 108 | // These controls are based on the Github CODEOWNERS syntax documentation 109 | // https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 110 | // as well as the gitignore pattern format which it extends 111 | // https://git-scm.com/docs/gitignore#_pattern_format 112 | 113 | const isGlobString = isString(glob); 114 | const isNotEmpty = glob.length > 0; 115 | const isNotNegated = !glob.startsWith('!'); 116 | const doesNotUseBraceExpansion = !glob.includes('{') && !glob.includes('}'); 117 | const doesNotStartWithEscapedHash = !glob.startsWith('\\#'); 118 | const doesNotUseCharacterRange = !isGlob(glob) || !CHARACTER_RANGE_PATTERN.test(glob); 119 | 120 | return ( 121 | isGlobString && 122 | isNotEmpty && 123 | isNotNegated && 124 | doesNotUseBraceExpansion && 125 | doesNotStartWithEscapedHash && 126 | doesNotUseCharacterRange 127 | ); 128 | }; 129 | 130 | const translateGlob = (glob: string) => { 131 | if (glob.startsWith('/')) { 132 | // Patterns starting with a slash should match based on the current dir. 133 | return glob; 134 | } 135 | 136 | if (!glob.includes('/') && !glob.includes('**')) { 137 | // For patterns that are might be globs but not globstars, they match 138 | // they match files in any folder. 139 | // This matches e.g. `*`, `*.ts`, `something.ts`, and `something`. 140 | return join('**', glob); 141 | } 142 | 143 | if (!isGlob(glob) && glob.indexOf('/') === glob.length - 1) { 144 | // For patterns that are not globs and contain one slash trailing slash (e.g. `apps/`), 145 | // they match directories in any folder. 146 | return join('**', glob); 147 | } 148 | 149 | return glob; 150 | }; 151 | 152 | const createMatcherCodeownersRule = (filePath: string, rule: string) => { 153 | const parts = rule.split(/\s+/); 154 | const [glob, ...owners] = parts; 155 | 156 | if (isValidCodeownersGlob(glob)) { 157 | return { 158 | glob: join('/', dirname(filePath), translateGlob(glob)), 159 | owners, 160 | }; 161 | } else { 162 | throw new Error(`${rule} in ${filePath} can not be parsed`); 163 | } 164 | }; 165 | export const loadCodeOwnerFiles = async (dirname: string, files: string[]): Promise => { 166 | const codeOwners = await Promise.all( 167 | files.map(async (filePath) => { 168 | const content = await readContent(filePath); 169 | 170 | return parseCodeOwner(filePath.replace(`${dirname}/`, ''), content); 171 | }) 172 | ); 173 | return codeOwners.reduce((memo, rules) => [...memo, ...rules], []); 174 | }; 175 | 176 | interface PACKAGE { 177 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 178 | maintainers: any[]; 179 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 180 | contributors: any[]; 181 | author?: Record; 182 | } 183 | 184 | const getOwnersFromMaintainerField = (filePath: string, content: string): ownerRule => { 185 | try { 186 | const { maintainers = [], contributors = [], author } = JSON.parse(content) as PACKAGE; 187 | 188 | const packageOwners = [...maintainers, ...contributors]; 189 | 190 | if (author) { 191 | packageOwners.unshift(author); 192 | } 193 | 194 | let owners = [] as string[]; 195 | if (packageOwners.length) { 196 | owners = packageOwners.reduce((memo, maintainer) => { 197 | if (isString(maintainer)) { 198 | const matches = maintainer.match(MAINTAINERS_EMAIL_PATTERN); 199 | if (matches?.length) return [...memo, matches[1]]; 200 | } else if (isObject(maintainer) && 'email' in maintainer && isString(maintainer.email)) { 201 | return [...memo, maintainer.email]; 202 | } 203 | 204 | return memo; 205 | }, [] as string[]); 206 | 207 | if (!owners.length) { 208 | throw new Error( 209 | `malformed maintainer entry ${maintainers} this file will be skipped. For more info https://classic.yarnpkg.com/en/docs/package-json/#toc-maintainers` 210 | ); 211 | } 212 | 213 | let glob = join('/', dirname(filePath), '/'); 214 | 215 | if (glob === '/') { 216 | // A slash ('/') for the root is not valid, using a glob-star is probably more reasonable. 217 | glob = '*'; 218 | } 219 | 220 | return { 221 | filePath, 222 | glob, 223 | owners, 224 | }; 225 | } else { 226 | throw new Error('No maintainers found, this file will be skipped.'); 227 | } 228 | } catch (e) { 229 | throw new Error(`Parsing ${filePath}: ${e}`); 230 | } 231 | }; 232 | export const loadOwnersFromPackage = async (dirname: string, files: string[]): Promise => { 233 | const codeOwners = await Promise.all( 234 | files.map(async (filePath) => { 235 | const content = await readContent(filePath); 236 | 237 | try { 238 | return getOwnersFromMaintainerField(filePath.replace(`${dirname}/`, ''), content); 239 | } catch (e) { 240 | return undefined; 241 | } 242 | }) 243 | ); 244 | 245 | // https://github.com/microsoft/TypeScript/issues/30621 246 | return codeOwners.filter(Boolean) as ownerRule[]; 247 | }; 248 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import { stripIndents } from 'common-tags'; 2 | export const SUCCESS_SYMBOL = '💫'; 3 | export const SHRUG_SYMBOL = '¯\\_(ツ)_/¯'; 4 | export const OUTPUT = 'CODEOWNERS'; 5 | export const INCLUDES = ['**/CODEOWNERS', '!CODEOWNERS', '!.github/CODEOWNERS', '!docs/CODEOWNERS', '!node_modules']; 6 | export const PACKAGE_JSON_PATTERN = ['**/package.json']; 7 | export const IGNORE_ROOT_PACKAGE_JSON_PATTERN = ['!package.json']; 8 | export const MAINTAINERS_EMAIL_PATTERN = /<(.+)>/; 9 | export const IGNORE_FILES_PATTERN = ['.gitignore']; 10 | export const CHARACTER_RANGE_PATTERN = /\[(?:.-.)+\]/; 11 | // A line ending can either be CRLF (\r\n) or LF (\n), common in Windows- and Unix-based systems respectively. 12 | export const LINE_ENDING_PATTERN = /\r?\n/; 13 | 14 | export const CONTENT_MARK = stripIndents` 15 | #################################### Generated content - do not edit! #################################### 16 | `; 17 | -------------------------------------------------------------------------------- /src/utils/debug.ts: -------------------------------------------------------------------------------- 1 | import debug, { Debugger } from 'debug'; 2 | import { name } from '../../package.json'; 3 | export function logger(nameSpace: string): Debugger { 4 | const log = debug(`${name}:${nameSpace}`); 5 | log.log = console.log.bind(console); 6 | return log; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/getCustomConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { cosmiconfig } from 'cosmiconfig'; 2 | import { SUCCESS_SYMBOL } from './constants'; 3 | import ora from 'ora'; 4 | 5 | import packageJSON from '../../package.json'; 6 | import { logger } from './debug'; 7 | 8 | const debug = logger('customConfiguration'); 9 | 10 | export type CustomConfig = { 11 | includes?: string[]; 12 | output?: string; 13 | useMaintainers?: boolean; 14 | useRootMaintainers?: boolean; 15 | groupSourceComments?: boolean; 16 | preserveBlockPosition?: boolean; 17 | customRegenerationCommand?: string; 18 | }; 19 | 20 | export const getCustomConfiguration = async (): Promise => { 21 | const loader = ora('Loading configuration').start(); 22 | 23 | try { 24 | const explorer = cosmiconfig(packageJSON.name); 25 | const result = await explorer.search(); 26 | if (result?.config && typeof result.config === 'object') { 27 | loader.stopAndPersist({ text: `Custom configuration found in ${result.filepath}`, symbol: SUCCESS_SYMBOL }); 28 | debug('custom configuration found:', result); 29 | return result.config as CustomConfig; 30 | } else { 31 | loader.stopAndPersist({ text: 'No custom configuration found', symbol: SUCCESS_SYMBOL }); 32 | return {}; 33 | } 34 | } catch (e) { 35 | loader.fail('We found an error looking for custom configuration, we will use cli options if provided'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/getGlobalOptions.ts: -------------------------------------------------------------------------------- 1 | import { CustomConfig, getCustomConfiguration } from './getCustomConfiguration'; 2 | 3 | export type GlobalOptions = CustomConfig; 4 | export interface Command { 5 | parent: Partial; 6 | } 7 | 8 | const makeArray = (field: unknown) => (field && Array.isArray(field) ? field : [field].filter(Boolean)); 9 | 10 | const getOptionsFromCommand = (command: Command): GlobalOptions => { 11 | const { 12 | parent: { includes }, 13 | } = command; 14 | 15 | return { includes: makeArray(includes) }; 16 | }; 17 | 18 | export const getGlobalOptions = async (command: Command): Promise => { 19 | const options = getOptionsFromCommand(command); 20 | 21 | const customConfiguration = await getCustomConfiguration(); 22 | 23 | if (!options.includes?.length && customConfiguration && customConfiguration.includes?.length) { 24 | return { ...customConfiguration, ...options, includes: customConfiguration.includes }; 25 | } 26 | 27 | return { ...customConfiguration, ...options }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/getPatternsFromIgnoreFiles.ts: -------------------------------------------------------------------------------- 1 | import { logger } from './debug'; 2 | import { sync } from 'fast-glob'; 3 | import { IGNORE_FILES_PATTERN, LINE_ENDING_PATTERN } from './constants'; 4 | import { readContent } from './readContent'; 5 | 6 | const debug = logger('parseIgnoreFiles'); 7 | 8 | export const getPatternsFromIgnoreFiles = async (): Promise => { 9 | const matches = sync(IGNORE_FILES_PATTERN, { 10 | onlyFiles: true, 11 | absolute: true, 12 | }); 13 | 14 | debug('found', matches); 15 | 16 | const filesContent = await Promise.all( 17 | matches.map(async (filePath) => { 18 | try { 19 | const content = await readContent(filePath); 20 | const lines = content.split(LINE_ENDING_PATTERN).filter((line) => line && !line.startsWith('#')); 21 | 22 | return lines; 23 | } catch (e) { 24 | debug(`We failed when reading ${filePath}, skipping file. reason: `, e); 25 | return []; 26 | } 27 | }) 28 | ); 29 | 30 | const patterns = filesContent.reduce((memo, patterns) => [...memo, ...patterns], [] as string[]); 31 | 32 | debug('ignore patterns found:', patterns); 33 | 34 | return patterns; 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/readContent.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { promisify } from 'util'; 3 | import { logger } from './debug'; 4 | 5 | const debug = logger('readFile'); 6 | const readFile = promisify(fs.readFile); 7 | export const readContent = async (filePath: string): Promise => { 8 | debug('reading...', filePath); 9 | 10 | const rawContent = await readFile(filePath); 11 | 12 | const content = rawContent.toString(); 13 | return content; 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/templates.ts: -------------------------------------------------------------------------------- 1 | import { stripIndents } from 'common-tags'; 2 | import { CONTENT_MARK } from './constants'; 3 | 4 | const getContentLegend = (customRegenerationCommand?: string) => stripIndents` 5 | # This block has been generated with codeowners-generator (for more information https://github.com/gagoar/codeowners-generator) 6 | # ${ 7 | customRegenerationCommand ? `To re-generate, run \`${customRegenerationCommand}\`. ` : '' 8 | }Don't worry, the content outside this block will be kept. 9 | `; 10 | 11 | export const generatedContentTemplate = (generatedContent: string, customRegenerationCommand?: string) => { 12 | return stripIndents` 13 | ${CONTENT_MARK} 14 | ${getContentLegend(customRegenerationCommand)}\n 15 | ${generatedContent}\n 16 | ${CONTENT_MARK}\n 17 | `; 18 | }; 19 | export const rulesBlockTemplate = (source: string, entries: string[]): string => { 20 | return stripIndents` 21 | # Rule${entries.length > 1 ? 's' : ''} extracted from ${source}${ 22 | entries.some((rule) => rule.trim().includes(' ')) ? '' : ' (containing path exclusions)' 23 | } 24 | ${entries.join('\n')} 25 | `; 26 | }; 27 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["index.ts", "__tests__/**/*.ts", "__mocks__/**/*.ts", "src/**/*.ts", "jest.config.js"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "__tests__", "index.ts"], 3 | "compilerOptions": { 4 | "target": "es2017", 5 | "module": "es6", 6 | "lib": ["dom", "esnext"], 7 | "emitDecoratorMetadata": true, 8 | "importHelpers": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "resolveJsonModule": true, 24 | "experimentalDecorators": true, 25 | "baseUrl": "./", 26 | "jsx": "react", 27 | "esModuleInterop": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | --------------------------------------------------------------------------------