Skip to content

Commit 2ee0a8d

Browse files
committed
feat(ci): allow refs without shas, fetch if needed
1 parent 61b7ba4 commit 2ee0a8d

File tree

10 files changed

+95
-37
lines changed

10 files changed

+95
-37
lines changed

packages/ci/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ If only the `head` is supplied, then Code PushUp will collect a new report and o
6464
If triggered by a pull request, then specify the `base` ref as well.
6565
This will additionally compare reports from both source and target branches and post a comment to the PR.
6666

67-
| Property | Required | Type | Description |
68-
| :------- | :------: | :----------------------------- | :-------------------- |
69-
| `head` | yes | `{ ref: string, sha: string }` | Current branch/commit |
70-
| `base` | no | `{ ref: string, sha: string }` | Branch targeted by PR |
67+
| Property | Required | Type | Description |
68+
| :------- | :------: | :--------------------------------------- | :-------------------- |
69+
| `head` | yes | `string \| { ref: string, sha: string }` | Current branch/commit |
70+
| `base` | no | `string \| { ref: string, sha: string }` | Branch targeted by PR |
7171

7272
### Provider API client
7373

packages/ci/src/lib/git.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { DiffNameStatus, simpleGit } from 'simple-git';
1+
import { DiffNameStatus, GitError, simpleGit } from 'simple-git';
2+
import type { GitBranch } from './models.js';
23

34
export type ChangedFiles = Record<string, ChangedFile>;
45

@@ -12,6 +13,29 @@ type LineChange = {
1213
curr: { line: number; count: number };
1314
};
1415

16+
export async function normalizeGitRef(
17+
ref: string | GitBranch,
18+
git = simpleGit(),
19+
): Promise<GitBranch> {
20+
if (typeof ref === 'object') {
21+
return ref;
22+
}
23+
try {
24+
const sha = await git.revparse(ref);
25+
return { ref, sha };
26+
} catch (error) {
27+
if (
28+
error instanceof GitError &&
29+
error.message.includes(`fatal: ambiguous argument '${ref}'`)
30+
) {
31+
await git.fetch(['origin', ref, '--depth=1']);
32+
const sha = await git.revparse('FETCH_HEAD');
33+
return { ref, sha };
34+
}
35+
throw error;
36+
}
37+
}
38+
1539
export async function listChangedFiles(
1640
refs: {
1741
base: string;

packages/ci/src/lib/models.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ export type Options = {
2828
export type Settings = Required<Options>;
2929

3030
/**
31-
* Branches/commits for {@link runInCI}
31+
* Branches/tags for {@link runInCI}
3232
*/
3333
export type GitRefs = {
34-
head: GitBranch;
35-
base?: GitBranch;
34+
head: string | GitBranch;
35+
base?: string | GitBranch;
3636
};
3737

3838
/**

packages/ci/src/lib/run-monorepo.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import {
3333
type RunEnv,
3434
checkPrintConfig,
3535
compareReports,
36-
ensureHeadBranch,
3736
loadCachedBaseReport,
3837
printPersistConfig,
3938
runInBaseBranch,
@@ -48,8 +47,6 @@ export async function runInMonorepoMode(
4847

4948
logger.info('Running Code PushUp in monorepo mode');
5049

51-
await ensureHeadBranch(env);
52-
5350
const { projects, runManyCommand } = await listMonorepoProjects(settings);
5451
const projectResults = runManyCommand
5552
? await runProjectsInBulk(projects, runManyCommand, env)

packages/ci/src/lib/run-standalone.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { commentOnPR } from './comment.js';
22
import type { StandaloneRunResult } from './models.js';
3-
import { type RunEnv, ensureHeadBranch, runOnProject } from './run-utils.js';
3+
import { type RunEnv, runOnProject } from './run-utils.js';
44

55
export async function runInStandaloneMode(
66
env: RunEnv,
@@ -10,8 +10,6 @@ export async function runInStandaloneMode(
1010

1111
logger.info('Running Code PushUp in standalone project mode');
1212

13-
await ensureHeadBranch(env);
14-
1513
const { files, newIssues } = await runOnProject(null, env);
1614

1715
const commentMdPath = files.diff?.md;

packages/ci/src/lib/run-utils.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
22
import path from 'node:path';
33
import type { SimpleGit } from 'simple-git';
44
import type { CoreConfig, Report, ReportsDiff } from '@code-pushup/models';
5-
import { stringifyError } from '@code-pushup/utils';
5+
import {
6+
removeUndefinedAndEmptyProps,
7+
stringifyError,
8+
} from '@code-pushup/utils';
69
import {
710
type CommandContext,
811
createCommandContext,
@@ -12,12 +15,14 @@ import {
1215
runPrintConfig,
1316
} from './cli/index.js';
1417
import { parsePersistConfig } from './cli/persist.js';
15-
import { listChangedFiles } from './git.js';
18+
import { DEFAULT_SETTINGS } from './constants.js';
19+
import { listChangedFiles, normalizeGitRef } from './git.js';
1620
import { type SourceFileIssue, filterRelevantIssues } from './issues.js';
1721
import type {
1822
GitBranch,
1923
GitRefs,
2024
Logger,
25+
Options,
2126
OutputFiles,
2227
ProjectRunResult,
2328
ProviderAPIClient,
@@ -26,12 +31,17 @@ import type {
2631
import type { ProjectConfig } from './monorepo/index.js';
2732

2833
export type RunEnv = {
29-
refs: GitRefs;
34+
refs: NormalizedGitRefs;
3035
api: ProviderAPIClient;
3136
settings: Settings;
3237
git: SimpleGit;
3338
};
3439

40+
type NormalizedGitRefs = {
41+
head: GitBranch;
42+
base?: GitBranch;
43+
};
44+
3545
export type CompareReportsArgs = {
3646
project: ProjectConfig | null;
3747
env: RunEnv;
@@ -53,6 +63,28 @@ export type BaseReportArgs = {
5363
ctx: CommandContext;
5464
};
5565

66+
export async function createRunEnv(
67+
refs: GitRefs,
68+
api: ProviderAPIClient,
69+
options: Options | undefined,
70+
git: SimpleGit,
71+
): Promise<RunEnv> {
72+
const [head, base] = await Promise.all([
73+
normalizeGitRef(refs.head, git),
74+
refs.base && normalizeGitRef(refs.base, git),
75+
]);
76+
77+
return {
78+
refs: { head, ...(base && { base }) },
79+
api,
80+
settings: {
81+
...DEFAULT_SETTINGS,
82+
...(options && removeUndefinedAndEmptyProps(options)),
83+
},
84+
git,
85+
};
86+
}
87+
5688
export async function runOnProject(
5789
project: ProjectConfig | null,
5890
env: RunEnv,
@@ -81,9 +113,7 @@ export async function runOnProject(
81113

82114
const noDiffOutput = {
83115
name: project?.name ?? '-',
84-
files: {
85-
report: reportFiles,
86-
},
116+
files: { report: reportFiles },
87117
} satisfies ProjectRunResult;
88118

89119
if (base == null) {
@@ -223,13 +253,6 @@ export async function loadCachedBaseReport(
223253
return null;
224254
}
225255

226-
export async function ensureHeadBranch({ refs, git }: RunEnv): Promise<void> {
227-
const { head } = refs;
228-
if (head.sha !== (await git.revparse('HEAD'))) {
229-
await git.checkout(['-f', head.ref]);
230-
}
231-
}
232-
233256
export async function runInBaseBranch<T>(
234257
base: GitBranch,
235258
env: RunEnv,

packages/ci/src/lib/run.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { type SimpleGit, simpleGit } from 'simple-git';
2-
import { DEFAULT_SETTINGS } from './constants.js';
32
import type {
43
GitRefs,
54
Options,
65
ProviderAPIClient,
76
RunResult,
8-
Settings,
97
} from './models.js';
108
import { runInMonorepoMode } from './run-monorepo.js';
119
import { runInStandaloneMode } from './run-standalone.js';
12-
import type { RunEnv } from './run-utils.js';
10+
import { createRunEnv } from './run-utils.js';
1311

1412
/**
1513
* Runs Code PushUp in CI environment.
@@ -25,14 +23,9 @@ export async function runInCI(
2523
options?: Options,
2624
git: SimpleGit = simpleGit(),
2725
): Promise<RunResult> {
28-
const settings: Settings = {
29-
...DEFAULT_SETTINGS,
30-
...options,
31-
};
26+
const env = await createRunEnv(refs, api, options, git);
3227

33-
const env: RunEnv = { refs, api, settings, git };
34-
35-
if (settings.monorepo) {
28+
if (env.settings.monorepo) {
3629
return runInMonorepoMode(env);
3730
}
3831

packages/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export {
114114
objectToCliArgs,
115115
objectToEntries,
116116
objectToKeys,
117+
removeUndefinedAndEmptyProps,
117118
toArray,
118119
toJsonLines,
119120
toNumberPrecision,

packages/utils/src/lib/transform.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,11 @@ export function toOrdinal(value: number): string {
151151

152152
return `${value}th`;
153153
}
154+
155+
export function removeUndefinedAndEmptyProps<T extends object>(obj: T): T {
156+
return Object.fromEntries(
157+
Object.entries(obj).filter(
158+
([, value]) => value !== undefined && value !== '',
159+
),
160+
) as T;
161+
}

packages/utils/src/lib/transform.unit.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
objectToCliArgs,
1010
objectToEntries,
1111
objectToKeys,
12+
removeUndefinedAndEmptyProps,
1213
toArray,
1314
toJsonLines,
1415
toNumberPrecision,
@@ -303,3 +304,16 @@ describe('toOrdinal', () => {
303304
expect(toOrdinal(value)).toBe(ordinalValue);
304305
});
305306
});
307+
308+
describe('removeUndefinedAndEmptyProps', () => {
309+
it('should omit empty strings and undefined', () => {
310+
expect(
311+
removeUndefinedAndEmptyProps({ foo: '', bar: undefined }),
312+
).toStrictEqual({});
313+
});
314+
315+
it('should preserve other values', () => {
316+
const obj = { a: 'hello', b: 42, c: [], d: {}, e: null };
317+
expect(removeUndefinedAndEmptyProps(obj)).toStrictEqual(obj);
318+
});
319+
});

0 commit comments

Comments
 (0)