authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-08-21 01:07:36-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-08-21 01:08:38-07:00
log1052a1ccc3d91a32648090733bef9d1ae6819c55
tree853667b7a14dde52c1925639df356c786f84f94d
parent0a5d9bfc17d0d56d6a972fa18bdb48e77708982b

feat: support 'prettierd' in autofmt

prettierd is a daemon for prettier, allowing lower latency formats see https://github.com/fsouza/prettierd

2 files changed, 55 insertions(+), 11 deletions(-)

packages/autofmt/autofmt.js+52-9
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1#!/usr/bin/env node1#!/usr/bin/env node
2// autofmt v1 by paper clover2// autofmt v2 by paper clover
3// https://paperclover.dev/nix/config/src/branch/main/packages/autofmt/autofmt.js3// https://paperclover.dev/nix/config/src/branch/main/packages/autofmt/autofmt.js
4//4//
5// Different codebases use different formatters. Autofmt looks for project5// Different codebases use different formatters. Autofmt looks for project
...@@ -25,6 +25,10 @@...@@ -25,6 +25,10 @@
25// }25// }
26// },26// },
27//27//
28// CHANGELOG
29// v2: added 'prettierd' as a supported formatter option.
30// v1: initial release
31//
28// @ts-nocheck32// @ts-nocheck
2933
30// -- definitions --34// -- definitions --
...@@ -72,6 +76,15 @@ const formatters = {...@@ -72,6 +76,15 @@ const formatters = {
72 cmd: ["deno", "fmt", "--", "$files"],76 cmd: ["deno", "fmt", "--", "$files"],
73 stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"],77 stdin: (file) => ["deno", "fmt", "--ext", path.extname(file).slice(1), "-"],
74 },78 },
79 prettierd: {
80 // keep in mind that pretterd never exits.
81 // https://github.com/fsouza/prettierd/issues/645
82 languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
83 mustMatchFile: true,
84 files: { "node_modules/.bin/prettier": () => which("prettierd") },
85 cmd: null,
86 stdin: (file) => ["prettierd", file],
87 },
75 prettier: {88 prettier: {
76 languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],89 languages: ["javascript", "markdown", "json", "yaml", "mdx", "html", "css"],
77 files: ["node_modules/.bin/prettier"],90 files: ["node_modules/.bin/prettier"],
...@@ -91,7 +104,7 @@ const formatters = {...@@ -91,7 +104,7 @@ const formatters = {
91 "nixfmt-rfc-style": {104 "nixfmt-rfc-style": {
92 languages: ["nix"],105 languages: ["nix"],
93 files: {106 files: {
94 "flake.nix": (contents) => contents.includes("nixfmt"),107 "flake.nix": ({ text }) => text.includes("nixfmt"),
95 },108 },
96 cmd: ["nixfmt", "--", "$files"],109 cmd: ["nixfmt", "--", "$files"],
97 stdin: (file) => ["nixfmt", `--filename=${file}`],110 stdin: (file) => ["nixfmt", `--filename=${file}`],
...@@ -99,7 +112,7 @@ const formatters = {...@@ -99,7 +112,7 @@ const formatters = {
99 alejandra: {112 alejandra: {
100 languages: ["nix"],113 languages: ["nix"],
101 files: {114 files: {
102 "flake.nix": (contents) => contents.includes("alejandra"),115 "flake.nix": ({ text }) => text.includes("alejandra"),
103 },116 },
104 cmd: ["alejandra", "--", "$files"],117 cmd: ["alejandra", "--", "$files"],
105 stdin: () => ["alejandra"],118 stdin: () => ["alejandra"],
...@@ -498,24 +511,31 @@ function pickFormatter(file) {...@@ -498,24 +511,31 @@ function pickFormatter(file) {
498 return walkUp(dir, (x) => {511 return walkUp(dir, (x) => {
499 const children = readDir(x);512 const children = readDir(x);
500 for (const fmt of order) {513 for (const fmt of order) {
514 if (formatters[fmt][stdio ? "stdin" : "cmd"] == null) continue;
515
501 let matches = false;516 let matches = false;
502 if (formatters[fmt].files === true) {517 if (formatters[fmt].files === true) {
503 matches = true;518 matches = true;
504 }519 }
505 if (!matches) {520 if (!matches) {
506 const filesToCheck = Array.isArray(formatters[fmt].files)521 const filesToCheck = Array.isArray(formatters[fmt].files)
507 ? formatters[fmt].files.map((base) => ({ base, contents: true }))522 ? formatters[fmt].files.map((base) => ({ base, check: true }))
508 : Object.entries(formatters[fmt].files)523 : Object.entries(formatters[fmt].files)
509 .map(([base, contents]) => ({ base, contents }));524 .map(([base, check]) => ({ base, check }));
510 for (const { base, contents } of filesToCheck) {525 for (const { base, check } of filesToCheck) {
511 if (base.includes("/")) {526 if (base.includes("/")) {
512 matches = readDir(path.join(x, path.dirname(base))) //527 matches = readDir(path.join(x, path.dirname(base))) //
513 .includes(path.basename(base));528 .includes(path.basename(base));
514 } else {529 } else {
515 matches = children.includes(base);530 matches = children.includes(base);
516 }531 }
517 if (matches && typeof contents === "function") {532 if (matches && typeof check === "function") {
518 matches = !!contents(fs.readFileSync(path.join(x, base), "utf-8"));533 let text;
534 matches = !!check({
535 get text() {
536 return text ??= fs.readFileSync(path.join(x, base), "utf-8");
537 },
538 });
519 }539 }
520 }540 }
521 }541 }
...@@ -526,7 +546,7 @@ function pickFormatter(file) {...@@ -526,7 +546,7 @@ function pickFormatter(file) {
526 return formatterId;546 return formatterId;
527 }547 }
528 }548 }
529 }) ?? possible[0];549 }) ?? possible.find((fmt) => !formatters[fmt].mustMatchFile);
530}550}
531551
532/** @param dir {string} @param find {(x: string) => any} */552/** @param dir {string} @param find {(x: string) => any} */
...@@ -581,6 +601,29 @@ function readGitIgnore(dir) {...@@ -581,6 +601,29 @@ function readGitIgnore(dir) {
581 }601 }
582}602}
583603
604/** @param {string} name @returns {string | null} */
605export function which(name) {
606 const paths =
607 process.env.PATH?.split(process.platform === "win32" ? ";" : ":") ?? [];
608 const exts = process.platform === "win32"
609 ? (process.env.PATHEXT?.split(";") ?? [".exe", ".cmd", ".bat", ".com"])
610 : [""];
611
612 for (const dir of paths) {
613 if (!dir) continue;
614 try {
615 const entries = readDir(dir);
616 for (const ext of exts) {
617 const target = name + ext;
618 if (entries.includes(target)) {
619 return dir + sep + target;
620 }
621 }
622 } catch {}
623 }
624 return null;
625}
626
584function escapeGlob(str) {627function escapeGlob(str) {
585 return str.replace(/[\\*,{}]/g, "\\$&");628 return str.replace(/[\\*,{}]/g, "\\$&");
586}629}
packages/autofmt/default.nix+3-2
...@@ -3,12 +3,13 @@ pkgs.writeShellApplication {...@@ -3,12 +3,13 @@ pkgs.writeShellApplication {
3 name = "autofmt";3 name = "autofmt";
4 runtimeInputs = with pkgs; [4 runtimeInputs = with pkgs; [
5 # include only a couple of formatters by default5 # include only a couple of formatters by default
6 clang-tools
6 deno7 deno
7 nixfmt-rfc-style
8 dprint8 dprint
9 nixfmt-rfc-style
10 prettierd # autofmt only runs on a local build of prettier
9 rustfmt11 rustfmt
10 zig12 zig
11 clang-tools
12 ];13 ];
13 text = ''exec ${pkgs.nodejs}/bin/node ${./autofmt.js} "$@"'';14 text = ''exec ${pkgs.nodejs}/bin/node ${./autofmt.js} "$@"'';
14}15}