diff --git a/README.md b/README.md
index aa630a28..7aa8628e 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ This action requires that you set the [`CC_TEST_REPORTER_ID`](https://docs.codec
| `coverageLocations` | | Locations to find code coverage as a multiline string.
Each line should be of the form `:`.
`type` can be any one of `clover, cobertura, coverage.py, excoveralls, gcov, gocov, jacoco, lcov, lcov-json, simplecov, xccov`. See examples below. |
| `prefix` | `undefined` | See [`--prefix`](https://docs.codeclimate.com/docs/configuring-test-coverage) |
| `verifyDownload` | `true` | Verifies the downloaded Code Climate reporter binary's checksum and GPG signature. See [Verifying binaries](https://github.com/codeclimate/test-reporter#verifying-binaries) |
+| `verifyEnvironment` | `true` | Verifies the current runtime environment (operating system and CPU architecture) is supported by the Code Climate reporter. See [list of supported platforms](https://github.com/codeclimate/test-reporter#binaries) |
> **Note**
> If you are a Ruby developer using [SimpleCov](https://github.com/simplecov-ruby/simplecov), other users have recommended installing an additional gem â `gem "simplecov_json_formatter"` â this gem fixes `json` error from the default `coverage/.resultset.json` output from SimpleCov.
diff --git a/package.json b/package.json
index 648826ee..a7992747 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"@actions/exec": "1.1.1",
"@actions/github": "6.0.0",
"@actions/glob": "0.4.0",
+ "arch": "3.0.0",
"hook-std": "3.0.0",
"node-fetch": "3.3.2",
"openpgp": "5.11.1"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a14142da..fbc1ea5a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@actions/glob':
specifier: 0.4.0
version: 0.4.0
+ arch:
+ specifier: 3.0.0
+ version: 3.0.0
hook-std:
specifier: 3.0.0
version: 3.0.0
@@ -723,6 +726,9 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
+ arch@3.0.0:
+ resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==}
+
arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
@@ -2493,6 +2499,8 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.1
+ arch@3.0.0: {}
+
arg@4.1.3: {}
asn1.js@5.4.1:
diff --git a/src/main.ts b/src/main.ts
index aa8a8348..4e21012f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,5 +1,4 @@
import { unlinkSync } from 'node:fs';
-import { arch, platform } from 'node:os';
import { resolve } from 'node:path';
import { chdir } from 'node:process';
import { fileURLToPath } from 'node:url';
@@ -11,6 +10,7 @@ import * as glob from '@actions/glob';
import {
downloadToFile,
getOptionalString,
+ getSupportedEnvironmentInfo,
parsePathAndFormat,
verifyChecksum,
verifySignature,
@@ -36,14 +36,17 @@ export interface ActionArguments {
coveragePrefix?: string;
/** Verifies the downloaded binary with a Code Climate-provided SHA 256 checksum and GPG sinature. @default true */
verifyDownload?: string;
+ /** Verifies if the current OS and CPU architecture is supported by CodeClimate test reporter. */
+ verifyEnvironment?: string;
}
-const PLATFORM = platform();
+const CURRENT_ENVIRONMENT = getSupportedEnvironmentInfo();
+const PLATFORM = CURRENT_ENVIRONMENT.platform;
// REFER: https://docs.codeclimate.com/docs/configuring-test-coverage#locations-of-pre-built-binaries
/** Canonical download URL for the official CodeClimate reporter. */
export const DOWNLOAD_URL = `https://codeclimate.com/downloads/test-reporter/test-reporter-latest-${
PLATFORM === 'win32' ? 'windows' : PLATFORM
-}-${arch() === 'arm64' ? 'arm64' : 'amd64'}`;
+}-${CURRENT_ENVIRONMENT.architecture === 'arm64' ? 'arm64' : 'amd64'}`;
/** Local file name of the CodeClimate reporter. */
export const EXECUTABLE = './cc-reporter';
export const CODECLIMATE_GPG_PUBLIC_KEY_ID =
@@ -55,6 +58,7 @@ const DEFAULT_WORKING_DIRECTORY = '';
const DEFAULT_CODECLIMATE_DEBUG = 'false';
const DEFAULT_COVERAGE_LOCATIONS = '';
const DEFAULT_VERIFY_DOWNLOAD = 'true';
+const DEFAULT_VERIFY_ENVIRONMENT = 'true';
const SUPPORTED_GITHUB_EVENTS = [
// Regular PRs.
@@ -204,10 +208,24 @@ export async function run({
coverageLocationsParam = DEFAULT_COVERAGE_LOCATIONS,
coveragePrefix,
verifyDownload = DEFAULT_VERIFY_DOWNLOAD,
+ verifyEnvironment = DEFAULT_VERIFY_ENVIRONMENT,
}: ActionArguments = {}): Promise {
let lastExitCode = 1;
+ if (verifyEnvironment === 'true') {
+ debug('âšī¸ Verifying environment...');
+ const { supported, platform, architecture } = getSupportedEnvironmentInfo();
+ if (!supported) {
+ const errorMessage = `Unsupported platform and architecture! CodeClimate Test Reporter currently is not available for ${architecture} on ${platform} OS`;
+ error(errorMessage);
+ setFailed('đ¨ Environment verification failed!');
+ throw new Error(errorMessage);
+ }
+ lastExitCode = 0;
+ debug('â
Environment verification completed...');
+ }
+
if (workingDirectory) {
- debug(`Changing working directory to ${workingDirectory}`);
+ debug(`âšī¸ Changing working directory to ${workingDirectory}`);
try {
chdir(workingDirectory);
lastExitCode = 0;
@@ -410,6 +428,11 @@ if (isThisFileBeingRunViaCLI) {
'verifyDownload',
DEFAULT_VERIFY_DOWNLOAD,
);
+ const verifyEnvironment = getOptionalString(
+ 'verifyEnvironment',
+ DEFAULT_VERIFY_ENVIRONMENT,
+ );
+
try {
run({
downloadUrl: DOWNLOAD_URL,
@@ -420,6 +443,7 @@ if (isThisFileBeingRunViaCLI) {
coverageLocationsParam: coverageLocations,
coveragePrefix,
verifyDownload,
+ verifyEnvironment,
});
} finally {
// Finally clean up all artifacts that we downloaded.
diff --git a/src/utils.ts b/src/utils.ts
index be952d00..014d554b 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,6 +3,7 @@ import { createWriteStream, readFile } from 'node:fs';
import { platform } from 'node:os';
import { promisify } from 'node:util';
import { getInput } from '@actions/core';
+import arch from 'arch';
import fetch from 'node-fetch';
import {
type VerificationResult,
@@ -15,6 +16,18 @@ import {
const readFileAsync = promisify(readFile);
type ReadFileAsyncOptions = Omit[1], 'string'>;
+/** List of environments not supported by the CodeClimate test reporter. */
+// REFER: https://github.com/codeclimate/test-reporter#download
+export const UNSUPPORTED_ENVIRONMENTS: Array<{
+ platform: ReturnType;
+ architecture: ReturnType;
+}> = [
+ {
+ platform: 'darwin',
+ architecture: 'arm64',
+ },
+];
+
/**
* Parses GitHub Action input and returns the optional value as a string.
*
@@ -249,3 +262,25 @@ export function parsePathAndFormat(coverageConfigLine: string): {
const pattern = lineParts.slice(0, -1)[0] as string;
return { format, pattern };
}
+
+/**
+ * Reads information about the current operating system and the CPU architecture
+ * and tells if the CodeClimate test reporter supports it.
+ */
+export function getSupportedEnvironmentInfo() {
+ const currentEnvironment = {
+ platform: platform(),
+ architecture: arch(),
+ };
+
+ return {
+ supported: !UNSUPPORTED_ENVIRONMENTS.some((e) => {
+ return (
+ e.architecture === currentEnvironment.architecture &&
+ e.platform === currentEnvironment.platform
+ );
+ }),
+ platform: currentEnvironment.platform,
+ architecture: currentEnvironment.architecture,
+ };
+}
diff --git a/test/integration.test.ts b/test/integration.test.ts
index 52c80c95..2472f308 100644
--- a/test/integration.test.ts
+++ b/test/integration.test.ts
@@ -1,5 +1,6 @@
import { unlinkSync } from 'node:fs';
-import { EOL, arch, platform } from 'node:os';
+import { EOL, platform } from 'node:os';
+import arch from 'arch';
import { hookStd } from 'hook-std';
import t from 'tap';
import {
diff --git a/test/main.test.ts b/test/main.test.ts
index 71c5d56b..c3a1974f 100644
--- a/test/main.test.ts
+++ b/test/main.test.ts
@@ -18,6 +18,7 @@ import t from 'tap';
import which from 'which';
import { CODECLIMATE_GPG_PUBLIC_KEY_ID, prepareEnv, run } from '../src/main.js';
import * as utils from '../src/utils.js';
+import { getSupportedEnvironmentInfo } from '../src/utils.js';
/**
* Dev Notes
@@ -181,6 +182,7 @@ t.test('đ§Ē run() should run the CC reporter (happy path).', async (t) => {
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -246,6 +248,7 @@ t.test(
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -283,6 +286,122 @@ t.test(
},
);
+t.test(
+ 'đ§Ē run() should run environment verification if configured and fail on unsupported platforms.',
+ {
+ skip: getSupportedEnvironmentInfo().supported
+ ? 'Skipping as test is targeted only for unsupported platforms.'
+ : false,
+ },
+ async (t) => {
+ t.plan(1);
+ t.teardown(() => sandbox.restore());
+
+ let capturedOutput = '';
+ const stdHook = hookStd((text: string) => {
+ capturedOutput += text;
+ });
+
+ try {
+ await run({
+ downloadUrl: 'http://localhost.test/dummy-cc-reporter',
+ verifyDownload: 'false',
+ verifyEnvironment: 'true',
+ });
+ t.fail({ error: 'should have thrown an error on unsupported platforms' });
+ stdHook.unhook();
+ } catch (err) {
+ stdHook.unhook();
+ t.equal(
+ capturedOutput,
+ [
+ '::debug::âšī¸ Verifying environment...',
+ `::error::Unsupported platform and architecture! CodeClimate Test Reporter currently is not available for ${getSupportedEnvironmentInfo().architecture} on ${getSupportedEnvironmentInfo().platform} OS`,
+ '::error::đ¨ Environment verification failed!',
+ '',
+ ].join(EOL),
+ 'should execute all steps (including environment verification).',
+ );
+ } finally {
+ nock.cleanAll();
+ }
+ t.end();
+ },
+);
+
+t.test(
+ 'đ§Ē run() should run environment verification if configured.',
+ {
+ skip: getSupportedEnvironmentInfo().supported
+ ? false
+ : 'Skipping as test is targeted only for supported platforms.',
+ },
+ async (t) => {
+ t.plan(1);
+ t.teardown(() => sandbox.restore());
+ const filePath = `./test.${EXE_EXT}`;
+ nock('http://localhost.test')
+ .get('/dummy-cc-reporter')
+ .reply(200, async () => {
+ const dummyReporterFile = joinPath(
+ THIS_MODULE_DIRNAME,
+ `../test/fixtures/dummy-cc-reporter.${EXE_EXT}`,
+ );
+ const dummyReporter = await readFileAsync(dummyReporterFile);
+ return intoStream(dummyReporter);
+ });
+
+ let capturedOutput = '';
+ const stdHook = hookStd((text: string) => {
+ capturedOutput += text;
+ });
+
+ try {
+ await run({
+ downloadUrl: 'http://localhost.test/dummy-cc-reporter',
+ executable: filePath,
+ coverageCommand: `${ECHO_CMD} 'coverage ok'`,
+ verifyDownload: 'false',
+ verifyEnvironment: 'true',
+ });
+ stdHook.unhook();
+ } catch (err) {
+ stdHook.unhook();
+ t.fail({ error: err });
+ } finally {
+ nock.cleanAll();
+ }
+
+ t.equal(
+ capturedOutput,
+ [
+ '::debug::âšī¸ Verifying environment...',
+ '::debug::â
Environment verification completed...',
+ '::debug::âšī¸ Downloading CC Reporter from http://localhost.test/dummy-cc-reporter ...',
+ '::debug::â
CC Reporter downloaded...',
+ PLATFORM === 'win32'
+ ? `[command]${EXE_PATH_PREFIX} "${DEFAULT_WORKDIR}\\test.${EXE_EXT} before-build"`
+ : `[command]${DEFAULT_WORKDIR}/test.${EXE_EXT} before-build`,
+ 'before-build',
+ '::debug::â
CC Reporter before-build checkin completed...',
+ `[command]${ECHO_CMD} 'coverage ok'`,
+ `'coverage ok'`,
+ '::debug::â
Coverage run completed...',
+ PLATFORM === 'win32'
+ ? `[command]${EXE_PATH_PREFIX} "${DEFAULT_WORKDIR}\\test.${EXE_EXT} after-build --exit-code 0"`
+ : `[command]${DEFAULT_WORKDIR}/test.${EXE_EXT} after-build --exit-code 0`,
+ 'after-build --exit-code 0',
+ '::debug::â
CC Reporter after-build checkin completed!',
+ '',
+ ].join(EOL),
+ 'should execute all steps (including environment verification).',
+ );
+ unlinkSync(filePath);
+ nock.cleanAll();
+ t.end();
+ },
+);
+
t.test(
'đ§Ē run() should run the CC reporter without a coverage command.',
async (t) => {
@@ -311,6 +430,7 @@ t.test(
executable: filePath,
coverageCommand: '',
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -435,6 +555,7 @@ t.test(
coverageCommand: '',
coverageLocationsParam: filePattern,
codeClimateDebug: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -538,6 +659,7 @@ t.test(
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
workingDirectory: CUSTOM_WORKDIR,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -550,7 +672,7 @@ t.test(
t.equal(
capturedOutput,
[
- `::debug::Changing working directory to ${CUSTOM_WORKDIR}`,
+ `::debug::âšī¸ Changing working directory to ${CUSTOM_WORKDIR}`,
'::debug::â
Changing working directory completed...',
'::debug::âšī¸ Downloading CC Reporter from http://localhost.test/dummy-cc-reporter ...',
'::debug::â
CC Reporter downloaded...',
@@ -613,6 +735,7 @@ t.test(
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
verifyDownload: 'true',
+ verifyEnvironment: 'false',
});
t.fail('should have thrown an error');
stdHook.unhook();
@@ -708,6 +831,7 @@ t.test(
downloadUrl: 'http://localhost.test/dummy-cc-reporter',
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -776,6 +900,7 @@ t.test(
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -835,6 +960,7 @@ t.test(
executable: filePath,
coverageCommand: `${ECHO_CMD} 'coverage ok'`,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
} catch (err) {
@@ -902,6 +1028,7 @@ t.test(
executable: filePath,
coverageCommand: COVERAGE_COMMAND,
verifyDownload: 'false',
+ verifyEnvironment: 'false',
});
stdHook.unhook();
t.fail('Should throw an error.');