├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
codeowners-generator
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | this project is sponsored by:
28 |
29 |
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 |
288 |
289 |
290 |
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 |
--------------------------------------------------------------------------------