From cd4f1a539797cd9fdc0ce8fcfbae9d40d3aac422 Mon Sep 17 00:00:00 2001 From: EfeDurmaz16 Date: Fri, 15 May 2026 13:25:53 +0300 Subject: [PATCH] test: cover utf8 file pipe regressions Signed-off-by: EfeDurmaz16 --- .changeset/dry-worms-mix.md | 5 +++++ examples/custom-command/README.md | 13 +++++++------ .../src/commands/jq/jq.utf8-stdin.test.ts | 14 ++++++++++++++ packages/just-bash/src/custom-commands.test.ts | 17 +++++++++++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 .changeset/dry-worms-mix.md diff --git a/.changeset/dry-worms-mix.md b/.changeset/dry-worms-mix.md new file mode 100644 index 00000000..460f1808 --- /dev/null +++ b/.changeset/dry-worms-mix.md @@ -0,0 +1,5 @@ +--- +"just-bash": patch +--- + +Add regression coverage for UTF-8 piped file input. diff --git a/examples/custom-command/README.md b/examples/custom-command/README.md index 6a1f09e0..13d3d9c8 100644 --- a/examples/custom-command/README.md +++ b/examples/custom-command/README.md @@ -46,14 +46,15 @@ This demonstrates how custom commands can: Use `defineCommand` from just-bash: ```typescript -import { defineCommand } from "just-bash"; +import { decodeBytesToUtf8, defineCommand } from "just-bash"; const myCommand = defineCommand("mycommand", async (args, ctx) => { // args: command arguments (string[]) - // ctx: CommandContext with fs, cwd, env, stdin, exec - + // ctx.stdin is a ByteString. Decode it before text operations. + const input = decodeBytesToUtf8(ctx.stdin); + return { - stdout: "output here\n", + stdout: input.toUpperCase(), stderr: "", exitCode: 0, }; @@ -75,6 +76,6 @@ Your command receives a context object with: - `fs` - Virtual filesystem interface - `cwd` - Current working directory - `env` - Environment variables -- `stdin` - Standard input (from pipes) +- `stdin` - Standard input from pipes as a `ByteString`; use + `decodeBytesToUtf8(ctx.stdin)` before text parsing or string operations - `exec` - Function to run subcommands - diff --git a/packages/just-bash/src/commands/jq/jq.utf8-stdin.test.ts b/packages/just-bash/src/commands/jq/jq.utf8-stdin.test.ts index 53053dd6..ff5651c5 100644 --- a/packages/just-bash/src/commands/jq/jq.utf8-stdin.test.ts +++ b/packages/just-bash/src/commands/jq/jq.utf8-stdin.test.ts @@ -13,4 +13,18 @@ describe("jq reads UTF-8 from stdin", () => { expect(result.exitCode).toBe(0); expect(result.stdout).toBe("한글 / café / 漢字\n"); }); + + it("preserves multibyte string values when file input is redirected", async () => { + const env = new Bash({ + files: { + "/places.json": JSON.stringify({ name: "Florida — Miami" }), + }, + }); + + const result = await env.exec("jq '.name' /places.json > /out.json"); + expect(result.exitCode).toBe(0); + + const out = await env.fs.readFile("/out.json", "utf8"); + expect(out).toBe('"Florida — Miami"\n'); + }); }); diff --git a/packages/just-bash/src/custom-commands.test.ts b/packages/just-bash/src/custom-commands.test.ts index 8f35d609..fc554efa 100644 --- a/packages/just-bash/src/custom-commands.test.ts +++ b/packages/just-bash/src/custom-commands.test.ts @@ -129,6 +129,23 @@ describe("custom-commands", () => { expect(result.exitCode).toBe(0); }); + it("custom command decodes multibyte stdin from a file pipe", async () => { + const capture = defineCommand("capture", async (_args, ctx) => ({ + stdout: decodeBytesToUtf8(ctx.stdin), + stderr: "", + exitCode: 0, + })); + + const bash = new Bash({ + customCommands: [capture], + files: { "/place.json": '{"name":"Florida — Miami"}' }, + }); + const result = await bash.exec("cat /place.json | capture"); + + expect(result.stdout).toBe('{"name":"Florida — Miami"}'); + expect(result.exitCode).toBe(0); + }); + it("custom command can read files via ctx.fs", async () => { const reader = defineCommand("reader", async (args, ctx) => { const content = await ctx.fs.readFile(args[0]);