Skip to content

Commit 516986b

Browse files
Copilotmarcelocra
andauthored
feat: add /install/prettier endpoint to quickly install prettier+husky+lintstaged (#61)
* Initial plan * feat: add /install/prettier endpoint with package manager support - Create install scripts data structure (install-scripts.yml) - Implement install-prettier.sh script supporting pnpm, npm, yarn, bun - Add dynamic route for /install/[script] with package manager query param - Update /install route to list available scripts when accessed from browser - Script installs Prettier, Husky, lint-staged and creates GitHub Actions workflow - Tested with npm and yarn, works correctly Co-authored-by: marcelocra <2532492+marcelocra@users.noreply.github.com> * refactor: improve script readability and remove hardcoded pnpm version - Format Node.js inline script for better readability - Remove hardcoded pnpm version from GitHub Actions workflow - pnpm/action-setup@v4 will now use version from package.json Co-authored-by: marcelocra <2532492+marcelocra@users.noreply.github.com> * refactor: use Next.js page.tsx instead of inline HTML and improve bash script idioms - Move install scripts listing from inline HTML to proper page.tsx at /install-scripts - Restore /install/route.ts to original functionality (serve devmagic.sh only) - Rewrite install-prettier.sh with proper bash idioms: - Use [[ ]] instead of [ ] for all conditionals - Add set -euo pipefail for strict error handling - Organize code with main() function and clear pipeline structure - Extract all logic into named functions for better readability - Use readonly for constants Co-authored-by: marcelocra <2532492+marcelocra@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: marcelocra <2532492+marcelocra@users.noreply.github.com>
1 parent 68dc005 commit 516986b

6 files changed

Lines changed: 698 additions & 0 deletions

File tree

setup/install-prettier.sh

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
#!/usr/bin/env bash
2+
# DevMagic - Prettier Installation Script
3+
# Installs and configures Prettier, Husky, and lint-staged
4+
# Usage:
5+
# Latest version: curl -fsSL https://devmagic.run/install/prettier | bash
6+
# With npm: curl -fsSL https://devmagic.run/install/prettier?pm=npm | bash
7+
8+
set -euo pipefail
9+
10+
# --- Configuration ---
11+
PACKAGE_MANAGER="${PACKAGE_MANAGER:-pnpm}"
12+
13+
# --- Colors for output ---
14+
readonly RED='\033[0;31m'
15+
readonly GREEN='\033[0;32m'
16+
readonly YELLOW='\033[1;33m'
17+
readonly BLUE='\033[0;34m'
18+
readonly PURPLE='\033[0;35m'
19+
readonly NC='\033[0m'
20+
21+
# --- Helper Functions ---
22+
23+
show_header() {
24+
echo -e "${PURPLE}"
25+
echo "✨ Prettier Installation"
26+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━"
27+
echo "Setting up automatic code formatting"
28+
echo -e "Package Manager: ${PACKAGE_MANAGER}${NC}"
29+
echo
30+
}
31+
32+
check_prerequisites() {
33+
echo -e "${BLUE}Checking prerequisites...${NC}"
34+
35+
# Check if package.json exists
36+
if [[ ! -f "package.json" ]]; then
37+
echo -e "${RED}❌ package.json not found. This script must be run in a Node.js project.${NC}"
38+
exit 1
39+
fi
40+
41+
# Check if Git repository exists
42+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
43+
echo -e "${RED}❌ Not a Git repository. Husky requires Git to function.${NC}"
44+
exit 1
45+
fi
46+
47+
# Verify package manager is installed
48+
if ! command -v "$PACKAGE_MANAGER" &>/dev/null; then
49+
echo -e "${RED}${PACKAGE_MANAGER} is not installed. Please install it first.${NC}"
50+
exit 1
51+
fi
52+
53+
# Validate package manager
54+
case "$PACKAGE_MANAGER" in
55+
pnpm|npm|yarn|bun)
56+
;;
57+
*)
58+
echo -e "${RED}❌ Unsupported package manager: ${PACKAGE_MANAGER}${NC}"
59+
echo -e "${YELLOW} Supported: pnpm, npm, yarn, bun${NC}"
60+
exit 1
61+
;;
62+
esac
63+
64+
echo -e "${GREEN}✓ Prerequisites check passed${NC}"
65+
echo
66+
}
67+
68+
is_workspace() {
69+
if [[ "$PACKAGE_MANAGER" == "pnpm" ]]; then
70+
[[ -f "pnpm-workspace.yaml" ]]
71+
elif [[ "$PACKAGE_MANAGER" == "npm" || "$PACKAGE_MANAGER" == "yarn" ]]; then
72+
grep -q '"workspaces"' package.json 2>/dev/null
73+
else
74+
return 1
75+
fi
76+
}
77+
78+
get_install_cmd() {
79+
local packages="$1"
80+
local dev_flag="$2"
81+
82+
case "$PACKAGE_MANAGER" in
83+
pnpm)
84+
if is_workspace; then
85+
echo "pnpm add -w ${dev_flag} ${packages}"
86+
else
87+
echo "pnpm add ${dev_flag} ${packages}"
88+
fi
89+
;;
90+
npm)
91+
echo "npm install ${dev_flag} ${packages}"
92+
;;
93+
yarn)
94+
echo "yarn add ${dev_flag} ${packages}"
95+
;;
96+
bun)
97+
echo "bun add ${dev_flag} ${packages}"
98+
;;
99+
esac
100+
}
101+
102+
get_dev_flag() {
103+
case "$PACKAGE_MANAGER" in
104+
pnpm|npm)
105+
echo "--save-dev"
106+
;;
107+
yarn|bun)
108+
echo "-D"
109+
;;
110+
esac
111+
}
112+
113+
get_exact_flag() {
114+
case "$PACKAGE_MANAGER" in
115+
pnpm|npm)
116+
echo "--save-exact"
117+
;;
118+
yarn)
119+
echo "--exact"
120+
;;
121+
bun)
122+
echo ""
123+
;;
124+
esac
125+
}
126+
127+
get_exec_cmd() {
128+
local cmd="$1"
129+
130+
case "$PACKAGE_MANAGER" in
131+
pnpm|npm|yarn)
132+
echo "${PACKAGE_MANAGER} exec ${cmd}"
133+
;;
134+
bun)
135+
echo "bunx ${cmd}"
136+
;;
137+
esac
138+
}
139+
140+
install_prettier() {
141+
echo -e "${BLUE}📦 Step 1/7: Installing Prettier...${NC}"
142+
143+
local dev_flag
144+
dev_flag=$(get_dev_flag)
145+
local exact_flag
146+
exact_flag=$(get_exact_flag)
147+
local install_cmd
148+
install_cmd=$(get_install_cmd "prettier" "${dev_flag} ${exact_flag}")
149+
150+
echo -e "${YELLOW}Running: ${install_cmd}${NC}"
151+
eval "$install_cmd"
152+
153+
echo -e "${GREEN}✓ Prettier installed${NC}"
154+
echo
155+
}
156+
157+
create_prettierrc() {
158+
echo -e "${BLUE}📝 Step 2/7: Creating .prettierrc...${NC}"
159+
160+
if [[ -f ".prettierrc" ]]; then
161+
echo -e "${YELLOW}⚠️ .prettierrc already exists, skipping...${NC}"
162+
else
163+
cat > .prettierrc << 'EOF'
164+
{}
165+
EOF
166+
echo -e "${GREEN}✓ .prettierrc created${NC}"
167+
fi
168+
echo
169+
}
170+
171+
create_prettierignore() {
172+
echo -e "${BLUE}📝 Step 3/7: Creating .prettierignore...${NC}"
173+
174+
if [[ -f ".prettierignore" ]]; then
175+
echo -e "${YELLOW}⚠️ .prettierignore already exists, skipping...${NC}"
176+
else
177+
cat > .prettierignore << 'EOF'
178+
# Ignore artifacts:
179+
build
180+
coverage
181+
dist
182+
.next
183+
out
184+
node_modules
185+
pnpm-lock.yaml
186+
yarn.lock
187+
package-lock.json
188+
bun.lockb
189+
EOF
190+
echo -e "${GREEN}✓ .prettierignore created${NC}"
191+
fi
192+
echo
193+
}
194+
195+
create_editorconfig() {
196+
echo -e "${BLUE}📝 Step 4/7: Creating .editorconfig...${NC}"
197+
198+
if [[ -f ".editorconfig" ]]; then
199+
echo -e "${YELLOW}⚠️ .editorconfig already exists, skipping...${NC}"
200+
else
201+
cat > .editorconfig << 'EOF'
202+
root = true
203+
204+
[*]
205+
indent_style = space
206+
indent_size = 2
207+
insert_final_newline = true
208+
trim_trailing_whitespace = true
209+
end_of_line = lf
210+
charset = utf-8
211+
spelling_languages = en-US,en,pt-BR,pt
212+
max_line_length = 120
213+
214+
[*.{lua,toml,yaml,yml,json,jsonc,json5}]
215+
max_line_length = 80
216+
217+
[*.{fs,fsi,fsx}]
218+
indent_size = 4
219+
# Simplifies reordering and adding new items. Details:
220+
# https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#multiline-bracket-formatting-style
221+
fsharp_multiline_bracket_style = stroustrup
222+
223+
[*.{py,sh,zsh,bash,gitconfig,tmux.conf}]
224+
indent_size = 4
225+
max_line_length = 80
226+
227+
[*.{md,markdown,mdx}]
228+
indent_size = 4
229+
max_line_length = unset # Not wrapping lines reduces diffs.
230+
trim_trailing_whitespace = false # Double trailing spaces can be used to break lines.
231+
EOF
232+
echo -e "${GREEN}✓ .editorconfig created${NC}"
233+
fi
234+
echo
235+
}
236+
237+
install_husky_lintstaged() {
238+
echo -e "${BLUE}📦 Step 5/7: Installing Husky and lint-staged...${NC}"
239+
240+
local dev_flag
241+
dev_flag=$(get_dev_flag)
242+
local install_cmd
243+
install_cmd=$(get_install_cmd "husky lint-staged" "${dev_flag}")
244+
245+
echo -e "${YELLOW}Running: ${install_cmd}${NC}"
246+
eval "$install_cmd"
247+
248+
echo -e "${GREEN}✓ Husky and lint-staged installed${NC}"
249+
echo
250+
}
251+
252+
configure_husky() {
253+
echo -e "${BLUE}🪝 Step 6/7: Configuring Husky...${NC}"
254+
255+
local husky_init
256+
husky_init=$(get_exec_cmd "husky init")
257+
echo -e "${YELLOW}Running: ${husky_init}${NC}"
258+
eval "$husky_init"
259+
260+
# Create pre-commit hook
261+
local pre_commit_cmd
262+
pre_commit_cmd=$(get_exec_cmd "lint-staged")
263+
cat > .husky/pre-commit << EOF
264+
${pre_commit_cmd}
265+
EOF
266+
267+
chmod +x .husky/pre-commit
268+
269+
echo -e "${GREEN}✓ Husky configured${NC}"
270+
echo
271+
}
272+
273+
configure_lintstaged() {
274+
echo -e "${BLUE}⚙️ Step 7/7: Configuring lint-staged...${NC}"
275+
276+
# Check if lint-staged config already exists (look for the config object, not just dependency name)
277+
if grep -q '"lint-staged"[[:space:]]*:[[:space:]]*{' package.json; then
278+
echo -e "${YELLOW}⚠️ lint-staged configuration already exists in package.json, skipping...${NC}"
279+
else
280+
# Use node to add lint-staged config to package.json
281+
node --eval "
282+
const fs = require('fs');
283+
const pkgPath = 'package.json';
284+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
285+
pkg['lint-staged'] = {
286+
'**/*': 'prettier --write --ignore-unknown'
287+
};
288+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
289+
"
290+
echo -e "${GREEN}✓ lint-staged configured in package.json${NC}"
291+
fi
292+
echo
293+
}
294+
295+
create_github_workflow() {
296+
echo -e "${BLUE}🔄 Creating GitHub Actions workflow...${NC}"
297+
298+
local workflow_dir=".github/workflows"
299+
local workflow_file="${workflow_dir}/format-check.yml"
300+
301+
if [[ -f "$workflow_file" ]]; then
302+
echo -e "${YELLOW}⚠️ GitHub Actions workflow already exists, skipping...${NC}"
303+
else
304+
mkdir -p "$workflow_dir"
305+
306+
# Determine the install command for CI
307+
local setup_step install_cmd_ci check_cmd
308+
case "$PACKAGE_MANAGER" in
309+
pnpm)
310+
setup_step=' - uses: pnpm/action-setup@v4'
311+
install_cmd_ci='pnpm install --frozen-lockfile'
312+
check_cmd='pnpm exec prettier --check .'
313+
;;
314+
npm)
315+
setup_step=''
316+
install_cmd_ci='npm ci'
317+
check_cmd='npm exec prettier -- --check .'
318+
;;
319+
yarn)
320+
setup_step=''
321+
install_cmd_ci='yarn install --frozen-lockfile'
322+
check_cmd='yarn exec prettier --check .'
323+
;;
324+
bun)
325+
setup_step=' - uses: oven-sh/setup-bun@v2'
326+
install_cmd_ci='bun install --frozen-lockfile'
327+
check_cmd='bunx prettier --check .'
328+
;;
329+
esac
330+
331+
cat > "$workflow_file" << EOF
332+
name: Format Check
333+
on: [push, pull_request]
334+
335+
jobs:
336+
check-format:
337+
runs-on: ubuntu-latest
338+
steps:
339+
- uses: actions/checkout@v4
340+
${setup_step}
341+
- uses: actions/setup-node@v4
342+
with:
343+
node-version: "20"
344+
cache: "${PACKAGE_MANAGER}"
345+
- run: ${install_cmd_ci}
346+
- run: ${check_cmd}
347+
EOF
348+
echo -e "${GREEN}✓ GitHub Actions workflow created at ${workflow_file}${NC}"
349+
fi
350+
echo
351+
}
352+
353+
show_completion() {
354+
echo -e "${GREEN}"
355+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
356+
echo "✨ Prettier setup complete!"
357+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
358+
echo -e "${NC}"
359+
echo "What was configured:"
360+
echo " ✓ Prettier installed with exact version pinning"
361+
echo " ✓ .prettierrc configuration file"
362+
echo " ✓ .prettierignore for excluding files"
363+
echo " ✓ .editorconfig for editor consistency"
364+
echo " ✓ Husky for Git hooks"
365+
echo " ✓ lint-staged for pre-commit formatting"
366+
echo " ✓ GitHub Actions workflow for CI checks"
367+
echo
368+
echo "Next steps:"
369+
echo " 1. Customize .prettierrc with your preferences (optional)"
370+
echo " 2. Run '$(get_exec_cmd "prettier --write .")' to format all files"
371+
echo " 3. Commit your changes - formatting will run automatically!"
372+
echo
373+
echo -e "${YELLOW}Note: Your commits will now be automatically formatted.${NC}"
374+
echo
375+
}
376+
377+
# --- Main Pipeline ---
378+
main() {
379+
show_header
380+
check_prerequisites
381+
install_prettier
382+
create_prettierrc
383+
create_prettierignore
384+
create_editorconfig
385+
install_husky_lintstaged
386+
configure_husky
387+
configure_lintstaged
388+
create_github_workflow
389+
show_completion
390+
}
391+
392+
# Run main function
393+
main "$@"

0 commit comments

Comments
 (0)