feat: support 'prettierd' in autofmt

prettierd is a daemon for prettier, allowing lower latency formats
see https://github.com/fsouza/prettierd
This commit is contained in:
clover caruso 2025-08-21 01:07:36 -07:00
parent 0a5d9bfc17
commit 1052a1ccc3
2 changed files with 55 additions and 11 deletions

View file

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// autofmt v1 by paper clover // autofmt v2 by paper clover
// https://paperclover.dev/nix/config/src/branch/main/packages/autofmt/autofmt.js // https://paperclover.dev/nix/config/src/branch/main/packages/autofmt/autofmt.js
// //
// Different codebases use different formatters. Autofmt looks for project // Different codebases use different formatters. Autofmt looks for project
@ -25,6 +25,10 @@
// } // }
// }, // },
// //
// CHANGELOG
// v2: added 'prettierd' as a supported formatter option.
// v1: initial release
//
// @ts-nocheck // @ts-nocheck
// -- definitions -- // -- definitions --
@ -72,6 +76,15 @@ const formatters = {
cmd: ["deno", "fmt", "--", "$files"], cmd: ["deno", "fmt", "--", "$files"],
stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"], stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"],
}, },
prettierd: {
// keep in mind that pretterd never exits.
// https://github.com/fsouza/prettierd/issues/645
languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
mustMatchFile: true,
files: { "node_modules/.bin/prettier": () => which("prettierd") },
cmd: null,
stdin: (file) => ["prettierd", file],
},
prettier: { prettier: {
languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"], languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
files: ["node_modules/.bin/prettier"], files: ["node_modules/.bin/prettier"],
@ -91,7 +104,7 @@ const formatters = {
"nixfmt-rfc-style": { "nixfmt-rfc-style": {
languages: ["nix"], languages: ["nix"],
files: { files: {
"flake.nix": (contents) => contents.includes("nixfmt"), "flake.nix": ({ text }) => text.includes("nixfmt"),
}, },
cmd: ["nixfmt", "--", "$files"], cmd: ["nixfmt", "--", "$files"],
stdin: (file) => ["nixfmt", `--filename=${file}`], stdin: (file) => ["nixfmt", `--filename=${file}`],
@ -99,7 +112,7 @@ const formatters = {
alejandra: { alejandra: {
languages: ["nix"], languages: ["nix"],
files: { files: {
"flake.nix": (contents) => contents.includes("alejandra"), "flake.nix": ({ text }) => text.includes("alejandra"),
}, },
cmd: ["alejandra", "--", "$files"], cmd: ["alejandra", "--", "$files"],
stdin: () => ["alejandra"], stdin: () => ["alejandra"],
@ -498,24 +511,31 @@ function pickFormatter(file) {
return walkUp(dir, (x) => { return walkUp(dir, (x) => {
const children = readDir(x); const children = readDir(x);
for (const fmt of order) { for (const fmt of order) {
if (formatters[fmt][stdio ? "stdin" : "cmd"] == null) continue;
let matches = false; let matches = false;
if (formatters[fmt].files === true) { if (formatters[fmt].files === true) {
matches = true; matches = true;
} }
if (!matches) { if (!matches) {
const filesToCheck = Array.isArray(formatters[fmt].files) const filesToCheck = Array.isArray(formatters[fmt].files)
? formatters[fmt].files.map((base) => ({ base, contents: true })) ? formatters[fmt].files.map((base) => ({ base, check: true }))
: Object.entries(formatters[fmt].files) : Object.entries(formatters[fmt].files)
.map(([base, contents]) => ({ base, contents })); .map(([base, check]) => ({ base, check }));
for (const { base, contents } of filesToCheck) { for (const { base, check } of filesToCheck) {
if (base.includes("/")) { if (base.includes("/")) {
matches = readDir(path.join(x, path.dirname(base))) // matches = readDir(path.join(x, path.dirname(base))) //
.includes(path.basename(base)); .includes(path.basename(base));
} else { } else {
matches = children.includes(base); matches = children.includes(base);
} }
if (matches && typeof contents === "function") { if (matches && typeof check === "function") {
matches = !!contents(fs.readFileSync(path.join(x, base), "utf-8")); let text;
matches = !!check({
get text() {
return text ??= fs.readFileSync(path.join(x, base), "utf-8");
},
});
} }
} }
} }
@ -526,7 +546,7 @@ function pickFormatter(file) {
return formatterId; return formatterId;
} }
} }
}) ?? possible[0]; }) ?? possible.find((fmt) => !formatters[fmt].mustMatchFile);
} }
/** @param dir {string} @param find {(x: string) => any} */ /** @param dir {string} @param find {(x: string) => any} */
@ -581,6 +601,29 @@ function readGitIgnore(dir) {
} }
} }
/** @param {string} name @returns {string | null} */
export function which(name) {
const paths =
process.env.PATH?.split(process.platform === "win32" ? ";" : ":") ?? [];
const exts = process.platform === "win32"
? (process.env.PATHEXT?.split(";") ?? [".exe", ".cmd", ".bat", ".com"])
: [""];
for (const dir of paths) {
if (!dir) continue;
try {
const entries = readDir(dir);
for (const ext of exts) {
const target = name + ext;
if (entries.includes(target)) {
return dir + sep + target;
}
}
} catch {}
}
return null;
}
function escapeGlob(str) { function escapeGlob(str) {
return str.replace(/[\\*,{}]/g, "\\$&"); return str.replace(/[\\*,{}]/g, "\\$&");
} }

View file

@ -3,12 +3,13 @@ pkgs.writeShellApplication {
name = "autofmt"; name = "autofmt";
runtimeInputs = with pkgs; [ runtimeInputs = with pkgs; [
# include only a couple of formatters by default # include only a couple of formatters by default
clang-tools
deno deno
nixfmt-rfc-style
dprint dprint
nixfmt-rfc-style
prettierd # autofmt only runs on a local build of prettier
rustfmt rustfmt
zig zig
clang-tools
]; ];
text = ''exec ${pkgs.nodejs}/bin/node ${./autofmt.js} "$@"''; text = ''exec ${pkgs.nodejs}/bin/node ${./autofmt.js} "$@"'';
} }