diff --git a/.github/workflows/pr_check_webapp_dotnet_windows.yml b/.github/workflows/pr_check_webapp_dotnet_windows.yml index 81cf8c238..11d70db40 100644 --- a/.github/workflows/pr_check_webapp_dotnet_windows.yml +++ b/.github/workflows/pr_check_webapp_dotnet_windows.yml @@ -71,11 +71,8 @@ jobs: - name: Installing dependencies and building latest changes run: | cd webapps-deploy - if (-NOT(TEST-PATH node_modules)) - { - npm install - npm run build - } + npm install + npm run package - name: Azure authentication uses: azure/login@v2 diff --git a/.github/workflows/pr_check_windows_container_pubprofile.yml b/.github/workflows/pr_check_windows_container_pubprofile.yml index 759cd4703..4231c45d0 100644 --- a/.github/workflows/pr_check_windows_container_pubprofile.yml +++ b/.github/workflows/pr_check_windows_container_pubprofile.yml @@ -92,11 +92,8 @@ jobs: - name: Installing dependencies and building latest changes in action run: | cd webapps-deploy - if (-NOT(TEST-PATH node_modules)) - { - npm install - npm run build - } + npm install + npm run package - name: 'Deploy to Azure WebApp' uses: ./webapps-deploy/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..63df9e4f0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,153 @@ +name: Release + +on: + release: + types: [published] + +jobs: + # Job 1: Build and create minor version tag (e.g., v3.2.1) + build-release: + runs-on: ubuntu-latest + environment: release-minor + permissions: + contents: write + outputs: + major: ${{ steps.source.outputs.major }} + env: + TAG_NAME: ${{ github.event.release.tag_name }} + + steps: + - name: Validate tag format + run: | + TAG="${{ env.TAG_NAME }}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "โŒ Invalid tag format: $TAG" + echo "Tag must match pattern: v.. (e.g., v3.2.1)" + exit 1 + fi + echo "โœ… Valid tag format: $TAG" + + - name: Determine source branch + id: source + run: | + TAG="${{ env.TAG_NAME }}" + MAJOR=$(echo "$TAG" | sed 's/v//' | cut -d. -f1) + # Map major version to release branch (releases/v2, releases/v3, etc.) + echo "branch=releases/v${MAJOR}" >> $GITHUB_OUTPUT + echo "major=v${MAJOR}" >> $GITHUB_OUTPUT + + - name: Checkout source branch + uses: actions/checkout@v4 + with: + ref: ${{ steps.source.outputs.branch }} + fetch-depth: 0 + + - name: Show changes being released + env: + MAJOR: ${{ steps.source.outputs.major }} + BRANCH: ${{ steps.source.outputs.branch }} + run: | + # Get previous tag for this major version + PREV_TAG=$(git tag --sort=-v:refname | grep -E "^${MAJOR}\.[0-9]+\.[0-9]+$" | head -1) + + echo "## ๐Ÿ“‹ Changes being released in ${{ env.TAG_NAME }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Source branch:** \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -n "$PREV_TAG" ]; then + echo "### Commits since $PREV_TAG" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + git log --oneline ${PREV_TAG}..HEAD >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Files changed" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + git diff --stat ${PREV_TAG}..HEAD >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿ”— [View full diff](https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${BRANCH})" >> $GITHUB_STEP_SUMMARY + else + echo "First release for ${MAJOR} - no previous tag found" >> $GITHUB_STEP_SUMMARY + fi + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Update version file + run: | + echo "// This file is auto-updated during release" > src/version.ts + echo "export const VERSION = '${{ env.TAG_NAME }}';" >> src/version.ts + cat src/version.ts + + - name: Build TypeScript + run: npm run build + + - name: Bundle with ncc + run: | + npm install -g @vercel/ncc + ncc build lib/main.js -o dist + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit dist and create minor tag + run: | + # Add dist folder (force add even if in .gitignore) + git add dist/ -f + git commit -m "Build dist for release ${{ env.TAG_NAME }}" + + # Update the release tag to include dist + git tag -fa ${{ env.TAG_NAME }} -m "Release ${{ env.TAG_NAME }}" + + # Push minor version tag + git push origin ${{ env.TAG_NAME }} --force + + echo "## โœ… Minor tag ${{ env.TAG_NAME }} created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Users can now use: \`Azure/webapps-deploy@${{ env.TAG_NAME }}\`" >> $GITHUB_STEP_SUMMARY + + # Job 2: Update major version tag (e.g., v3 -> v3.2.1) + update-major-tag: + needs: build-release + runs-on: ubuntu-latest + environment: release-major + permissions: + contents: write + env: + TAG_NAME: ${{ github.event.release.tag_name }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Update major tag to point to minor + env: + MAJOR: ${{ needs.build-release.outputs.major }} + run: | + # Fetch the minor tag + git fetch origin tag ${{ env.TAG_NAME }} --no-tags + + # Update major version tag to point to minor tag + git tag -fa ${MAJOR} ${{ env.TAG_NAME }} -m "Point ${MAJOR} to ${{ env.TAG_NAME }}" + + # Push major version tag + git push origin ${MAJOR} --force + + echo "## โœ… Major tag ${MAJOR} now points to ${{ env.TAG_NAME }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Users on \`Azure/webapps-deploy@${MAJOR}\` will now get ${{ env.TAG_NAME }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml new file mode 100644 index 000000000..bb7e0cfec --- /dev/null +++ b/.github/workflows/test-release.yml @@ -0,0 +1,119 @@ +name: Test Release Build + +on: + workflow_dispatch: + inputs: + tag_name: + description: 'Tag to simulate (e.g., v3.2.1)' + required: true + type: string + test_branch_name: + description: 'Test branch name to push (e.g., test-release-v3)' + required: true + type: string + default: 'test-release' + +jobs: + build-and-push-test-branch: + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + branch: ${{ steps.source.outputs.branch }} + + steps: + - name: Validate tag format + run: | + TAG="${{ inputs.tag_name }}" + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "โŒ Invalid tag format: $TAG" + echo "Tag must match pattern: v.. (e.g., v3.2.1)" + exit 1 + fi + echo "โœ… Valid tag format: $TAG" + + - name: Determine source branch + id: source + run: | + TAG="${{ inputs.tag_name }}" + MAJOR=$(echo "$TAG" | sed 's/v//' | cut -d. -f1) + # TODO: Change back to releases/v${MAJOR} after ncc changes are cherry-picked + echo "branch=master" >> $GITHUB_OUTPUT + echo "major=v${MAJOR}" >> $GITHUB_OUTPUT + + - name: Checkout source branch + uses: actions/checkout@v4 + with: + ref: ${{ steps.source.outputs.branch }} + fetch-depth: 0 + + - name: Show what will be built + env: + MAJOR: ${{ steps.source.outputs.major }} + BRANCH: ${{ steps.source.outputs.branch }} + run: | + PREV_TAG=$(git tag --sort=-v:refname | grep -E "^${MAJOR}\.[0-9]+\.[0-9]+$" | head -1) + + echo "## ๐Ÿงช Test Build for ${{ inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Source branch:** \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY + echo "**Test branch:** \`${{ inputs.test_branch_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ -n "$PREV_TAG" ]; then + echo "### Changes since $PREV_TAG" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + git log --oneline ${PREV_TAG}..HEAD >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm install + + - name: Update version file + run: | + echo "// This file is auto-updated during release" > src/version.ts + echo "export const VERSION = '${{ inputs.tag_name }}';" >> src/version.ts + cat src/version.ts + + - name: Build and bundle + run: npm run package + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit and push test branch + env: + TEST_BRANCH: ${{ inputs.test_branch_name }} + run: | + # Add dist folder + git add dist/ -f + git commit -m "Test build for ${{ inputs.tag_name }}" + + # Create and push test branch + git checkout -b ${TEST_BRANCH} + git push origin ${TEST_BRANCH} --force + + echo "## โœ… Test branch pushed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "You can now test with:" >> $GITHUB_STEP_SUMMARY + echo '```yaml' >> $GITHUB_STEP_SUMMARY + echo "- uses: Azure/webapps-deploy@${TEST_BRANCH}" >> $GITHUB_STEP_SUMMARY + echo " with:" >> $GITHUB_STEP_SUMMARY + echo " app-name: your-app-name" >> $GITHUB_STEP_SUMMARY + echo " package: ./your-package" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Cleanup" >> $GITHUB_STEP_SUMMARY + echo "After testing, delete the branch:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "git push origin --delete ${TEST_BRANCH}" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index 3e759b75b..c5adeefd7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +# Bundled output (built during release) +dist/ + # User-specific files *.suo *.user diff --git a/action.yml b/action.yml index d21392e56..3a7c4cc91 100644 --- a/action.yml +++ b/action.yml @@ -52,4 +52,4 @@ branding: color: 'blue' runs: using: 'node20' - main: 'lib/main.js' + main: 'dist/index.js' diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md new file mode 100644 index 000000000..9b338cf6c --- /dev/null +++ b/docs/RELEASE_PROCESS.md @@ -0,0 +1,193 @@ +# Release Process Documentation + +## Overview + +This document describes the automated release process for `azure/webapps-deploy` GitHub Action. + +--- + +## Branch Structure + +| Branch | Purpose | Notes | +|--------|---------|-------| +| `master` | Active development | - | +| `releases/v3` | v3.x releases | Synced with `master` via cherry-pick | +| `releases/v2` | v2.x releases | Same codebase structure, some files have different logic | + +> **Note:** `releases/v2` and `releases/v3` share the same codebase structure. Only a few major files have different implementations. The release process is identical for both. + +--- + +## How Users Consume This Action + +```yaml +# Users reference major version tag +- uses: Azure/webapps-deploy@v3 # Gets latest v3.x.x +- uses: Azure/webapps-deploy@v2 # Gets latest v2.x.x +``` + +--- + +## Release Workflow + +### Step 1: Prepare Changes + +**For v3 releases:** +1. Develop feature on feature branch +2. Merge PR to `master` +3. Create a feature branch from `releases/v3`: + ```bash + git checkout releases/v3 + git pull origin releases/v3 + git checkout -b feature/my-feature-v3 + ``` +4. Cherry-pick commits from `master`: + ```bash + git cherry-pick + ``` +5. Push and create PR to `releases/v3`: + ```bash + git push origin feature/my-feature-v3 + ``` +6. Get PR reviewed and merge to `releases/v3` + +**For v2 releases:** +1. Develop feature on feature branch +2. Merge PR to `master` +3. Create a feature branch from `releases/v2`: + ```bash + git checkout releases/v2 + git pull origin releases/v2 + git checkout -b feature/my-feature-v2 + ``` +4. Cherry-pick commits from `master`: + ```bash + git cherry-pick + ``` +5. Push and create PR to `releases/v2`: + ```bash + git push origin feature/my-feature-v2 + ``` +6. Get PR reviewed and merge to `releases/v2` + +> **Note:** Using feature branches allows for code review before changes land in release branches. + +--- + +### Step 2: Create Release + +1. Go to **GitHub โ†’ Releases โ†’ Create a new release** +2. Click **Choose a tag** โ†’ Type new tag (e.g., `v3.2.1`) +3. Select **Target branch**: Choose the corresponding release branch + - `v3.x.x` โ†’ `releases/v3` + - `v2.x.x` โ†’ `releases/v2` +4. Add release notes +5. Click **Publish release** + +--- + +### Step 3: Two-Stage Approval & Release + +When release is published, the workflow has **two approval stages**: + +**Stage 1: Minor Tag (`release-minor` environment)** +1. Workflow shows changes in summary +2. Approver reviews and approves +3. Builds code and creates minor tag (e.g., `v3.2.1`) +4. Users can test with `Azure/webapps-deploy@v3.2.1` + +**Stage 2: Major Tag (`release-major` environment)** +1. After testing minor tag, approver approves +2. Updates major tag (`v3`) to point to minor (`v3.2.1`) +3. Users on `Azure/webapps-deploy@v3` now get the new version + +### One-time Setup + +Go to **repo Settings โ†’ Environments** and create two environments: + +1. **`release-minor`** - Required reviewers โ†’ Add your team +2. **`release-major`** - Required reviewers โ†’ Add your team + +--- + +## What's Different from Before + +| Aspect | Old Process | New Process | +|--------|-------------|-------------| +| `node_modules` | Committed to branch | โŒ Not needed (bundled with `ncc`) | +| Build artifacts | In release branch | In tag only (`dist/index.js`) | +| PR review | PR to releases/vX with node_modules | Review via workflow summary | +| Manual steps | npm install, npm build, push | Automated | +| Version tracking | Not tracked | Embedded in user agent | + +--- + +## Rollback Procedure + +### Quick Rollback (move v3 tag back) + +```bash +# Point v3 back to previous version +git tag -fa v3 v3.2.0 -m "Rollback to v3.2.0" +git push origin v3 --force +``` + +### Hotfix Release + +1. Fix the bug in `releases/v3` branch +2. Create new release `v3.2.2` +3. Workflow auto-updates `v3` tag + +--- + +## Tag Naming Convention + +| Tag | Description | +|-----|-------------| +| `v3.2.1` | Specific release version | +| `v3` | Always points to latest v3.x.x | +| `v2.1.5` | Specific v2 release | +| `v2` | Always points to latest v2.x.x | + +--- + + +## User Agent Tracking + +API calls now include version information: + +``` +GITHUBACTIONS_DeployWebAppToAzure_v3.2.1_ +``` + +This helps track which release version is being used. + +--- + +## Security + +- Tag names are validated against pattern: `^v[0-9]+\.[0-9]+\.[0-9]+$` +- Only maintainers can create releases +- No secrets are exposed in the workflow + +--- + +## Quick Reference + +### Create a release (same for v2 and v3) +``` +1. Create feature branch from releases/vX +2. Cherry-pick changes from master +3. Create PR to releases/vX โ†’ Review โ†’ Merge +4. Create release with tag vX.x.x โ†’ Publish +5. Approve Stage 1 (release-minor) โ†’ Creates v3.2.1 +6. Test v3.2.1 if needed +7. Approve Stage 2 (release-major) โ†’ Updates v3 +8. Done! +``` + +--- + +## Questions? + +Contact the team or refer to the workflow file at `.github/workflows/release.yml`. diff --git a/lib/main.js b/lib/main.js index 5a68a7d35..5af56e9b4 100644 --- a/lib/main.js +++ b/lib/main.js @@ -50,6 +50,7 @@ const AuthorizerFactory_1 = require("azure-actions-webclient/AuthorizerFactory") const BaseWebAppDeploymentProvider_1 = require("./DeploymentProvider/Providers/BaseWebAppDeploymentProvider"); const DeploymentProviderFactory_1 = require("./DeploymentProvider/DeploymentProviderFactory"); const ValidatorFactory_1 = require("./ActionInputValidator/ValidatorFactory"); +const version_1 = require("./version"); var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : ""; function main() { return __awaiter(this, void 0, void 0, function* () { @@ -58,7 +59,7 @@ function main() { // Set user agent variable let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); let actionName = 'DeployWebAppToAzure'; - let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${usrAgentRepo}`; + let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${version_1.VERSION}_${usrAgentRepo}`; core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString); // Initialize action inputs let endpoint = !!core.getInput('publish-profile') ? null : yield AuthorizerFactory_1.AuthorizerFactory.getAuthorizer(); diff --git a/lib/version.js b/lib/version.js new file mode 100644 index 000000000..edd40a5ba --- /dev/null +++ b/lib/version.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VERSION = void 0; +// This file is auto-updated during release +exports.VERSION = 'dev'; diff --git a/package-lock.json b/package-lock.json index f5bac5f0b..3361d9175 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "^20.12.2", + "@vercel/ncc": "^0.38.1", "jest": "29.7.0", "ts-jest": "29.1.2", "ts-node": "^10.9.1", @@ -1272,6 +1273,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@vercel/ncc": { + "version": "0.38.4", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.4.tgz", + "integrity": "sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==", + "dev": true, + "license": "MIT", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", diff --git a/package.json b/package.json index 51bde97df..4b7a27352 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,11 @@ "name": "webapps-deploy", "version": "3.0.0", "description": "Deploy web apps and containerized web apps to Azure", - "main": "lib/main.js", + "main": "dist/index.js", "scripts": { "build": "tsc", + "bundle": "ncc build lib/main.js -o dist", + "package": "npm run build && npm run bundle", "test": "jest" }, "repository": { @@ -25,6 +27,7 @@ "devDependencies": { "@types/jest": "^29.5.12", "@types/node": "^20.12.2", + "@vercel/ncc": "^0.38.1", "jest": "29.7.0", "ts-jest": "29.1.2", "ts-node": "^10.9.1", diff --git a/src/main.ts b/src/main.ts index 752c79395..964253ede 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import { DEPLOYMENT_PROVIDER_TYPES } from "./DeploymentProvider/Providers/BaseWe import { DeploymentProviderFactory } from './DeploymentProvider/DeploymentProviderFactory'; import { IAuthorizer } from 'azure-actions-webclient/Authorizer/IAuthorizer'; import { ValidatorFactory } from './ActionInputValidator/ValidatorFactory'; +import { VERSION } from './version'; var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : ""; @@ -18,7 +19,7 @@ export async function main() { // Set user agent variable let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); let actionName = 'DeployWebAppToAzure'; - let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${usrAgentRepo}`; + let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${VERSION}_${usrAgentRepo}`; core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString); // Initialize action inputs diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 000000000..f2772bbb1 --- /dev/null +++ b/src/version.ts @@ -0,0 +1,2 @@ +// This file is auto-updated during release +export const VERSION = 'dev';