Add support for formatters locally installed via yarn 2+ pnp mode (#200)

This adds support for formatters installed locally in project
directories via yarn 2's "zero install" [pnp
mode](https://yarnpkg.com/features/pnp).

It's quite similar to the support for formatters installed locally in a
project's `node_modules` via npm, and leverages the `npx` symbol, so
existing formatter definitions should work without modification.

This checks for a `.pnp.cjs` file (expected in the project root for yarn
pnp projects), then looks for a yarn executable, and checks the version
of yarn to make sure it supports pnp. If that works, we just push
`"yarn"` onto the front of `command`.

I've only tested this with a locally installed `prettier.js`. It's very
much a works-for-me draft, I'm putting in a PR to make sure this is a
workable approach before going any further with it.

---------

Co-authored-by: Radon Rosborough <radon@intuitiveexplanations.com>
This commit is contained in:
Ed Slocomb 2023-11-05 12:08:58 -08:00 committed by GitHub
parent 47547ea694
commit 54a192c345
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 140 additions and 52 deletions

View file

@ -27,6 +27,10 @@ The format is based on [Keep a Changelog].
* Prettier is now enabled in `svelte-mode`.
* More tree-sitter based major modes have been added to
`apheleia-mode-alist` ([#191]).
* Built-in formatters now use a new `"apheleia-npx"` built-in script
instead of the legacy `npx` keyword. The effect of the new script is
the same, except that it also works with Yarn PNP projects as well
as `node_modules` style projects ([#200]).
* Autoload the apheleia-goto-error command ([#215]).
* Use `lisp-indent` as default formatter for `emacs-lisp-mode` ([#223])
* Use `hclfmt` for formatting hashicorp HCL files ([#231])
@ -34,6 +38,9 @@ The format is based on [Keep a Changelog].
### Internal Changes
* Refactored the organisation of the apheleia package for ease of
understanding and usability ([#215]).
* The new `scripts/pnp-bin.js` script is standalone minified nodejs built
from the [`pnp-bin`](https://github.com/PuddleByteComputing/pnp-bin) repo,
extracted from apheleia PR [#200].
### Bugs fixed
* `ktlint` would emit log messages into its stdout when formatting,

View file

@ -81,42 +81,45 @@
(perltidy . ("perltidy" "--quiet" "--standard-error-output"))
(phpcs . ("apheleia-phpcs"))
(prettier
. (npx "prettier" "--stdin-filepath" filepath
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--stdin-filepath" filepath
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-css
. (npx "prettier" "--stdin-filepath" filepath "--parser=css"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=css"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-html
. (npx "prettier" "--stdin-filepath" filepath "--parser=html"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=html"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-graphql
. (npx "prettier" "--stdin-filepath" filepath "--parser=graphql"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=graphql"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-javascript
. (npx "prettier" "--stdin-filepath" filepath "--parser=babel-flow"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=babel-flow"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-json
. (npx "prettier" "--stdin-filepath" filepath "--parser=json"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=json"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-markdown
. (npx "prettier" "--stdin-filepath" filepath "--parser=markdown"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=markdown"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-ruby
. (npx "prettier" "--stdin-filepath" filepath "--parser=ruby"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--stdin-filepath" "dummy.rb"
"--plugin=@prettier/plugin-ruby"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-scss
. (npx "prettier" "--stdin-filepath" filepath "--parser=scss"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--stdin-filepath" filepath
"--parser=scss"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-svelte
. (npx "prettier" "--stdin-filepath" filepath "--parser=svelte"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--stdin-filepath" filepath
"--plugin=prettier-plugin-svelte"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-typescript
. (npx "prettier" "--stdin-filepath" filepath "--parser=typescript"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
. ("apheleia-npx" "prettier" "--parser=typescript"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(prettier-yaml
. (npx "prettier" "--stdin-filepath" filepath "--parser=yaml"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(purs-tidy . (npx "purs-tidy" "format"))
. ("apheleia-npx" "prettier" "--parser=yaml"
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
(purs-tidy . ("apheleia-npx" "purs-tidy" "format"))
(rubocop . ("rubocop" "--stdin" filepath "--auto-correct"
"--stderr" "--format" "quiet" "--fail-level" "fatal"))
(ruby-standard . ("standardrb" "--stdin" filepath "--fix" "--stderr"
@ -185,6 +188,11 @@ then the first string element of the command list is resolved
inside node_modules/.bin if such a directory exists anywhere
above the current `default-directory'.
\(However, instead of using `npx', consider using
\"apheleia-npx\", which is a built-in script that will replicate
the effect, but will also work with Yarn PNP projects and other
npm project types that may exist in the future.)
Any list elements that are not strings and not any of the special
symbols mentioned above will be evaluated when the formatter is
invoked, and spliced into the list. A form can evaluate either to
@ -834,7 +842,7 @@ it's first in the sequence"))
(unless remote-match
(error "Formatter uses `file' but process will run on different \
machine from the machine file is available on"))
(setq stdin nil)
(setq stdin nil)
;; If `buffer-file-name' is nil then there is no backing
;; file, so `buffer-modified-p' should be ignored (it always
;; returns non-nil).

View file

@ -7,6 +7,7 @@ find=(
find .
-name .git -prune -o
-name .log -prune -o
-path ./scripts/pnp-bin.js -prune -o
-path ./test/formatters -prune -o
-name "*.elc" -o
-type f -print

71
scripts/formatters/apheleia-npx Executable file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env bash
# This script is like npx but also works for yarn pnp projects, and
# never tries to install anything. It is very fast.
#
# The script takes as arguments a command to execute and the arguments
# to pass to it. If the command is installed as a binary by an npm
# module in the current project, then that binary is used. Otherwise,
# the script is execed as normal from $PATH. In either case, the
# working directory is preserved.
if (( "$#" == 0 )); then
echo >&2 "usage: apheleia-npx CMD [ARG...]"
exit 1
fi
# location of this script
apheleia_dir="$(cd $(dirname ${BASH_SOURCE[0]})/../.. &>/dev/null && pwd)"
pnp_bin="${apheleia_dir}/scripts/pnp-bin.js"
# This function prints the name of the current directory if it
# contains a file or directory named after the first argument, or the
# parent directory if it contains such a file, or the parent's parent,
# and so on. If no such file is found it returns nonzero.
# https://unix.stackexchange.com/a/22215
find_upwards() {
fname="$1"
path="${PWD}"
while [[ -n "${path}" && ! -e "${path}/${fname}" ]]; do
path="${path%/*}"
done
[[ -n "${path}" ]] && echo "${path}"
}
dir="$(find_upwards package.json)"
if [[ -d $dir ]]; then
cd $dir
pnp_root=$(find_upwards '.pnp.cjs')
npm_root=$(find_upwards 'node_modules')
if [[ -n ${pnp_root} && ${#pnp_root} -gt ${#npm_root} ]]; then
# trying pnp
pnp_path="${pnp_root}/.pnp.cjs"
bin="$(${pnp_bin} ${pnp_path} $1)"
# note: $bin might not be on the real filesystem,
# might be in a zip archive
if [[ -n $bin ]]; then
if [[ -f "${pnp_path}/.pnp.loader.mjs" ]]; then
loader_opt="--loader ${pnp_path}/.pnp.loader.mjs"
fi
node=$(which node)
command="${node} --require ${pnp_path} ${loader_opt} ${bin} ${@:2}"
exec ${command}
fi
elif [[ -n ${npm_root} ]]; then
# trying npm
node_modules_paths=(\
$(node -e 'console.log(require.resolve.paths("").join("\n"))'))
for path in ${node_modules_paths[@]}; do
if [[ -x "${path}/.bin/$1" ]]; then
exec "${path}/.bin/$1" "${@:2}"
fi
done
fi
fi
# Fall back to executing the command if it's installed and on the user's $PATH
exec "$@"

2
scripts/pnp-bin.js Executable file

File diff suppressed because one or more lines are too long

View file

@ -101,11 +101,12 @@ relative to repo root, as returned by git diff --name-only."
((string-match
"^scripts/formatters/\\([^/]+\\)$" changed-file)
(let ((script (match-string 1 changed-file)))
(map-keys
(map-filter
(lambda (fmt def)
(member script def))
apheleia-formatters)))))))
(mapcar #'symbol-name
(map-keys
(map-filter
(lambda (fmt def)
(and (listp def) (member script def)))
apheleia-formatters))))))))
(defun apheleia-ft--get-formatters-for-pull-request ()
"Return list of formatter string names that were touched in this PR.
@ -133,7 +134,10 @@ main."
"Print to stdout a comma-delimited list of formatters changed in this PR."
(princ (concat
(string-join
(apheleia-ft--get-formatters-for-pull-request) ",")
(cl-remove-duplicates
(apheleia-ft--get-formatters-for-pull-request)
:test 'string=)
",")
"\n")))
(defun apheleia-ft--read-file (filename)

View file

@ -6,14 +6,10 @@ export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y ca-certificates curl gnupg lsb-release software-properties-common
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
ubuntu_name="$(lsb_release -cs)"
node_repo="$(curl -fsSL https://deb.nodesource.com/setup_current.x | grep NODEREPO= | grep -Eo 'node_[0-9]+\.x' | head -n1)"
tee -a /etc/apt/sources.list.d/nodejs.list >/dev/null <<EOF
deb [arch=amd64] https://deb.nodesource.com/${node_repo} ${ubuntu_name} main
EOF
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
add-apt-repository -n ppa:longsleep/golang-backports
@ -30,9 +26,6 @@ nodejs
sudo
unzip
wget
sudo
unzip
wget
"

View file

@ -1,15 +1,16 @@
# Need ruby for gem, need gcc and ruby headers for native gem deps
apt-get install -y ruby ruby-dev gcc
# Install the plugin
npm install -g prettier @prettier/plugin-ruby
# Have to install from source because release not tagged yet
# https://github.com/ruby-syntax-tree/syntax_tree-rbs/pull/34
# https://stackoverflow.com/a/11767563
gem install specific_install
gem specific_install -l https://github.com/ruby-syntax-tree/syntax_tree-rbs.git
# Apparently rubygems does not know how to do dependency resolution.
# So we have to manually install an old version of this dependency to
# avoid the latest version getting installed and then failing to build
# because it does not support ruby 2.7 which is what we have.
gem install rbs -v 3.1.3
# These are required dependencies documented at
# https://www.npmjs.com/package/@prettier/plugin-ruby
gem install prettier_print syntax_tree syntax_tree-haml syntax_tree-rbs
# Install the plugin
cd /tmp
npm install --save-dev prettier @prettier/plugin-ruby

View file

@ -1 +1,2 @@
npm install -g prettier-plugin-svelte prettier
cd /tmp
npm install --save-dev prettier-plugin-svelte prettier