Skip to content

Commit 5da7421

Browse files
committed
feat(ci): style top-level logs
1 parent 513f187 commit 5da7421

File tree

13 files changed

+172
-68
lines changed

13 files changed

+172
-68
lines changed

packages/ci/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@code-pushup/models": "0.91.0",
3030
"@code-pushup/portal-client": "^0.16.0",
3131
"@code-pushup/utils": "0.91.0",
32+
"ansis": "^3.3.2",
3233
"glob": "^11.0.1",
3334
"simple-git": "^3.20.0",
3435
"yaml": "^2.5.1",

packages/ci/src/lib/cli/exec.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,25 @@ export async function executeCliCommand(
4848
};
4949
const bin = serializeCommandWithArgs(config);
5050

51-
await logger.command(bin, async () => {
52-
try {
53-
await executeProcess(config);
54-
} catch (error) {
55-
// ensure output of failed process is always logged for debugging
56-
if (context.silent) {
57-
logger.newline();
58-
logger.info(output, { noIndent: true });
59-
if (!output.endsWith('\n')) {
51+
try {
52+
await logger.command(bin, async () => {
53+
try {
54+
await executeProcess(config);
55+
} catch (error) {
56+
// ensure output of failed process is always logged for debugging
57+
if (context.silent) {
6058
logger.newline();
59+
logger.info(output, { noIndent: true });
60+
if (!output.endsWith('\n')) {
61+
logger.newline();
62+
}
6163
}
64+
throw error;
6265
}
63-
throw error;
64-
}
65-
});
66+
});
67+
} finally {
68+
logger.newline();
69+
}
6670
}
6771

6872
function combineArgs(

packages/ci/src/lib/cli/exec.unit.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ describe('executeCliCommand', () => {
130130
`
131131
- $ npx code-pushup
132132
✔ $ npx code-pushup (42 ms)
133+
133134
`.trimStart(),
134135
);
135136
});
@@ -152,6 +153,7 @@ WARN: API key is missing, skipping upload
152153
Collected report files in .code-pushup directory
153154
154155
✔ $ npx code-pushup (42 ms)
156+
155157
`.trimStart(),
156158
);
157159
});
@@ -197,6 +199,7 @@ Collected report
197199
Uploaded report to portal
198200
199201
✔ $ npx code-pushup (42 ms)
202+
200203
`.trimStart(),
201204
);
202205
});
@@ -222,6 +225,7 @@ Code PushUp CLI v0.42.0
222225
ERROR: Config file not found
223226
224227
✖ $ npx code-pushup
228+
225229
`.trimStart(),
226230
);
227231
});
@@ -236,6 +240,7 @@ ERROR: Config file not found
236240
`
237241
- $ npx code-pushup
238242
✔ $ npx code-pushup (42 ms)
243+
239244
`.trimStart(),
240245
);
241246
});
@@ -261,6 +266,7 @@ Code PushUp CLI v0.42.0
261266
ERROR: Config file not found
262267
263268
✖ $ npx code-pushup
269+
264270
`.trimStart(),
265271
);
266272
});

packages/ci/src/lib/comment.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile } from 'node:fs/promises';
2-
import { logger } from '@code-pushup/utils';
2+
import { logDebug, logInfo, logWarning } from './log.js';
33
import type { ProviderAPIClient, Settings } from './models.js';
44

55
export async function commentOnPR(
@@ -17,32 +17,32 @@ export async function commentOnPR(
1717
);
1818

1919
const comments = await api.listComments();
20-
logger.debug(`Fetched ${comments.length} comments for pull request`);
20+
logDebug(`Fetched ${comments.length} comments for pull request`);
2121

2222
const prevComment = comments.find(comment =>
2323
comment.body.includes(identifier),
2424
);
25-
logger.debug(
25+
logDebug(
2626
prevComment
2727
? `Found previous comment ${prevComment.id} from Code PushUp`
2828
: 'Previous Code PushUp comment not found',
2929
);
3030

3131
if (prevComment) {
3232
const updatedComment = await api.updateComment(prevComment.id, body);
33-
logger.info(`Updated body of comment ${updatedComment.url}`);
33+
logInfo(`Updated body of comment ${updatedComment.url}`);
3434
return updatedComment.id;
3535
}
3636

3737
const createdComment = await api.createComment(body);
38-
logger.info(`Created new comment ${createdComment.url}`);
38+
logInfo(`Created new comment ${createdComment.url}`);
3939
return createdComment.id;
4040
}
4141

4242
function truncateBody(body: string, max: number): string {
4343
const truncateWarning = '...*[Comment body truncated]*';
4444
if (body.length > max) {
45-
logger.warn(`Comment body is too long. Truncating to ${max} characters.`);
45+
logWarning(`Comment body is too long. Truncating to ${max} characters.`);
4646
return body.slice(0, max - truncateWarning.length) + truncateWarning;
4747
}
4848
return body;

packages/ci/src/lib/comment.unit.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ansis from 'ansis';
12
import { vol } from 'memfs';
23
import { writeFile } from 'node:fs/promises';
34
import path from 'node:path';
@@ -112,7 +113,7 @@ describe('commentOnPR', () => {
112113
expect.stringContaining('...*[Comment body truncated]*'),
113114
);
114115
expect(logger.warn).toHaveBeenCalledWith(
115-
'Comment body is too long. Truncating to 1000000 characters.',
116+
`${ansis.bold.blue('<✓>')} Comment body is too long. Truncating to 1000000 characters.\n`,
116117
);
117118
});
118119
});

packages/ci/src/lib/log.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import ansis from 'ansis';
2+
import {
3+
CODE_PUSHUP_UNICODE_LOGO,
4+
logger,
5+
transformLines,
6+
} from '@code-pushup/utils';
7+
8+
const LOG_PREFIX = ansis.bold.blue(CODE_PUSHUP_UNICODE_LOGO);
9+
10+
/**
11+
* Logs error message with top-level CI log styles (lines prefixed with logo, ends in empty line).
12+
* @param message Log message
13+
*/
14+
export function logError(message: string): void {
15+
log('error', message);
16+
}
17+
18+
/**
19+
* Logs warning message with top-level CI log styles (lines prefixed with logo, ends in empty line).
20+
* @param message Log message
21+
*/
22+
export function logWarning(message: string): void {
23+
log('warn', message);
24+
}
25+
26+
/**
27+
* Logs info message with top-level CI log styles (lines prefixed with logo, ends in empty line).
28+
* @param message Log message
29+
*/
30+
export function logInfo(message: string): void {
31+
log('info', message);
32+
}
33+
34+
/**
35+
* Logs debug message with top-level CI log styles (lines prefixed with logo, ends in empty line).
36+
* @param message Log message
37+
*/
38+
export function logDebug(message: string): void {
39+
log('debug', message);
40+
}
41+
42+
/**
43+
* Prefixes CI logs with logo and ensures each CI log is followed by an empty line.
44+
* This is to make top-level CI logs more visually distinct from printed process logs.
45+
* @param level Log level
46+
* @param message Log message
47+
*/
48+
export function log(
49+
level: 'error' | 'warn' | 'info' | 'debug',
50+
message: string,
51+
): void {
52+
const prefixedLines = transformLines(
53+
message.trim(),
54+
line => `${LOG_PREFIX} ${line}`,
55+
);
56+
const styledMessage = `${prefixedLines}\n`;
57+
58+
logger[level](styledMessage);
59+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import ansis from 'ansis';
2+
import { logger } from '@code-pushup/utils';
3+
import { log } from './log.js';
4+
5+
describe('log', () => {
6+
it('should add logo prefix and ending line-break to message', () => {
7+
log('info', 'Running Code PushUp in standalone mode');
8+
expect(logger.info).toHaveBeenCalledWith(
9+
`${ansis.bold.blue('<✓>')} Running Code PushUp in standalone mode\n`,
10+
);
11+
});
12+
13+
it('should add logo prefix to each line', () => {
14+
log('debug', 'Found 3 Nx projects:\n- api\n- backoffice\n- frontoffice');
15+
expect(logger.debug).toHaveBeenCalledWith(
16+
`
17+
${ansis.bold.blue('<✓>')} Found 3 Nx projects:
18+
${ansis.bold.blue('<✓>')} - api
19+
${ansis.bold.blue('<✓>')} - backoffice
20+
${ansis.bold.blue('<✓>')} - frontoffice
21+
`.trimStart(),
22+
);
23+
});
24+
25+
it('should not add final line-break if already present', () => {
26+
log('warn', 'Comment body is too long, truncating to 1000 characters\n');
27+
expect(logger.warn).toHaveBeenCalledWith(
28+
`${ansis.bold.blue('<✓>')} Comment body is too long, truncating to 1000 characters\n`,
29+
);
30+
});
31+
});

packages/ci/src/lib/monorepo/list-projects.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { glob } from 'glob';
22
import path from 'node:path';
3-
import { logger } from '@code-pushup/utils';
3+
import { logDebug, logInfo } from '../log.js';
44
import type { Settings } from '../models.js';
55
import { detectMonorepoTool } from './detect-tool.js';
66
import { getToolHandler } from './handlers/index.js';
@@ -31,8 +31,8 @@ export async function listMonorepoProjects(
3131
if (tool) {
3232
const handler = getToolHandler(tool);
3333
const projects = await handler.listProjects(options);
34-
logger.info(`Found ${projects.length} projects in ${tool} monorepo`);
35-
logger.debug(`Projects: ${projects.map(({ name }) => name).join(', ')}`);
34+
logInfo(`Found ${projects.length} projects in ${tool} monorepo`);
35+
logDebug(`Projects: ${projects.map(({ name }) => name).join(', ')}`);
3636
return {
3737
tool,
3838
projects,
@@ -70,15 +70,15 @@ async function resolveMonorepoTool(
7070
}
7171

7272
if (typeof settings.monorepo === 'string') {
73-
logger.info(`Using monorepo tool "${settings.monorepo}" from inputs`);
73+
logInfo(`Using monorepo tool "${settings.monorepo}" from inputs`);
7474
return settings.monorepo;
7575
}
7676

7777
const tool = await detectMonorepoTool(options);
7878
if (tool) {
79-
logger.info(`Auto-detected monorepo tool ${tool}`);
79+
logInfo(`Auto-detected monorepo tool ${tool}`);
8080
} else {
81-
logger.info("Couldn't auto-detect any supported monorepo tool");
81+
logInfo("Couldn't auto-detect any supported monorepo tool");
8282
}
8383

8484
return tool;
@@ -110,12 +110,12 @@ async function listProjectsByGlobs(args: {
110110
{ cwd },
111111
);
112112

113-
logger.info(
113+
logInfo(
114114
`Found ${directories.length} project folders matching "${patterns.join(
115115
', ',
116116
)}" from configuration`,
117117
);
118-
logger.debug(`Projects: ${directories.join(', ')}`);
118+
logDebug(`Projects: ${directories.join(', ')}`);
119119

120120
return directories.toSorted().map(directory => ({
121121
name: directory,
@@ -132,8 +132,8 @@ async function listProjectsByNpmPackages(args: {
132132

133133
const packages = await listPackages(cwd);
134134

135-
logger.info(`Found ${packages.length} NPM packages in repository`);
136-
logger.debug(`Projects: ${packages.map(({ name }) => name).join(', ')}`);
135+
logInfo(`Found ${packages.length} NPM packages in repository`);
136+
logDebug(`Projects: ${packages.map(({ name }) => name).join(', ')}`);
137137

138138
return packages.map(({ name, directory }) => ({
139139
name,

packages/ci/src/lib/output-files.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { copyFile, mkdir } from 'node:fs/promises';
22
import path from 'node:path';
33
import { DEFAULT_PERSIST_FILENAME, type Format } from '@code-pushup/models';
4-
import { logger, objectFromEntries, objectToKeys } from '@code-pushup/utils';
4+
import { objectFromEntries, objectToKeys } from '@code-pushup/utils';
5+
import { logDebug } from './log.js';
56
import type { OutputFiles, Settings } from './models.js';
67
import type { ProjectConfig } from './monorepo/tools.js';
78

@@ -45,7 +46,7 @@ export async function saveOutputFiles<T extends Partial<OutputFiles>>({
4546
const src = files[format]!;
4647
const dest = outputs[format];
4748
await copyFile(src, dest);
48-
logger.debug(`Copied ${type} report from ${src} to ${dest}`);
49+
logDebug(`Copied ${type} report from ${src} to ${dest}`);
4950
}),
5051
);
5152

packages/ci/src/lib/output-files.unit.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ansis from 'ansis';
12
import { vol } from 'memfs';
23
import { readFile } from 'node:fs/promises';
34
import path from 'node:path';
@@ -120,10 +121,10 @@ describe('saveOutputFiles', () => {
120121
});
121122

122123
expect(logger.debug).toHaveBeenCalledWith(
123-
`Copied current report from ${path.join(MEMFS_VOLUME, 'report.json')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.json')}`,
124+
`${ansis.bold.blue('<✓>')} Copied current report from ${path.join(MEMFS_VOLUME, 'report.json')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.json')}\n`,
124125
);
125126
expect(logger.debug).toHaveBeenCalledWith(
126-
`Copied current report from ${path.join(MEMFS_VOLUME, 'report.md')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.md')}`,
127+
`${ansis.bold.blue('<✓>')} Copied current report from ${path.join(MEMFS_VOLUME, 'report.md')} to ${path.join(MEMFS_VOLUME, '.code-pushup/.ci/.current/report.md')}\n`,
127128
);
128129
});
129130

0 commit comments

Comments
 (0)