diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b687497f..7251f3c2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,6 +3,10 @@ ## Connected Issues diff --git a/.github/scripts/check-authorized-release-actor.sh b/.github/scripts/check-authorized-release-actor.sh new file mode 100755 index 00000000..29417ba9 --- /dev/null +++ b/.github/scripts/check-authorized-release-actor.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Verifies github.actor is listed in CODEOWNERS (individual users only). +# Usage: check-authorized-release-actor.sh + +set -euo pipefail + +ACTOR="${GITHUB_ACTOR:?GITHUB_ACTOR required}" +CODEOWNERS_FILE="${1:-.github/CODEOWNERS}" + +AUTHORIZED_USERS=$(grep -o '@[a-zA-Z0-9_-]*[^/]' "$CODEOWNERS_FILE" | grep -v '@devrev/' | sed 's/@//' | tr '\n' ' ') + +if ! echo "$AUTHORIZED_USERS" | grep -q "\b${ACTOR}\b"; then + echo "::error::User $ACTOR is not authorized to run release workflows" >&2 + echo "Only users listed in $CODEOWNERS_FILE can trigger releases" >&2 + exit 1 +fi + +echo "Authorized release actor: $ACTOR" diff --git a/.github/scripts/check-npm-minor-line.sh b/.github/scripts/check-npm-minor-line.sh new file mode 100755 index 00000000..82d76abb --- /dev/null +++ b/.github/scripts/check-npm-minor-line.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Fails if npm already has a version in the same minor line >= target version. +# Usage: check-npm-minor-line.sh +# Example: check-npm-minor-line.sh @devrev/ts-adaas 1.19.0 + +set -euo pipefail + +PACKAGE="${1:?package name required}" +TARGET="${2:?target version required}" + +MAJOR=$(echo "$TARGET" | cut -d. -f1) +MINOR=$(echo "$TARGET" | cut -d. -f2) +PREFIX="${MAJOR}.${MINOR}." + +VERSIONS_JSON=$(npm view "$PACKAGE" versions --json 2>/dev/null || echo "[]") + +CONFLICT=$(echo "$VERSIONS_JSON" | jq -r --arg prefix "$PREFIX" --arg target "$TARGET" ' + [.[] | select(startswith($prefix))] | + map(select(. >= $target)) | + if length > 0 then .[-1] else empty end +') + +if [ -n "$CONFLICT" ]; then + echo "::error::Cannot publish $TARGET: npm already has version $CONFLICT in the ${MAJOR}.${MINOR} line" >&2 + exit 1 +fi + +echo "No conflicting versions found in ${MAJOR}.${MINOR} line for target $TARGET" diff --git a/.github/scripts/ci-commitlint.sh b/.github/scripts/ci-commitlint.sh new file mode 100755 index 00000000..867094de --- /dev/null +++ b/.github/scripts/ci-commitlint.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Validates PR title and all commits in a pull request for conventional or release-command format. +# Requires: PR_TITLE, BASE_SHA, HEAD_SHA environment variables. + +set -euo pipefail + +PR_TITLE="${PR_TITLE:?PR_TITLE required}" +BASE_SHA="${BASE_SHA:?BASE_SHA required}" +HEAD_SHA="${HEAD_SHA:?HEAD_SHA required}" + +lint_message() { + local msg="$1" + local first + first=$(echo "$msg" | head -n1) + + case "$first" in + beta/*|release/*|patch/*) + bash .github/scripts/validate-release-command.sh "$first" + ;; + *) + echo "$msg" | npx commitlint + ;; + esac +} + +echo "Validating PR title: $PR_TITLE" +lint_message "$PR_TITLE" + +if [ "$BASE_SHA" = "$HEAD_SHA" ]; then + echo "No commits to validate" + exit 0 +fi + +echo "Validating commits between $BASE_SHA and $HEAD_SHA" +while read -r sha; do + [ -z "$sha" ] && continue + msg=$(git log -1 --pretty=%B "$sha") + echo "Checking commit $sha" + lint_message "$msg" +done < <(git rev-list "${BASE_SHA}..${HEAD_SHA}") + +echo "All commit messages valid" diff --git a/.github/scripts/parse-release-commit.sh b/.github/scripts/parse-release-commit.sh new file mode 100755 index 00000000..bdaff656 --- /dev/null +++ b/.github/scripts/parse-release-commit.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Parses the first line of a commit message for release commands. +# Usage: parse-release-commit.sh "" "" +# Writes command, version, and release_line to GITHUB_OUTPUT when set. + +set -euo pipefail + +COMMIT_MSG="${1:?commit message required}" +REF_NAME="${2:?ref name required}" + +FIRST_LINE=$(echo "$COMMIT_MSG" | head -n1) + +write_output() { + if [ -n "${GITHUB_OUTPUT:-}" ]; then + echo "command=$1" >> "$GITHUB_OUTPUT" + echo "version=${2:-}" >> "$GITHUB_OUTPUT" + echo "release_line=${3:-}" >> "$GITHUB_OUTPUT" + else + echo "command=$1" + echo "version=${2:-}" + echo "release_line=${3:-}" + fi +} + +if [[ "$FIRST_LINE" =~ ^beta/([0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+)$ ]]; then + if [[ "$REF_NAME" != "main" ]]; then + echo "::error::beta release commands must be merged to main (current branch: $REF_NAME)" >&2 + exit 1 + fi + write_output "beta" "${BASH_REMATCH[1]}" "" + exit 0 +fi + +if [[ "$FIRST_LINE" =~ ^release/([0-9]+\.[0-9]+)$ ]]; then + if [[ "$REF_NAME" != "main" ]]; then + echo "::error::release commands must be merged to main (current branch: $REF_NAME)" >&2 + exit 1 + fi + MINOR="${BASH_REMATCH[1]}" + write_output "release" "${MINOR}.0" "$MINOR" + exit 0 +fi + +if [[ "$FIRST_LINE" =~ ^patch/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + MINOR=$(echo "$VERSION" | cut -d. -f1-2) + EXPECTED_BRANCH="release/$MINOR" + if [[ "$REF_NAME" != "$EXPECTED_BRANCH" ]]; then + echo "::error::patch/$VERSION must be merged to $EXPECTED_BRANCH (current branch: $REF_NAME)" >&2 + exit 1 + fi + write_output "patch" "$VERSION" "$MINOR" + exit 0 +fi + +write_output "none" "" "" diff --git a/.github/scripts/pr-release-comment.sh b/.github/scripts/pr-release-comment.sh new file mode 100755 index 00000000..15dc08e6 --- /dev/null +++ b/.github/scripts/pr-release-comment.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# Prints a PR comment body for a release-command PR title. +# Usage: pr-release-comment.sh "" "" + +set -euo pipefail + +TITLE="${1:?PR title required}" +BASE="${2:?base branch required}" +FIRST_LINE=$(echo "$TITLE" | head -n1) + +if [[ "$FIRST_LINE" =~ ^beta/([0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + if [[ "$BASE" != "main" ]]; then + echo "::error::Beta releases must target \`main\` (current base: \`$BASE\`)" + exit 1 + fi + cat <= \`$VERSION\`. + +**Requirements:** squash merge with PR title as the commit message; only CODEOWNERS can merge. +EOF + exit 0 +fi + +if [[ "$FIRST_LINE" =~ ^patch/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + MINOR=$(echo "$VERSION" | cut -d. -f1-2) + EXPECTED="release/$MINOR" + if [[ "$BASE" != "$EXPECTED" ]]; then + echo "::error::Patch releases must target \`$EXPECTED\` (current base: \`$BASE\`)" + exit 1 + fi + cat <&2 +exit 1 diff --git a/.github/scripts/validate-release-command.sh b/.github/scripts/validate-release-command.sh new file mode 100755 index 00000000..e8b91a5c --- /dev/null +++ b/.github/scripts/validate-release-command.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Validates release command format (branch-agnostic). Exits 0 on valid format, 1 otherwise. +# Usage: validate-release-command.sh "" + +set -euo pipefail + +MSG="${1:?commit message required}" +FIRST_LINE=$(echo "$MSG" | head -n1) + +if [[ "$FIRST_LINE" =~ ^beta/[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then + exit 0 +fi + +if [[ "$FIRST_LINE" =~ ^release/[0-9]+\.[0-9]+$ ]]; then + exit 0 +fi + +if [[ "$FIRST_LINE" =~ ^patch/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + exit 0 +fi + +echo "Invalid release command format: $FIRST_LINE" >&2 +echo "Expected one of:" >&2 +echo " beta/X.Y.Z-beta.N (e.g. beta/1.19.4-beta.0)" >&2 +echo " release/X.Y (e.g. release/1.19)" >&2 +echo " patch/X.Y.Z (e.g. patch/1.19.5)" >&2 +exit 1 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 418d420b..86e59277 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,12 +5,40 @@ on: branches: - main - v2 + - "release/**" push: branches: - main - v2 + - "release/**" jobs: + commitlint: + name: Commit message lint + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.14.0 + + - name: Install dependencies + run: npm ci + + - name: Validate PR title and commits + env: + PR_TITLE: ${{ github.event.pull_request.title }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: bash .github/scripts/ci-commitlint.sh + build: name: Build runs-on: ubuntu-latest @@ -18,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: npm ci @@ -33,7 +61,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: npm ci @@ -51,7 +79,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: npm ci @@ -73,7 +101,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: npm ci diff --git a/.github/workflows/release-automation.yml b/.github/workflows/release-automation.yml new file mode 100644 index 00000000..8760524c --- /dev/null +++ b/.github/workflows/release-automation.yml @@ -0,0 +1,331 @@ +# Commit-message-driven releases for @devrev/ts-adaas. +# +# Merge a PR using the PR title (squash merge) as: +# beta/X.Y.Z-beta.N -> publish beta to npm from main +# release/X.Y -> create release/X.Y branch and publish X.Y.0 to npm +# patch/X.Y.Z -> publish patch from release/X.Y and open backport PR to main +# +# See CONTRIBUTING.md for the full release process. + +name: Release automation + +on: + push: + branches: + - main + - "release/**" + +concurrency: + group: release-automation + cancel-in-progress: false + +env: + NPM_PACKAGE: "@devrev/ts-adaas" + NODE_VERSION: "22.14.0" + +jobs: + parse-release-command: + name: Parse release command + runs-on: ubuntu-latest + outputs: + command: ${{ steps.parse.outputs.command }} + version: ${{ steps.parse.outputs.version }} + release_line: ${{ steps.parse.outputs.release_line }} + steps: + - uses: actions/checkout@v4 + - name: Parse commit message + id: parse + env: + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + REF_NAME: ${{ github.ref_name }} + run: bash .github/scripts/parse-release-commit.sh "$COMMIT_MESSAGE" "$REF_NAME" + + beta-release: + name: Beta release + needs: parse-release-command + if: needs.parse-release-command.outputs.command == 'beta' + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + pull-requests: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GH_SVC_TOKEN }} + + - name: Check authorization + run: bash .github/scripts/check-authorized-release-actor.sh + + - uses: actions/setup-node@v4 + with: + registry-url: https://registry.npmjs.org/ + node-version: ${{ env.NODE_VERSION }} + scope: "@devrev" + + - name: Ensure npm 11.5.1+ + run: npm install -g 'npm@>=11.5.1' + + - name: Git configuration + run: | + git config --global user.email "svc-devrev-sdk@devrev.ai" + git config --global user.name "svc-devrev-sdk" + + - name: Set version + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: npm version "$VERSION" --no-git-tag-version --allow-same-version + + - name: Run tests + run: | + npm ci + npm run build + npm run test + + - name: Commit, tag, and publish + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: | + git add package.json package-lock.json + git diff --staged --quiet || git commit -m "chore: release $VERSION" + git tag -f "$VERSION" + npm publish --provenance --verbose --access public --tag beta + + - name: Push changes + env: + GITHUB_TOKEN: ${{ secrets.GH_SVC_TOKEN }} + run: git push origin main && git push --tags + + release-line: + name: Release line + needs: parse-release-command + if: needs.parse-release-command.outputs.command == 'release' + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + outputs: + new_version: ${{ needs.parse-release-command.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GH_SVC_TOKEN }} + + - name: Check authorization + run: bash .github/scripts/check-authorized-release-actor.sh + + - name: Check release branch does not exist + env: + RELEASE_LINE: ${{ needs.parse-release-command.outputs.release_line }} + run: | + if git ls-remote --exit-code --heads origin "release/$RELEASE_LINE" >/dev/null 2>&1; then + echo "::error::Branch release/$RELEASE_LINE already exists" + exit 1 + fi + + - uses: actions/setup-node@v4 + with: + registry-url: https://registry.npmjs.org/ + node-version: ${{ env.NODE_VERSION }} + scope: "@devrev" + + - name: Ensure npm 11.5.1+ + run: npm install -g 'npm@>=11.5.1' + + - name: Check npm minor line + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: bash .github/scripts/check-npm-minor-line.sh "$NPM_PACKAGE" "$VERSION" + + - name: Git configuration + run: | + git config --global user.email "svc-devrev-sdk@devrev.ai" + git config --global user.name "svc-devrev-sdk" + + - name: Create release branch and set version + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + RELEASE_LINE: ${{ needs.parse-release-command.outputs.release_line }} + run: | + git checkout -b "release/$RELEASE_LINE" + npm version "$VERSION" --no-git-tag-version --allow-same-version + + - name: Run tests + run: | + npm ci + npm run build + npm run test + + - name: Commit, tag, and publish + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + RELEASE_LINE: ${{ needs.parse-release-command.outputs.release_line }} + run: | + git add package.json package-lock.json + git commit -m "chore: release $VERSION" + git tag "$VERSION" + npm publish --provenance --verbose --access public --tag latest + + - name: Push release branch + env: + GITHUB_TOKEN: ${{ secrets.GH_SVC_TOKEN }} + RELEASE_LINE: ${{ needs.parse-release-command.outputs.release_line }} + run: | + git push -u origin "release/$RELEASE_LINE" + git push --tags + + patch-release: + name: Patch release + needs: parse-release-command + if: needs.parse-release-command.outputs.command == 'patch' + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + outputs: + new_version: ${{ needs.parse-release-command.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GH_SVC_TOKEN }} + + - name: Check authorization + run: bash .github/scripts/check-authorized-release-actor.sh + + - uses: actions/setup-node@v4 + with: + registry-url: https://registry.npmjs.org/ + node-version: ${{ env.NODE_VERSION }} + scope: "@devrev" + + - name: Ensure npm 11.5.1+ + run: npm install -g 'npm@>=11.5.1' + + - name: Git configuration + run: | + git config --global user.email "svc-devrev-sdk@devrev.ai" + git config --global user.name "svc-devrev-sdk" + + - name: Set version + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: npm version "$VERSION" --no-git-tag-version --allow-same-version + + - name: Run tests + run: | + npm ci + npm run build + npm run test + + - name: Commit, tag, and publish + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: | + git add package.json package-lock.json + git diff --staged --quiet || git commit -m "chore: release $VERSION" + git tag -f "$VERSION" + npm publish --provenance --verbose --access public --tag latest + + - name: Push changes + env: + GITHUB_TOKEN: ${{ secrets.GH_SVC_TOKEN }} + run: git push origin HEAD && git push --tags + + backport-to-main: + name: Backport patch to main + needs: [parse-release-command, patch-release] + if: needs.parse-release-command.outputs.command == 'patch' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GH_SVC_TOKEN }} + + - name: Check authorization + run: bash .github/scripts/check-authorized-release-actor.sh + + - name: Git configuration + run: | + git config --global user.email "svc-devrev-sdk@devrev.ai" + git config --global user.name "svc-devrev-sdk" + + - name: Cherry-pick patch merge onto main + env: + VERSION: ${{ needs.parse-release-command.outputs.version }} + MERGE_SHA: ${{ github.sha }} + run: | + BACKPORT_BRANCH="backport/patch-$VERSION" + if git ls-remote --exit-code --heads origin "$BACKPORT_BRANCH" >/dev/null 2>&1; then + echo "::error::Backport branch $BACKPORT_BRANCH already exists" + exit 1 + fi + git fetch origin main + git checkout -b "$BACKPORT_BRANCH" origin/main + git cherry-pick "$MERGE_SHA" + + - name: Push backport branch + env: + GITHUB_TOKEN: ${{ secrets.GH_SVC_TOKEN }} + VERSION: ${{ needs.parse-release-command.outputs.version }} + run: git push origin "backport/patch-$VERSION" + + - name: Open backport pull request + env: + GH_TOKEN: ${{ secrets.GH_SVC_TOKEN }} + VERSION: ${{ needs.parse-release-command.outputs.version }} + RELEASE_LINE: ${{ needs.parse-release-command.outputs.release_line }} + run: | + EXISTING=$(gh pr list --head "backport/patch-$VERSION" --base main --json number --jq '.[0].number') + if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then + echo "Backport PR already exists: #$EXISTING" + exit 0 + fi + gh pr create \ + --base main \ + --head "backport/patch-$VERSION" \ + --title "backport: patch/$VERSION" \ + --body "$(cat < comment.md + + - name: Post or update PR comment + uses: actions/github-script@v7 + env: + COMMENT_FILE: comment.md + with: + script: | + const fs = require('fs'); + const marker = ''; + const body = `${marker}\n${fs.readFileSync(process.env.COMMENT_FILE, 'utf8')}`; + const { owner, repo } = context.repo; + const issue_number = context.payload.pull_request.number; + + const { data: comments } = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + + const existing = comments.find((c) => + c.user.type === 'Bot' && c.body.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body, + }); + } diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 31452734..c052714e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,6 +1,9 @@ -# This workflow is used to release a new version of @devrev/ts-adaas package to -# npm registry and generate release notes using softprops/action-gh-release -# action. It consists of two jobs: +# EMERGENCY ONLY — normal releases use commit-message automation (see +# .github/workflows/release-automation.yml and CONTRIBUTING.md). +# +# This workflow is used to manually release a new version of @devrev/ts-adaas +# to the npm registry and generate release notes using softprops/action-gh-release. +# It consists of two jobs: # # - release: # 1. User runs the "Release package" workflow manually via the GitHub Actions UI. @@ -21,14 +24,14 @@ # 1. Generates release notes using softprops/action-gh-release action and updates # the GitHub release documentation. -name: Release package -run-name: "Release ${{ inputs.release-type }} by ${{ github.actor }}${{ inputs.dry-run && ' --dry-run' || '' }}" +name: "Emergency release (manual)" +run-name: "Emergency release ${{ inputs.release-type }} by ${{ github.actor }}${{ inputs.dry-run && ' --dry-run' || '' }}" on: workflow_dispatch: inputs: release-type: - description: "Release type" + description: "Release type (emergency use only — prefer commit-message releases)" type: choice required: true options: [patch, minor, beta] diff --git a/.gitignore b/.gitignore index fa61c0ef..dbe25ded 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,7 @@ dist .idea .claude .cursor +.husky/_/ src/tests/backwards-compatibility/temp/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 00000000..80f0e3fd --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,12 @@ +#!/usr/bin/env sh + +FIRST_LINE=$(head -n1 "$1") + +case "$FIRST_LINE" in + beta/*|release/*|patch/*) + bash .github/scripts/validate-release-command.sh "$FIRST_LINE" || exit 1 + exit 0 + ;; +esac + +npx --no -- commitlint --edit "$1" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ae19ca1d..90a2661b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,52 @@ We follow Semantic Versioning (SemVer) for versioning, with the format `MAJOR.MI - `MINOR` - New features, backwards compatible - `PATCH` - Bug fixes, no new features +## Commit Messages + +- Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). +- Example: `feat: add retry handling` +- Local git hooks reject non-conforming commit messages. + +Release commands use a separate format (see [Releases](#releases) below) and are exempt from Conventional Commits rules. + +## Releases + +Releases are automated via GitHub Actions when a PR is **squash-merged** with the PR title used as the commit message. Enable **"Use pull request title as commit message"** in the repository merge settings. + +Only users listed in [`.github/CODEOWNERS`](.github/CODEOWNERS) can trigger release workflows. + +### Branch strategy + +- **`main`** — feature PRs (`feat:`, `fix:`, etc.) merge here during development. +- **`release/X.Y`** — long-lived branch for a minor release line (e.g. `release/1.19`). Hotfixes merge here. +- Patches published from a release branch are automatically backported to `main` via a PR. + +### Release commands (PR titles) + +| PR title | Target branch | On merge | +|----------|---------------|----------| +| `beta/1.19.4-beta.0` | `main` | Publish `@beta` to npm at that exact version | +| `release/1.19` | `main` | Create `release/1.19`, publish `1.19.0` to npm `@latest` | +| `patch/1.19.5` | `release/1.19` | Publish `1.19.5` to npm `@latest`, open backport PR to `main` | + +**Examples:** + +- `beta/1.19.4-beta.0` — prerelease on `main` +- `release/1.19` — cut the `1.19` maintenance line (fails if `release/1.19` exists or npm already has `1.19.x` ≥ `1.19.0`) +- `patch/1.19.5` — hotfix on `release/1.19` only + +### Hotfix workflow + +1. Branch from `release/X.Y`. +2. Open a PR to `release/X.Y` with normal `fix:` / `feat:` commits. +3. Merge a separate PR (or the same PR if it only contains the release) titled `patch/X.Y.Z` to publish and backport. + +The automated backport PR is titled `backport: patch/X.Y.Z`. Resolve cherry-pick conflicts manually if needed. + +### Emergency manual release + +If automation is unavailable, CODEOWNERS can run **Emergency release (manual)** in GitHub Actions (`.github/workflows/release.yaml`). This is not the normal release path. + ## Testing All new code must include comprehensive tests. Follow these testing guidelines: diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 00000000..84dcb122 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/package-lock.json b/package-lock.json index 9d56f7ed..0727b207 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ "yargs": "^17.7.2" }, "devDependencies": { + "@commitlint/cli": "^20.5.3", + "@commitlint/config-conventional": "^20.5.3", "@microsoft/api-extractor": "^7.57.6", "@microsoft/api-extractor-model": "^7.30.7", "@types/jest": "^29.5.14", @@ -27,6 +29,7 @@ "eslint": "9.32.0", "eslint-config-prettier": "^9.1.2", "eslint-plugin-prettier": "4.0.0", + "husky": "^9.1.7", "jest": "^29.7.0", "jiti": "^2.6.1", "prettier": "^2.8.3", @@ -551,6 +554,308 @@ "dev": true, "license": "MIT" }, + "node_modules/@commitlint/cli": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.3.tgz", + "integrity": "sha512-OJdL0EXWD5y9LPa0nr/geOwzaS8BsdaybKkcloB0JgsguGxNv2R+hC2FTPqrAcprg35zF33KOQerY0x8W1aesA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^20.5.0", + "@commitlint/lint": "^20.5.3", + "@commitlint/load": "^20.5.3", + "@commitlint/read": "^20.5.0", + "@commitlint/types": "^20.5.0", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.3.tgz", + "integrity": "sha512-j34Qqeaa152chJgz2ysyk0BCpHenJn1lV0Rx0VXf8k3ccQcED+48EZrzMvo9jLmJUyBrrBwvu89I+2er4gW7QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "conventional-changelog-conventionalcommits": "^9.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", + "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.3.tgz", + "integrity": "sha512-4i4AgNvH62owG9MwSiWKrle7HGNpBHHdLnWFIp5fTsHUYe5kRuh15t08L/0pdbbrRk8JKXQxxN4hZQcn+szkrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "es-toolkit": "^1.46.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", + "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", + "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", + "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@commitlint/lint": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.3.tgz", + "integrity": "sha512-M7JbWBNr2gXKaPc4i/KipsuW1gkDHpj35KPjWtKy3Z+2AQw5wu1gBi1LIO0uoaij67CqY4K8PxPZSGens4evCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^20.5.0", + "@commitlint/parse": "^20.5.0", + "@commitlint/rules": "^20.5.3", + "@commitlint/types": "^20.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.3.tgz", + "integrity": "sha512-1FDZWuKyu98Myb8i7Tp31jPU2rZpOwAdYRyJcy2KoGg7Xk2A+bgHN8smhMaaNSNkmE8fwt53BokywZq8Gv/5XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.5.0", + "@commitlint/execute-rule": "^20.0.0", + "@commitlint/resolve-extends": "^20.5.3", + "@commitlint/types": "^20.5.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", + "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", + "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^20.5.0", + "conventional-changelog-angular": "^8.2.0", + "conventional-commits-parser": "^6.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", + "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^20.4.3", + "@commitlint/types": "^20.5.0", + "git-raw-commits": "^5.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.3.tgz", + "integrity": "sha512-+ogW9v/u9JqpvAgTrLra/YTFo0KkjU6iNblF89pPsj4NebNc+DAWctsludwezI8YnsjBmfHpApSwcXprN/f/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^20.5.0", + "@commitlint/types": "^20.5.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "import-meta-resolve": "^4.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/rules": { + "version": "20.5.3", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.3.tgz", + "integrity": "sha512-MPlMnb9D3wbszYMp+1hPtuhtPJndRo6I6yfkZVA4+jR8w7Kqp0u2u/Y+gzbaItx5Lltq5rw7FSZQWJMoXUC4NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^20.5.3", + "@commitlint/message": "^20.4.3", + "@commitlint/to-lines": "^20.0.0", + "@commitlint/types": "^20.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", + "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", + "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", + "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-parser": "^6.3.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", + "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^1.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.4.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1565,6 +1870,35 @@ "string-argv": "~0.3.1" } }, + "node_modules/@simple-libs/child-process-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", + "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -2182,6 +2516,13 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2630,6 +2971,17 @@ "node": ">= 0.8" } }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2637,6 +2989,49 @@ "dev": true, "license": "MIT" }, + "node_modules/conventional-changelog-angular": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz", + "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz", + "integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2644,6 +3039,61 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", + "integrity": "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cosmiconfig-typescript-loader/node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -2775,6 +3225,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2827,6 +3290,16 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -2882,6 +3355,17 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3493,6 +3977,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/git-raw-commits": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", + "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@conventional-changelog/git-client": "^2.6.0", + "meow": "^13.0.0" + }, + "bin": { + "git-raw-commits": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3559,6 +4060,22 @@ "node": "*" } }, + "node_modules/global-directory": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "6.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -3692,6 +4209,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3749,6 +4282,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3778,6 +4322,16 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3853,6 +4407,29 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-retry-allowed": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", @@ -4794,6 +5371,19 @@ "node": ">= 0.4" } }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5684,6 +6274,16 @@ "node": "*" } }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/package.json b/package.json index 27d1e7fc..e7617a5b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "typings": "./dist/index.d.ts", "scripts": { "build": "tsc -p ./tsconfig.json", - "prepare": "npm run build", + "prepare": "husky && npm run build", "start": "ts-node src/index.ts", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -29,6 +29,8 @@ "author": "devrev", "license": "ISC", "devDependencies": { + "@commitlint/cli": "^20.5.3", + "@commitlint/config-conventional": "^20.5.3", "@microsoft/api-extractor": "^7.57.6", "@microsoft/api-extractor-model": "^7.30.7", "@types/jest": "^29.5.14", @@ -38,6 +40,7 @@ "eslint": "9.32.0", "eslint-config-prettier": "^9.1.2", "eslint-plugin-prettier": "4.0.0", + "husky": "^9.1.7", "jest": "^29.7.0", "jiti": "^2.6.1", "prettier": "^2.8.3",