Compare commits

...

3 Commits

Author SHA1 Message Date
Julien HENRY 0368e05ab1 Fix dist source maps with correct relative paths
Built from worktree with local node_modules so source map paths match
CI build environment (../node_modules/ instead of ../../../../node_modules/).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 17:00:05 +02:00
Julien HENRY a30e39dd44 Use mise 2026-06-04 16:41:27 +02:00
Julien HENRY 751b8e8c7a SQSCANGHA-135 Fix scanner binaries always re-downloaded due to incompatible 4-part version
GitHub's tool-cache library uses semver.clean() to look up cached tools, which
returns null for 4-part version strings like "8.0.1.6346". This caused
findAllVersions() to filter out any cached directory, resulting in a cache miss
on every run.

The fix converts the 4-part version to a semver pre-release format
(e.g. "8.0.1-build.6346") for tool-cache operations, while keeping the original
version string for download URLs and zip extraction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 16:35:44 +02:00
7 changed files with 102 additions and 5 deletions
+16 -2
View File
@@ -3862,6 +3862,19 @@ function getScannerDownloadURL({
const scannerDirName = (version, flavor) => const scannerDirName = (version, flavor) =>
`sonar-scanner-${version}-${flavor}`; `sonar-scanner-${version}-${flavor}`;
/**
* Converts a 4-part version string (e.g. "8.0.1.6346") to a SemVer 2.0 compatible
* string (e.g. "8.0.1-build.6346") for use with GitHub's tool-cache library,
* which requires SemVer-compliant version strings.
*/
function toSemVer(version) {
const parts = version.split(".");
if (parts.length === 4) {
return `${parts[0]}.${parts[1]}.${parts[2]}-build.${parts[3]}`;
}
return version;
}
/* /*
* sonarqube-scan-action * sonarqube-scan-action
* Copyright (C) 2025 SonarSource SA * Copyright (C) 2025 SonarSource SA
@@ -4151,9 +4164,10 @@ async function installSonarScanner({
skipSignatureVerification = false, skipSignatureVerification = false,
}) { }) {
const flavor = getPlatformFlavor(os$1.platform(), os$1.arch()); const flavor = getPlatformFlavor(os$1.platform(), os$1.arch());
const semVerVersion = toSemVer(scannerVersion);
// Check if tool is already cached // Check if tool is already cached
let toolDir = find(TOOLNAME, scannerVersion, flavor); let toolDir = find(TOOLNAME, semVerVersion, flavor);
if (!toolDir) { if (!toolDir) {
info( info(
@@ -4196,7 +4210,7 @@ async function installSonarScanner({
scannerDirName(scannerVersion, flavor) scannerDirName(scannerVersion, flavor)
); );
toolDir = await cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await cacheDir(scannerPath, TOOLNAME, semVerVersion, flavor);
info(`Sonar Scanner CLI cached to: ${toolDir}`); info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
[tools]
node = "24"
@@ -22,6 +22,7 @@ import assert from "node:assert/strict";
import { describe, it, mock } from "node:test"; import { describe, it, mock } from "node:test";
const SCANNER_VERSION = "6.2.0.4584"; const SCANNER_VERSION = "6.2.0.4584";
const SCANNER_SEMVER_VERSION = "6.2.0-build.4584";
const BINARIES_URL = "https://my.artifactory.example.com/sonar-scanner-cli"; const BINARIES_URL = "https://my.artifactory.example.com/sonar-scanner-cli";
const BINARY_DOWNLOAD_URL = `${BINARIES_URL}/sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip`; const BINARY_DOWNLOAD_URL = `${BINARIES_URL}/sonar-scanner-cli-${SCANNER_VERSION}-linux-x64.zip`;
@@ -31,6 +32,7 @@ function mockUtils(t) {
getPlatformFlavor: mock.fn(() => "linux-x64"), getPlatformFlavor: mock.fn(() => "linux-x64"),
getScannerDownloadURL: mock.fn(() => BINARY_DOWNLOAD_URL), getScannerDownloadURL: mock.fn(() => BINARY_DOWNLOAD_URL),
scannerDirName: mock.fn(() => `sonar-scanner-${SCANNER_VERSION}-linux-x64`), scannerDirName: mock.fn(() => `sonar-scanner-${SCANNER_VERSION}-linux-x64`),
toSemVer: mock.fn(() => SCANNER_SEMVER_VERSION),
}, },
}); });
} }
@@ -171,6 +173,50 @@ describe("installSonarScanner", () => {
assert.equal(downloadCalls[0].auth, "Bearer mytoken"); assert.equal(downloadCalls[0].auth, "Bearer mytoken");
}); });
it("should use semver-compatible version for tool-cache find and cacheDir", async (t) => {
const findFn = mock.fn(() => null);
const cacheDirFn = mock.fn(async () => "/tmp/cached");
mockUtils(t);
t.mock.module("@actions/tool-cache", {
namedExports: {
find: findFn,
downloadTool: mock.fn(async () => "/tmp/downloaded"),
extractZip: mock.fn(async () => "/tmp/extracted"),
cacheDir: cacheDirFn,
},
});
t.mock.module("@actions/core", {
namedExports: {
info: mock.fn(),
warning: mock.fn(),
addPath: mock.fn(),
},
});
t.mock.module("../gpg-verification.js", {
namedExports: {
verifySignature: mock.fn(async () => {}),
},
});
const { installSonarScanner } = await import(
`../install-sonar-scanner.js?test=semver-version`
);
await installSonarScanner({
scannerVersion: SCANNER_VERSION,
scannerBinariesUrl: BINARIES_URL,
});
assert.equal(findFn.mock.calls[0].arguments[1], SCANNER_SEMVER_VERSION,
"tc.find should be called with semver-compatible version");
assert.equal(cacheDirFn.mock.calls[0].arguments[2], SCANNER_SEMVER_VERSION,
"tc.cacheDir should be called with semver-compatible version");
});
it("should use cached tool when available and skip download", async (t) => { it("should use cached tool when available and skip download", async (t) => {
const downloadToolFn = mock.fn(); const downloadToolFn = mock.fn();
+20
View File
@@ -22,6 +22,7 @@ import {
getPlatformFlavor, getPlatformFlavor,
getScannerDownloadURL, getScannerDownloadURL,
scannerDirName, scannerDirName,
toSemVer,
} from "../utils.js"; } from "../utils.js";
describe("getPlatformFlavor", () => { describe("getPlatformFlavor", () => {
@@ -97,3 +98,22 @@ describe("scannerDirName", () => {
); );
}); });
}); });
describe("toSemVer", () => {
it("converts 4-part version to semver pre-release format", () => {
assert.equal(toSemVer("8.0.1.6346"), "8.0.1-build.6346");
});
it("leaves 3-part semver version unchanged", () => {
assert.equal(toSemVer("8.0.1"), "8.0.1");
});
it("leaves version with pre-release identifier unchanged", () => {
assert.equal(toSemVer("7.2.0-SNAPSHOT"), "7.2.0-SNAPSHOT");
});
it("converts different 4-part versions correctly", () => {
assert.equal(toSemVer("6.2.0.4584"), "6.2.0-build.4584");
assert.equal(toSemVer("8.1.0.6389"), "8.1.0-build.6389");
});
});
+4 -2
View File
@@ -24,6 +24,7 @@ import {
getPlatformFlavor, getPlatformFlavor,
getScannerDownloadURL, getScannerDownloadURL,
scannerDirName, scannerDirName,
toSemVer,
} from "./utils.js"; } from "./utils.js";
import { verifySignature } from "./gpg-verification.js"; import { verifySignature } from "./gpg-verification.js";
@@ -39,9 +40,10 @@ export async function installSonarScanner({
skipSignatureVerification = false, skipSignatureVerification = false,
}) { }) {
const flavor = getPlatformFlavor(os.platform(), os.arch()); const flavor = getPlatformFlavor(os.platform(), os.arch());
const semVerVersion = toSemVer(scannerVersion);
// Check if tool is already cached // Check if tool is already cached
let toolDir = tc.find(TOOLNAME, scannerVersion, flavor); let toolDir = tc.find(TOOLNAME, semVerVersion, flavor);
if (!toolDir) { if (!toolDir) {
core.info( core.info(
@@ -84,7 +86,7 @@ export async function installSonarScanner({
scannerDirName(scannerVersion, flavor) scannerDirName(scannerVersion, flavor)
); );
toolDir = await tc.cacheDir(scannerPath, TOOLNAME, scannerVersion, flavor); toolDir = await tc.cacheDir(scannerPath, TOOLNAME, semVerVersion, flavor);
core.info(`Sonar Scanner CLI cached to: ${toolDir}`); core.info(`Sonar Scanner CLI cached to: ${toolDir}`);
} else { } else {
+13
View File
@@ -51,3 +51,16 @@ export function getScannerDownloadURL({
export const scannerDirName = (version, flavor) => export const scannerDirName = (version, flavor) =>
`sonar-scanner-${version}-${flavor}`; `sonar-scanner-${version}-${flavor}`;
/**
* Converts a 4-part version string (e.g. "8.0.1.6346") to a SemVer 2.0 compatible
* string (e.g. "8.0.1-build.6346") for use with GitHub's tool-cache library,
* which requires SemVer-compliant version strings.
*/
export function toSemVer(version) {
const parts = version.split(".");
if (parts.length === 4) {
return `${parts[0]}.${parts[1]}.${parts[2]}-build.${parts[3]}`;
}
return version;
}