Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions eslint-factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This project hosts custom ESLint linters for `/actions/setup/js`.
| [`no-unsafe-catch-error-property`](#no-unsafe-catch-error-property) | Disallow unsafe property access on `catch` error bindings |
| [`no-unsafe-promise-catch-error-property`](#no-unsafe-promise-catch-error-property) | Disallow unsafe property access in promise rejection handlers |
| [`prefer-get-error-message`](#prefer-get-error-message) | Prefer `getErrorMessage(err)` over the inline ternary pattern |
| [`prefer-core-logging`](#prefer-core-logging) | Prefer `@actions/core` logging over `console.log` / `console.info` / `console.debug` |
| [`prefer-number-isnan`](#prefer-number-isnan) | Prefer `Number.isNaN()` over global `isNaN()` |
| [`require-async-entrypoint-catch`](#require-async-entrypoint-catch) | Require `.catch(...)` on bare async entrypoint calls |
| [`require-await-core-summary-write`](#require-await-core-summary-write) | Require `await` on `core.summary.write()` calls |
Expand Down Expand Up @@ -382,3 +383,21 @@ Why: when `spawnSync` cannot spawn the child process (e.g. `ENOENT`, `ETIMEDOUT`
- Passing the result object to a helper function that internally checks `.error` is not recognized.
- Mutable aliases (`let e = result.error; e = undefined; if (e) throw e`) are rejected because the original value may have been discarded before the guard.

### `prefer-core-logging`

Prefer `@actions/core` logging methods (`core.info`, `core.debug`) over `console.log`, `console.info`, and `console.debug`.

`core.*` logging methods integrate with the GitHub Actions annotation system (errors and warnings appear as file annotations in the UI) and produce structured log output. `global.core` is always available via `shim.cjs` in the Node.js context and via `github-script` in the Actions context.

**Covered methods and their replacements:**

| `console.*` method | Suggested replacement |
|---|---|
| `console.log` | `core.info` |
| `console.info` | `core.info` |
| `console.debug` | `core.debug` |

**Intentionally excluded: `console.error` and `console.warn`**

`console.error` and `console.warn` write to **`process.stderr`**, while `core.error` and `core.warning` emit GitHub Actions workflow commands to **`process.stdout`**. For processes that own stdout as a data/protocol channel — such as stdio MCP servers and transports — replacing stderr logging with stdout logging would corrupt the JSON-RPC stream. Because the stream change is not behavior-preserving, the rule never reports `console.error` or `console.warn` and offers no suggestion to replace them.

52 changes: 16 additions & 36 deletions eslint-factory/src/rules/prefer-core-logging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ describe("prefer-core-logging", () => {
{ messageId: "preferCoreLogging", data: { method: "log", replacement: "core.info" }, suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.info", args: `"hello"` }, output: `core.info("hello");` }] },
],
},
{
code: `console.error("bad thing");`,
errors: [
{
messageId: "preferCoreLogging",
data: { method: "error", replacement: "core.error" },
suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.error", args: `"bad thing"` }, output: `core.error("bad thing");` }],
},
],
},
{
code: `const foo = "bar"; console.log(foo);`,
errors: [
Expand Down Expand Up @@ -100,39 +90,29 @@ describe("prefer-core-logging", () => {
});
});

it("invalid: console.error when core is in scope", () => {
it("valid: console.error is not flagged — writes to stderr, not stdout", () => {
ruleTester.run("prefer-core-logging", preferCoreLoggingRule, {
valid: [],
invalid: [
{
code: `const core = require("@actions/core"); console.error("bad thing");`,
errors: [
{
messageId: "preferCoreLogging",
data: { method: "error", replacement: "core.error" },
suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.error", args: `"bad thing"` }, output: `const core = require("@actions/core"); core.error("bad thing");` }],
},
],
},
valid: [
// console.error writes to stderr; core.error writes workflow commands to stdout.
// Replacing stderr logging with stdout logging would corrupt stdio-protocol
// channels (e.g. MCP servers), so the rule intentionally exempts console.error.
`console.error("bad thing");`,
`const core = require("@actions/core"); console.error("bad thing");`,
`console.error("MCP transport error:", new Error("oops"));`,
],
invalid: [],
});
});

it("invalid: console.warn when core is in scope", () => {
it("valid: console.warn is not flagged — writes to stderr, not stdout", () => {
ruleTester.run("prefer-core-logging", preferCoreLoggingRule, {
valid: [],
invalid: [
{
code: `const core = require("@actions/core"); console.warn("warning");`,
errors: [
{
messageId: "preferCoreLogging",
data: { method: "warn", replacement: "core.warning" },
suggestions: [{ messageId: "replaceWithCoreMethod", data: { replacement: "core.warning", args: `"warning"` }, output: `const core = require("@actions/core"); core.warning("warning");` }],
},
],
},
valid: [
// console.warn writes to stderr; core.warning writes workflow commands to stdout.
// Same stream-semantics rationale as console.error above.
`console.warn("warning");`,
`const core = require("@actions/core"); console.warn("warning");`,
],
invalid: [],
Comment on lines +112 to +115
});
});

Expand Down
10 changes: 7 additions & 3 deletions eslint-factory/src/rules/prefer-core-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { AST_NODE_TYPES, ESLintUtils, TSESTree } from "@typescript-eslint/utils"

const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh-aw/tree/main/eslint-factory#${name}`);

// Maps console method → recommended core replacement
// Maps console method → recommended core replacement.
// NOTE: console.error and console.warn are intentionally excluded.
// Those methods write to process.stderr, while core.error / core.warning emit
// GitHub Actions workflow commands to process.stdout. For processes that own
// stdout as a data/protocol channel (e.g. stdio MCP servers), rewriting
// stderr logging to stdout would corrupt the stream. The substitution is not
// behavior-preserving and must not be auto-applied.
Comment on lines +6 to +11
const CONSOLE_TO_CORE: Record<string, string> = {
log: "core.info",
info: "core.info",
warn: "core.warning",
error: "core.error",
debug: "core.debug",
};

Expand Down
Loading