Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/RELEASE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Description:

## What's Changed
3 changes: 1 addition & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ jobs:
id: secrets
with:
secrets: |
development/artifactory/token/${{ github.repository_owner }}-${{ github.event.repository.name }}-qa-deployer access_token | ARTIFACTORY_DEPLOY_ACCESS_TOKEN;
development/artifactory/token/${{ github.repository_owner }}-${{ github.event.repository.name }}-private-reader access_token | ARTIFACTORY_ACCESS_TOKEN;

- name: Configure npm registry
Expand All @@ -94,7 +93,7 @@ jobs:

- name: Run integration tests
env:
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_DEPLOY_ACCESS_TOKEN }}
ARTIFACTORY_ACCESS_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN }}
run: npm run test-integration
shell: bash

Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
release_tag:
description: 'Release tag to test (e.g., 1.2.3)'
required: true
release_description:
description: 'Release description for Update Center (e.g., "Support new feature")'
required: true
prerelease:
description: 'Mark as prerelease (publishes to npm "next" tag instead of "latest")'
type: boolean
Expand All @@ -19,6 +22,10 @@ on:
description: 'Skip updating "latest" tag (use for older version patches)'
type: boolean
default: false
slack_channel:
description: 'Slack channel for release notification'
type: string
default: 'ask-squad-web'

jobs:
publish:
Expand All @@ -33,6 +40,33 @@ jobs:
ARTIFACTORY_REPOSITORY_NAME: 'sonarsource-npm-public-releases'
DRY_RUN: ${{ inputs.dry_run || false }}
steps:
- name: Validate release description
id: description
env:
RELEASE_BODY: ${{ github.event.release.body }}
INPUT_DESCRIPTION: ${{ inputs.release_description }}
run: |
# Use input description for manual trigger, otherwise extract from release body
if [ -n "$INPUT_DESCRIPTION" ]; then
DESCRIPTION="$INPUT_DESCRIPTION"
else
# Extract description from release body (line starting with "Description:")
DESCRIPTION=$(echo "$RELEASE_BODY" | grep -i "^Description:" | sed 's/^[Dd]escription:[[:space:]]*//')
fi

if [ -z "$DESCRIPTION" ]; then
echo "::error::Release body must contain a 'Description:' line for the Update Center entry."
echo "::error::Example format:"
echo "::error:: Description: Support new authentication method"
echo "::error::"
echo "::error:: ## What's Changed"
echo "::error:: * PR details..."
exit 1
fi

echo "description=$DESCRIPTION" >> $GITHUB_OUTPUT
echo "Extracted description: $DESCRIPTION"

- name: Fetch the secrets
if: ${{ !inputs.dry_run }}
id: secrets
Expand Down Expand Up @@ -113,3 +147,96 @@ jobs:
# Publish as sonarqube-scanner (legacy alias for backwards compatibility)
echo $(jq '.name = "sonarqube-scanner"' package.json) > package.json
npm publish --tag=${{ steps.npm-tag.outputs.tag }} --access=public

outputs:
description: ${{ steps.description.outputs.description }}

update-center:
needs: publish
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
steps:
- name: Fetch GitHub token
id: secrets
uses: SonarSource/vault-action-wrapper@v3
with:
secrets: development/github/token/SonarSource-sonar-scanner-npm-release-automation token | github_token;

- name: Checkout sonar-update-center-properties
uses: actions/checkout@v6
with:
repository: SonarSource/sonar-update-center-properties
token: ${{ fromJSON(steps.secrets.outputs.vault).github_token }}
path: update-center

- name: Checkout sonar-scanner-npm (for scripts)
uses: actions/checkout@v6
with:
path: sonar-scanner-npm

- name: Update scannernpm.properties
working-directory: update-center
run: |
bash ../sonar-scanner-npm/scripts/update-update-center.sh \
scannernpm.properties \
"${RELEASE_TAG}" \
"${{ needs.publish.outputs.description }}"

- name: Create Pull Request
id: create-pr
working-directory: update-center
env:
GH_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).github_token }}
run: |
BRANCH="scannernpm-${RELEASE_TAG}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add scannernpm.properties
git commit -m "Update SonarScanner for NPM to ${RELEASE_TAG}"
git push origin "$BRANCH"
PR_URL=$(gh pr create \
--title "Update SonarScanner for NPM to ${RELEASE_TAG}" \
--body "Automated PR to update SonarScanner for NPM to version ${RELEASE_TAG}.

Created by [sonar-scanner-npm release workflow](https://github.com/SonarSource/sonar-scanner-npm/actions/runs/${{ github.run_id }})." \
--base master)
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT

outputs:
pr_url: ${{ steps.create-pr.outputs.pr_url }}

notify:
needs: [publish, update-center]
permissions:
id-token: write
runs-on: ubuntu-latest
env:
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
steps:
- name: Fetch Slack token
id: secrets
uses: SonarSource/vault-action-wrapper@v3
with:
secrets: development/kv/data/slack token | slack_token;

- name: Send Slack notification
uses: slackapi/slack-github-action@v2.1.0
with:
method: chat.postMessage
token: ${{ fromJSON(steps.secrets.outputs.vault).slack_token }}
errors: true
payload: |
{
"channel": "${{ inputs.slack_channel || 'ask-squad-web' }}",
"attachments": [
{
"color": "#36a64f",
"text": ":package: *SonarScanner for NPM ${{ env.RELEASE_TAG }}* has been released!\n\n<https://github.com/SonarSource/sonar-scanner-npm/releases/tag/${{ env.RELEASE_TAG }}|View Release> | <https://www.npmjs.com/package/@sonar/scan/v/${{ env.RELEASE_TAG }}|npm package>\n\n:clipboard: *Next steps to complete the release:*\n1. Merge the Update Center PR: <${{ needs.update-center.outputs.pr_url }}|sonar-update-center-properties PR>\n2. Run the <https://github.com/SonarSource/sonar-update-center-properties/actions/workflows/deploy.yml|Deploy workflow> to publish the Update Center JSON\n3. Run the <https://github.com/SonarSource/sonarqube-documentation/actions/workflows/generate-release-notes.yml|Generate Release Notes workflow> and merge the resulting PR"
}
]
}
59 changes: 42 additions & 17 deletions docs/DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,23 @@ Releases are handled by the GitHub Actions workflow (`.github/workflows/release.

1. Create a new release on GitHub
2. Set the tag name (e.g., `1.2.3`)
3. The workflow will:
3. Add a `Description:` line in the release body (required for the Update Center)
4. The workflow will:
- Build the package with the release version
- Publish to Artifactory
- Publish to npm under two package names:
- `@sonar/scan` (primary)
- `sonarqube-scanner` (legacy alias for backwards compatibility)
- Create a PR in [sonar-update-center-properties](https://github.com/SonarSource/sonar-update-center-properties)

Example release body:

```
Description: Support new authentication method

## What's Changed
* SCANNPM-XXX Add new auth support by @user in #123
```

### npm Tags

Expand Down Expand Up @@ -86,35 +97,49 @@ You can test the release workflow without actually publishing by using the manua
3. Fill in the inputs:
- **Dry run**: ✅ checked (skips all publish steps)
- **Release tag**: The version to simulate (e.g., `1.2.3`)
- **Release description**: The description for the Update Center entry
- **Simulate prerelease**: Check to test prerelease behavior
- **Simulate [skip-latest]**: Check to test the skip-latest behavior

The workflow will run and display the npm tag that would be used without performing any actual build or publish operations.
The workflow will run and display the npm tag that would be used without performing any actual build or publish operations. The Update Center PR is still created during dry runs so you can verify the changes (just close the PR afterwards).

### Sonar Update Center

After publishing a new release, the [Sonar Update Center](https://xtranet-sonarsource.atlassian.net/wiki/spaces/DOC/pages/3385294896/The+Sonar+Update+Center) needs to be updated. This makes release information available at `downloads.sonarsource.com` for documentation and tooling.
The [Sonar Update Center](https://xtranet-sonarsource.atlassian.net/wiki/spaces/DOC/pages/3385294896/The+Sonar+Update+Center) is automatically updated by the release workflow. When a release is published, the workflow creates a PR in [sonar-update-center-properties](https://github.com/SonarSource/sonar-update-center-properties) to update `scannernpm.properties` that:

#### Update Process
- Adds the new version entry using the `Description:` from the release body
- Moves the previous public version to `archivedVersions`

1. **Create a PR** in [sonar-update-center-properties](https://github.com/SonarSource/sonar-update-center-properties) to update `scannernpm.properties`
#### Post-Release Steps

2. **Add the new version entry** with the following format:
After the release workflow completes, the following manual steps are required to fully publish the new version:

```properties
X.Y.Z.description=Short description of the release
X.Y.Z.date=YYYY-MM-DD
X.Y.Z.changelogUrl=https://github.com/SonarSource/sonar-scanner-npm/releases/tag/X.Y.Z
X.Y.Z.downloadUrl=https://www.npmjs.com/package/@sonar/scan/v/X.Y.Z
```
1. **Merge the Update Center PR**
- Review and merge the PR created in [sonar-update-center-properties](https://github.com/SonarSource/sonar-update-center-properties)
- The PR link is included in the Slack notification

3. **Update version lists**:
- Move the previous public version to `archivedVersions`
- Set the new version in `publicVersions`
2. **Deploy the Update Center**
- Run the [deploy workflow](https://github.com/SonarSource/sonar-update-center-properties/actions/workflows/deploy.yml) in sonar-update-center-properties
- This publishes the updated JSON to https://downloads.sonarsource.com/sonarqube/update/scannernpm.json

4. **After PR is merged**, run the scanner release notes GitHub Action on the [SonarQube-Documentation](https://github.com/SonarSource/SonarQube-Documentation) repo to create a PR that pushes the update to product docs
3. **Update the Documentation**
- Run the [generate-release-notes workflow](https://github.com/SonarSource/sonarqube-documentation/actions/workflows/generate-release-notes.yml) in sonarqube-documentation
- This creates a PR to update the scanner versions (example: [sonarqube-documentation#94](https://github.com/SonarSource/sonarqube-documentation/pull/94))
- Merge the generated PR to publish the new version at https://docs.sonarsource.com/sonarqube-server/analyzing-source-code/scanners/npm/installing

#### Version Entry Format

Each version entry in `scannernpm.properties` follows this format:

```properties
X.Y.Z.description=Short description of the release
X.Y.Z.date=YYYY-MM-DD
X.Y.Z.changelogUrl=https://github.com/SonarSource/sonar-scanner-npm/releases/tag/X.Y.Z
X.Y.Z.downloadUrl=https://www.npmjs.com/package/@sonar/scan/v/X.Y.Z
```

#### Reference

- Initial setup PR: [sonar-update-center-properties#742](https://github.com/SonarSource/sonar-update-center-properties/pull/742)
- Published JSON: `https://downloads.sonarsource.com/sonarqube/update/scannernpm.json`
- Published JSON: https://downloads.sonarsource.com/sonarqube/update/scannernpm.json
- Documentation page: https://docs.sonarsource.com/sonarqube-server/analyzing-source-code/scanners/npm/installing
59 changes: 59 additions & 0 deletions scripts/update-update-center.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash
#
# Updates the scannernpm.properties file in sonar-update-center-properties
# for a new release.
#
# Usage: update-update-center.sh <file> <version> <description>
#
# This script:
# 1. Moves the current publicVersions to archivedVersions
# 2. Sets the new version as publicVersions
# 3. Adds version metadata (description, date, URLs)
#
set -euo pipefail

if [ $# -ne 3 ]; then
echo "Usage: $0 <file> <version> <description>"
exit 1
fi

FILE="$1"
VERSION="$2"
DESCRIPTION="$3"
DATE=$(date +%Y-%m-%d)

if [ ! -f "$FILE" ]; then
echo "Error: File not found: $FILE"
exit 1
fi

# Get current publicVersions
CURRENT_PUBLIC=$(grep "^publicVersions=" "$FILE" | cut -d= -f2)

# Get current archivedVersions
CURRENT_ARCHIVED=$(grep "^archivedVersions=" "$FILE" | cut -d= -f2)

# Update archivedVersions: append current public versions
if [ -n "$CURRENT_ARCHIVED" ]; then
NEW_ARCHIVED="${CURRENT_ARCHIVED},${CURRENT_PUBLIC}"
else
NEW_ARCHIVED="${CURRENT_PUBLIC}"
fi

# Update the file
sed -i "s/^archivedVersions=.*/archivedVersions=${NEW_ARCHIVED}/" "$FILE"
sed -i "s/^publicVersions=.*/publicVersions=${VERSION}/" "$FILE"

# Find the line number of publicVersions and insert new version entry after it
LINE_NUM=$(grep -n "^publicVersions=" "$FILE" | cut -d: -f1)

# Create the new version entry
NEW_ENTRY="${VERSION}.description=${DESCRIPTION}
${VERSION}.date=${DATE}
${VERSION}.changelogUrl=https://github.com/SonarSource/sonar-scanner-npm/releases/tag/${VERSION}
${VERSION}.downloadUrl=https://www.npmjs.com/package/@sonar/scan/v/${VERSION}"

# Insert after the publicVersions line
awk -v line="$LINE_NUM" -v entry="$NEW_ENTRY" 'NR==line {print; print ""; print entry; next} 1' "$FILE" > tmp && mv tmp "$FILE"

echo "Updated $FILE for version $VERSION"
29 changes: 27 additions & 2 deletions test/integration/orchestrator/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import type { IncomingMessage } from 'node:http';
import * as https from 'node:https';
import * as fs from 'node:fs';
import { execSync } from 'node:child_process';
Expand Down Expand Up @@ -103,7 +104,29 @@ function download(
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(zipFilePath);
console.log(`Downloading ${url} and saving it into ${zipFilePath}`);
https.get(options, response => {

const handleResponse = (response: IncomingMessage) => {
// Handle redirects (3xx status codes)
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400) {
const redirectUrl = response.headers.location;
if (redirectUrl) {
console.log(`Following redirect to ${redirectUrl}`);
https.get(redirectUrl, handleResponse).on('error', reject);
return;
}
}

if (response.statusCode !== 200) {
file.close();
fs.unlinkSync(zipFilePath);
reject(
new Error(
`Failed to download SonarQube: HTTP ${response.statusCode}. ` +
`Make sure ARTIFACTORY_ACCESS_TOKEN is set and valid.`,
),
);
return;
}
response.pipe(file);
file.on('finish', () => {
file.close();
Expand All @@ -117,7 +140,9 @@ function download(
console.error('Error while downloading file', error);
reject(error);
});
});
};

https.get(options, handleResponse).on('error', reject);
});
}

Expand Down
Loading