Skip to content

Commit d162b2f

Browse files
committed
Add deployment hooks and ORM detection for v1.1.0
- Generate .shipnode/pre-deploy.sh with ORM-aware templates - Detect ORMs (Prisma, Drizzle, TypeORM, Sequelize, Knex) from package.json - Execute pre-deploy hooks during deployment - Extend user management functions - Update build system for lib/ directory packaging - Bump version to 1.1.0
1 parent 32a9845 commit d162b2f

9 files changed

Lines changed: 498 additions & 44 deletions

File tree

build-dist.sh

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ GREEN='\033[0;32m'
77
BLUE='\033[0;34m'
88
NC='\033[0m'
99

10-
VERSION="1.0.0"
10+
VERSION="1.1.0"
1111
DIST_DIR="dist"
1212
ARCHIVE_NAME="shipnode-payload.tar.gz"
1313
INSTALLER_NAME="shipnode-installer.sh"
@@ -34,6 +34,10 @@ cp LICENSE "$PACKAGE_DIR/"
3434
cp README.md "$PACKAGE_DIR/"
3535
cp INSTALL.md "$PACKAGE_DIR/"
3636

37+
# Copy lib directory
38+
mkdir -p "$PACKAGE_DIR/lib"
39+
cp -r lib/* "$PACKAGE_DIR/lib/"
40+
3741
# Copy templates
3842
mkdir -p "$PACKAGE_DIR/templates"
3943
cp templates/* "$PACKAGE_DIR/templates/"
@@ -62,7 +66,7 @@ YELLOW='\033[1;33m'
6266
BLUE='\033[0;34m'
6367
NC='\033[0m'
6468
65-
VERSION="1.0.0"
69+
VERSION="1.1.0"
6670
INSTALL_DIR="$HOME/.shipnode"
6771
6872
echo -e "${BLUE}╔════════════════════════════════════╗${NC}"
@@ -81,13 +85,28 @@ done
8185
# Extract embedded archive
8286
echo -e "${BLUE}→${NC} Extracting ShipNode..."
8387
88+
# Get absolute path of this script before changing directory (portable for Linux and macOS)
89+
if command -v realpath &> /dev/null; then
90+
SCRIPT_PATH="$(realpath "$0")"
91+
elif [[ "$OSTYPE" == "darwin"* ]]; then
92+
# macOS: construct absolute path manually since readlink -f doesn't exist
93+
if [ -L "$0" ]; then
94+
SCRIPT_PATH="$(cd "$(dirname "$0")" && cd "$(dirname "$(readlink "$0")")" && pwd)/$(basename "$(readlink "$0")")"
95+
else
96+
SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
97+
fi
98+
else
99+
# Linux: use readlink -f
100+
SCRIPT_PATH="$(readlink -f "$0")"
101+
fi
102+
84103
# Create temp directory
85104
TEMP_DIR=$(mktemp -d)
86105
cd "$TEMP_DIR"
87106
88107
# Extract the base64-encoded tar.gz from this script
89-
ARCHIVE_LINE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$0")
90-
tail -n +${ARCHIVE_LINE} "$0" | base64 -d | tar -xz
108+
ARCHIVE_LINE=$(awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' "$SCRIPT_PATH")
109+
tail -n +${ARCHIVE_LINE} "$SCRIPT_PATH" | base64 -d | tar -xz
91110
92111
# Check extraction
93112
if [ ! -d "shipnode" ]; then

lib/commands/env.sh

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
cmd_env() {
22
load_config
33

4-
# Check if .env file exists locally
5-
if [ ! -f .env ]; then
6-
error ".env file not found in current directory"
4+
# Resolve environment file path (default to .env)
5+
LOCAL_ENV="${ENV_FILE:-.env}"
6+
7+
# Check if environment file exists locally
8+
if [ ! -f "$LOCAL_ENV" ]; then
9+
error "Environment file not found: $LOCAL_ENV"
710
fi
811

9-
info "Uploading .env file to server..."
12+
info "Uploading $LOCAL_ENV to server..."
1013

1114
# Determine target path based on deployment mode
1215
if [ "$ZERO_DOWNTIME" = "true" ]; then
@@ -19,10 +22,10 @@ cmd_env() {
1922
ssh -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_PATH"
2023
fi
2124

22-
# Upload the .env file
23-
scp -P "$SSH_PORT" .env "$SSH_USER@$SSH_HOST:$TARGET_PATH"
25+
# Upload the environment file
26+
scp -P "$SSH_PORT" "$LOCAL_ENV" "$SSH_USER@$SSH_HOST:$TARGET_PATH"
2427

25-
success ".env file uploaded to $TARGET_PATH"
28+
success "Uploaded $LOCAL_ENV to $TARGET_PATH"
2629

2730
# Restart backend app if running to reload env vars
2831
if [ "$APP_TYPE" = "backend" ]; then

lib/commands/init.sh

Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,212 @@
11
# ============================================================================
22

3+
# Generate .shipnode/ directory with smart hook templates
4+
generate_shipnode_hooks() {
5+
# Create .shipnode directory
6+
mkdir -p .shipnode
7+
8+
# Detect ORM from package.json
9+
local orm_info=""
10+
local orm_name=""
11+
local migrate_cmd=""
12+
local generate_cmd=""
13+
14+
if [ -f "package.json" ]; then
15+
orm_info=$(detect_orm)
16+
IFS='|' read -r orm_name migrate_cmd generate_cmd <<< "$orm_info"
17+
else
18+
orm_name="none"
19+
fi
20+
21+
# Generate pre-deploy.sh
22+
cat > .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
23+
#!/bin/bash
24+
# ShipNode Pre-Deploy Hook
25+
# Runs BEFORE the app is activated (before PM2 reload)
26+
# Exit non-zero to abort deployment
27+
#
28+
# Available environment variables:
29+
# RELEASE_PATH - Path to the new release being deployed
30+
# REMOTE_PATH - Base deployment directory
31+
# PM2_APP_NAME - PM2 process name (backend only)
32+
# BACKEND_PORT - Application port (backend only)
33+
# SHARED_ENV_PATH - Path to the server's shared .env file
34+
35+
set -e # Exit on error
36+
37+
# Source the server's shared .env to use same variables as the app
38+
if [ -f "$SHARED_ENV_PATH" ]; then
39+
set -a
40+
source "$SHARED_ENV_PATH"
41+
set +a
42+
fi
43+
44+
cd "$RELEASE_PATH"
45+
46+
echo "Running pre-deploy hook for release: $RELEASE_PATH"
47+
48+
# ── ORM Database Migrations ──────────────────────────────────────
49+
50+
PREDEPLOY_EOF
51+
52+
# Add ORM-specific commands (uncommented if detected)
53+
if [ "$orm_name" = "Prisma" ]; then
54+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
55+
# Prisma (detected) - using package manager auto-detection
56+
if [ -f "pnpm-lock.yaml" ]; then
57+
pnpm prisma generate
58+
pnpm prisma migrate deploy
59+
elif [ -f "yarn.lock" ]; then
60+
yarn prisma generate
61+
yarn prisma migrate deploy
62+
else
63+
npx prisma generate
64+
npx prisma migrate deploy
65+
fi
66+
67+
PREDEPLOY_EOF
68+
else
69+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
70+
# Prisma (generate client + run migrations)
71+
# if [ -f "pnpm-lock.yaml" ]; then
72+
# pnpm prisma generate && pnpm prisma migrate deploy
73+
# elif [ -f "yarn.lock" ]; then
74+
# yarn prisma generate && yarn prisma migrate deploy
75+
# else
76+
# npx prisma generate && npx prisma migrate deploy
77+
# fi
78+
79+
PREDEPLOY_EOF
80+
fi
81+
82+
if [ "$orm_name" = "Drizzle" ]; then
83+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
84+
# Drizzle (detected)
85+
npx drizzle-kit migrate
86+
87+
PREDEPLOY_EOF
88+
else
89+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
90+
# Drizzle (run migrations)
91+
# npx drizzle-kit migrate
92+
93+
PREDEPLOY_EOF
94+
fi
95+
96+
if [ "$orm_name" = "TypeORM" ]; then
97+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
98+
# TypeORM (detected)
99+
npx typeorm migration:run
100+
101+
PREDEPLOY_EOF
102+
else
103+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
104+
# TypeORM (run migrations)
105+
# npx typeorm migration:run
106+
107+
PREDEPLOY_EOF
108+
fi
109+
110+
if [ "$orm_name" = "Sequelize" ]; then
111+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
112+
# Sequelize (detected)
113+
npx sequelize-cli db:migrate
114+
115+
PREDEPLOY_EOF
116+
else
117+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
118+
# Sequelize (run migrations)
119+
# npx sequelize-cli db:migrate
120+
121+
PREDEPLOY_EOF
122+
fi
123+
124+
if [ "$orm_name" = "Knex" ]; then
125+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
126+
# Knex (detected)
127+
npx knex migrate:latest
128+
129+
PREDEPLOY_EOF
130+
else
131+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
132+
# Knex (run migrations)
133+
# npx knex migrate:latest
134+
135+
PREDEPLOY_EOF
136+
fi
137+
138+
if [ "$orm_name" = "AdonisJS Lucid" ]; then
139+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
140+
# AdonisJS Lucid (detected)
141+
node ace migration:run --force
142+
143+
PREDEPLOY_EOF
144+
else
145+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
146+
# AdonisJS Lucid (run migrations)
147+
# node ace migration:run --force
148+
149+
PREDEPLOY_EOF
150+
fi
151+
152+
# Close pre-deploy.sh
153+
cat >> .shipnode/pre-deploy.sh << 'PREDEPLOY_EOF'
154+
# ─────────────────────────────────────────────────────────────────
155+
156+
echo "Pre-deploy hook completed successfully"
157+
exit 0
158+
PREDEPLOY_EOF
159+
160+
# Generate post-deploy.sh
161+
cat > .shipnode/post-deploy.sh << 'POSTDEPLOY_EOF'
162+
#!/bin/bash
163+
# ShipNode Post-Deploy Hook
164+
# Runs AFTER deployment completes. Failure won't rollback.
165+
#
166+
# Available environment variables:
167+
# RELEASE_PATH - Path to the current release (symlinked as 'current')
168+
# REMOTE_PATH - Base deployment directory
169+
# PM2_APP_NAME - PM2 process name (backend only)
170+
# BACKEND_PORT - Application port (backend only)
171+
# SHARED_ENV_PATH - Path to the server's shared .env file
172+
173+
set -e # Exit on error (but failure won't rollback deployment)
174+
175+
# Source the server's shared .env to use same variables as the app
176+
if [ -f "$SHARED_ENV_PATH" ]; then
177+
set -a
178+
source "$SHARED_ENV_PATH"
179+
set +a
180+
fi
181+
182+
cd "$RELEASE_PATH"
183+
184+
echo "Running post-deploy hook for release: $RELEASE_PATH"
185+
186+
# ── Examples ─────────────────────────────────────────────────────
187+
188+
# Seed database: npx prisma db seed
189+
# Clear cache: npm run cache:clear
190+
# Notify Slack: curl -X POST https://hooks.slack.com/... -d '{"text":"Deployed!"}'
191+
# Cleanup old logs: find /var/log/myapp -name "*.log" -mtime +7 -delete
192+
# Warm cache: curl -sf http://localhost:${BACKEND_PORT}/api/warmup
193+
194+
# ─────────────────────────────────────────────────────────────────
195+
196+
echo "Post-deploy hook completed successfully"
197+
exit 0
198+
POSTDEPLOY_EOF
199+
200+
# Make hooks executable
201+
chmod +x .shipnode/pre-deploy.sh .shipnode/post-deploy.sh
202+
203+
if [ "$orm_name" != "none" ]; then
204+
success "Generated .shipnode/ hooks with $orm_name commands"
205+
else
206+
success "Generated .shipnode/ hooks (no ORM detected)"
207+
fi
208+
}
209+
3210
# Legacy non-interactive init (backward compatibility)
4211
cmd_init_legacy() {
5212
if [ -f "shipnode.conf" ]; then
@@ -33,6 +240,9 @@ BACKEND_PORT=3000
33240
# Frontend-specific (optional)
34241
DOMAIN=myapp.com
35242
243+
# Environment file (optional)
244+
# ENV_FILE=.env
245+
36246
# Zero-downtime deployment (optional)
37247
ZERO_DOWNTIME=true
38248
KEEP_RELEASES=5
@@ -369,6 +579,13 @@ DOMAIN=$domain
369579
EOF
370580
fi
371581

582+
# Environment file (optional)
583+
cat >> shipnode.conf <<EOF
584+
585+
# Environment file
586+
# ENV_FILE=.env
587+
EOF
588+
372589
# Zero-downtime settings
373590
cat >> shipnode.conf <<EOF
374591
@@ -391,6 +608,10 @@ EOF
391608

392609
success "Created shipnode.conf"
393610

611+
# Generate .shipnode/ hooks
612+
echo ""
613+
generate_shipnode_hooks
614+
394615
# Users.yml wizard
395616
echo ""
396617
if prompt_yes_no "Add deployment users now?"; then
@@ -818,6 +1039,13 @@ DOMAIN=$domain
8181039
EOF
8191040
fi
8201041

1042+
# Environment file (optional)
1043+
cat >> shipnode.conf <<EOF
1044+
1045+
# Environment file
1046+
# ENV_FILE=.env
1047+
EOF
1048+
8211049
# Zero-downtime settings
8221050
cat >> shipnode.conf <<EOF
8231051
@@ -839,15 +1067,19 @@ EOF
8391067
fi
8401068

8411069
success "Created shipnode.conf"
842-
843-
# 9. Users.yml wizard
1070+
1071+
# 9. Generate .shipnode/ hooks
1072+
echo ""
1073+
generate_shipnode_hooks
1074+
1075+
# 10. Users.yml wizard
8441076
echo ""
8451077
if prompt_yes_no "Add deployment users now?"; then
8461078
init_users_yaml
8471079
else
8481080
info "You can add users later with: shipnode user sync"
8491081
fi
850-
1082+
8511083
echo ""
8521084
success "Initialization complete!"
8531085
info "Next steps:"

0 commit comments

Comments
 (0)