Skip to content

Commit 9fbba0e

Browse files
eunjae-leemillotp
andauthored
chore(ci): prevent commit including generated files (#379)
* chore(ci): add pre-commit hook to unstage generated codes * chore: test commit * chore: implement pre-commit script * chore: fix lint error * chore: fix lint error * chore: call js file directly * chore: update .cache_version * Update scripts/ci/husky/pre-commit.js Co-authored-by: Pierre Millot <[email protected]> * Update scripts/ci/husky/pre-commit.js Co-authored-by: Pierre Millot <[email protected]> * chore: use postinstall instead of prepare * chore: better error message * chore: fix patterns * chore: unstage files automatically and exit with 0 * chore: change loop * chore: skip deleted files * chore: split config to a separate file * chore: update docs * chore: update cache version Co-authored-by: Pierre Millot <[email protected]>
1 parent 04c1122 commit 9fbba0e

12 files changed

+204
-21
lines changed

.github/.cache_version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.0.5.1
1+
8.0.5.2

.husky/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
./scripts/ci/husky/pre-commit.js

config/generation.config.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// eslint-disable-next-line import/no-commonjs
2+
module.exports = {
3+
patterns: [
4+
// Ignore the roots and go down the tree by negating hand written files
5+
'clients/**',
6+
'!clients/README.md',
7+
'!clients/**/.openapi-generator-ignore',
8+
9+
// Java
10+
'!clients/algoliasearch-client-java-2/algoliasearch-core/src/com/algolia/exceptions/*',
11+
'!clients/algoliasearch-client-java-2/algoliasearch-core/src/com/algolia/utils/*',
12+
'clients/algoliasearch-client-java-2/algoliasearch-core/com/algolia/utils/echo/EchoResponse*.java',
13+
'!clients/algoliasearch-client-java-2/algoliasearch-core/com/algolia/utils/echo/EchoResponseInterface.java',
14+
15+
// JavaScript
16+
'!clients/algoliasearch-client-javascript/*',
17+
'!clients/algoliasearch-client-javascript/.github/**',
18+
'!clients/algoliasearch-client-javascript/.yarn/**',
19+
'!clients/algoliasearch-client-javascript/scripts/**',
20+
'!clients/algoliasearch-client-javascript/packages/algoliasearch/**',
21+
'!clients/algoliasearch-client-javascript/packages/requester-*/**',
22+
'!clients/algoliasearch-client-javascript/packages/client-common/**',
23+
24+
// PHP
25+
'!clients/algoliasearch-client-php/lib/Configuration/*',
26+
'clients/algoliasearch-client-php/lib/*.php',
27+
'clients/algoliasearch-client-php/lib/Api/*',
28+
'clients/algoliasearch-client-php/lib/Configuration/Configuration.php',
29+
],
30+
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"docker:setup": "yarn docker:clean && yarn docker:build && yarn docker:mount",
2323
"fix:json": "eslint --ext=json . --fix",
2424
"github-actions:lint": "eslint --ext=yml .github/",
25-
"postinstall": "yarn workspace eslint-plugin-automation-custom build",
25+
"postinstall": "husky install && yarn workspace eslint-plugin-automation-custom build",
2626
"playground:browser": "yarn workspace javascript-browser-playground start",
2727
"release": "yarn workspace scripts createReleaseIssue",
2828
"scripts:lint": "eslint --ext=ts scripts/",
@@ -50,6 +50,7 @@
5050
"eslint-plugin-prettier": "4.0.0",
5151
"eslint-plugin-unused-imports": "2.0.0",
5252
"eslint-plugin-yml": "0.14.0",
53+
"husky": "7.0.4",
5354
"json": "11.0.0",
5455
"mustache": "4.2.0",
5556
"prettier": "2.6.2",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/* eslint-disable import/no-commonjs */
2+
// eslint-disable-next-line @typescript-eslint/no-var-requires
3+
const { createMemoizedMicromatchMatcher } = require('../pre-commit');
4+
5+
describe('createMemoizedMicromatchMatcher', () => {
6+
it('matches correctly', () => {
7+
const matcher = createMemoizedMicromatchMatcher([
8+
'clients/**',
9+
'!clients/README.md',
10+
]);
11+
12+
expect(matcher('clients/README.md')).toEqual(false);
13+
expect(matcher('clients/CONTRIBUTING.md')).toEqual(true);
14+
});
15+
16+
it('prioritizes the exact match when two patterns conflict', () => {
17+
const matcher = createMemoizedMicromatchMatcher([
18+
'!lib/Configuration/*',
19+
'lib/Configuration/Configuration.php',
20+
]);
21+
22+
expect(matcher('lib/Configuration/Configuration.php')).toEqual(true);
23+
});
24+
});

scripts/ci/husky/pre-commit.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env node
2+
/* eslint-disable no-console */
3+
/* eslint-disable import/no-commonjs */
4+
/* eslint-disable @typescript-eslint/no-var-requires */
5+
const chalk = require('chalk');
6+
const execa = require('execa');
7+
const micromatch = require('micromatch');
8+
9+
const GENERATED_FILE_PATTERNS =
10+
require('../../../config/generation.config').patterns;
11+
12+
const run = async (command, { cwd } = {}) => {
13+
return (
14+
(await execa.command(command, { shell: 'bash', all: true, cwd })).all ?? ''
15+
);
16+
};
17+
18+
function createMemoizedMicromatchMatcher(patterns = []) {
19+
const exactMatchers = [];
20+
const positiveMatchers = [];
21+
const negativeMatchers = [];
22+
23+
patterns.forEach((pattern) => {
24+
if (pattern.startsWith('!')) {
25+
// Patterns starting with `!` are negated
26+
negativeMatchers.push(micromatch.matcher(pattern.slice(1)));
27+
} else if (!pattern.includes('*')) {
28+
exactMatchers.push(micromatch.matcher(pattern));
29+
} else {
30+
positiveMatchers.push(micromatch.matcher(pattern));
31+
}
32+
});
33+
34+
return function matcher(str) {
35+
if (exactMatchers.some((match) => match(str))) {
36+
return true;
37+
}
38+
39+
// As `some` returns false on empty array, test will always fail if we only
40+
// provide `negativeMatchers`. We fallback to `true` is it's the case.
41+
const hasPositiveMatchers =
42+
Boolean(positiveMatchers.length === 0 && negativeMatchers.length) ||
43+
positiveMatchers.some((match) => match(str));
44+
45+
return hasPositiveMatchers && !negativeMatchers.some((match) => match(str));
46+
};
47+
}
48+
49+
async function preCommit() {
50+
const stagedFiles = (await run(`git diff --name-only --cached`)).split('\n');
51+
const deletedFiles = new Set(
52+
(await run(`git ls-files --deleted`)).split('\n')
53+
);
54+
const matcher = createMemoizedMicromatchMatcher(GENERATED_FILE_PATTERNS);
55+
56+
for (const stagedFile of stagedFiles) {
57+
// keep the deleted files staged even if they were generated before.
58+
if (deletedFiles.has(stagedFile)) {
59+
continue;
60+
}
61+
62+
if (!matcher(stagedFile)) {
63+
continue;
64+
}
65+
66+
console.log(
67+
chalk.bgYellow('[INFO]'),
68+
`Generated file found, unstaging: ${stagedFile}`
69+
);
70+
71+
await run(`git restore --staged ${stagedFile}`);
72+
}
73+
}
74+
75+
if (require.main === module && !process.env.CI) {
76+
preCommit();
77+
}
78+
79+
module.exports = {
80+
createMemoizedMicromatchMatcher,
81+
};

scripts/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"cleanGeneratedBranch": "ts-node ci/codegen/cleanGeneratedBranch.ts",
66
"createMatrix": "ts-node ci/createMatrix.ts",
77
"createReleaseIssue": "ts-node release/create-release-issue.ts",
8+
"pre-commit": "./ci/husky/pre-commit.js",
89
"processRelease": "ts-node release/process-release.ts",
910
"pushGeneratedCode": "ts-node ci/codegen/pushGeneratedCode.ts",
1011
"setRunVariables": "ts-node ci/setRunVariables.ts",
@@ -19,9 +20,11 @@
1920
"@types/inquirer": "8.2.1",
2021
"@types/jest": "27.4.1",
2122
"@types/js-yaml": "4.0.5",
23+
"@types/micromatch": "4.0.2",
2224
"@types/mustache": "4.1.2",
2325
"@types/node": "16.11.26",
2426
"@types/semver": "7.3.9",
27+
"chalk": "4.1.2",
2528
"commander": "9.1.0",
2629
"dotenv": "16.0.0",
2730
"eslint": "8.12.0",
@@ -31,6 +34,7 @@
3134
"inquirer": "8.2.2",
3235
"jest": "27.5.1",
3336
"js-yaml": "4.1.0",
37+
"micromatch": "4.0.5",
3438
"mustache": "4.2.0",
3539
"openapi-types": "10.0.0",
3640
"ora-classic": "5.4.2",

website/docs/commitAndPullRequest.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: Commit and Pull-request
3+
---
4+
5+
# Commit and Pull-request
6+
7+
## Commit
8+
9+
If you accidentally include generated files in your commit, the `pre-commit` hook will automatically unstage them.
10+
11+
We create commits on the CI as well, and in that case, we skip this unstaging behavior with the environment variable `CI=true` given.
12+
13+
If you want to change the patterns of generated file paths, see [config/generation.config.js](https://github.com/algolia/api-clients-automation/blob/main/config/generation.config.js).
14+
15+
## Pull-request
16+
17+
Semantic title is required. It's validated by [GitHub Action](https://github.com/deepakputhraya/action-pr-title). See [pr-title.yml](https://github.com/algolia/api-clients-automation/blob/main/.github/workflows/pr-title.yml) for the complete regular expressions.

website/docs/introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ To contribute to the repository, make sure to take a look at our guidelines and
1313
- [Setup the repository tooling](/docs/setupRepository): to install our tooling.
1414
- [Add a new client](/docs/addNewClient): to add a new client spec to generate.
1515
- [Support a new language](/docs/addNewLanguage): to add a new supported language to the API clients.
16-
- [Pull-request](/docs/pullRequest): to see how to send pull-requests.
16+
- [Commit and Pull-request](/docs/commitAndPullRequest): to see how to commit and send pull-requests.
1717
- [Release process](/docs/releaseProcess): to see how to release API clients.
1818

1919
CLI commands can be found at [CLI > specs commands](/docs/specsCommands) and [CLI > generation commands](/docs/generationCommands)

website/docs/pullRequest.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

website/sidebars.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const sidebars = {
1919
},
2020
'addNewClient',
2121
'addNewLanguage',
22-
'pullRequest',
22+
'commitAndPullRequest',
2323
'releaseProcess',
2424
],
2525
},

yarn.lock

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ __metadata:
2626
eslint-plugin-prettier: 4.0.0
2727
eslint-plugin-unused-imports: 2.0.0
2828
eslint-plugin-yml: 0.14.0
29+
husky: 7.0.4
2930
json: 11.0.0
3031
mustache: 4.2.0
3132
prettier: 2.6.2
@@ -5528,6 +5529,13 @@ __metadata:
55285529
languageName: node
55295530
linkType: hard
55305531

5532+
"@types/braces@npm:*":
5533+
version: 3.0.1
5534+
resolution: "@types/braces@npm:3.0.1"
5535+
checksum: 3749f7673a03d498ddb2f199b222bb403e53e78ac05a599c757c2049703ece802602c78640af0ff826be0fd2ea8b03daff04ce18806ed739592302195b7a569b
5536+
languageName: node
5537+
linkType: hard
5538+
55315539
"@types/connect-history-api-fallback@npm:^1.3.5":
55325540
version: 1.3.5
55335541
resolution: "@types/connect-history-api-fallback@npm:1.3.5"
@@ -5746,6 +5754,15 @@ __metadata:
57465754
languageName: node
57475755
linkType: hard
57485756

5757+
"@types/micromatch@npm:4.0.2":
5758+
version: 4.0.2
5759+
resolution: "@types/micromatch@npm:4.0.2"
5760+
dependencies:
5761+
"@types/braces": "*"
5762+
checksum: 6c678e9c625d5b422c6d2c1001da1c502ecc4457248343bbd324b79fd798a6563e336a4d79630d80e8202312013dd7cf8b4440afa644d04477abd26fde6fba24
5763+
languageName: node
5764+
linkType: hard
5765+
57495766
"@types/mime@npm:^1":
57505767
version: 1.3.2
57515768
resolution: "@types/mime@npm:1.3.2"
@@ -12248,6 +12265,15 @@ __metadata:
1224812265
languageName: node
1224912266
linkType: hard
1225012267

12268+
"husky@npm:7.0.4":
12269+
version: 7.0.4
12270+
resolution: "husky@npm:7.0.4"
12271+
bin:
12272+
husky: lib/bin.js
12273+
checksum: c6ec4af63da2c9522da8674a20ad9b48362cc92704896cc8a58c6a2a39d797feb2b806f93fbd83a6d653fbdceb2c3b6e0b602c6b2e8565206ffc2882ef7db9e9
12274+
languageName: node
12275+
linkType: hard
12276+
1225112277
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
1225212278
version: 0.4.24
1225312279
resolution: "iconv-lite@npm:0.4.24"
@@ -14731,6 +14757,16 @@ __metadata:
1473114757
languageName: node
1473214758
linkType: hard
1473314759

14760+
"micromatch@npm:4.0.5, micromatch@npm:^4.0.5":
14761+
version: 4.0.5
14762+
resolution: "micromatch@npm:4.0.5"
14763+
dependencies:
14764+
braces: ^3.0.2
14765+
picomatch: ^2.3.1
14766+
checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc
14767+
languageName: node
14768+
linkType: hard
14769+
1473414770
"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4":
1473514771
version: 4.0.4
1473614772
resolution: "micromatch@npm:4.0.4"
@@ -14741,16 +14777,6 @@ __metadata:
1474114777
languageName: node
1474214778
linkType: hard
1474314779

14744-
"micromatch@npm:^4.0.5":
14745-
version: 4.0.5
14746-
resolution: "micromatch@npm:4.0.5"
14747-
dependencies:
14748-
braces: ^3.0.2
14749-
picomatch: ^2.3.1
14750-
checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc
14751-
languageName: node
14752-
linkType: hard
14753-
1475414780
"miller-rabin@npm:^4.0.0":
1475514781
version: 4.0.1
1475614782
resolution: "miller-rabin@npm:4.0.1"
@@ -19032,9 +19058,11 @@ __metadata:
1903219058
"@types/inquirer": 8.2.1
1903319059
"@types/jest": 27.4.1
1903419060
"@types/js-yaml": 4.0.5
19061+
"@types/micromatch": 4.0.2
1903519062
"@types/mustache": 4.1.2
1903619063
"@types/node": 16.11.26
1903719064
"@types/semver": 7.3.9
19065+
chalk: 4.1.2
1903819066
commander: 9.1.0
1903919067
dotenv: 16.0.0
1904019068
eslint: 8.12.0
@@ -19044,6 +19072,7 @@ __metadata:
1904419072
inquirer: 8.2.2
1904519073
jest: 27.5.1
1904619074
js-yaml: 4.1.0
19075+
micromatch: 4.0.5
1904719076
mustache: 4.2.0
1904819077
openapi-types: 10.0.0
1904919078
ora-classic: 5.4.2

0 commit comments

Comments
 (0)