diff --git a/.github/workflows/auto-publish.yml b/.github/workflows/auto-publish.yml new file mode 100644 index 00000000..2eb2889a --- /dev/null +++ b/.github/workflows/auto-publish.yml @@ -0,0 +1,400 @@ +name: Auto Publish to NPM + +on: + pull_request: + types: [closed] + branches: + - main + - master + +permissions: + contents: write + pull-requests: write + packages: write + actions: read + +jobs: + auto-publish: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + cache: 'yarn' + + - name: Setup Yarn + run: | + corepack enable + yarn --version + + - name: Validate branch patterns + id: validate-branch + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + echo "Branch name: $BRANCH_NAME" + + # Allow more flexible branch patterns + if [[ $BRANCH_NAME =~ ^(feat|feature|fix|bugfix|break|breaking|hotfix|chore)/[a-zA-Z0-9-_]+$ ]]; then + echo "โœ… Branch pattern accepted: $BRANCH_NAME" + echo "should_publish=true" >> $GITHUB_OUTPUT + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + else + echo "โŒ Branch '$BRANCH_NAME' doesn't match required patterns" + echo "should_publish=false" >> $GITHUB_OUTPUT + exit 0 + fi + + - name: Install dependencies + if: steps.validate-branch.outputs.should_publish == 'true' + run: yarn install --frozen-lockfile --prefer-offline + + - name: Validate package integrity + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + # Verify that package.json is well-formed + node -e "console.log('Package name:', require('./package.json').name)" + + # Verify that required scripts exist + if ! yarn run --help | grep -q "test:ci"; then + echo "โŒ Missing test:ci script in package.json" + exit 1 + fi + + if ! yarn run --help | grep -q "dist"; then + echo "โŒ Missing dist script in package.json" + exit 1 + fi + + - name: Run quality checks + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + # Linting + if yarn run --help | grep -q "lint"; then + echo "๐Ÿ” Running linter..." + yarn lint + fi + + # Type checking + if yarn run --help | grep -q "type-check"; then + echo "๐Ÿ” Type checking..." + yarn type-check + fi + + - name: Run tests + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + echo "๐Ÿงช Running tests..." + yarn test:ci + + # Check coverage if exists + if [ -f "coverage/lcov.info" ]; then + echo "๐Ÿ“Š Coverage report generated" + fi + + - name: Build package + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + echo "๐Ÿ—๏ธ Building package..." + yarn dist + + # Verify that the build generated files + if [ ! -d "dist" ] && [ ! -d "lib" ] && [ ! -d "build" ]; then + echo "โŒ No build output found" + exit 1 + fi + + - name: Configure Git + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + git config --local user.email "kubit-bot@github.com" + git config --local user.name "Kubit Release Bot" + + - name: Determine version bump (Enhanced) + if: steps.validate-branch.outputs.should_publish == 'true' + id: version-bump + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + PR_TITLE="${{ github.event.pull_request.title }}" + PR_BODY="${{ github.event.pull_request.body }}" + + echo "๐Ÿ” Analyzing PR for version bump..." + echo "Branch: $BRANCH_NAME" + echo "Title: $PR_TITLE" + + # 1. Check explicit breaking change markers + if echo "$PR_BODY" | grep -qi "BREAKING CHANGE:" || \ + echo "$PR_TITLE" | grep -q "!" || \ + echo "$PR_TITLE" | grep -qi "\[breaking\]" || \ + [[ $BRANCH_NAME =~ ^break/ ]] || \ + [[ $BRANCH_NAME =~ ^breaking/ ]]; then + VERSION_TYPE="major" + REASON="Breaking change detected" + echo "๐Ÿ’ฅ MAJOR: $REASON" + + # 2. Check conventional commits in title + elif echo "$PR_TITLE" | grep -Eq "^(feat|feature)(\(.+\))?!:" || \ + echo "$PR_TITLE" | grep -Eq "^(fix|bugfix)(\(.+\))?!:"; then + VERSION_TYPE="major" + REASON="Breaking change in conventional commit" + echo "๐Ÿ’ฅ MAJOR: $REASON" + + # 3. Check features (minor) + elif echo "$PR_TITLE" | grep -Eq "^(feat|feature)(\(.+\))?:" || \ + [[ $BRANCH_NAME =~ ^feat/ ]] || \ + [[ $BRANCH_NAME =~ ^feature/ ]] || \ + echo "$PR_TITLE" | grep -qi "\[feature\]"; then + VERSION_TYPE="minor" + REASON="New feature detected" + echo "โœจ MINOR: $REASON" + + # 4. Check fixes and other changes (patch) + else + VERSION_TYPE="patch" + REASON="Bug fix or other changes" + echo "๐Ÿ› PATCH: $REASON" + fi + + # Get current version + CURRENT_VERSION=$(node -p "require('./package.json').version") + echo "๐Ÿ“ฆ Current version: $CURRENT_VERSION" + + # Calculate new version + NEW_VERSION=$(node -e " + const semver = require('semver'); + const current = '$CURRENT_VERSION'; + console.log(semver.inc(current, '$VERSION_TYPE')); + ") + + echo "๐Ÿš€ New version will be: $NEW_VERSION" + echo "๐ŸŽฏ Decision reason: $REASON" + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT + echo "reason=$REASON" >> $GITHUB_OUTPUT + + - name: Check if version already exists + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + NEW_VERSION="${{ steps.version-bump.outputs.new_version }}" + PACKAGE_NAME=$(node -p "require('./package.json').name") + + # Check if version already exists in NPM + if npm view "$PACKAGE_NAME@$NEW_VERSION" version 2>/dev/null; then + echo "โŒ Version $NEW_VERSION already exists in NPM" + exit 1 + fi + + # Check if tag already exists + if git tag -l | grep -q "^v$NEW_VERSION$"; then + echo "โŒ Tag v$NEW_VERSION already exists" + exit 1 + fi + + - name: Update version and commit + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + NEW_VERSION="${{ steps.version-bump.outputs.new_version }}" + VERSION_TYPE="${{ steps.version-bump.outputs.version_type }}" + BRANCH_NAME="${{ steps.validate-branch.outputs.branch_name }}" + REASON="${{ steps.version-bump.outputs.reason }}" + + # Update package.json + npm version $NEW_VERSION --no-git-tag-version + + # Commit and tag + git add package.json package-lock.json 2>/dev/null || git add package.json + git commit -m "chore(release): $NEW_VERSION + + Released from: $BRANCH_NAME + Type: $VERSION_TYPE + Reason: $REASON + + [skip ci]" + + git tag "v$NEW_VERSION" -m "Release v$NEW_VERSION" + + - name: Dry run publish (verification) + if: steps.validate-branch.outputs.should_publish == 'true' + run: | + echo "๐Ÿ” Performing dry run..." + npm publish --dry-run --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish to NPM + if: steps.validate-branch.outputs.should_publish == 'true' + id: npm-publish + run: | + NEW_VERSION="${{ steps.version-bump.outputs.new_version }}" + VERSION_TYPE="${{ steps.version-bump.outputs.version_type }}" + + echo "๐Ÿ“ฆ Publishing to NPM..." + + if [[ "$VERSION_TYPE" == "major" ]]; then + echo "โš ๏ธ Publishing MAJOR version $NEW_VERSION" + npm publish --access public --tag latest + else + npm publish --access public --tag latest + fi + + echo "โœ… Successfully published to NPM" + echo "published=true" >> $GITHUB_OUTPUT + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Push changes and tags + if: steps.npm-publish.outputs.published == 'true' + run: | + echo "๐Ÿ“ค Pushing changes to repository..." + git push origin main + git push origin --tags + echo "โœ… Changes pushed successfully" + + - name: Create GitHub Release + if: steps.npm-publish.outputs.published == 'true' + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.version-bump.outputs.new_version }} + name: Release v${{ steps.version-bump.outputs.new_version }} + body: | + ## ๐Ÿš€ Release v${{ steps.version-bump.outputs.new_version }} + + **Type:** ${{ steps.version-bump.outputs.version_type }} release + **Branch:** `${{ steps.validate-branch.outputs.branch_name }}` + **Previous:** `${{ steps.version-bump.outputs.current_version }}` + + ### ๐Ÿ“ Changes + - ${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }}) + + ### ๐Ÿ“ฆ Installation + ```bash + npm install @kubit-ui-web/react-components@${{ steps.version-bump.outputs.new_version }} + # or + yarn add @kubit-ui-web/react-components@${{ steps.version-bump.outputs.new_version }} + ``` + + ### ๐Ÿ”— Links + - [NPM Package](https://www.npmjs.com/package/@kubit-ui-web/react-components/v/${{ steps.version-bump.outputs.new_version }}) + - [Full Changelog](https://github.com/${{ github.repository }}/compare/v${{ steps.version-bump.outputs.current_version }}...v${{ steps.version-bump.outputs.new_version }}) + draft: false + prerelease: ${{ steps.version-bump.outputs.version_type == 'major' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Success notification + if: steps.npm-publish.outputs.published == 'true' + uses: actions/github-script@v7 + with: + script: | + const { version_type, new_version, current_version } = { + version_type: '${{ steps.version-bump.outputs.version_type }}', + new_version: '${{ steps.version-bump.outputs.new_version }}', + current_version: '${{ steps.version-bump.outputs.current_version }}' + }; + const branchName = '${{ steps.validate-branch.outputs.branch_name }}'; + + const emoji = version_type === 'major' ? '๐Ÿ’ฅ' : version_type === 'minor' ? 'โœจ' : '๐Ÿ›'; + + const comment = `## ${emoji} Auto-publish Successful! + + | Field | Value | + |-------|-------| + | **Branch** | \`${branchName}\` | + | **Type** | \`${version_type}\` | + | **Version** | \`${current_version}\` โ†’ \`${new_version}\` | + | **NPM** | [@kubit-ui-web/react-components@${new_version}](https://www.npmjs.com/package/@kubit-ui-web/react-components/v/${new_version}) | + + ### ๐Ÿ“ฆ Installation + \`\`\`bash + npm install @kubit-ui-web/react-components@${new_version} + # or + yarn add @kubit-ui-web/react-components@${new_version} + \`\`\` + + ### โœ… Completed Steps + - [x] Quality checks passed + - [x] Tests passed + - [x] Build successful + - [x] Published to NPM + - [x] GitHub release created + - [x] Repository tagged + + ๐ŸŽ‰ **Ready to use in production!**`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + - name: Failure notification + if: failure() && steps.validate-branch.outputs.should_publish == 'true' + uses: actions/github-script@v7 + with: + script: | + const comment = `## โŒ Auto-publish Failed + + The automatic publication process failed. Please check the [workflow logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + + ### ๐Ÿ”ง Common Solutions + - Verify NPM_TOKEN is valid and has publish permissions + - Check if version already exists + - Ensure all tests pass locally + - Verify build process completes successfully + + ### ๐Ÿ“ž Next Steps + 1. Fix the issue based on the error logs + 2. Create a new PR with the same changes + 3. Or use manual publish workflow if urgent`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + + - name: Skip notification + if: steps.validate-branch.outputs.should_publish == 'false' + uses: actions/github-script@v7 + with: + script: | + const branchName = '${{ github.event.pull_request.head.ref }}'; + + const comment = `## โ„น๏ธ Auto-publish Skipped + + Branch \`${branchName}\` doesn't match required patterns for auto-publishing. + + ### ๐Ÿ“‹ Required Patterns + | Pattern | Version Bump | Detection Method | + |---------|--------------|------------------| + | \`feat/\*\` or \`feature/\*\` | **minor** | Branch prefix or PR title | + | \`fix/\*\` or \`bugfix/\*\` | **patch** | Branch prefix or default | + | \`break/\*\` or \`breaking/\*\` | **major** | Branch prefix | + | \`hotfix/\*\` or \`chore/\*\` | **patch** | Branch prefix | + + ### ๐ŸŽฏ Advanced Version Detection + - **MAJOR**: \`BREAKING CHANGE:\` in PR body, \`!\` in title, or \`[breaking]\` tag + - **MINOR**: \`feat:\` or \`feature:\` in PR title, or \`[feature]\` tag + - **PATCH**: Default for fixes and other changes + + ### ๐Ÿš€ To Auto-publish + Create a new PR from a branch with the appropriate prefix, or use the [manual publish workflow](https://github.com/${{ github.repository }}/actions/workflows/manual-publish.yml).`; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); \ No newline at end of file diff --git a/README.md b/README.md index 81744231..18673973 100644 --- a/README.md +++ b/README.md @@ -104,26 +104,28 @@ This will run the tests and show the results in the terminal. We are open to contributions. If you want to contribute to the project, please follow the steps below: +### Development Workflow + 1. **Fork the Repository**: Click the "Fork" button in the upper right corner of the repository's page on GitHub. This will create a copy of the repository in your account. 2. **Clone the Repository**: Use `git clone` to clone the repository to your local machine. ```sh - git clone + git clone https://github.com/your-username/kubit-react-components.git ``` -3. **Create a Branch**: Use `git checkout` to create a new branch for your changes. +3. **Create a Branch**: Use proper branch naming conventions for automatic version detection. ```sh - git checkout -b + git checkout -b / ``` -4. **Make Changes**: Make any necessary changes to the codebase. And **test** the changes. +4. **Make Changes**: Make any necessary changes to the codebase and **test** the changes thoroughly. -5. **Commit Changes**: Use `git commit` to commit your changes to the branch. +5. **Commit Changes**: Use conventional commit messages when possible. ```sh - git commit -m "Your commit message" + git commit -m "feat: add new component feature" ``` 6. **Push Changes**: Use `git push` to push your changes to your forked repository. @@ -134,6 +136,139 @@ We are open to contributions. If you want to contribute to the project, please f 7. **Open a Pull Request**: Go to the original repository on GitHub and click the "New pull request" button. Fill out the form with details about your changes and submit the pull request. -Once your pull request has been submitted, a maintainer will review your changes and provide feedback. If everything looks good, your pull request will be merged and your changes will become part of the project. +### Branch Naming & Automatic Publishing + +This repository uses an **automatic publishing system** that determines the version bump based on your branch name and PR content. When your PR is merged, the package will be automatically published to NPM. + +#### Branch Naming Patterns + +Use these branch prefixes to ensure automatic publishing works correctly: + +| Branch Pattern | Version Bump | Example | Description | +|----------------|--------------|---------|-------------| +| `feat/` or `feature/` | **MINOR** | `feat/add-tooltip` | New features or enhancements | +| `fix/` or `bugfix/` | **PATCH** | `fix/button-hover-state` | Bug fixes | +| `break/` or `breaking/` | **MAJOR** | `break/remove-old-api` | Breaking changes | +| `hotfix/` | **PATCH** | `hotfix/critical-security-fix` | Urgent fixes | +| `chore/` | **PATCH** | `chore/update-dependencies` | Maintenance tasks | + +#### Advanced Version Detection + +The system also analyzes your **PR title** and **description** for more precise version detection: + +##### MAJOR (Breaking Changes) +- `BREAKING CHANGE:` in PR description +- `!` in PR title (e.g., `feat!: redesign button API`) +- `[breaking]` tag in PR title +- Conventional commits with `!` (e.g., `feat(api)!: change interface`) + +##### MINOR (New Features) +- PR titles starting with `feat:` or `feature:` +- `[feature]` tag in PR title +- Conventional commits like `feat(ui): add dark mode` + +##### PATCH (Bug Fixes & Others) +- PR titles starting with `fix:` or `bugfix:` +- All other changes (default behavior) +- Conventional commits like `fix(button): hover state` + +#### Examples + +**Adding a new feature:** +```sh +git checkout -b feat/dark-mode-support +# Make your changes +git commit -m "feat(theme): add dark mode support for all components" +# Create PR with title: "feat(theme): add dark mode support" +# Result: MINOR version bump (e.g., 1.0.0 โ†’ 1.1.0) +``` + +**Fixing a bug:** +```sh +git checkout -b fix/button-accessibility +# Make your changes +git commit -m "fix(button): improve keyboard navigation" +# Create PR with title: "fix(button): improve keyboard navigation" +# Result: PATCH version bump (e.g., 1.0.0 โ†’ 1.0.1) +``` + +**Breaking changes:** +```sh +git checkout -b break/remove-deprecated-props +# Make your changes +git commit -m "feat!: remove deprecated size prop from Button" +# Create PR with title: "feat!: remove deprecated size prop" +# PR description: "BREAKING CHANGE: The 'size' prop has been removed..." +# Result: MAJOR version bump (e.g., 1.0.0 โ†’ 2.0.0) +``` + +### Quality Assurance + +Before publishing, the system automatically runs: + +- โœ… **Linting** - Code style validation +- โœ… **Type Checking** - TypeScript validation +- โœ… **Tests** - Full test suite execution +- โœ… **Build** - Package compilation +- โœ… **Integration Tests** - Component functionality validation + +### Publishing Process + +When your PR is merged: + +1. **Automatic Analysis** - System analyzes branch name, PR title, and description +2. **Version Calculation** - Determines MAJOR/MINOR/PATCH version bump +3. **Quality Checks** - Runs all tests and validations +4. **NPM Publication** - Publishes to NPM with appropriate version +5. **GitHub Release** - Creates GitHub release with changelog +6. **Notifications** - Posts success/failure status in PR comments + +### Manual Override + +If you need to override the automatic version detection, you can: + +1. Use explicit markers in your PR description: + ``` + BREAKING CHANGE: This removes the old authentication API + ``` + +2. Use conventional commit format in PR title: + ``` + feat!: redesign component API structure + ``` + +3. Add tags to PR title: + ``` + [breaking] Update component props interface + ``` + +### Testing Your Changes + +Before submitting a PR, make sure to: + +```bash +# Install dependencies +yarn install + +# Run linter +yarn lint + +# Run type checking +yarn type-check + +# Run tests +yarn test + +# Build the package +yarn build +``` + +### Getting Help + +If you have questions about contributing or the automatic publishing system: + +- Check existing [GitHub Issues](https://github.com/kubit-ui/kubit-react-components/issues) +- Review [GitHub Discussions](https://github.com/kubit-ui/kubit-react-components/discussions) +- Read the full `CONTRIBUTING.md` file -For more information on contributing to our projects, please refer to the `CONTRIBUTING.md` file. +Once your pull request has been submitted, a maintainer will review your changes and provide feedback. If everything looks good, your pull request will be merged and your changes will be automatically published to NPM!