Repository navigation
Expand file tree
/
Copy pathdev
More file actions
executable file
·149 lines (137 loc) · 3.75 KB
/
Copy pathdev
File metadata and controls
executable file
·149 lines (137 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env bash
# The project's dev server or test watcher, run with the package manager its
# lockfile names (npm, pnpm, yarn or bun):
#
# dev server the "dev" script, else "start"
# dev test Vitest or Jest watching, starting with the tests your changes touch
#
# In iTerm2 the tab's status dot follows along (triggers in config/iterm2).
set -euo pipefail
usage() {
sed -n '2,8p' "$0" | sed -E 's/^# ?//'
}
# The nearest package.json at or above the current folder
project() {
local dir=$PWD
while [[ "$dir" != / ]]; do
if [[ -f "$dir/package.json" ]]; then
echo "$dir"
return 0
fi
dir=$(dirname "$dir")
done
return 1
}
# In a monorepo the lockfile sits at the root, above the package
package_manager() {
local dir=$1
while :; do
if [[ -f "$dir/pnpm-lock.yaml" ]]; then
echo pnpm
return
elif [[ -f "$dir/yarn.lock" ]]; then
echo yarn
return
elif [[ -f "$dir/bun.lock" || -f "$dir/bun.lockb" ]]; then
echo bun
return
elif [[ -f "$dir/package-lock.json" || "$dir" == / ]]; then
echo npm
return
fi
dir=$(dirname "$dir")
done
}
has_script() { # <project> <script>
node -e 'process.exit(require(process.argv[1]).scripts?.[process.argv[2]] ? 0 : 1)' "$1/package.json" "$2"
}
# Vitest or Jest, whichever the project's package.json names. A monorepo often
# installs its runner only at the top: without one of its own, the project gets
# the nearest one above, up to the monorepo's root, as npx finds it, unless the
# project's test script runs something else
test_runner() { # <project>
node -e '
const fs = require("fs")
const path = require("path")
const read = (dir) => {
try {
return JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"))
} catch {
return undefined
}
}
const runner = (p) => ["vitest", "jest"].find((name) => ({ ...p?.dependencies, ...p?.devDependencies })[name])
const top = (dir, p) => p?.workspaces || fs.existsSync(path.join(dir, "pnpm-workspace.yaml")) || dir === path.dirname(dir)
let dir = process.argv[1]
const project = read(dir)
let found = runner(project)
for (let p = project; !found && !top(dir, p); ) {
dir = path.dirname(dir)
p = read(dir)
found = runner(p)
}
const script = project?.scripts?.test
if (found && (runner(project) || !script || script.split(/[\s;&|()]+/).includes(found))) {
console.log(found)
}' "$1"
}
in_iterm() {
[[ "${TERM_PROGRAM:-}" == iTerm.app || "${LC_TERMINAL:-}" == iTerm2 ]]
}
status() { # <text> <dot colour>
if in_iterm; then printf '\e]21337;status=%s;indicator=%s\a' "$1" "$2"; fi
}
clear_status() {
if in_iterm; then printf '\e]21337;status=;indicator=;status-color=\a'; fi
}
case "${1:-}" in
server | test) ;;
-h | --help)
usage
exit 0
;;
*)
usage >&2
exit 2
;;
esac
dir=$(project) || {
echo "dev: no package.json here or above" >&2
exit 1
}
pm=$(package_manager "$dir")
case "$pm" in
npm) run=(npm run) exec=(npx) ;;
pnpm) run=(pnpm run) exec=(pnpm exec) ;;
yarn) run=(yarn run) exec=(yarn) ;;
bun) run=(bun run) exec=(bunx) ;;
esac
if [[ "$1" == server ]]; then
if has_script "$dir" dev; then
cmd=("${run[@]}" dev)
elif has_script "$dir" start; then
cmd=("${run[@]}" start)
else
echo "dev: $dir/package.json has no dev or start script" >&2
exit 1
fi
state=starting
else
runner=$(test_runner "$dir")
if [[ "$runner" == vitest ]]; then
cmd=("${exec[@]}" vitest --changed)
elif [[ "$runner" == jest ]]; then
cmd=("${exec[@]}" jest --watch)
elif has_script "$dir" test; then
cmd=("${run[@]}" test)
else
echo "dev: no Vitest, Jest or test script in $dir/package.json" >&2
exit 1
fi
state=testing
fi
cd "$dir"
echo "→ ${cmd[*]}"
trap clear_status EXIT
status "$state" '#d29922'
"${cmd[@]}"