Skip to content

Commit

Permalink
feat(core): send authors to api
Browse files Browse the repository at this point in the history
  • Loading branch information
3v0k4 committed Jul 20, 2023
1 parent 4495f99 commit 69e1e00
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-crabs-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@knapsack-pro/core": minor
---

Send authors to the API
22 changes: 19 additions & 3 deletions packages/core/__tests__/ci-env.config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ import {
TravisCI,
UnsupportedCI,
} from '../src/ci-providers';
import { CIEnvConfig } from '../src/config/ci-env.config';
import { isCI, detectCI } from '../src/config/ci-env.config';

describe('KnapsackProEnvConfig', () => {
// Since we use CircleCI for testing, we prevent it from interfering with the tests.
delete process.env.CIRCLECI;
delete process.env.CI;
const ENV = { ...process.env };

afterEach(() => {
process.env = { ...ENV };
});

describe('.detectCi', () => {
describe('detectCI', () => {
const TESTS: [string, object, typeof CIProviderBase][] = [
['AppVeyor', { APPVEYOR: 'whatever' }, AppVeyor],
['Buildkite', { BUILDKITE: 'whatever' }, Buildkite],
Expand All @@ -50,7 +51,22 @@ describe('KnapsackProEnvConfig', () => {
it(`detects ${ci}`, () => {
process.env = { ...process.env, ...env };

expect(CIEnvConfig.detectCi()).toEqual(expected);
expect(detectCI()).toEqual(expected);
});
});
});

describe('isCI', () => {
const TESTS: [string, object, boolean][] = [
['CI from env', { CI: 'True' }, true],
['AppVeyor', { APPVEYOR: 'whatever' }, true],
['Unsupported CI', {}, false],
];
TESTS.forEach(([ci, env, expected]) => {
it(`detects ${ci}`, () => {
process.env = { ...process.env, ...env };

expect(isCI()).toEqual(expected);
});
});
});
Expand Down
88 changes: 87 additions & 1 deletion packages/core/__tests__/knapsack-pro-env.config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { KnapsackProEnvConfig } from '../src/config/knapsack-pro-env.config';
import {
KnapsackProEnvConfig,
buildAuthor,
commitAuthors,
} from '../src/config/knapsack-pro-env.config';
import { KnapsackProLogger } from '../src/knapsack-pro-logger';
import * as Urls from '../src/urls';

describe('KnapsackProEnvConfig', () => {
// Since we use CircleCI for testing, we prevent it from interfering with the tests.
delete process.env.CIRCLECI;
delete process.env.CI;
const ENV = { ...process.env };

afterEach(() => {
Expand Down Expand Up @@ -296,4 +301,85 @@ describe('KnapsackProEnvConfig', () => {
});
});
});

describe('.maskedUserSeat', () => {
describe('when KNAPSACK_PRO_USER_SEAT is set', () => {
it('it returns the masked KNAPSACK_PRO_USER_SEAT', () => {
process.env.KNAPSACK_PRO_USER_SEAT = 'riccardo';

expect(KnapsackProEnvConfig.maskedUserSeat).toEqual('ri******');
});
});

describe('when CI is CircleCI', () => {
it('it returns the masked CIRCLE_USERNAME', () => {
process.env.CIRCLECI = 'whatever';
process.env.CIRCLE_USERNAME = 'riccardo';

expect(KnapsackProEnvConfig.maskedUserSeat).toEqual('ri******');
});
});

describe('when both KNAPSACK_PRO_USER_SEAT is set and CI is CircleCI', () => {
it('it returns the masked KNAPSACK_PRO_USER_SEAT', () => {
process.env.KNAPSACK_PRO_USER_SEAT = 'jane';
process.env.CIRCLECI = 'whatever';
process.env.CIRCLE_USERNAME = 'riccardo';

expect(KnapsackProEnvConfig.maskedUserSeat).toEqual('ja**');
});
});
});

describe('buildAuthor', () => {
it('returns the masked build author', () => {
const actual = buildAuthor(() =>
Buffer.from('John Doe <[email protected]>\n'),
);

expect(actual).toEqual('Jo** Do* <jo**.do*@ex*****.co*>');
});

describe('when the command raises an exception', () => {
it('returns the no-git author', () => {
const actual = buildAuthor(() => {
throw new Error();
});

expect(actual).toEqual('no git <[email protected]>');
});
});
});

describe('commitAuthors', () => {
it('returns the masked commit authors', () => {
const actual = commitAuthors(() =>
Buffer.from(
[
' 5\t3v0k4 <[email protected]>\n',
' 10\tArtur Nowak <[email protected]>\n',
' 2\tRiccardo <[email protected]>\n',
' 3 \tshadre <[email protected]>\n',
].join(''),
),
);

expect(actual).toEqual([
{ commits: 5, author: '3v0*4 <ri******@ex*****.co*>' },
{ commits: 10, author: 'Ar*** No*** <ar***@ex*****.co*>' },
{ commits: 2, author: 'Ri****** <ri******@ex*****.co*>' },
{ commits: 3, author: 'sh**** <sh***@ex*****.co*>' },
]);
});

describe('when the command raises an exception', () => {
it('returns []', () => {
const actual = commitAuthors(() => {
throw new Error();
});

expect(actual).toEqual([]);
});
});
});
});
4 changes: 3 additions & 1 deletion packages/core/src/ci-providers/unsupported-ci.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export abstract class UnsupportedCI {
import { CIProviderBase } from '.';

export class UnsupportedCI extends CIProviderBase {
public static get ciNodeTotal(): string | undefined {
return undefined;
}
Expand Down
48 changes: 26 additions & 22 deletions packages/core/src/config/ci-env.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,31 @@ import {
UnsupportedCI,
} from '../ci-providers';

export const detectCI = (): typeof CIProviderBase => {
const detected = [
AppVeyor,
Buildkite,
CircleCI,
CirrusCI,
CodefreshCI,
Codeship,
GithubActions,
GitlabCI,
HerokuCI,
SemaphoreCI,
SemaphoreCI2,
TravisCI,
]
.map((provider) => provider.detect)
.filter(Boolean)[0];

return detected || UnsupportedCI;
};

export const isCI = (): boolean =>
(process.env.CI || 'false').toLowerCase() === 'true' ||
detectCI() !== UnsupportedCI;

type CIProviderMethod =
| 'ciNodeTotal'
| 'ciNodeIndex'
Expand Down Expand Up @@ -61,27 +86,6 @@ export class CIEnvConfig {
private static ciEnvFor<T extends CIProviderMethod>(
functionName: T,
): (typeof CIProviderBase)[T] {
return this.detectCi()[functionName];
}

public static detectCi() {
const detected = [
AppVeyor,
Buildkite,
CircleCI,
CirrusCI,
CodefreshCI,
Codeship,
GithubActions,
GitlabCI,
HerokuCI,
SemaphoreCI,
SemaphoreCI2,
TravisCI,
]
.map((provider) => provider.detect)
.filter(Boolean)[0];

return detected || UnsupportedCI;
return detectCI()[functionName];
}
}
65 changes: 58 additions & 7 deletions packages/core/src/config/knapsack-pro-env.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import childProcess = require('child_process');
import { CIEnvConfig } from '.';
import { CIEnvConfig, isCI } from '.';
import { KnapsackProLogger } from '../knapsack-pro-logger';
import * as Urls from '../urls';

const { spawnSync } = childProcess;
const { spawnSync, execSync } = childProcess;

function logLevel(): string {
if (process.env.KNAPSACK_PRO_LOG_LEVEL) {
Expand All @@ -15,6 +15,12 @@ function logLevel(): string {

const knapsackProLogger = new KnapsackProLogger(logLevel());

const mask = (string: string): string => {
const regexp = /(?<=\w{2})[a-zA-Z]/g;
const maskingChar = '*';
return string.replace(regexp, maskingChar);
};

export class KnapsackProEnvConfig {
private static $fixedQueueSplit: boolean | undefined;

Expand Down Expand Up @@ -211,18 +217,63 @@ export class KnapsackProEnvConfig {
}

public static get maskedUserSeat(): string | void {
const regexp = /(?<=\w{2})[a-zA-Z]/g;
const maskingChar = '*';

if (process.env.KNAPSACK_PRO_USER_SEAT) {
return process.env.KNAPSACK_PRO_USER_SEAT.replace(regexp, maskingChar);
return mask(process.env.KNAPSACK_PRO_USER_SEAT);
}

const { userSeat } = CIEnvConfig;
if (userSeat) {
userSeat.replace(regexp, maskingChar);
return mask(userSeat);
}

return undefined;
}
}

const $buildAuthor = (command: () => Buffer): string => {
try {
const author = command().toString().trim();
return mask(author);
} catch (error) {
return 'no git <[email protected]>';
}
};

const gitBuildAuthor = () =>
execSync('git log --format="%aN <%aE>" -1 2>/dev/null');

export const buildAuthor = (command = gitBuildAuthor): string =>
$buildAuthor(command);

const $commitAuthors = (
command: () => Buffer,
): { commits: number; author: string }[] => {
try {
return command()
.toString()
.split('\n')
.filter((line) => line !== '')
.map((line) => line.trim())
.map((line) => line.split('\t'))
.map(([commits, author]) => ({
commits: parseInt(commits, 10),
author: mask(author),
}));
} catch (error) {
return [];
}
};

const gitCommitAuthors = () => {
if (isCI) {
execSync(`git fetch --shallow-since "one month ago" --quiet 2>/dev/null`);
}

return execSync(
`git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`,
);
};

export const commitAuthors = (
command = gitCommitAuthors,
): { commits: number; author: string }[] => $commitAuthors(command);
11 changes: 7 additions & 4 deletions packages/core/src/knapsack-pro-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios, { AxiosError, AxiosInstance, AxiosPromise } from 'axios';

import { KnapsackProEnvConfig } from './config';
import { KnapsackProEnvConfig, buildAuthor, commitAuthors } from './config';
import { KnapsackProLogger } from './knapsack-pro-logger';
import { TestFile } from './models';

Expand Down Expand Up @@ -28,8 +28,6 @@ export class KnapsackProAPI {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): AxiosPromise<any> {
const url = '/v1/queues/queue';
const shouldSendTestFilesInPayload =
initializeQueue && !attemptConnectToQueue;
const data = {
test_suite_token: KnapsackProEnvConfig.testSuiteToken,
can_initialize_queue: initializeQueue,
Expand All @@ -41,7 +39,12 @@ export class KnapsackProAPI {
node_index: KnapsackProEnvConfig.ciNodeIndex,
node_build_id: KnapsackProEnvConfig.ciNodeBuildId,
user_seat: KnapsackProEnvConfig.maskedUserSeat,
...(shouldSendTestFilesInPayload && { test_files: allTestFiles }),
...(initializeQueue &&
!attemptConnectToQueue && {
test_files: allTestFiles,
build_author: buildAuthor(),
commit_authors: commitAuthors(),
}),
};

return this.api.post(url, data);
Expand Down

0 comments on commit 69e1e00

Please sign in to comment.