diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1dc1897..4ca218c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,15 +13,15 @@ ] }, "extensions": [ - "mads-hartmann.bash-ide-vscode" + "mads-hartmann.bash-ide-vscode", + "mhutchie.git-graph" ] } }, "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, - "../src/gitutils": {}, - "../src/gitversion": {} + "ghcr.io/tomgrv/devcontainer-features/gitutils": {}, + "ghcr.io/tomgrv/devcontainer-features/githooks": {} }, - "remoteUser": "node", "updateContentCommand": "npm install -g @devcontainers/cli" } \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b175d8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.devcontainer/githooks/_commit-and-tag-version.json +.devcontainer/githooks/_commitlint.json +.devcontainer/githooks/_config.json +.devcontainer/githooks/_git-precommit-checks.json +.devcontainer/githooks/_husky.json +.devcontainer/githooks/_lint-staged.json +.devcontainer/githooks/_prettier.json +.devcontainer/githooks/_scripts.json +.devcontainer/githooks/commit-msg +.devcontainer/githooks/config.json +.devcontainer/githooks/config.sh +.devcontainer/githooks/devcontainer-feature.json +.devcontainer/githooks/install.sh +.devcontainer/githooks/post-checkout +.devcontainer/githooks/post-merge +.devcontainer/githooks/pre-commit +.devcontainer/githooks/pre-push +.devcontainer/githooks/prepare-commit-msg +.devcontainer/githooks/README.md +.devcontainer/githooks/_commit-msg.sh +.devcontainer/githooks/_post-checkout.sh +.devcontainer/githooks/_post-merge.sh +.devcontainer/githooks/_pre-commit.sh +.devcontainer/githooks/_pre-push.sh +.devcontainer/githooks/_prepare-commit-msg.sh diff --git a/package.json b/package.json new file mode 100644 index 0000000..2858c56 --- /dev/null +++ b/package.json @@ -0,0 +1,131 @@ +{ + "commit-and-tag-version": { + "bumpFiles": [ + { + "filename": "composer.json", + "type": "json" + }, + { + "filename": "package.json", + "type": "json" + }, + { + "filename": "VERSION", + "type": "plain-text" + } + ], + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "hidden": true + }, + { + "type": "docs", + "hidden": true + }, + { + "type": "style", + "hidden": true + }, + { + "type": "refactor", + "hidden": true + }, + { + "type": "perf", + "hidden": true + }, + { + "type": "test", + "hidden": true + } + ], + "scripts": { + "prebump": "gitversion -config .gitversion -showvariable MajorMinorPatch" + } + }, + "scripts": { + "release": "commit-and-tag-version --no-verify --", + "lint": "lint-staged", + "update": "npm-check-updates -i -u", + "update-all": "npm run update -ws --root", + "test": "echo \"Warning: no test specified\"" + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ], + "rules": { + "subject-case": [ + 2, + "never", + [ + "start-case", + "pascal-case", + "upper-case" + ] + ], + "scope-enum": [ + 2, + "always", + [ + "deps", + "release", + "security", + "i18n", + "config", + "add", + "remove", + "breaking", + "modules", + "packages", + "ui-ux", + "api", + "model" + ] + ] + } + }, + "config": { + "commitizen": { + "path": "@commitlint/cz-commitlint" + } + }, + "git-precommit-checks": { + "rules": [ + { + "message": "You've got leftover conflict markers", + "regex": "/^[<>|=]{4,}/m" + }, + { + "filter": "(^package\\.json|\\.git-precommit-checks.json)$", + "message": "You have unfinished devs", + "nonBlocking": "true", + "regex": "(?:FIXME|TODO)" + } + ] + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,md,html,css,json,vue, yaml, yml}": [ + "prettier --write" + ], + "*.php": [ + "composer lint" + ] + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "insertPragma": true + } +} diff --git a/src/githooks/README.md b/src/githooks/README.md new file mode 100644 index 0000000..01266e0 --- /dev/null +++ b/src/githooks/README.md @@ -0,0 +1,42 @@ + +# Git Utils + +This feature provides a set of utilities for working with Git repositories. + +The following aliases are included: [./alias.json](./src/gitutils/alias.json) + +## Example Usage + +```json +"features": { + "ghcr.io/tomgrv/devcontainer-features/gitutils:1": { + "version": "latest" + } +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | The version of GitUtils to install. | string | latest | + +## GitFlow + +Additionnaly, the feature installs the [git-flow](https:://github.com/nvie/gitflow) extension and sets up the Git configuration to use it. + +Shortcuts are also added to the `git` command to make it easier to use the `git-flow` commands: + +- `git beta` is a shortcut for `git flow release start` +- `git hfix` is a shortcut for `git flow hotfix start` +- `git prod` is a shortcut for `git flow release finish` and `git flow hotfix finish` + +Those shortcuts work in cunjunction with the `gitversion` utility to automatically update the version number of the application. + +## Interactive Utilities + +- `git fixup` - Amend the specified commit with current changes and rebase + +## Contributing + +If you have a feature that you would like to add to this repository, please open an issue or submit a pull request. diff --git a/src/githooks/_commit-and-tag-version.json b/src/githooks/_commit-and-tag-version.json new file mode 100644 index 0000000..7ec9301 --- /dev/null +++ b/src/githooks/_commit-and-tag-version.json @@ -0,0 +1,55 @@ +{ + "commit-and-tag-version": { + "bumpFiles": [ + { + "filename": "composer.json", + "type": "json" + }, + { + "filename": "package.json", + "type": "json" + }, + { + "filename": "VERSION", + "type": "plain-text" + } + ], + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "hidden": true + }, + { + "type": "docs", + "hidden": true + }, + { + "type": "style", + "hidden": true + }, + { + "type": "refactor", + "hidden": true + }, + { + "type": "perf", + "hidden": true + }, + { + "type": "test", + "hidden": true + } + ] + }, + "scripts": { + "release": "commit-and-tag-version --no-verify --" + } +} diff --git a/src/githooks/_commit-msg.sh b/src/githooks/_commit-msg.sh new file mode 100644 index 0000000..d32d67c --- /dev/null +++ b/src/githooks/_commit-msg.sh @@ -0,0 +1,12 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Apply commitlint rules to the latest commit message +npx chalk-cli --no-stdin -t "{blue →} Applying commitlint rules to the latest commit..." +PLUGINS=$(cat package.json | npx jqn '.commitlint.extends' | tr -d "'[]:") +npm list --global $PLUGINS 2>/dev/null 1>&2 || npm install --global $PLUGINS 2>/dev/null 1>&2 && npx commitlint --edit "$1" && npx devmoji -e diff --git a/src/githooks/_commitlint.json b/src/githooks/_commitlint.json new file mode 100644 index 0000000..972a804 --- /dev/null +++ b/src/githooks/_commitlint.json @@ -0,0 +1,31 @@ +{ + "commitlint": { + "extends": ["@commitlint/config-conventional"], + "rules": { + "subject-case": [ + 2, + "never", + ["start-case", "pascal-case", "upper-case"] + ], + "scope-enum": [ + 2, + "always", + [ + "deps", + "release", + "security", + "i18n", + "config", + "add", + "remove", + "breaking", + "modules", + "packages", + "ui-ux", + "api", + "model" + ] + ] + } + } +} diff --git a/src/githooks/_config.json b/src/githooks/_config.json new file mode 100644 index 0000000..d3f0e06 --- /dev/null +++ b/src/githooks/_config.json @@ -0,0 +1,7 @@ +{ + "config": { + "commitizen": { + "path": "@commitlint/cz-commitlint" + } + } +} diff --git a/src/githooks/_git-precommit-checks.json b/src/githooks/_git-precommit-checks.json new file mode 100644 index 0000000..f3b5e6c --- /dev/null +++ b/src/githooks/_git-precommit-checks.json @@ -0,0 +1,16 @@ +{ + "git-precommit-checks": { + "rules": [ + { + "message": "You've got leftover conflict markers", + "regex": "/^[<>|=]{4,}/m" + }, + { + "filter": "(^package\\.json|\\.git-precommit-checks.json)$", + "message": "You have unfinished devs", + "nonBlocking": "true", + "regex": "(?:FIXME|TODO)" + } + ] + } +} diff --git a/src/githooks/_lint-staged.json b/src/githooks/_lint-staged.json new file mode 100644 index 0000000..6f151ec --- /dev/null +++ b/src/githooks/_lint-staged.json @@ -0,0 +1,13 @@ +{ + "scripts": { + "lint": "lint-staged" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,md,html,css,json,vue, yaml, yml}": [ + "prettier --write" + ], + "*.php": [ + "composer lint" + ] + } +} \ No newline at end of file diff --git a/src/githooks/_post-checkout.sh b/src/githooks/_post-checkout.sh new file mode 100644 index 0000000..63ddb63 --- /dev/null +++ b/src/githooks/_post-checkout.sh @@ -0,0 +1,23 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Check if file changed +isChanged() { + git diff --name-only HEAD@{1} HEAD | grep "^$1" >/dev/null 2>&1 +} + +# Check if rebase +isRebase() { + git rev-parse --git-dir | grep -q 'rebase-merge' || git rev-parse --git-dir | grep -q 'rebase-apply' >/dev/null 2>&1 +} + +# Check if the current Git command is a rebase +if test "$GIT_COMMAND" = "rebase"; then + npx chalk-cli --no-stdin -t "{green ✔} Skip post-checkout hook during rebase." + exit 0 +fi diff --git a/src/githooks/_post-merge.sh b/src/githooks/_post-merge.sh new file mode 100644 index 0000000..691f863 --- /dev/null +++ b/src/githooks/_post-merge.sh @@ -0,0 +1,19 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Check if file is changed +isChanged() { + git diff --name-only HEAD@{1} HEAD | grep "^$1" >/dev/null 2>&1 +} + +# Checkout composer.lock or package-lock.json if changed +if isChanged 'composer.lock' || isChanged 'package-lock.json'; then + git checkout --theirs composer.lock package-lock.json && git add composer.lock package-lock.json + npx chalk-cli --no-stdin -t "{green.bold Files or changed.}" + npx chalk-cli --no-stdin -t "{green.bold Run composer/npm install to bring your dependencies up to date.}" +fi diff --git a/src/githooks/_pre-commit.sh b/src/githooks/_pre-commit.sh new file mode 100644 index 0000000..59ad1b4 --- /dev/null +++ b/src/githooks/_pre-commit.sh @@ -0,0 +1,42 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Check if the current Git command is a rebase +if test "$GIT_COMMAND" = "rebase"; then + npx chalk-cli --no-stdin -t "{green ✔} Skip pre-commit hook during rebase" + exit 0 +fi + +# Check if the current commit contains package.json changes +if git diff --cached --name-only | grep -q "package.json"; then + + # ensure that the package.json is valid and package-lock.json is up-to-date + WORKSP=$(cat package.json | npx jqn '.workspaces' | tr -d "'[]:") + if test "$WORKSP" = "undefined"; then + npm install || true + else + npm install --ws --if-present --include-workspace-root || true + fi + + # commit the updated package-lock.json + git add package-lock.json +fi + +# Check if the current commit contains composer.json changes +if git diff --cached --name-only | grep -q "composer.json"; then + + # ensure that the composer.json is valid and composer.lock is up-to-date + composer validate --no-check-publish || true + composer update --no-interaction --no-progress --no-suggest --no-dev || true + + # commit the updated composer.lock + git add composer.lock +fi + +npx git-precommit-checks +npx lint-staged --cwd ${INIT_CWD:-$PWD} diff --git a/src/githooks/_pre-push.sh b/src/githooks/_pre-push.sh new file mode 100644 index 0000000..71e477c --- /dev/null +++ b/src/githooks/_pre-push.sh @@ -0,0 +1,10 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Check if the current Git branch name is valid +npx validate-branch-name diff --git a/src/githooks/_prepare-commit-msg.sh b/src/githooks/_prepare-commit-msg.sh new file mode 100644 index 0000000..626e924 --- /dev/null +++ b/src/githooks/_prepare-commit-msg.sh @@ -0,0 +1,14 @@ +#!/bin/sh +export PATH=/usr/bin:$PATH + +# Enable colors +if [ -t 1 ]; then + exec >/dev/tty 2>&1 +fi + +# Edit commit message +if [ $(grep -cv -e '^#' -e '^$' .git/COMMIT_EDITMSG) -eq 0 ]; then + (exec /dev/null + +### Create package.json if not exists or is empty +if [ ! -f package.json -o ! -s package.json ]; then + echo "{}" >package.json +fi + +### Merge all package folder json files into top level package.json +find $hookDir -name _*.json | sort | while read file; do + + echo "Merge $file" | npx chalk-cli --stdin yellow + jq -s '.[1] * .[0]' $file package.json >/tmp/package.json + + #jq -S . /tmp/package.json > ./package.json + mv -f /tmp/package.json package.json +done diff --git a/src/githooks/devcontainer-feature.json b/src/githooks/devcontainer-feature.json new file mode 100644 index 0000000..9453ddf --- /dev/null +++ b/src/githooks/devcontainer-feature.json @@ -0,0 +1,13 @@ +{ + "name": "Git Hooks", + "id": "githooks", + "version": "1.0.6", + "description": "A feature to add useful Git hooks to your project", + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ], + "dependsOn": { + "ghcr.io/devcontainers/features/node": {} + }, + "entrypoint": "git /usr/local/share/githooks/config.sh" +} \ No newline at end of file diff --git a/src/githooks/install.sh b/src/githooks/install.sh new file mode 100644 index 0000000..8bbdf26 --- /dev/null +++ b/src/githooks/install.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +echo "Activating feature 'githooks'" + +### Get current directory +feature=$(dirname $(readlink -f $0)) +gitHooks=/usr/local/share/githooks + +### Create hooks directory if not exists +mkdir -p $gitHooks + +### For each sh hook starting with _ in the feature directory, copy it to the hooks directory +echo "Copying hooks to $gitHooks..." +find $feature -type f -name "_*.sh" | while read hook; do + hookName=$(basename $hook | sed 's/^_//;s/\.sh$//') + cp $hook $gitHooks/$hookName + chmod +x $gitHooks/$hookName + echo "Copied hook $hook => $gitHooks/$hookName" +done + +### For each json file starting with _ in the feature directory, copy it to the hooks directory +echo "Copying json files to $gitHooks..." +find $feature -type f -name "_*.json" | while read file; do + cp $file $gitHooks/$(basename $file) + echo "Copied $file => $gitHooks/$(basename $file)" +done + +### Copy the config script to the hooks directory and create a git alias for it +echo "Creating git alias for githooks configuration..." +cp $feature/configure.sh $gitHooks/configure.sh +chmod +x $gitHooks/configure.sh + +### Create git alias to configure hooks behaviors in current repository +git config --system alias.init-hooks "!sh -c '$feature/configure.sh' - "