diff --git a/.github/RELEASE_WORKFLOW.md b/.github/RELEASE_WORKFLOW.md new file mode 100644 index 0000000..5d13120 --- /dev/null +++ b/.github/RELEASE_WORKFLOW.md @@ -0,0 +1,270 @@ +# Release Workflow Guide + +This document explains how to use the automated release workflow for the +CSS/SCSS Modules IntelliSense extension. + +## 🚀 Quick Start + +### Quick Steps to Release + +1. **Update CHANGELOG.md** - Add your changes under `[Unreleased]`: + + ```markdown + ## [Unreleased] + + ### Added + + - Your new features + + ### Fixed + + - Your bug fixes + ``` + +2. **Go to GitHub Actions** + + - Visit: + - Click "Release and Publish" + - Click "Run workflow" + +3. **Select Version Type** + + - `patch` → Bug fixes (0.1.4 → 0.1.5) + - `minor` → New features (0.1.4 → 0.2.0) + - `major` → Breaking changes (0.1.4 → 1.0.0) + +4. **Click "Run workflow"** ✅ + +### What Happens Automatically + +- ✅ Updates CHANGELOG.md +- ✅ Bumps package.json version +- ✅ Runs tests +- ✅ Creates git tag +- ✅ Publishes to VS Code Marketplace +- ✅ Creates GitHub Release +- ❌ **Rolls back everything if anything fails!** + +--- + +## Overview + +The `release.yml` GitHub Actions workflow automates the entire release process: + +1. ✅ Updates CHANGELOG.md from `[Unreleased]` to versioned release +2. ✅ Bumps version in package.json +3. ✅ Runs compilation and tests +4. ✅ Creates a git tag with release notes +5. ✅ Publishes to VS Code Marketplace +6. ✅ Creates a GitHub Release with VSIX file +7. ✅ **Automatic rollback** if anything fails + +## Prerequisites + +### 1. Setup VS Code Marketplace Token + +You need a Personal Access Token (PAT) from Visual Studio Marketplace: + +1. Go to [https://dev.azure.com/](https://dev.azure.com/) +2. Click on your profile → Security → Personal Access Tokens +3. Create a new token with: + - **Organization:** All accessible organizations + - **Scopes:** Marketplace → Manage +4. Copy the token + +### 2. Add Secret to GitHub + +1. Go to your GitHub repository +2. Settings → Secrets and variables → Actions +3. Click "New repository secret" +4. Name: `VSCE_PAT` +5. Value: Paste your token +6. Click "Add secret" + +## How to Release + +### Step 1: Add Changes to CHANGELOG + +Before releasing, ensure your `CHANGELOG.md` has +an `[Unreleased]` section with your changes: + +```markdown +## [Unreleased] + +### Added + +- New feature descriptions + +### Changed + +- Modified functionality + +### Fixed + +- Bug fixes +``` + +### Step 2: Trigger the Workflow + +1. Go to your GitHub repository +2. Click on **Actions** tab +3. Select **Release and Publish** workflow +4. Click **Run workflow** button +5. Select version type: + - **patch** (0.1.4 → 0.1.5) - Bug fixes + - **minor** (0.1.4 → 0.2.0) - New features + - **major** (0.1.4 → 1.0.0) - Breaking changes +6. Click **Run workflow** + +### Step 3: Monitor Progress + +The workflow will: + +- ⏳ Extract changes from `[Unreleased]` +- ⏳ Update CHANGELOG.md and package.json +- ⏳ Run tests +- ⏳ Create git tag +- ⏳ Publish to marketplace +- ⏳ Create GitHub release + +### Step 4: Verify Release + +Once complete, verify: + +- ✅ New version appears on [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=lokesh-garg.css-scss-modules-intellisense) +- ✅ GitHub release created with tag +- ✅ CHANGELOG.md updated +- ✅ package.json version bumped + +## What Happens on Failure? + +If **any step fails**, the workflow automatically: + +1. ❌ Restores original package.json and CHANGELOG.md +2. ❌ Resets git to the commit before the workflow ran +3. ❌ Deletes any created tags (local and remote) +4. ❌ Force pushes to reset the remote branch +5. ❌ Exits with an error message + +**You can safely retry after fixing the issue!** + +## Workflow Steps Detail + +### Version Calculation + +- **Current version:** Read from package.json +- **New version:** Calculated based on semver (major.minor.patch) + +### CHANGELOG Update + +```markdown +## [Unreleased] + +## [0.1.5] – 2025-12-02 + +### Added + +- Your changes here +``` + +The workflow: + +1. Keeps `[Unreleased]` section header (empty for next release) +2. Converts previous unreleased content to new version +3. Adds comparison link at bottom + +### Tag Creation + +- Tag name: `v{version}` (e.g., `v0.1.5`) +- Tag message: Content from `[Unreleased]` section +- Pushed to remote repository + +### Publishing + +- Packages extension as `.vsix` file +- Publishes to VS Code Marketplace using `vsce publish` +- Uploads `.vsix` to GitHub Release + +## Troubleshooting + +### "No [Unreleased] section found" + +**Solution:** Add an `[Unreleased]` section to CHANGELOG.md with your changes + +### "No changes found in [Unreleased] section" + +**Solution:** Add at least one change under the `[Unreleased]` heading + +### "VSCE_PAT secret not found" + +**Solution:** Add your Visual Studio Marketplace token as a GitHub secret (see Prerequisites) + +### "Tests failed" + +**Solution:** Fix test failures locally first, then retry the workflow + +### "Permission denied" errors + +**Solution:** Ensure the workflow has write permissions in repository settings + +## Manual Rollback + +If you need to manually rollback a release: + +```bash +# Delete local tag +git tag -d v0.1.5 + +# Delete remote tag +git push origin :refs/tags/v0.1.5 + +# Reset to previous commit +git reset --hard HEAD~1 +git push origin main --force +``` + +Then manually revert CHANGELOG.md and package.json changes. + +## Best Practices + +1. **Always test locally** before releasing +2. **Keep [Unreleased] section updated** as you make changes +3. **Use semantic versioning** appropriately: + - patch: backwards-compatible bug fixes + - minor: backwards-compatible new features + - major: breaking changes +4. **Review the CHANGELOG** before triggering release +5. **Monitor the workflow** in Actions tab during release + +## Version History Format + +The CHANGELOG follows [Keep a Changelog](https://keepachangelog.com/) format: + +```markdown +## [Unreleased] + +## [0.2.0] – 2025-12-15 + +### Added + +- New feature + +## [0.1.5] – 2025-12-02 + +### Fixed + +- Bug fix +``` + +## GitHub Actions Limits + +- Public repositories: Unlimited minutes +- Private repositories: 2,000 minutes/month (free tier) +- Workflow typically takes 3-5 minutes + +## Security + +- **Never commit** your VSCE_PAT token to the repository +- Use GitHub Secrets for sensitive data +- Tokens are masked in workflow logs +- Review workflow permissions regularly diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..67d7e11 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,302 @@ +name: Release and Publish + +on: + workflow_dispatch: + inputs: + version_type: + description: 'Version type (major, minor, patch)' + required: true + default: 'patch' + type: choice + options: + - major + - minor + - patch + +jobs: + release: + 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' + + - name: Install dependencies + run: npm ci + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Backup current state + id: backup + run: | + # Store current branch and commit + echo "ORIGINAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT + echo "ORIGINAL_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + # Backup current files + cp package.json package.json.backup + cp CHANGELOG.md CHANGELOG.md.backup + echo "Backup created successfully" + + - name: Check for Unreleased section + id: check_unreleased + run: | + if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then + echo "ERROR: No [Unreleased] section found in CHANGELOG.md" + echo "Please add changes to an [Unreleased] section before releasing" + exit 1 + fi + echo "Unreleased section found" + + - name: Calculate new version + id: version + run: | + # Get current version from package.json + CURRENT_VERSION=$(node -p "require('./package.json').version") + echo "Current version: $CURRENT_VERSION" + + # Calculate new version based on input + IFS='.' read -r major minor patch <<< "$CURRENT_VERSION" + + case "${{ github.event.inputs.version_type }}" in + major) + NEW_VERSION="$((major + 1)).0.0" + ;; + minor) + NEW_VERSION="${major}.$((minor + 1)).0" + ;; + patch) + NEW_VERSION="${major}.${minor}.$((patch + 1))" + ;; + esac + + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "CURRENT_VERSION=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "New version will be: $NEW_VERSION" + + - name: Extract Unreleased changes + id: extract_changes + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + TODAY=$(date +%Y-%m-%d) + + # Extract content between [Unreleased] and the next version section + CHANGES=$(awk '/## \[Unreleased\]/,/## \[.*\]/ { + if (/## \[Unreleased\]/) next; + if (/## \[.*\]/) exit; + print + }' CHANGELOG.md | sed '/^$/d' | head -c 5000) + + # Check if changes exist + if [ -z "$(echo "$CHANGES" | xargs)" ]; then + echo "ERROR: No changes found in [Unreleased] section" + exit 1 + fi + + # Create formatted release body with header + echo "# CSS Modules IntelliSense ${NEW_VERSION} – ${TODAY}" > /tmp/release_body.txt + echo "" >> /tmp/release_body.txt + echo "$CHANGES" >> /tmp/release_body.txt + + # Also save just the changes for git tag + echo "$CHANGES" > /tmp/release_notes.txt + + echo "Extracted changes successfully" + echo "Release body:" + cat /tmp/release_body.txt + + - name: Update CHANGELOG.md + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + CURRENT_VERSION="${{ steps.version.outputs.CURRENT_VERSION }}" + TODAY=$(date +%Y-%m-%d) + + # Create updated changelog + awk -v new_version="$NEW_VERSION" -v today="$TODAY" -v current_version="$CURRENT_VERSION" ' + /## \[Unreleased\]/ { + print "## [Unreleased]\n" + print "## [" new_version "] – " today + next + } + { print } + ' CHANGELOG.md > CHANGELOG.md.tmp + + # Add new version link at the bottom + echo "" >> CHANGELOG.md.tmp + echo "[${NEW_VERSION}]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v${CURRENT_VERSION}...v${NEW_VERSION}" >> CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + echo "CHANGELOG.md updated successfully" + + - name: Update package.json version + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + npm version $NEW_VERSION --no-git-tag-version + echo "package.json updated to version $NEW_VERSION" + + - name: Compile and lint in pretest + id: pretest + run: npm run pretest + + - name: Run tests + id: test + run: | + # Install xvfb for headless testing + sudo apt-get update && sudo apt-get install -y xvfb + + # Run tests with xvfb + xvfb-run -a npm test || { + echo "Tests failed!" + exit 1 + } + + - name: Package extension + id: package + run: | + # Install vsce globally + npm install -g @vscode/vsce + + # Package the extension + vsce package + + # Get the package filename + PACKAGE_FILE=$(ls *.vsix | head -n 1) + echo "PACKAGE_FILE=$PACKAGE_FILE" >> $GITHUB_OUTPUT + echo "Extension packaged: $PACKAGE_FILE" + + - name: Commit changes + id: commit + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + + git add package.json package-lock.json CHANGELOG.md + git commit -m "chore: release v${NEW_VERSION}" + + echo "Changes committed" + + - name: Create and push tag + id: tag + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + + # Create annotated tag with release notes + git tag -a "v${NEW_VERSION}" -F /tmp/release_notes.txt + + # Push commit and tag + git push origin HEAD:main + git push origin "v${NEW_VERSION}" + + echo "Tag v${NEW_VERSION} created and pushed" + + - name: Publish to VS Code Marketplace + id: publish + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + run: | + if [ -z "$VSCE_PAT" ]; then + echo "ERROR: VSCE_PAT secret not found" + echo "Please add your Visual Studio Marketplace Personal Access Token as a secret named VSCE_PAT" + exit 1 + fi + + # Publish to marketplace + vsce publish -p $VSCE_PAT + + echo "Extension published to VS Code Marketplace successfully!" + + - name: Create GitHub Release + id: github_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.NEW_VERSION }} + release_name: CSS Modules IntelliSense ${{ steps.version.outputs.NEW_VERSION }} + body_path: /tmp/release_body.txt + draft: false + prerelease: false + + - name: Upload VSIX to GitHub Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.github_release.outputs.upload_url }} + asset_path: ./${{ steps.package.outputs.PACKAGE_FILE }} + asset_name: ${{ steps.package.outputs.PACKAGE_FILE }} + asset_content_type: application/vsix + + # Rollback steps - these run if any previous step fails + - name: Rollback on failure + if: failure() + run: | + echo "⚠️ Workflow failed! Rolling back changes..." + + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + ORIGINAL_COMMIT="${{ steps.backup.outputs.ORIGINAL_COMMIT }}" + + # Restore backup files if they exist + if [ -f "package.json.backup" ]; then + mv package.json.backup package.json + echo "✓ Restored package.json" + fi + + if [ -f "CHANGELOG.md.backup" ]; then + mv CHANGELOG.md.backup CHANGELOG.md + echo "✓ Restored CHANGELOG.md" + fi + + # Reset git if commit was made + if [ "${{ steps.commit.outcome }}" == "success" ]; then + git reset --hard $ORIGINAL_COMMIT + echo "✓ Reset git to original commit" + fi + + # Delete tag if it was created + if [ "${{ steps.tag.outcome }}" == "success" ] && [ -n "$NEW_VERSION" ]; then + git tag -d "v${NEW_VERSION}" 2>/dev/null || true + git push origin ":refs/tags/v${NEW_VERSION}" 2>/dev/null || true + echo "✓ Deleted tag v${NEW_VERSION}" + fi + + # Force push to reset remote if push succeeded + if [ "${{ steps.tag.outcome }}" == "success" ]; then + git push origin HEAD:main --force + echo "✓ Force pushed to reset remote branch" + fi + + echo "❌ Rollback completed. All changes have been reverted." + echo "Please fix the errors and try again." + exit 1 + + - name: Cleanup backup files + if: success() + run: | + rm -f package.json.backup CHANGELOG.md.backup + echo "✓ Cleanup completed successfully" + + - name: Success summary + if: success() + run: | + NEW_VERSION="${{ steps.version.outputs.NEW_VERSION }}" + echo "🎉 Release v${NEW_VERSION} completed successfully!" + echo "" + echo "✓ CHANGELOG.md updated" + echo "✓ package.json version bumped" + echo "✓ Git tag created and pushed" + echo "✓ Published to VS Code Marketplace" + echo "✓ GitHub Release created" + echo "" + echo "View the release at: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/releases/tag/v${NEW_VERSION}" diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..89b93fd --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.18.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b27a842..c26b3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [Unreleased] + +### Added + +### Changed + +### Fixed + ## [0.1.4] – 2025-11-07 ### Added @@ -117,13 +125,13 @@ All notable changes to this project will be documented in this file. - Go-to-Definition support for `styles.className` references. - Autocompletion of class names in JavaScript and TypeScript. -[0.1.4]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.3...v0.1.4 -[0.1.3]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.2...v0.1.3 -[0.1.2]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.1...v0.1.2 -[0.1.1]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.0...v0.1.1 -[0.1.0]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.5...v0.1.0 -[0.0.5]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.4...v0.0.5 -[0.0.4]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.3...v0.0.4 -[0.0.3]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.2...v0.0.3 -[0.0.2]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.1...v0.0.2 [0.0.1]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/releases/tag/v0.0.1 +[0.0.2]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.1...v0.0.2 +[0.0.3]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.2...v0.0.3 +[0.0.4]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.3...v0.0.4 +[0.0.5]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.4...v0.0.5 +[0.1.0]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.0.5...v0.1.0 +[0.1.1]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.0...v0.1.1 +[0.1.2]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.1...v0.1.2 +[0.1.3]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.2...v0.1.3 +[0.1.4]: https://github.com/Lokesh-Garg-22/CSS-Modules-IntelliSense/compare/v0.1.3...v0.1.4