Skip to content

chore(ci): prevent commit including generated files #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3e75735
chore(ci): add pre-commit hook to unstage generated codes
eunjae-lee Apr 12, 2022
227edcc
chore: test commit
eunjae-lee Apr 12, 2022
9167fc9
chore: implement pre-commit script
eunjae-lee Apr 14, 2022
b9d1057
chore: fix lint error
eunjae-lee Apr 14, 2022
ff0b1b8
Merge branch 'main' into chore/githook
eunjae-lee Apr 14, 2022
054c031
chore: fix lint error
eunjae-lee Apr 14, 2022
3fe4723
chore: call js file directly
eunjae-lee Apr 15, 2022
40cbd5e
chore: update .cache_version
eunjae-lee Apr 15, 2022
c5d7781
Merge branch 'main' into chore/githook
eunjae-lee Apr 15, 2022
fbfba2b
Merge branch 'main' into chore/githook
eunjae-lee Apr 19, 2022
c8c3c6e
Update scripts/ci/husky/pre-commit.js
eunjae-lee Apr 19, 2022
0b07721
Update scripts/ci/husky/pre-commit.js
eunjae-lee Apr 19, 2022
4dc5a01
chore: use postinstall instead of prepare
eunjae-lee Apr 19, 2022
202169c
chore: better error message
eunjae-lee Apr 19, 2022
6d34e74
chore: fix patterns
eunjae-lee Apr 19, 2022
62c75fe
chore: unstage files automatically and exit with 0
eunjae-lee Apr 19, 2022
541a167
Merge branch 'main' into chore/githook
eunjae-lee Apr 19, 2022
a3d66a6
Merge branch 'main' into chore/githook
eunjae-lee Apr 20, 2022
fc73866
chore: change loop
eunjae-lee Apr 21, 2022
958ff64
Merge branch 'main' into chore/githook
eunjae-lee Apr 21, 2022
d5fa011
chore: skip deleted files
eunjae-lee Apr 21, 2022
958c1fa
Merge branch 'chore/githook' of github.com:algolia/api-clients-automa…
eunjae-lee Apr 21, 2022
613fca9
Merge branch 'main' into chore/githook
eunjae-lee Apr 22, 2022
73528a0
chore: split config to a separate file
eunjae-lee Apr 22, 2022
b7e752e
chore: update docs
eunjae-lee Apr 22, 2022
bb700fd
Merge branch 'main' into chore/githook
eunjae-lee Apr 22, 2022
32dd38e
chore: update cache version
eunjae-lee Apr 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/.cache_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8.0.5.1
8.0.5.2
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

./scripts/ci/husky/pre-commit.js
30 changes: 30 additions & 0 deletions config/generation.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can maybe add a description of what this file does exactly

patterns: [
// Ignore the roots and go down the tree by negating hand written files
'clients/**',
'!clients/README.md',
'!clients/**/.openapi-generator-ignore',

// Java
'!clients/algoliasearch-client-java-2/algoliasearch-core/src/com/algolia/exceptions/*',
'!clients/algoliasearch-client-java-2/algoliasearch-core/src/com/algolia/utils/*',
'clients/algoliasearch-client-java-2/algoliasearch-core/com/algolia/utils/echo/EchoResponse*.java',
'!clients/algoliasearch-client-java-2/algoliasearch-core/com/algolia/utils/echo/EchoResponseInterface.java',

// JavaScript
'!clients/algoliasearch-client-javascript/*',
'!clients/algoliasearch-client-javascript/.github/**',
'!clients/algoliasearch-client-javascript/.yarn/**',
'!clients/algoliasearch-client-javascript/scripts/**',
'!clients/algoliasearch-client-javascript/packages/algoliasearch/**',
'!clients/algoliasearch-client-javascript/packages/requester-*/**',
'!clients/algoliasearch-client-javascript/packages/client-common/**',

// PHP
'!clients/algoliasearch-client-php/lib/Configuration/*',
'clients/algoliasearch-client-php/lib/*.php',
'clients/algoliasearch-client-php/lib/Api/*',
'clients/algoliasearch-client-php/lib/Configuration/Configuration.php',
],
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"docker:setup": "yarn docker:clean && yarn docker:build && yarn docker:mount",
"fix:json": "eslint --ext=json . --fix",
"github-actions:lint": "eslint --ext=yml .github/",
"postinstall": "yarn workspace eslint-plugin-automation-custom build",
"postinstall": "husky install && yarn workspace eslint-plugin-automation-custom build",
"playground:browser": "yarn workspace javascript-browser-playground start",
"release": "yarn workspace scripts createReleaseIssue",
"scripts:lint": "eslint --ext=ts scripts/",
Expand Down Expand Up @@ -50,6 +50,7 @@
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-unused-imports": "2.0.0",
"eslint-plugin-yml": "0.14.0",
"husky": "7.0.4",
"json": "11.0.0",
"mustache": "4.2.0",
"prettier": "2.6.2",
Expand Down
24 changes: 24 additions & 0 deletions scripts/ci/husky/__tests__/pre-commit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable import/no-commonjs */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test can still be in TS 😇

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(joking)

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { createMemoizedMicromatchMatcher } = require('../pre-commit');

describe('createMemoizedMicromatchMatcher', () => {
it('matches correctly', () => {
const matcher = createMemoizedMicromatchMatcher([
'clients/**',
'!clients/README.md',
]);

expect(matcher('clients/README.md')).toEqual(false);
expect(matcher('clients/CONTRIBUTING.md')).toEqual(true);
});

it('prioritizes the exact match when two patterns conflict', () => {
const matcher = createMemoizedMicromatchMatcher([
'!lib/Configuration/*',
'lib/Configuration/Configuration.php',
]);

expect(matcher('lib/Configuration/Configuration.php')).toEqual(true);
});
});
81 changes: 81 additions & 0 deletions scripts/ci/husky/pre-commit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env node
/* eslint-disable no-console */
/* eslint-disable import/no-commonjs */
/* eslint-disable @typescript-eslint/no-var-requires */
const chalk = require('chalk');
const execa = require('execa');
const micromatch = require('micromatch');

const GENERATED_FILE_PATTERNS =
require('../../../config/generation.config').patterns;

const run = async (command, { cwd } = {}) => {
return (
(await execa.command(command, { shell: 'bash', all: true, cwd })).all ?? ''
);
};

function createMemoizedMicromatchMatcher(patterns = []) {
const exactMatchers = [];
const positiveMatchers = [];
const negativeMatchers = [];

patterns.forEach((pattern) => {
if (pattern.startsWith('!')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this simplification of splitting negative and positive will work for every case, for example if we have a sequence of [positive, negative, positive, negative], it will loose the pattern, but I don't have a better solution 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm not too sure either. But I couldn't think of any edge case so far. Once we face it in the future, it will reveal itself and still won't ruin anything. Then we can adjust the logic to cover the case.

// Patterns starting with `!` are negated
negativeMatchers.push(micromatch.matcher(pattern.slice(1)));
} else if (!pattern.includes('*')) {
exactMatchers.push(micromatch.matcher(pattern));
} else {
positiveMatchers.push(micromatch.matcher(pattern));
}
});

return function matcher(str) {
if (exactMatchers.some((match) => match(str))) {
return true;
}

// As `some` returns false on empty array, test will always fail if we only
// provide `negativeMatchers`. We fallback to `true` is it's the case.
const hasPositiveMatchers =
Boolean(positiveMatchers.length === 0 && negativeMatchers.length) ||
positiveMatchers.some((match) => match(str));

return hasPositiveMatchers && !negativeMatchers.some((match) => match(str));
};
}

async function preCommit() {
const stagedFiles = (await run(`git diff --name-only --cached`)).split('\n');
const deletedFiles = new Set(
(await run(`git ls-files --deleted`)).split('\n')
);
const matcher = createMemoizedMicromatchMatcher(GENERATED_FILE_PATTERNS);

for (const stagedFile of stagedFiles) {
// keep the deleted files staged even if they were generated before.
if (deletedFiles.has(stagedFile)) {
continue;
}

if (!matcher(stagedFile)) {
continue;
}

console.log(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really nice output 😃

chalk.bgYellow('[INFO]'),
`Generated file found, unstaging: ${stagedFile}`
);

await run(`git restore --staged ${stagedFile}`);
}
}

if (require.main === module && !process.env.CI) {
preCommit();
}

module.exports = {
createMemoizedMicromatchMatcher,
};
4 changes: 4 additions & 0 deletions scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"cleanGeneratedBranch": "ts-node ci/codegen/cleanGeneratedBranch.ts",
"createMatrix": "ts-node ci/createMatrix.ts",
"createReleaseIssue": "ts-node release/create-release-issue.ts",
"pre-commit": "./ci/husky/pre-commit.js",
"processRelease": "ts-node release/process-release.ts",
"pushGeneratedCode": "ts-node ci/codegen/pushGeneratedCode.ts",
"setRunVariables": "ts-node ci/setRunVariables.ts",
Expand All @@ -19,9 +20,11 @@
"@types/inquirer": "8.2.1",
"@types/jest": "27.4.1",
"@types/js-yaml": "4.0.5",
"@types/micromatch": "4.0.2",
"@types/mustache": "4.1.2",
"@types/node": "16.11.26",
"@types/semver": "7.3.9",
"chalk": "4.1.2",
"commander": "9.1.0",
"dotenv": "16.0.0",
"eslint": "8.12.0",
Expand All @@ -31,6 +34,7 @@
"inquirer": "8.2.2",
"jest": "27.5.1",
"js-yaml": "4.1.0",
"micromatch": "4.0.5",
"mustache": "4.2.0",
"openapi-types": "10.0.0",
"ora-classic": "5.4.2",
Expand Down
17 changes: 17 additions & 0 deletions website/docs/commitAndPullRequest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: Commit and Pull-request
---

# Commit and Pull-request

## Commit

If you accidentally include generated files in your commit, the `pre-commit` hook will automatically unstage them.

We create commits on the CI as well, and in that case, we skip this unstaging behavior with the environment variable `CI=true` given.

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).

## Pull-request

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.
2 changes: 1 addition & 1 deletion website/docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ To contribute to the repository, make sure to take a look at our guidelines and
- [Setup the repository tooling](/docs/setupRepository): to install our tooling.
- [Add a new client](/docs/addNewClient): to add a new client spec to generate.
- [Support a new language](/docs/addNewLanguage): to add a new supported language to the API clients.
- [Pull-request](/docs/pullRequest): to see how to send pull-requests.
- [Commit and Pull-request](/docs/commitAndPullRequest): to see how to commit and send pull-requests.
- [Release process](/docs/releaseProcess): to see how to release API clients.

CLI commands can be found at [CLI > specs commands](/docs/specsCommands) and [CLI > generation commands](/docs/generationCommands)
Expand Down
7 changes: 0 additions & 7 deletions website/docs/pullRequest.md

This file was deleted.

2 changes: 1 addition & 1 deletion website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const sidebars = {
},
'addNewClient',
'addNewLanguage',
'pullRequest',
'commitAndPullRequest',
'releaseProcess',
],
},
Expand Down
49 changes: 39 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ __metadata:
eslint-plugin-prettier: 4.0.0
eslint-plugin-unused-imports: 2.0.0
eslint-plugin-yml: 0.14.0
husky: 7.0.4
json: 11.0.0
mustache: 4.2.0
prettier: 2.6.2
Expand Down Expand Up @@ -5528,6 +5529,13 @@ __metadata:
languageName: node
linkType: hard

"@types/braces@npm:*":
version: 3.0.1
resolution: "@types/braces@npm:3.0.1"
checksum: 3749f7673a03d498ddb2f199b222bb403e53e78ac05a599c757c2049703ece802602c78640af0ff826be0fd2ea8b03daff04ce18806ed739592302195b7a569b
languageName: node
linkType: hard

"@types/connect-history-api-fallback@npm:^1.3.5":
version: 1.3.5
resolution: "@types/connect-history-api-fallback@npm:1.3.5"
Expand Down Expand Up @@ -5746,6 +5754,15 @@ __metadata:
languageName: node
linkType: hard

"@types/micromatch@npm:4.0.2":
version: 4.0.2
resolution: "@types/micromatch@npm:4.0.2"
dependencies:
"@types/braces": "*"
checksum: 6c678e9c625d5b422c6d2c1001da1c502ecc4457248343bbd324b79fd798a6563e336a4d79630d80e8202312013dd7cf8b4440afa644d04477abd26fde6fba24
languageName: node
linkType: hard

"@types/mime@npm:^1":
version: 1.3.2
resolution: "@types/mime@npm:1.3.2"
Expand Down Expand Up @@ -12248,6 +12265,15 @@ __metadata:
languageName: node
linkType: hard

"husky@npm:7.0.4":
version: 7.0.4
resolution: "husky@npm:7.0.4"
bin:
husky: lib/bin.js
checksum: c6ec4af63da2c9522da8674a20ad9b48362cc92704896cc8a58c6a2a39d797feb2b806f93fbd83a6d653fbdceb2c3b6e0b602c6b2e8565206ffc2882ef7db9e9
languageName: node
linkType: hard

"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
Expand Down Expand Up @@ -14731,6 +14757,16 @@ __metadata:
languageName: node
linkType: hard

"micromatch@npm:4.0.5, micromatch@npm:^4.0.5":
version: 4.0.5
resolution: "micromatch@npm:4.0.5"
dependencies:
braces: ^3.0.2
picomatch: ^2.3.1
checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc
languageName: node
linkType: hard

"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4":
version: 4.0.4
resolution: "micromatch@npm:4.0.4"
Expand All @@ -14741,16 +14777,6 @@ __metadata:
languageName: node
linkType: hard

"micromatch@npm:^4.0.5":
version: 4.0.5
resolution: "micromatch@npm:4.0.5"
dependencies:
braces: ^3.0.2
picomatch: ^2.3.1
checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc
languageName: node
linkType: hard

"miller-rabin@npm:^4.0.0":
version: 4.0.1
resolution: "miller-rabin@npm:4.0.1"
Expand Down Expand Up @@ -19032,9 +19058,11 @@ __metadata:
"@types/inquirer": 8.2.1
"@types/jest": 27.4.1
"@types/js-yaml": 4.0.5
"@types/micromatch": 4.0.2
"@types/mustache": 4.1.2
"@types/node": 16.11.26
"@types/semver": 7.3.9
chalk: 4.1.2
commander: 9.1.0
dotenv: 16.0.0
eslint: 8.12.0
Expand All @@ -19044,6 +19072,7 @@ __metadata:
inquirer: 8.2.2
jest: 27.5.1
js-yaml: 4.1.0
micromatch: 4.0.5
mustache: 4.2.0
openapi-types: 10.0.0
ora-classic: 5.4.2
Expand Down