Skip to content

Commit 0e9c699

Browse files
authored
fix: force NO_COLOR on cargo subprocesses so the parser stops missing colorized version: lines (#22)
* fix: force NO_COLOR on cargo subprocesses so the parser stops missing colorized `version:` lines When a workflow sets `CARGO_TERM_COLOR=always` (or the user's env enables it), cargo wraps field labels in ANSI SGR escapes: \e[1m\e[92mversion:\e[0m 0.6.0 The cargo info parser at lib.js:155 was anchored — `^version:` — so the escape-prefixed line didn't match, `parseCargoInfoVersion` returned null, `getPublishedVersion` rejected, and `filterPublishable` warned and skipped the candidate. Net effect: `roas` (and any other crate run under a color-on workflow) silently dropped out of the publish output even though it's published on crates.io. Fix in two layers: - Spawn `cargo metadata` and `cargo info` with `CARGO_TERM_COLOR=never` and `NO_COLOR=1` layered over `process.env` via a `plainEnv()` helper. Cargo never produces ANSI escapes for the output we parse, regardless of the calling workflow's preferences. - Strip ANSI SGR escapes in `parseCargoInfoVersion` before the regex match — defense-in-depth so the parser stays correct even if a future caller bypasses `plainEnv()`. Tests: new regression case feeds the parser the exact colorized `version:` line cargo emits under `CARGO_TERM_COLOR=always`. 33/33 pass; dist rebuilt. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com> * chore: bump to 1.1.1 Patch bump for the cargo-info color-env fix in this branch. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com> --------- Signed-off-by: Sergey Vilgelm <sergey@vilgelm.com>
1 parent 7c4e86d commit 0e9c699

5 files changed

Lines changed: 47 additions & 11 deletions

File tree

dist/index.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28024,7 +28024,7 @@ function runCargoMetadata(manifestPath) {
2802428024
"--format-version",
2802528025
"1",
2802628026
],
28027-
{ cwd },
28027+
{ cwd, env: plainEnv() },
2802828028
);
2802928029

2803028030
const stdoutChunks = [];
@@ -28062,6 +28062,15 @@ function runCargoMetadata(manifestPath) {
2806228062
});
2806328063
}
2806428064

28065+
// Force cargo to emit plain text regardless of the workflow's
28066+
// CARGO_TERM_COLOR setting. We parse stdout ourselves; ANSI escapes wrap
28067+
// the field labels (`\e[1m\e[92mversion:\e[0m 0.6.0`) and break anchored
28068+
// regex matches. NO_COLOR is set too as a belt-and-suspenders for any
28069+
// non-cargo subprocess that might inherit this.
28070+
function plainEnv() {
28071+
return { ...process.env, CARGO_TERM_COLOR: "never", NO_COLOR: "1" };
28072+
}
28073+
2806528074
// Cargo's `publish` field semantics:
2806628075
// null → unrestricted (publishable)
2806728076
// [] → `publish = false` in Cargo.toml (NOT publishable)
@@ -28132,9 +28141,13 @@ function compareSemver(a, b) {
2813228141
return 0;
2813328142
}
2813428143

28135-
// Pulls the published version from cargo info's stdout.
28144+
// Pulls the published version from cargo info's stdout. Strips ANSI SGR
28145+
// escapes first — `getPublishedVersion` forces NO_COLOR on the spawn, but
28146+
// the parser stays robust if anything else (e.g. a future caller) feeds it
28147+
// colorized output.
2813628148
function parseCargoInfoVersion(stdout) {
28137-
const m = stdout.match(/^version:\s*(\S+)/im);
28149+
const plain = stdout.replace(/\x1b\[[0-9;]*m/g, "");
28150+
const m = plain.match(/^version:\s*(\S+)/im);
2813828151
return m ? m[1] : null;
2813928152
}
2814028153

@@ -28156,7 +28169,7 @@ function parseCargoInfoVersion(stdout) {
2815628169
function getPublishedVersion(pkgName, cwd, registry) {
2815728170
return new Promise((resolve, reject) => {
2815828171
const args = ["info", pkgName, "--registry", registry || "crates-io"];
28159-
const cmd = (0,child_process__WEBPACK_IMPORTED_MODULE_1__.spawn)("cargo", args, { cwd });
28172+
const cmd = (0,child_process__WEBPACK_IMPORTED_MODULE_1__.spawn)("cargo", args, { cwd, env: plainEnv() });
2816028173

2816128174
const stdoutChunks = [];
2816228175
const stderrChunks = [];

lib.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function runCargoMetadata(manifestPath) {
4242
"--format-version",
4343
"1",
4444
],
45-
{ cwd },
45+
{ cwd, env: plainEnv() },
4646
);
4747

4848
const stdoutChunks = [];
@@ -80,6 +80,15 @@ export function runCargoMetadata(manifestPath) {
8080
});
8181
}
8282

83+
// Force cargo to emit plain text regardless of the workflow's
84+
// CARGO_TERM_COLOR setting. We parse stdout ourselves; ANSI escapes wrap
85+
// the field labels (`\e[1m\e[92mversion:\e[0m 0.6.0`) and break anchored
86+
// regex matches. NO_COLOR is set too as a belt-and-suspenders for any
87+
// non-cargo subprocess that might inherit this.
88+
function plainEnv() {
89+
return { ...process.env, CARGO_TERM_COLOR: "never", NO_COLOR: "1" };
90+
}
91+
8392
// Cargo's `publish` field semantics:
8493
// null → unrestricted (publishable)
8594
// [] → `publish = false` in Cargo.toml (NOT publishable)
@@ -150,9 +159,13 @@ export function compareSemver(a, b) {
150159
return 0;
151160
}
152161

153-
// Pulls the published version from cargo info's stdout.
162+
// Pulls the published version from cargo info's stdout. Strips ANSI SGR
163+
// escapes first — `getPublishedVersion` forces NO_COLOR on the spawn, but
164+
// the parser stays robust if anything else (e.g. a future caller) feeds it
165+
// colorized output.
154166
export function parseCargoInfoVersion(stdout) {
155-
const m = stdout.match(/^version:\s*(\S+)/im);
167+
const plain = stdout.replace(/\x1b\[[0-9;]*m/g, "");
168+
const m = plain.match(/^version:\s*(\S+)/im);
156169
return m ? m[1] : null;
157170
}
158171

@@ -174,7 +187,7 @@ export function parseCargoInfoVersion(stdout) {
174187
export function getPublishedVersion(pkgName, cwd, registry) {
175188
return new Promise((resolve, reject) => {
176189
const args = ["info", pkgName, "--registry", registry || "crates-io"];
177-
const cmd = spawn("cargo", args, { cwd });
190+
const cmd = spawn("cargo", args, { cwd, env: plainEnv() });
178191

179192
const stdoutChunks = [];
180193
const stderrChunks = [];

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rust-metadata-action",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "GitHub action to retrieve metadata, including crates, features, and other relevant information of the Rust project",
55
"keywords": [
66
"GitHub",

tests/parse-metadata.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,16 @@ test("parseCargoInfoVersion is case-insensitive on the key", () => {
271271
assert.equal(parseCargoInfoVersion("Version: 0.1.0\n"), "0.1.0");
272272
});
273273

274+
test("parseCargoInfoVersion strips ANSI escapes from cargo's colorized output", () => {
275+
// What `cargo info` emits when CARGO_TERM_COLOR=always is set in env —
276+
// the field label is wrapped in SGR codes, so a naive `^version:` regex
277+
// would miss it. Reproduces the CI warning seen against sv-tools/roas.
278+
const stdout =
279+
"\x1b[1m\x1b[92mversion:\x1b[0m 0.6.0\n" +
280+
"\x1b[1m\x1b[92mlicense:\x1b[0m MIT\n";
281+
assert.equal(parseCargoInfoVersion(stdout), "0.6.0");
282+
});
283+
274284
test("asyncPool preserves input order", async () => {
275285
// Reverse-correlate delay with index so a naive implementation that
276286
// assigned results in completion order would scramble the output.

0 commit comments

Comments
 (0)