Skip to content

Commit 5854938

Browse files
jasekiwAndarist
andauthored
Keep detected line endings flavor of package.json files on Windows when updating those files (#260)
* fix: line endings are now respected on windows * Update .changeset/clever-feet-move.md Co-authored-by: Mateusz Burzyński <[email protected]> * Add tests for respecting line endings * Update .changeset/clever-feet-move.md --------- Co-authored-by: Mateusz Burzyński <[email protected]>
1 parent fc5d5e0 commit 5854938

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

.changeset/clever-feet-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@manypkg/cli": patch
3+
---
4+
5+
Keep detected line endings flavor of `package.json` files on Windows when updating those files

packages/cli/src/run.test.ts

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { describe, expect, it } from "vitest";
22
import fixturez from "fixturez";
33
import stripAnsi from "strip-ansi";
44
import { exec } from "tinyexec";
5+
import fs from "node:fs";
6+
import path from "node:path";
57

68
const f = fixturez(__dirname);
79

@@ -28,18 +30,11 @@ describe("Run command", () => {
2830
])(
2931
'should execute "%s %s" and exit with %i',
3032
async (arg0, arg1, expectedExitCode) => {
31-
const { exitCode, stdout, stderr } = await exec(
32-
"node",
33-
[require.resolve("../bin.js"), "run", arg0, arg1],
34-
{
35-
nodeOptions: {
36-
cwd: f.find("basic-with-scripts"),
37-
env: {
38-
...process.env,
39-
NODE_OPTIONS: "--experimental-strip-types",
40-
},
41-
},
42-
}
33+
const { exitCode, stdout, stderr } = await executeBin(
34+
f.find("basic-with-scripts"),
35+
"run",
36+
arg0,
37+
arg1
4338
);
4439
expect(exitCode).toBe(expectedExitCode);
4540
expect(stripAnsi(stdout.toString())).toMatchSnapshot("stdout");
@@ -49,3 +44,64 @@ describe("Run command", () => {
4944
}
5045
);
5146
});
47+
48+
describe("Fix command", () => {
49+
it.each([
50+
["package-one", "fix", "lf", 0],
51+
["package-one", "fix", "crlf", 0],
52+
] as const satisfies [string, string, LineEndings, number][])(
53+
'should execute "%s %s" without changing line endings from %s',
54+
async (arg0, arg1, sourceLineEnding, expectedExitCode) => {
55+
// arrange
56+
const temp = f.copy("basic-with-scripts");
57+
const filePath = path.join(temp, "package.json");
58+
convertFileLineEndings(filePath, sourceLineEnding);
59+
// act
60+
const { exitCode } = await executeBin(temp, "fix", arg0, arg1);
61+
// assert
62+
expect(exitCode).toBe(expectedExitCode);
63+
const fixedPackageFile = fs.readFileSync(filePath, "utf8");
64+
f.cleanup();
65+
expect(detectLineEndings(fixedPackageFile)).toBe(sourceLineEnding);
66+
}
67+
);
68+
});
69+
70+
type LineEndings = "crlf" | "lf";
71+
72+
function convertFileLineEndings(path: string, targetLineEnding: LineEndings) {
73+
let file = fs.readFileSync(path, "utf8");
74+
// detect mixed line endings
75+
if (
76+
file.includes("\r\n") &&
77+
(file.match(/\r\n/g) || []).length !== (file.match(/\n/g) || []).length
78+
) {
79+
throw new Error("mixed line endings in fixture file: " + path);
80+
}
81+
// if the line endings match, we don't need to convert the file
82+
if (file.includes("\r\n") === (targetLineEnding === "crlf")) {
83+
return;
84+
}
85+
const sourceLineEndingText = targetLineEnding === "crlf" ? "\n" : "\r\n";
86+
const targetLineEndingText = targetLineEnding === "crlf" ? "\r\n" : "\r\n";
87+
fs.writeFileSync(
88+
path,
89+
file.replaceAll(sourceLineEndingText, targetLineEndingText)
90+
);
91+
}
92+
93+
function executeBin(path: string, command: string, ...args: string[]) {
94+
return exec("node", [require.resolve("../bin.js"), command, ...args], {
95+
nodeOptions: {
96+
cwd: path,
97+
env: {
98+
...process.env,
99+
NODE_OPTIONS: "--experimental-strip-types",
100+
},
101+
},
102+
});
103+
}
104+
105+
function detectLineEndings(content: string) {
106+
return (content.includes("\r\n") ? "crlf" : "lf") satisfies LineEndings;
107+
}

packages/cli/src/utils.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import detectIndent from "detect-indent";
77
export async function writePackage(pkg: Package) {
88
let pkgRaw = await fs.readFile(path.join(pkg.dir, "package.json"), "utf-8");
99
let indent = detectIndent(pkgRaw).indent || " ";
10-
return fs.writeFile(
11-
path.join(pkg.dir, "package.json"),
12-
JSON.stringify(pkg.packageJson, null, indent) +
13-
(pkgRaw.endsWith("\n") ? "\n" : "")
14-
);
10+
// Determine original EOL style and whether there was a trailing newline
11+
const eol = pkgRaw.includes("\r\n") ? "\r\n" : "\n";
12+
// Stringify and then normalize EOLs to match the original file
13+
let json = JSON.stringify(pkg.packageJson, null, indent);
14+
json = eol !== "\n" ? json.replace(/\n/g, eol) : json;
15+
if (pkgRaw.endsWith("\n") /* true for both LF and CRLF */) {
16+
json += eol;
17+
}
18+
return fs.writeFile(path.join(pkg.dir, "package.json"), json);
1519
}
1620

1721
export async function install(toolType: string, cwd: string) {

0 commit comments

Comments
 (0)