diff --git a/.github/workflows/check-branch-name.yml b/.github/workflows/check-branch-name.yml new file mode 100644 index 000000000..b4cb8f73d --- /dev/null +++ b/.github/workflows/check-branch-name.yml @@ -0,0 +1,19 @@ +name: Validate Branch Name + +on: + pull_request: + types: [ opened, synchronize, reopened ] + +jobs: + check-branch-name: + runs-on: ubuntu-latest + steps: + - name: Check branch name format + run: | + BRANCH_NAME="${{ github.head_ref }}" + echo "Checking branch name: $BRANCH_NAME" + if [[ ! "$BRANCH_NAME" =~ ^(chore|feature|fix|hotfix|patch|refactor)/[a-z0-9._-]+$ ]]; then + echo "❌ Invalid branch name: $BRANCH_NAME" + echo "✅ Should match: feature/foo-bar, hotfix/issue-123, etc." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/check-commit-message.yml b/.github/workflows/check-commit-message.yml new file mode 100644 index 000000000..11999b519 --- /dev/null +++ b/.github/workflows/check-commit-message.yml @@ -0,0 +1,10 @@ +name: Check Commit Messages + +on: [pull_request] + +jobs: + lint-commits: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: wagoid/commitlint-github-action@v5 \ No newline at end of file diff --git a/.gitmessage.txt b/.gitmessage.txt new file mode 100644 index 000000000..c02d378a4 --- /dev/null +++ b/.gitmessage.txt @@ -0,0 +1,26 @@ +# Conventional Commit template +# Lines starting with # are ignored by Git. +# Keep header ≤72 chars; subject in imperative mood. +# Examples of : feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert + +(): + +# Why? +# - What problem does this change solve? +# - Why now / context? + +# What changed? +# - Key changes, trade-offs, alternatives considered +# - Any user-facing behavior changes + +# How was it tested? +# - Test strategy, manual steps, screenshots if relevant + +# Notes +# - Known limitations, follow-ups, migrations + +# Footer (one per line) +# BREAKING CHANGE: +# Closes #123 +# Relates-to #456 +# Co-authored-by: Name diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 000000000..c3c62bfc0 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx commitlint --edit "$1" \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..5b811e532 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.19.4 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2b5add4e4..49f2d7998 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,9 +7,10 @@ ARG XDEBUG_VERSION="xdebug-3.3.2" ENV COMPOSER_ALLOW_SUPERUSER=1 ENV GITHUB_OAUTH_TOKEN=$GITHUB_OAUTH_TOKEN ENV PHP_DIR /usr/local/etc/php +ARG YARN_VERSION="1.22.22" +ARG NVM_VERSION="v0.40.3" +ARG NODE_VERSION="20.19.4" -ARG NVM_VERSION="v0.39.7" -ARG NODE_VERSION="18.20.2" # base packages RUN apt-get update RUN apt-get install -y \ @@ -33,16 +34,18 @@ RUN apt-get install -y \ libmagickwand-dev -# nvm # nvm + node + yarn via corepack ENV NVM_DIR=/root/.nvm RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh | bash # Install Node, enable Corepack (Yarn) -RUN bash -lc "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && corepack enable && corepack prepare yarn@stable --activate" - +RUN bash -lc "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && corepack enable && corepack prepare yarn@$YARN_VERSION --activate" RUN apt clean && rm -rf /var/lib/apt/lists/* +# Set up our PATH correctly so we don't have to long-reference npm, node, &c. +ENV NODE_PATH=$NVM_DIR/versions/node/v$NODE_VERSION/lib/node_modules +ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN install-php-extensions bcmath exif gettext gd imagick mbstring openssl pcntl pdo pdo_mysql sockets ${XDEBUG_VERSION} zip apcu redis igbinary diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 000000000..2bcf6ba3e --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,76 @@ +const WIP_REGEX = /^wip[:]?$/i; +const RULE_ERROR_LEVEL = 2; +const HEADER_MAX_LENGTH = 100; +const SUBJECT_MIN_LENGTH = 5; + +module.exports = { + extends: ["@commitlint/config-conventional"], + rules: { + "custom-subject-empty": [RULE_ERROR_LEVEL, "never"], + "custom-type-enum": [RULE_ERROR_LEVEL, "always"], + "custom-no-wip": [RULE_ERROR_LEVEL, "always"], + "custom-no-wip-subject": [RULE_ERROR_LEVEL, "always"], + "subject-min-length": [RULE_ERROR_LEVEL, "always", SUBJECT_MIN_LENGTH], + "subject-case": [0], // optional: allow flexibility in subject case + "header-max-length": [RULE_ERROR_LEVEL, "always", HEADER_MAX_LENGTH] + }, + plugins: [ + { + rules: { + "custom-subject-empty": ({ subject }) => + subject && subject.trim().length > 0 + ? [true] + : [ + false, + "The commit needs a description after the colon (:). Eg: feat: add new feature" + ], + "custom-type-enum": ({ type }) => { + const allowedTypes = [ + "feat", + "fix", + "hotfix", + "docs", + "style", + "refactor", + "test", + "chore" + ]; + + if (!type) { + return [ + false, + "Missing type. Use: feat, fix, chore, refactor, etc." + ]; + } + + if (!allowedTypes.includes(type)) { + return [ + false, + `Type "${type} is invalid". Allowed types: ${allowedTypes.join( + ", " + )}` + ]; + } + + return [true]; + }, + "custom-no-wip": ({ header }) => { + const isWipOnly = WIP_REGEX.test(header.trim()); + return [ + !isWipOnly, + "Commit message cannot be just \"WIP\" (use a descriptive message)." + ]; + }, + "custom-no-wip-subject": ({ subject }) => { + if (!subject) return [true]; + + const isWipOnly = WIP_REGEX.test(subject.trim()); + return [ + !isWipOnly, + "Subject cannot be just \"WIP\". Use a descriptive message like \"implement user login\" instead." + ]; + } + } + } + ] +}; \ No newline at end of file diff --git a/gitmessage.txt b/gitmessage.txt new file mode 100644 index 000000000..c02d378a4 --- /dev/null +++ b/gitmessage.txt @@ -0,0 +1,26 @@ +# Conventional Commit template +# Lines starting with # are ignored by Git. +# Keep header ≤72 chars; subject in imperative mood. +# Examples of : feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert + +(): + +# Why? +# - What problem does this change solve? +# - Why now / context? + +# What changed? +# - Key changes, trade-offs, alternatives considered +# - Any user-facing behavior changes + +# How was it tested? +# - Test strategy, manual steps, screenshots if relevant + +# Notes +# - Known limitations, follow-ups, migrations + +# Footer (one per line) +# BREAKING CHANGE: +# Closes #123 +# Relates-to #456 +# Co-authored-by: Name diff --git a/package.json b/package.json index 87481c73f..a4a40aa24 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,13 @@ "private": true, "scripts": { "prod": "gulp --production", - "dev": "gulp watch" + "dev": "gulp watch", + "prepare": "husky", + "postinstall":"git config --local commit.template .gitmessage.txt || true" }, "devDependencies": { - "gulp": "^3.9.1", - "laravel-elixir": "^5.0.0", - "bootstrap-sass": "^3.3.0" - } + "@commitlint/config-conventional": "^19.8.1", + "husky": "^9.0.11" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..9399a6f4a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,80 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@commitlint/config-conventional@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz#eab42df58cda44f18410ae0cbd6785ece00f214b" + integrity sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ== + dependencies: + "@commitlint/types" "^19.8.1" + conventional-changelog-conventionalcommits "^7.0.2" + +"@commitlint/types@^19.8.1": + version "19.8.1" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-19.8.1.tgz#7971fbd56b0cfb31692a4e1941b74ac8217c44e5" + integrity sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw== + dependencies: + "@types/conventional-commits-parser" "^5.0.0" + chalk "^5.3.0" + +"@types/conventional-commits-parser@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz#8cb81cf170853496cbc501a3b32dcf5e46ffb61a" + integrity sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "24.5.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446" + integrity sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ== + dependencies: + undici-types "~7.12.0" + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== + +chalk@^5.3.0: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== + dependencies: + array-ify "^1.0.0" + dot-prop "^5.1.0" + +conventional-changelog-conventionalcommits@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz#aa5da0f1b2543094889e8cf7616ebe1a8f5c70d5" + integrity sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w== + dependencies: + compare-func "^2.0.0" + +dot-prop@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +husky@^9.0.11: + version "9.1.7" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" + integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +undici-types@~7.12.0: + version "7.12.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" + integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==