|
1
|
#!/bin/bash |
|
2
|
# Copy frontend files from backup to artifact and handle Tailwind/theme setup. |
|
3
|
# Usage: bash fullstack_copy_frontend.sh <app-name> [--client-dir <dir>] [--tw-version v3|v4] [--has-theme] |
|
4
|
# |
|
5
|
# Run AFTER createArtifact has created the artifact directory. |
|
6
|
# If flags are omitted, auto-detects from .migration-backup/. |
|
7
|
|
|
8
|
set -euo pipefail |
|
9
|
|
|
10
|
BACKUP=".migration-backup" |
|
11
|
|
|
12
|
if [ $# -lt 1 ]; then |
|
13
|
echo "Usage: bash fullstack_copy_frontend.sh <app-name> [--client-dir <dir>] [--tw-version v3|v4] [--has-theme]" |
|
14
|
exit 1 |
|
15
|
fi |
|
16
|
|
|
17
|
APP_NAME="$1"; shift |
|
18
|
CLIENT_DIR="" |
|
19
|
TW_VERSION="" |
|
20
|
HAS_THEME="false" |
|
21
|
|
|
22
|
while [[ $# -gt 0 ]]; do |
|
23
|
case "$1" in |
|
24
|
--client-dir) CLIENT_DIR="$2"; shift 2 ;; |
|
25
|
--tw-version) TW_VERSION="$2"; shift 2 ;; |
|
26
|
--has-theme) HAS_THEME="true"; shift ;; |
|
27
|
*) echo "Unknown arg: $1"; exit 1 ;; |
|
28
|
esac |
|
29
|
done |
|
30
|
|
|
31
|
# --- Auto-discover client dir --- |
|
32
|
if [ -z "$CLIENT_DIR" ]; then |
|
33
|
if [ -d "$BACKUP/client/src" ]; then |
|
34
|
CLIENT_DIR="client" |
|
35
|
elif [ -d "$BACKUP/src/components" ] || [ -d "$BACKUP/src/pages" ] || [ -f "$BACKUP/src/App.tsx" ] || [ -f "$BACKUP/src/main.tsx" ]; then |
|
36
|
CLIENT_DIR="src" |
|
37
|
else |
|
38
|
echo "ERROR: Could not find client directory. Pass --client-dir explicitly." |
|
39
|
exit 1 |
|
40
|
fi |
|
41
|
fi |
|
42
|
|
|
43
|
# --- Auto-detect Tailwind version --- |
|
44
|
if [ -z "$TW_VERSION" ]; then |
|
45
|
TW_VERSION="v4" |
|
46
|
CSS_FILE="$BACKUP/$CLIENT_DIR/src/index.css" |
|
47
|
[ "$CLIENT_DIR" = "src" ] && CSS_FILE="$BACKUP/src/index.css" |
|
48
|
if [ -f "$CSS_FILE" ] && grep -q '@tailwind base' "$CSS_FILE" 2>/dev/null; then |
|
49
|
TW_VERSION="v3" |
|
50
|
fi |
|
51
|
fi |
|
52
|
|
|
53
|
# --- Auto-detect theme.json --- |
|
54
|
if [ "$HAS_THEME" = "false" ] && [ -f "$BACKUP/theme.json" ]; then |
|
55
|
HAS_THEME="true" |
|
56
|
fi |
|
57
|
|
|
58
|
ARTIFACT_DIR="artifacts/$APP_NAME" |
|
59
|
|
|
60
|
if [ ! -d "$ARTIFACT_DIR" ]; then |
|
61
|
echo "ERROR: $ARTIFACT_DIR does not exist. Run createArtifact first." |
|
62
|
exit 1 |
|
63
|
fi |
|
64
|
|
|
65
|
echo "==== FRONTEND COPY ====" |
|
66
|
echo "App: $APP_NAME | Client: $CLIENT_DIR | Tailwind: $TW_VERSION | Theme: $HAS_THEME" |
|
67
|
|
|
68
|
# --- Copy source files --- |
|
69
|
SRC_DIR="$BACKUP/$CLIENT_DIR/src" |
|
70
|
if [ "$CLIENT_DIR" = "src" ]; then |
|
71
|
SRC_DIR="$BACKUP/src" |
|
72
|
fi |
|
73
|
|
|
74
|
if [ -d "$SRC_DIR" ]; then |
|
75
|
cp -r "$SRC_DIR"/* "$ARTIFACT_DIR/src/" |
|
76
|
echo "COPIED: $CLIENT_DIR/src/* → $ARTIFACT_DIR/src/" |
|
77
|
else |
|
78
|
echo "WARNING: $SRC_DIR not found — agent must copy source files manually" |
|
79
|
fi |
|
80
|
|
|
81
|
# --- Copy public assets --- |
|
82
|
PUBLIC_DIR="$BACKUP/$CLIENT_DIR/public" |
|
83
|
if [ "$CLIENT_DIR" = "src" ]; then |
|
84
|
PUBLIC_DIR="$BACKUP/public" |
|
85
|
fi |
|
86
|
|
|
87
|
if [ -d "$PUBLIC_DIR" ]; then |
|
88
|
cp -r "$PUBLIC_DIR"/* "$ARTIFACT_DIR/public/" 2>/dev/null || true |
|
89
|
echo "COPIED: public/* → $ARTIFACT_DIR/public/" |
|
90
|
fi |
|
91
|
|
|
92
|
# --- Copy index.html --- |
|
93
|
INDEX_HTML="$BACKUP/$CLIENT_DIR/index.html" |
|
94
|
if [ "$CLIENT_DIR" = "src" ]; then |
|
95
|
INDEX_HTML="$BACKUP/index.html" |
|
96
|
fi |
|
97
|
|
|
98
|
if [ -f "$INDEX_HTML" ]; then |
|
99
|
cp "$INDEX_HTML" "$ARTIFACT_DIR/index.html" |
|
100
|
echo "COPIED: index.html → $ARTIFACT_DIR/index.html" |
|
101
|
|
|
102
|
# Warn about meta image paths that reference source assets instead of public/ |
|
103
|
for meta_attr in "og:image" "twitter:image"; do |
|
104
|
META_PATH=$(grep -oE "property=\"$meta_attr\"[^>]*content=\"[^\"]+\"" "$ARTIFACT_DIR/index.html" 2>/dev/null | grep -oE 'content="[^"]+"' | sed 's/content="//;s/"$//' || true) |
|
105
|
if [ -n "$META_PATH" ] && echo "$META_PATH" | grep -qv "^http"; then |
|
106
|
# Check if the referenced file exists in public/ |
|
107
|
if [ ! -f "$ARTIFACT_DIR/public${META_PATH}" ] && [ ! -f "$ARTIFACT_DIR/public/$META_PATH" ]; then |
|
108
|
echo "WARNING: $meta_attr references $META_PATH which is not in public/ — social previews may break" |
|
109
|
fi |
|
110
|
fi |
|
111
|
done |
|
112
|
else |
|
113
|
echo "WARNING: index.html not found at $INDEX_HTML — check if fonts/meta tags need manual copy" |
|
114
|
fi |
|
115
|
|
|
116
|
# --- Copy local Vite plugin files --- |
|
117
|
# If the old vite.config imports local files (./vite-plugin-foo), copy them |
|
118
|
VITE_CFG="" |
|
119
|
for candidate in "$BACKUP/vite.config.ts" "$BACKUP/client/vite.config.ts" "$BACKUP/vite.config.js" "$BACKUP/client/vite.config.js"; do |
|
120
|
[ -f "$candidate" ] && VITE_CFG="$candidate" && break |
|
121
|
done |
|
122
|
if [ -n "$VITE_CFG" ]; then |
|
123
|
LOCAL_IMPORTS=$(grep -oE "from ['\"]\\./[^'\"]+['\"]" "$VITE_CFG" 2>/dev/null | sed "s/from [\"']//;s/[\"']//" || true) |
|
124
|
for imp in $LOCAL_IMPORTS; do |
|
125
|
# Resolve relative to the vite config's directory |
|
126
|
VITE_DIR=$(dirname "$VITE_CFG") |
|
127
|
SRC_FILE="$VITE_DIR/$imp" |
|
128
|
# Try with .ts extension if no extension |
|
129
|
[ ! -f "$SRC_FILE" ] && SRC_FILE="${SRC_FILE}.ts" |
|
130
|
[ ! -f "$SRC_FILE" ] && SRC_FILE="${VITE_DIR}/${imp}.js" |
|
131
|
if [ -f "$SRC_FILE" ]; then |
|
132
|
cp "$SRC_FILE" "$ARTIFACT_DIR/" |
|
133
|
echo "COPIED: local Vite plugin $(basename "$SRC_FILE") → $ARTIFACT_DIR/" |
|
134
|
fi |
|
135
|
done |
|
136
|
fi |
|
137
|
|
|
138
|
# --- Tailwind v3 handling --- |
|
139
|
VITE_EDITS_NEEDED="" |
|
140
|
if [ "$TW_VERSION" = "v3" ]; then |
|
141
|
echo "" |
|
142
|
echo "--- Tailwind v3 setup ---" |
|
143
|
|
|
144
|
# Copy tailwind config |
|
145
|
TW_CONFIG_COPIED="false" |
|
146
|
for candidate in \ |
|
147
|
"$BACKUP/tailwind.config.ts" \ |
|
148
|
"$BACKUP/tailwind.config.js" \ |
|
149
|
"$BACKUP/tailwind.config.cjs" \ |
|
150
|
"$BACKUP/$CLIENT_DIR/tailwind.config.ts" \ |
|
151
|
"$BACKUP/$CLIENT_DIR/tailwind.config.js"; do |
|
152
|
if [ -f "$candidate" ]; then |
|
153
|
cp "$candidate" "$ARTIFACT_DIR/" |
|
154
|
echo "COPIED: $(basename "$candidate") → $ARTIFACT_DIR/" |
|
155
|
TW_CONFIG_COPIED="true" |
|
156
|
break |
|
157
|
fi |
|
158
|
done |
|
159
|
if [ "$TW_CONFIG_COPIED" = "false" ]; then |
|
160
|
echo "WARNING: No tailwind config found — agent may need to create one" |
|
161
|
else |
|
162
|
# Fix content paths — old configs reference ./client/src/**, ./app/**, etc. |
|
163
|
# After migration, source lives at ./src/** relative to artifact root. |
|
164
|
for tw_file in "$ARTIFACT_DIR"/tailwind.config.*; do |
|
165
|
[ -f "$tw_file" ] || continue |
|
166
|
sed -i 's|"\./client/src/|"./src/|g' "$tw_file" 2>/dev/null || true |
|
167
|
sed -i "s|'./client/src/|'./src/|g" "$tw_file" 2>/dev/null || true |
|
168
|
sed -i 's|"\./client/|"./|g' "$tw_file" 2>/dev/null || true |
|
169
|
sed -i "s|'./client/|'./|g" "$tw_file" 2>/dev/null || true |
|
170
|
sed -i 's|"\./app/|"./src/|g' "$tw_file" 2>/dev/null || true |
|
171
|
sed -i "s|'./app/|'./src/|g" "$tw_file" 2>/dev/null || true |
|
172
|
done |
|
173
|
echo "FIXED: tailwind content paths updated for artifact structure" |
|
174
|
fi |
|
175
|
|
|
176
|
# Install v3 deps, remove v4 plugin |
|
177
|
pnpm --filter "@workspace/$APP_NAME" add -D tailwindcss@3 postcss autoprefixer 2>&1 || true |
|
178
|
pnpm --filter "@workspace/$APP_NAME" remove @tailwindcss/vite 2>&1 || true |
|
179
|
echo "INSTALLED: tailwindcss@3 postcss autoprefixer" |
|
180
|
echo "REMOVED: @tailwindcss/vite" |
|
181
|
|
|
182
|
VITE_EDITS_NEEDED="$VITE_EDITS_NEEDED |
|
183
|
- Remove @tailwindcss/vite import and plugin entry |
|
184
|
- Add css.postcss.plugins with autoprefixer + tailwindcss" |
|
185
|
fi |
|
186
|
|
|
187
|
# --- Theme.json handling --- |
|
188
|
if [ "$HAS_THEME" = "true" ]; then |
|
189
|
echo "" |
|
190
|
echo "--- Theme setup ---" |
|
191
|
|
|
192
|
cp "$BACKUP/theme.json" "$ARTIFACT_DIR/theme.json" |
|
193
|
echo "COPIED: theme.json → $ARTIFACT_DIR/theme.json" |
|
194
|
|
|
195
|
pnpm --filter "@workspace/$APP_NAME" add -D @replit/vite-plugin-shadcn-theme-json 2>&1 || true |
|
196
|
echo "INSTALLED: @replit/vite-plugin-shadcn-theme-json" |
|
197
|
|
|
198
|
VITE_EDITS_NEEDED="$VITE_EDITS_NEEDED |
|
199
|
- Add: import themePlugin from \"@replit/vite-plugin-shadcn-theme-json\" |
|
200
|
- Add: themePlugin() to plugins array" |
|
201
|
fi |
|
202
|
|
|
203
|
# --- Copy postcss config only for Tailwind v3 --- |
|
204
|
# For v4, the scaffold uses @tailwindcss/vite — a v3 postcss config would conflict. |
|
205
|
if [ "$TW_VERSION" = "v3" ]; then |
|
206
|
for candidate in "$BACKUP/postcss.config.js" "$BACKUP/postcss.config.cjs" "$BACKUP/postcss.config.mjs"; do |
|
207
|
if [ -f "$candidate" ]; then |
|
208
|
cp "$candidate" "$ARTIFACT_DIR/" |
|
209
|
echo "COPIED: $(basename "$candidate") → $ARTIFACT_DIR/" |
|
210
|
break |
|
211
|
fi |
|
212
|
done |
|
213
|
else |
|
214
|
# Flag if old postcss config exists but we're skipping it |
|
215
|
for candidate in "$BACKUP/postcss.config.js" "$BACKUP/postcss.config.cjs" "$BACKUP/postcss.config.mjs"; do |
|
216
|
if [ -f "$candidate" ]; then |
|
217
|
echo "SKIPPED: $(basename "$candidate") — conflicts with @tailwindcss/vite (Tailwind v4)" |
|
218
|
break |
|
219
|
fi |
|
220
|
done |
|
221
|
fi |
|
222
|
|
|
223
|
# --- Fix Vite fs.strict for @assets alias --- |
|
224
|
# The scaffold sets fs.strict: true, which blocks access to attached_assets/ outside the artifact root. |
|
225
|
# If attached_assets exist, the agent needs to flip fs.strict to false. |
|
226
|
if [ -d "$BACKUP/attached_assets" ] || [ -d "attached_assets" ]; then |
|
227
|
VITE_EDITS_NEEDED="$VITE_EDITS_NEEDED |
|
228
|
- Set server.fs.strict to false (attached_assets lives outside artifact root)" |
|
229
|
fi |
|
230
|
|
|
231
|
# --- Auto-patch fonts from index.html into index.css --- |
|
232
|
FONT_PATCHED="false" |
|
233
|
if [ -f "$ARTIFACT_DIR/index.html" ] && [ -f "$ARTIFACT_DIR/src/index.css" ]; then |
|
234
|
# Extract font family names from Google Fonts URL |
|
235
|
FONT_FAMILIES=$(grep -oE 'family=[^&"]+' "$ARTIFACT_DIR/index.html" 2>/dev/null | sed 's/family=//g; s/+/ /g; s/:.*//g' | head -3 || true) |
|
236
|
if [ -n "$FONT_FAMILIES" ]; then |
|
237
|
PRIMARY_FONT=$(echo "$FONT_FAMILIES" | head -1) |
|
238
|
if [ -n "$PRIMARY_FONT" ]; then |
|
239
|
# Try to update --app-font-sans or --font-sans in index.css |
|
240
|
if grep -q "\-\-app-font-sans:" "$ARTIFACT_DIR/src/index.css" 2>/dev/null; then |
|
241
|
sed -i "s/--app-font-sans:.*$/--app-font-sans: '$PRIMARY_FONT', sans-serif;/" "$ARTIFACT_DIR/src/index.css" 2>/dev/null && FONT_PATCHED="true" |
|
242
|
elif grep -q "\-\-font-sans:" "$ARTIFACT_DIR/src/index.css" 2>/dev/null; then |
|
243
|
sed -i "s/--font-sans:.*$/--font-sans: '$PRIMARY_FONT', sans-serif;/" "$ARTIFACT_DIR/src/index.css" 2>/dev/null && FONT_PATCHED="true" |
|
244
|
fi |
|
245
|
if [ "$FONT_PATCHED" = "true" ]; then |
|
246
|
echo "PATCHED: index.css — updated font-sans to '$PRIMARY_FONT'" |
|
247
|
else |
|
248
|
echo "FONT DETECTED: '$PRIMARY_FONT' in index.html — agent must update CSS variable manually" |
|
249
|
fi |
|
250
|
fi |
|
251
|
fi |
|
252
|
fi |
|
253
|
|
|
254
|
# --- Detect wouter usage and BASE_URL requirement --- |
|
255
|
NEEDS_WOUTER_BASE="false" |
|
256
|
APP_TSX="$ARTIFACT_DIR/src/App.tsx" |
|
257
|
if [ -f "$APP_TSX" ]; then |
|
258
|
if grep -q "from ['\"]wouter['\"]" "$APP_TSX" 2>/dev/null; then |
|
259
|
if ! grep -q "BASE_URL\|base=" "$APP_TSX" 2>/dev/null; then |
|
260
|
NEEDS_WOUTER_BASE="true" |
|
261
|
# Auto-patch the import to include Router |
|
262
|
if ! grep -q "Router" "$APP_TSX" 2>/dev/null; then |
|
263
|
sed -i "s/from ['\"]wouter['\"]/from 'wouter'/" "$APP_TSX" 2>/dev/null || true |
|
264
|
# Add Router import if Switch or Route is imported |
|
265
|
sed -i "s/import { \(.*\) } from 'wouter'/import { \1, Router as WouterRouter } from 'wouter'/" "$APP_TSX" 2>/dev/null || true |
|
266
|
echo "PATCHED: App.tsx — added WouterRouter import" |
|
267
|
fi |
|
268
|
fi |
|
269
|
fi |
|
270
|
fi |
|
271
|
|
|
272
|
# --- Detect custom queryClient --- |
|
273
|
HAS_QC="false" |
|
274
|
for qc in "$ARTIFACT_DIR/src/lib/queryClient.ts" "$ARTIFACT_DIR/src/lib/queryClient.tsx" "$ARTIFACT_DIR/src/queryClient.ts"; do |
|
275
|
if [ -f "$qc" ]; then |
|
276
|
HAS_QC="true" |
|
277
|
break |
|
278
|
fi |
|
279
|
done |
|
280
|
|
|
281
|
# --- Scan for missing dependencies --- |
|
282
|
# Extract third-party imports from copied source, compare against package.json, |
|
283
|
# and install anything missing in one batch. |
|
284
|
ARTIFACT_PKG="$ARTIFACT_DIR/package.json" |
|
285
|
if [ -f "$ARTIFACT_PKG" ]; then |
|
286
|
# Node built-ins to skip |
|
287
|
BUILTINS="assert buffer child_process cluster console crypto dgram dns domain events fs http http2 https inspector module net os path perf_hooks process punycode querystring readline repl stream string_decoder sys timers tls trace_events tty url util v8 vm worker_threads zlib" |
|
288
|
|
|
289
|
# Extract package names from imports. For scoped packages (@scope/name), |
|
290
|
# keep @scope/name. For unscoped (react, wouter), keep just the name. |
|
291
|
# Skip: relative imports, @/ aliases, @shared/ workspace refs, @workspace/ |
|
292
|
MISSING_DEPS=$(find "$ARTIFACT_DIR/src" \( -name "*.ts" -o -name "*.tsx" \) -print0 2>/dev/null | \ |
|
293
|
xargs -0 grep -ohE "from ['\"][^'\"./@][^'\"]*['\"]|from ['\"]@[a-z][^'\"]+['\"]" 2>/dev/null | \ |
|
294
|
sed "s/from ['\"]//;s/['\"]$//" | \ |
|
295
|
sed -E 's|^(@[^/]+/[^/]+)/.*|\1|; s|^([^@][^/]*)/.*|\1|' | \ |
|
296
|
sort -u | \ |
|
297
|
while read -r pkg; do |
|
298
|
[ -z "$pkg" ] && continue |
|
299
|
# Skip workspace/alias packages |
|
300
|
case "$pkg" in @workspace/*|@shared/*|@db/*|@assets/*) continue ;; esac |
|
301
|
# Skip node built-ins |
|
302
|
echo " $BUILTINS " | grep -q " $pkg " && continue |
|
303
|
# Skip if already in package.json |
|
304
|
grep -q "\"$pkg\"" "$ARTIFACT_PKG" 2>/dev/null && continue |
|
305
|
echo "$pkg" |
|
306
|
done || true) |
|
307
|
|
|
308
|
if [ -n "$MISSING_DEPS" ]; then |
|
309
|
DEP_LIST=$(echo "$MISSING_DEPS" | tr '\n' ' ') |
|
310
|
echo "" |
|
311
|
echo "--- Installing missing dependencies ---" |
|
312
|
echo "DETECTED: $DEP_LIST" |
|
313
|
# Intentional word splitting for package list |
|
314
|
# shellcheck disable=SC2086 |
|
315
|
pnpm --filter "@workspace/$APP_NAME" add $DEP_LIST 2>&1 || true |
|
316
|
echo "INSTALLED: $DEP_LIST" |
|
317
|
fi |
|
318
|
fi |
|
319
|
|
|
320
|
echo "" |
|
321
|
echo "==== FRONTEND COPY COMPLETE ====" |
|
322
|
|
|
323
|
if [ -n "$VITE_EDITS_NEEDED" ]; then |
|
324
|
echo "" |
|
325
|
echo "AGENT TODO — edit $ARTIFACT_DIR/vite.config.ts:$VITE_EDITS_NEEDED" |
|
326
|
fi |
|
327
|
echo "" |
|
328
|
echo "AGENT TODO:" |
|
329
|
echo " 1. Fix frontend imports (@shared/ → generated hooks from @workspace/api-client-react)" |
|
330
|
if [ "$NEEDS_WOUTER_BASE" = "true" ]; then |
|
331
|
echo " 2. REQUIRED: Wrap wouter Router with base path — add <WouterRouter base={import.meta.env.BASE_URL.replace(/\\/$/, '')}> around your routes in App.tsx. Navigation will silently break without this." |
|
332
|
fi |
|
333
|
if [ "$HAS_QC" = "true" ]; then |
|
334
|
echo " 3. Custom queryClient.ts was copied — verify App.tsx imports from it instead of creating a bare new QueryClient()" |
|
335
|
fi |