Skip to content

Commit 38c07da

Browse files
author
John Doe
committed
Merge branch 'main' into refactor/nx-plugin/fix-verbose-handling
# Conflicts: # e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts # packages/nx-plugin/src/executors/cli/executor.ts # packages/nx-plugin/src/executors/cli/executor.unit.test.ts
2 parents 5ff3627 + 068984b commit 38c07da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+412
-398
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 0.92.0 (2025-12-02)
2+
3+
### 🚀 Features
4+
5+
- **ci:** style top-level logs ([5da74215](https://github.com/code-pushup/cli/commit/5da74215))
6+
7+
### ❤️ Thank You
8+
9+
- Matěj Chalk
10+
111
## 0.91.0 (2025-12-01)
212

313
### 🚀 Features

e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Tree } from '@nx/devkit';
22
import path from 'node:path';
3-
import * as process from 'node:process';
43
import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration';
54
import { afterEach, expect } from 'vitest';
65
import { generateCodePushupConfig } from '@code-pushup/nx-plugin';
@@ -104,29 +103,6 @@ describe('nx-plugin', () => {
104103
});
105104
});
106105

107-
it('should consider plugin option bin in configuration target', async () => {
108-
const cwd = path.join(testFileDir, 'configuration-option-bin');
109-
registerPluginInWorkspace(tree, {
110-
plugin: '@code-pushup/nx-plugin',
111-
options: {
112-
bin: 'XYZ',
113-
},
114-
});
115-
await materializeTree(tree, cwd);
116-
117-
const { code, projectJson } = await nxShowProjectJson(cwd, project);
118-
119-
expect(code).toBe(0);
120-
121-
expect(projectJson.targets).toStrictEqual({
122-
'code-pushup--configuration': expect.objectContaining({
123-
options: {
124-
command: `nx g XYZ:configuration --project="${project}"`,
125-
},
126-
}),
127-
});
128-
});
129-
130106
it('should NOT add config targets dynamically if the project is configured', async () => {
131107
const cwd = path.join(testFileDir, 'configuration-already-configured');
132108
registerPluginInWorkspace(tree, '@code-pushup/nx-plugin');

packages/ci/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@code-pushup/ci",
3-
"version": "0.91.0",
3+
"version": "0.92.0",
44
"description": "CI automation logic for Code PushUp (provider-agnostic)",
55
"license": "MIT",
66
"homepage": "https://github.com/code-pushup/cli/tree/main/packages/ci#readme",
@@ -26,9 +26,10 @@
2626
},
2727
"type": "module",
2828
"dependencies": {
29-
"@code-pushup/models": "0.91.0",
29+
"@code-pushup/models": "0.92.0",
3030
"@code-pushup/portal-client": "^0.16.0",
31-
"@code-pushup/utils": "0.91.0",
31+
"@code-pushup/utils": "0.92.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: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,56 @@ import {
88
} from '@code-pushup/utils';
99
import type { CommandContext } from './context.js';
1010

11+
/**
12+
* Executes Code PushUp CLI command and logs output in a way that's more readable in CI.
13+
* @param args Arguments for Code PushUp CLI
14+
* @param context Command context
15+
* @param options Optional information on whether all persist formats are set (if known)
16+
*/
1117
export async function executeCliCommand(
1218
args: string[],
1319
context: CommandContext,
1420
options?: { hasFormats: boolean },
1521
): Promise<void> {
22+
const { logOutputChunk, logOutputEnd, logSilencedOutput } =
23+
createLogCallbacks(context);
24+
25+
const observer: ProcessObserver = {
26+
onStdout: logOutputChunk,
27+
onStderr: logOutputChunk,
28+
onComplete: logOutputEnd,
29+
onError: logOutputEnd,
30+
};
31+
32+
const config: ProcessConfig = {
33+
command: context.bin,
34+
args: combineArgs(args, context, options),
35+
cwd: context.directory,
36+
observer,
37+
silent: true,
38+
};
39+
const bin = serializeCommandWithArgs(config);
40+
41+
try {
42+
await logger.command(bin, async () => {
43+
try {
44+
await executeProcess(config);
45+
} catch (error) {
46+
// ensure output of failed process is always logged for debugging
47+
logSilencedOutput();
48+
throw error;
49+
}
50+
});
51+
} finally {
52+
logger.newline();
53+
}
54+
}
55+
56+
function createLogCallbacks(context: Pick<CommandContext, 'silent'>) {
1657
// eslint-disable-next-line functional/no-let
1758
let output = '';
1859

19-
const logRaw = (message: string) => {
60+
const logOutputChunk = (message: string) => {
2061
if (!context.silent) {
2162
if (!output) {
2263
logger.newline();
@@ -26,43 +67,23 @@ export async function executeCliCommand(
2667
output += message;
2768
};
2869

29-
const logEnd = () => {
70+
const logOutputEnd = () => {
3071
if (!context.silent && output) {
3172
logger.newline();
3273
}
3374
};
3475

35-
const observer: ProcessObserver = {
36-
onStdout: logRaw,
37-
onStderr: logRaw,
38-
onComplete: logEnd,
39-
onError: logEnd,
40-
};
41-
42-
const config: ProcessConfig = {
43-
command: context.bin,
44-
args: combineArgs(args, context, options),
45-
cwd: context.directory,
46-
observer,
47-
silent: true,
48-
};
49-
const bin = serializeCommandWithArgs(config);
50-
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) {
76+
const logSilencedOutput = () => {
77+
if (context.silent) {
78+
logger.newline();
79+
logger.info(output, { noIndent: true });
80+
if (!output.endsWith('\n')) {
5781
logger.newline();
58-
logger.info(output, { noIndent: true });
59-
if (!output.endsWith('\n')) {
60-
logger.newline();
61-
}
6282
}
63-
throw error;
6483
}
65-
});
84+
};
85+
86+
return { logOutputChunk, logOutputEnd, logSilencedOutput };
6687
}
6788

6889
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+
});

0 commit comments

Comments
 (0)