Skip to content

get-files.mjs strips trailing newline from committed file content #5

Description

@jazzlyn

get-files.mjs strips trailing newline from committed file content

Summary

Every commit made through this action strips the trailing newline from each changed file's content, even when the source file on disk has one. This silently corrupts files over repeated runs (missing EOF newline, and risk of a later append-style edit landing on the same line as existing content).

Root cause

In src/get-files.mjs:

const content = await $`cat ${path}`
...
const item = {
  path,
  content: `${content}`,   // <-- strips trailing newline(s)
  ...
}

content is a zx ProcessOutput. Interpolating it into a plain template literal (`${content}`) strips trailing newline characters from the captured stdout before it's sent as blob content to the GitHub Blobs API in src/commit.mjs.

Minimal repro (zx, independent of this repo):

import 'zx/globals'
$.verbose = false
const content = await $`cat f.yaml`   // f.yaml ends with "line1\nline2\n"
console.log(JSON.stringify(`${content}`))
// => "line1\nline2"   (trailing \n gone)

This strips all trailing newlines, not just one — a file ending in \n\n\n becomes line1\nline2 with none. So there's no way for a caller to work around this by padding the file before running the action; the fix needs to be in get-files.mjs.

Suggested fix

Read the file directly from disk instead of shelling out through cat + zx template-literal stringification, e.g.:

import { readFile } from 'node:fs/promises'
...
const raw = await readFile(path)
const item = {
  path,
  content: encoding === 'base64' ? raw.toString('base64') : raw.toString('utf-8'),
  encoding,
  ...
}

This avoids the ProcessOutput → string coercion entirely and preserves file bytes exactly, including a trailing newline or the lack of one.

Verification

  • Unit-level: test with files ending in \n, \n\n, and no trailing newline; confirm produced content byte-for-byte matches the file on disk in all three cases.
  • End-to-end: run the action against a test repo/branch on a file that ends with a newline, change a value, and confirm the committed file still ends with exactly one trailing newline (git show <sha>:<path> | xxd | tail).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions