Skip to content
This repository was archived by the owner on Jul 23, 2025. It is now read-only.

Commit f0efca8

Browse files
authored
feat(core): Support Windows
BREAKING CHANGE: Support Windows, now that CodeClimate has released a Windows binary of the reporter – see codeclimate.com/changelog/7dd79ee1cf1af7141b2bd18b Fixes #665.
2 parents daa1013 + c08aaec commit f0efca8

23 files changed

+546
-302
lines changed

.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# REFER: https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/
2+
3+
# All files are checked into the repo with LF
4+
* text=auto
5+
6+
# These files are checked out using CRLF locally
7+
*.bat eol=crlf

.github/workflows/ci.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
name: "PR checks"
2-
on: [pull_request, push]
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
37

48
jobs:
59
check_pr:
@@ -46,10 +50,10 @@ jobs:
4650
os:
4751
- { index: 1, os-name: 'ubuntu-latest', os-label: 'Linux' }
4852
- { index: 2, os-name: 'macos-latest', os-label: 'macOS' }
49-
# NOTE: Windows is not supported because CodeClimate does not publish a Windows build of the reporter (yet).
53+
- { index: 3, os-name: 'windows-latest', os-label: 'Windows' }
5054
runs-on: ${{ matrix.os.os-name }}
5155
concurrency:
52-
group: ${{ github.workflow }}-${{ github.ref }}
56+
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os.os-name }}
5357
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
5458
steps:
5559
- name: checkout code

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66

77
A GitHub action that publishes your code coverage to [Code Climate](http://codeclimate.com/).
88

9-
> **Note**
10-
>
11-
> Please use a _specific_ version of this action – for example, `v4.0.0`, instead of using only major versions like `v4` or `v4.0` – these will **not** work!
12-
139
> **Warning**
1410
>
1511
> Please upgrade to v3.1.1 (or higher) immediately. v3.1.0 was recently broken inadverdently, and the only fix is to upgrade your action to v3.1.1 or higher. Please see [#626](https://github.com/paambaati/codeclimate-action/issues/626) for more details.
@@ -129,6 +125,6 @@ steps:
129125

130126
Example projects
131127

132-
1. [paambaati/websight](https://github.com/paambaati/websight/blob/89f03007680531587dd5ff5c673e6d813a298d8c/.github/workflows/ci.yml#L33-L50)
128+
1. [paambaati/websight](https://github.com/paambaati/websight/blob/5ab56bcc365ee73dd7937e87267db30f6357c4cd/.github/workflows/ci.yml#L33-L50)
133129

134130
2. [MartinNuc/coverage-ga-test](https://github.com/MartinNuc/coverage-ga-test/blob/master/.github/workflows/ci.yaml)

package-lock.json

Lines changed: 10 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import * as glob from '@actions/glob';
88
import {
99
downloadToFile,
1010
getOptionalString,
11+
parsePathAndFormat,
1112
verifyChecksum,
1213
verifySignature,
1314
} from './utils';
1415
import type { ExecOptions } from '@actions/exec/lib/interfaces';
1516

17+
const PLATFORM = platform();
1618
// REFER: https://docs.codeclimate.com/docs/configuring-test-coverage#locations-of-pre-built-binaries
1719
/** Canonical download URL for the official CodeClimate reporter. */
18-
export const DOWNLOAD_URL = `https://codeclimate.com/downloads/test-reporter/test-reporter-latest-${platform()}-${
19-
arch() === 'arm64' ? 'arm64' : 'amd64'
20-
}`;
20+
export const DOWNLOAD_URL = `https://codeclimate.com/downloads/test-reporter/test-reporter-latest-${
21+
PLATFORM === 'win32' ? 'windows' : PLATFORM
22+
}-${arch() === 'arm64' ? 'arm64' : 'amd64'}`;
2123
/** Local file name of the CodeClimate reporter. */
2224
export const EXECUTABLE = './cc-reporter';
2325
export const CODECLIMATE_GPG_PUBLIC_KEY_ID =
@@ -143,12 +145,8 @@ async function getLocationLines(
143145
.filter((pat) => pat)
144146
.map((pat) => pat.trim());
145147

146-
const patternsAndFormats = coverageLocationPatternsLines.map((line) => {
147-
const lineParts = line.split(':');
148-
const format = lineParts.slice(-1)[0];
149-
const pattern = lineParts.slice(0, -1)[0];
150-
return { format, pattern };
151-
});
148+
const patternsAndFormats =
149+
coverageLocationPatternsLines.map(parsePathAndFormat);
152150

153151
const pathsWithFormat = await Promise.all(
154152
patternsAndFormats.map(async ({ format, pattern }) => {
@@ -179,13 +177,6 @@ export function run(
179177
verifyDownload: string = DEFAULT_VERIFY_DOWNLOAD
180178
): Promise<void> {
181179
return new Promise(async (resolve, reject) => {
182-
if (platform() === 'win32') {
183-
const err = new Error('CC Reporter is not supported on Windows!');
184-
error(err.message);
185-
setFailed('🚨 CodeClimate Reporter will not run on Windows!');
186-
return reject(err);
187-
}
188-
189180
let lastExitCode = 1;
190181
if (workingDirectory) {
191182
debug(`Changing working directory to ${workingDirectory}`);
@@ -267,7 +258,9 @@ export function run(
267258
// Run format-coverage on each location.
268259
const parts: Array<string> = [];
269260
for (const i in coverageLocations) {
270-
const [location, type] = coverageLocations[i].split(':');
261+
const { format: type, pattern: location } = parsePathAndFormat(
262+
coverageLocations[i]
263+
);
271264
if (!type) {
272265
const err = new Error(`Invalid formatter type ${type}`);
273266
debug(

src/utils.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createHash, timingSafeEqual } from 'crypto';
22
import { readFile, createWriteStream } from 'fs';
3+
import { platform } from 'os';
34
import { promisify } from 'util';
45
import { getInput } from '@actions/core';
56
import fetch from 'node-fetch';
@@ -44,7 +45,16 @@ export function downloadToFile(
4445
): Promise<void> {
4546
return new Promise(async (resolve, reject) => {
4647
try {
47-
const response = await fetch(url, { timeout: 2 * 60 * 1000 }); // Timeout in 2 minutes.
48+
const response = await fetch(url, {
49+
redirect: 'follow',
50+
follow: 5,
51+
timeout: 2 * 60 * 1000, // Timeout in 2 minutes.
52+
});
53+
if (response.status < 200 || response.status > 299) {
54+
throw new Error(
55+
`Download of '${url}' failed with response status code ${response.status}`
56+
);
57+
}
4858
const writer = createWriteStream(file, { mode });
4959
response.body.pipe(writer);
5060
writer.on('close', () => {
@@ -106,7 +116,7 @@ export async function getFileChecksum(
106116
* Note that the checksum file is of the format `<checksum> <filename>`.
107117
*
108118
* @param originalFile Original file for which the checksum was generated.
109-
* @param checksumFile Checksum file.
119+
* @param checksumFile Checksum file. Note that the checksum file has to be of the format <filename> <checksum>
110120
* @param algorithm (Optional) Hashing algorithm. @default `sha256`
111121
* @returns Returns `true` if checksums match, `false` if they don't.
112122
*/
@@ -120,7 +130,7 @@ export async function verifyChecksum(
120130
const declaredChecksum = declaredChecksumFileContents
121131
.toString()
122132
.trim()
123-
.split(' ')[0];
133+
.split(/\s+/)[0];
124134
try {
125135
return timingSafeEqual(
126136
Buffer.from(binaryChecksum),
@@ -171,3 +181,49 @@ export async function verifySignature(
171181
return false;
172182
}
173183
}
184+
185+
/**
186+
* Parses a given coverage config line that looks like this –
187+
*
188+
* ```
189+
* /Users/gp/projects/cc/*.lcov:lcov
190+
* ```
191+
*
192+
* or –
193+
*
194+
* ```
195+
* D:\Users\gp\projects\cc\*.lcov:lcov
196+
* ```
197+
*
198+
* into –
199+
*
200+
* ```json
201+
* { "format": "lcov", "pattern": "/Users/gp/projects/cc/*.lcov" }
202+
* ```
203+
*
204+
* or –
205+
*
206+
* ```json
207+
* { "format": "lcov", "pattern": "D:\Users\gp\projects\cc\*.lcov" }
208+
* ```
209+
* @param coverageConfigLine
210+
* @returns
211+
*/
212+
export function parsePathAndFormat(coverageConfigLine: string): {
213+
format: string;
214+
pattern: string;
215+
} {
216+
let lineParts = coverageConfigLine.split(':');
217+
// On Windows, if the glob received an absolute path, the path will
218+
// include the Drive letter and the path – for example, `C:\Users\gp\projects\cc\*.lcov:lcov`
219+
// which leads to 2 colons. So we handle this special case.
220+
if (
221+
platform() === 'win32' &&
222+
(coverageConfigLine.match(/:/g) || []).length > 1
223+
) {
224+
lineParts = [lineParts.slice(0, -1).join(':'), lineParts.slice(-1)[0]];
225+
}
226+
const format = lineParts.slice(-1)[0];
227+
const pattern = lineParts.slice(0, -1)[0];
228+
return { format, pattern };
229+
}

test/fixtures/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Notes on how to create fixtures
2+
3+
Currently, this is cumbersome, but it is recommended to check checksums on target platforms as there's minor differences in line endings, and as such, checksums vary for the same file, based on platform.
4+
5+
### Windows
6+
7+
1. To create the SHA256 checksum of a file –
8+
9+
```
10+
certUtil -hashfile test/fixtures/filename.bat SHA256
11+
```
12+
13+
### macOS
14+
15+
1. To create the SHA256 checksum of a file –
16+
17+
```
18+
shasum -a 256 test/fixtures/filename.sh
19+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
:: Dummy shell script that exits with a non-zero code when the argument 'after-build' is given.
2+
@echo off
3+
IF "%*" == "after-build --exit-code 0" (
4+
EXIT /b 69
5+
) ELSE (
6+
:: `CALL` is a no-op.
7+
CALL
8+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dc9edd7421a4cda22b95f340a5ccc34bbeedf8a7376e9b5819d01537eee4bfef test/fixtures/dummy-cc-reporter-after-build-error.bat

test/fixtures/dummy-cc-reporter-after-build-error.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# Dummy shell script that with a non-zero code when the argument 'after-build' is given.
2+
# Dummy shell script that exits with a non-zero code when the argument 'after-build' is given.
33
if [[ "$*" == "after-build --exit-code 0" ]]
44
then exit 69
55
else

0 commit comments

Comments
 (0)