showpane 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/bundle/meta/scaffold-manifest.json +73 -0
- package/bundle/scaffold/VERSION +1 -0
- package/bundle/scaffold/__dot__env.example +24 -0
- package/bundle/scaffold/__dot__gitignore +41 -0
- package/bundle/scaffold/docker/Caddyfile +3 -0
- package/bundle/scaffold/docker/Dockerfile +30 -0
- package/bundle/scaffold/docker-compose.yml +53 -0
- package/bundle/scaffold/next.config.ts +20 -0
- package/bundle/scaffold/package-lock.json +5843 -0
- package/bundle/scaffold/package.json +42 -0
- package/bundle/scaffold/postcss.config.js +6 -0
- package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +143 -0
- package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +6 -0
- package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +2 -0
- package/bundle/scaffold/prisma/migrations/migration_lock.toml +3 -0
- package/bundle/scaffold/prisma/schema.local.prisma +131 -0
- package/bundle/scaffold/prisma/schema.prisma +128 -0
- package/bundle/scaffold/prisma/seed.ts +49 -0
- package/bundle/scaffold/public/example-avatar.svg +4 -0
- package/bundle/scaffold/public/example-logo.svg +4 -0
- package/bundle/scaffold/public/robots.txt +2 -0
- package/bundle/scaffold/scripts/backup.sh +19 -0
- package/bundle/scaffold/scripts/e2e-verify.sh +487 -0
- package/bundle/scaffold/scripts/prisma-db-push.mjs +7 -0
- package/bundle/scaffold/scripts/prisma-generate.mjs +3 -0
- package/bundle/scaffold/scripts/prisma-schema.mjs +74 -0
- package/bundle/scaffold/scripts/restore.sh +31 -0
- package/bundle/scaffold/src/__tests__/client-portals.test.ts +80 -0
- package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +32 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/page.tsx +79 -0
- package/bundle/scaffold/src/app/(portal)/client/[slug]/s/[token]/route.ts +22 -0
- package/bundle/scaffold/src/app/(portal)/client/example/example-client.tsx +372 -0
- package/bundle/scaffold/src/app/(portal)/client/example/page.tsx +5 -0
- package/bundle/scaffold/src/app/(portal)/client/layout.tsx +7 -0
- package/bundle/scaffold/src/app/(portal)/client/page.tsx +18 -0
- package/bundle/scaffold/src/app/api/client-auth/route.ts +82 -0
- package/bundle/scaffold/src/app/api/client-auth/share/route.ts +30 -0
- package/bundle/scaffold/src/app/api/client-events/route.ts +87 -0
- package/bundle/scaffold/src/app/api/client-files/[...path]/route.ts +80 -0
- package/bundle/scaffold/src/app/api/client-files/client-upload/route.ts +118 -0
- package/bundle/scaffold/src/app/api/client-files/route.ts +37 -0
- package/bundle/scaffold/src/app/api/client-files/upload/route.ts +131 -0
- package/bundle/scaffold/src/app/api/health/route.ts +19 -0
- package/bundle/scaffold/src/app/globals.css +7 -0
- package/bundle/scaffold/src/app/layout.tsx +25 -0
- package/bundle/scaffold/src/app/page.tsx +171 -0
- package/bundle/scaffold/src/components/portal-login.tsx +169 -0
- package/bundle/scaffold/src/components/portal-shell.tsx +373 -0
- package/bundle/scaffold/src/lib/abuse-controls.ts +43 -0
- package/bundle/scaffold/src/lib/branding.ts +50 -0
- package/bundle/scaffold/src/lib/client-auth.ts +98 -0
- package/bundle/scaffold/src/lib/client-portals.ts +134 -0
- package/bundle/scaffold/src/lib/control-plane.ts +100 -0
- package/bundle/scaffold/src/lib/db.ts +7 -0
- package/bundle/scaffold/src/lib/files.ts +124 -0
- package/bundle/scaffold/src/lib/load-app-env.ts +42 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +69 -0
- package/bundle/scaffold/src/lib/prisma-client.ts +5 -0
- package/bundle/scaffold/src/lib/runtime-state.ts +69 -0
- package/bundle/scaffold/src/lib/storage.ts +204 -0
- package/bundle/scaffold/src/lib/token.ts +186 -0
- package/bundle/scaffold/src/lib/utils.ts +6 -0
- package/bundle/scaffold/src/middleware.ts +61 -0
- package/bundle/scaffold/tailwind.config.ts +15 -0
- package/bundle/scaffold/tests/__dot__gitkeep +0 -0
- package/bundle/scaffold/tsconfig.json +23 -0
- package/bundle/scaffold/vitest.config.ts +13 -0
- package/bundle/toolchain/VERSION +1 -0
- package/bundle/toolchain/bin/check-slug.ts +59 -0
- package/bundle/toolchain/bin/create-deploy-bundle.ts +93 -0
- package/bundle/toolchain/bin/create-portal.ts +71 -0
- package/bundle/toolchain/bin/delete-portal.ts +48 -0
- package/bundle/toolchain/bin/export-file-manifest.ts +84 -0
- package/bundle/toolchain/bin/export-runtime-state.ts +90 -0
- package/bundle/toolchain/bin/generate-share-link.ts +68 -0
- package/bundle/toolchain/bin/list-portals.ts +53 -0
- package/bundle/toolchain/bin/materialize-file.ts +35 -0
- package/bundle/toolchain/bin/query-analytics.ts +88 -0
- package/bundle/toolchain/bin/rotate-credentials.ts +57 -0
- package/bundle/toolchain/bin/showpane-config +63 -0
- package/bundle/toolchain/bin/tsconfig.json +13 -0
- package/bundle/toolchain/skills/VERSION +1 -0
- package/bundle/toolchain/skills/portal-analytics/SKILL.md +263 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +341 -0
- package/bundle/toolchain/skills/portal-credentials/SKILL.md +274 -0
- package/bundle/toolchain/skills/portal-delete/SKILL.md +265 -0
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +721 -0
- package/bundle/toolchain/skills/portal-dev/SKILL.md +301 -0
- package/bundle/toolchain/skills/portal-list/SKILL.md +253 -0
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +277 -0
- package/bundle/toolchain/skills/portal-preview/SKILL.md +257 -0
- package/bundle/toolchain/skills/portal-setup/SKILL.md +309 -0
- package/bundle/toolchain/skills/portal-share/SKILL.md +234 -0
- package/bundle/toolchain/skills/portal-status/SKILL.md +268 -0
- package/bundle/toolchain/skills/portal-update/SKILL.md +348 -0
- package/bundle/toolchain/skills/portal-upgrade/SKILL.md +235 -0
- package/bundle/toolchain/skills/portal-verify/SKILL.md +265 -0
- package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +49 -0
- package/bundle/toolchain/skills/shared/platform-constraints.md +33 -0
- package/bundle/toolchain/skills/shared/preamble.md +137 -0
- package/bundle/toolchain/templates/consulting/consulting-client.tsx +205 -0
- package/bundle/toolchain/templates/onboarding/onboarding-client.tsx +237 -0
- package/bundle/toolchain/templates/sales-followup/sales-followup-client.tsx +283 -0
- package/dist/index.js +875 -159
- package/package.json +4 -2
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: portal-deploy
|
|
3
|
+
description: |
|
|
4
|
+
Deploy the Showpane portal app. Runs pre-flight checks, applies migrations, and deploys via Docker or Vercel.
|
|
5
|
+
Trigger phrases: "portal deploy", "deploy portals", "push to production", "ship the portals". (showpane)
|
|
6
|
+
allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, WebSearch]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Before proposing or executing cloud deploy behavior, read `skills/shared/platform-constraints.md`
|
|
10
|
+
and apply any relevant current platform limits.
|
|
11
|
+
|
|
12
|
+
## Preamble (run first)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Read config
|
|
16
|
+
CONFIG="$HOME/.showpane/config.json"
|
|
17
|
+
if [ ! -f "$CONFIG" ]; then
|
|
18
|
+
echo "Showpane not configured. Run /portal setup first."
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
|
|
22
|
+
DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
|
|
23
|
+
ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
|
|
24
|
+
CLOUD_API_TOKEN=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('api_token', d.get('accessToken','')))" 2>/dev/null)
|
|
25
|
+
CLOUD_ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('org_slug', d.get('orgSlug','')))" 2>/dev/null)
|
|
26
|
+
CLOUD_PORTAL_URL=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('portal_url', d.get('portalUrl','')))" 2>/dev/null)
|
|
27
|
+
CLOUD_API_BASE="${SHOWPANE_CLOUD_URL:-https://app.showpane.com}"
|
|
28
|
+
APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
|
|
29
|
+
if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
|
|
30
|
+
DATABASE_URL="${DATABASE_URL:-}"
|
|
31
|
+
if [ ! -d "$APP_PATH/node_modules/.prisma" ]; then
|
|
32
|
+
echo "App dependencies not installed. Run: cd $APP_PATH && npm install"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
SKILL_DIR="${SHOWPANE_TOOLCHAIN_DIR:-$HOME/.showpane/current}"
|
|
36
|
+
SKILL_VERSION=$(cat "$SKILL_DIR/VERSION" 2>/dev/null || echo "unknown")
|
|
37
|
+
echo "SHOWPANE: v$SKILL_VERSION | MODE: $DEPLOY_MODE | APP: $APP_PATH"
|
|
38
|
+
LEARN_FILE="$HOME/.showpane/learnings.jsonl"
|
|
39
|
+
[ -f "$LEARN_FILE" ] && echo "LEARNINGS: $(wc -l < "$LEARN_FILE" | tr -d ' ') loaded" || echo "LEARNINGS: 0"
|
|
40
|
+
|
|
41
|
+
# Predictive next-skill suggestion
|
|
42
|
+
if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
|
|
43
|
+
_RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
|
|
44
|
+
[ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Search relevant learnings
|
|
48
|
+
LEARN_FILE="$HOME/.showpane/learnings.jsonl"
|
|
49
|
+
if [ -f "$LEARN_FILE" ]; then
|
|
50
|
+
_LEARN_COUNT=$(wc -l < "$LEARN_FILE" 2>/dev/null | tr -d ' ')
|
|
51
|
+
echo "LEARNINGS: $_LEARN_COUNT entries"
|
|
52
|
+
if [ "$_LEARN_COUNT" -gt 0 ] 2>/dev/null; then
|
|
53
|
+
echo "RECENT_LEARNINGS:"
|
|
54
|
+
tail -5 "$LEARN_FILE" 2>/dev/null
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Track skill execution
|
|
59
|
+
SHOWPANE_TIMELINE="$HOME/.showpane/timeline.jsonl"
|
|
60
|
+
mkdir -p "$(dirname "$SHOWPANE_TIMELINE")"
|
|
61
|
+
echo '{"skill":"portal-deploy","event":"started","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$SHOWPANE_TIMELINE" 2>/dev/null
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If RECENT_SKILLS is shown, suggest the likely next skill:
|
|
65
|
+
- After portal-create → suggest /portal-preview
|
|
66
|
+
- After portal-preview → suggest /portal-deploy or /portal-share
|
|
67
|
+
- After portal-deploy → suggest /portal-status or /portal-verify
|
|
68
|
+
- After portal-setup → suggest /portal-create
|
|
69
|
+
- After portal-credentials → suggest /portal-share
|
|
70
|
+
- After portal-update → suggest /portal-deploy
|
|
71
|
+
|
|
72
|
+
If RECENT_LEARNINGS is shown, review them before proceeding. Past learnings may contain
|
|
73
|
+
relevant warnings or tips for this operation. Apply them where relevant but don't
|
|
74
|
+
mention them unless they directly affect the current task.
|
|
75
|
+
|
|
76
|
+
## Steps
|
|
77
|
+
|
|
78
|
+
### Step 0: Choose deployment target
|
|
79
|
+
|
|
80
|
+
Check the `DEPLOY_MODE` variable from the preamble. Route accordingly:
|
|
81
|
+
|
|
82
|
+
- If `DEPLOY_MODE` is `"cloud"` — skip to **Cloud Deploy Flow** (after Step 6)
|
|
83
|
+
- If `DEPLOY_MODE` is `"docker"` or `"vercel"` or unset — continue with the existing self-hosted deploy flow (Steps 1-6)
|
|
84
|
+
|
|
85
|
+
If no deploy mode is configured, present the options:
|
|
86
|
+
|
|
87
|
+
1. **Self-host with Docker** (free) — Deploy with Docker Compose on your own server
|
|
88
|
+
2. **Showpane Cloud** ($29/month, 7-day free trial) — Managed hosting at {org}.showpane.com
|
|
89
|
+
|
|
90
|
+
If the user chooses Cloud but has not run `showpane login` yet (no `CLOUD_API_TOKEN`), tell them:
|
|
91
|
+
|
|
92
|
+
> "Run `showpane login` first to authenticate with Showpane Cloud, then re-run this deploy."
|
|
93
|
+
|
|
94
|
+
### Step 1: Pre-flight checks
|
|
95
|
+
|
|
96
|
+
Run all checks before deploying. Any failure here should block the deploy.
|
|
97
|
+
|
|
98
|
+
#### 1a. TypeScript type check
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
cd "$APP_PATH" && npx tsc --noEmit 2>&1
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If type errors are found, display them clearly and stop. Do not deploy with type errors. Common type errors during portal development:
|
|
105
|
+
|
|
106
|
+
- Missing imports (forgot to import an icon from lucide-react)
|
|
107
|
+
- PortalShell prop mismatch (wrong type for contact, missing required prop)
|
|
108
|
+
- Unused variables in tab content functions
|
|
109
|
+
|
|
110
|
+
For simple issues (missing import, typo in a prop name), offer to fix them before retrying the deploy. For complex type errors in shared code, suggest the user investigate and fix manually.
|
|
111
|
+
|
|
112
|
+
The type check typically takes 10-30 seconds depending on project size. If it takes longer than 60 seconds, it may indicate a problem with the TypeScript configuration.
|
|
113
|
+
|
|
114
|
+
#### 1b. Verify all portals have credentials
|
|
115
|
+
|
|
116
|
+
Run the list-portals script to check portal status:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" --org-id <org_id>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The output is a JSON array of portals. Check each portal for a `username` field. If any active portal lacks credentials, warn the user:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
WARNING: The following portals have no login credentials:
|
|
126
|
+
- acme-health
|
|
127
|
+
- new-client
|
|
128
|
+
|
|
129
|
+
Clients won't be able to log in to these portals.
|
|
130
|
+
Run /portal credentials <slug> to create credentials, or continue anyway?
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This is a warning, not a blocker — the user can choose to continue. Some portals may be works in progress that don't need credentials yet.
|
|
134
|
+
|
|
135
|
+
#### 1c. Verify deployment config exists
|
|
136
|
+
|
|
137
|
+
**For Docker mode:**
|
|
138
|
+
```bash
|
|
139
|
+
ls "$APP_PATH/docker-compose.yml" 2>/dev/null || ls "$APP_PATH/compose.yml" 2>/dev/null
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**For Vercel mode:**
|
|
143
|
+
```bash
|
|
144
|
+
ls "$APP_PATH/.vercel/project.json" 2>/dev/null || ls "$APP_PATH/vercel.json" 2>/dev/null
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If the config doesn't exist, inform the user and provide setup guidance:
|
|
148
|
+
- Docker: "No docker-compose.yml found. Create one or switch deploy mode to vercel."
|
|
149
|
+
- Vercel: "No Vercel config found. Run `npx vercel link` to connect your project."
|
|
150
|
+
|
|
151
|
+
#### 1d. Check for uncommitted changes (Vercel mode only)
|
|
152
|
+
|
|
153
|
+
For Vercel deploys, check if there are uncommitted changes:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
cd "$APP_PATH" && git status --porcelain
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If there are changes, show the user what will be committed as part of the deploy:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
cd "$APP_PATH" && git diff --stat
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
List the changed files and ask the user to confirm. Pay attention to:
|
|
166
|
+
- `.env` files — these should NEVER be committed. If `.env` appears in the changes, warn the user and exclude it.
|
|
167
|
+
- Large binary files — these will slow down the deploy. Suggest adding them to `.gitignore`.
|
|
168
|
+
- Files outside the portal directories — these may be unintended changes.
|
|
169
|
+
|
|
170
|
+
If there are no changes to commit and the current branch is up to date with the remote, inform the user that there is nothing new to deploy.
|
|
171
|
+
|
|
172
|
+
### Step 2: Apply database migrations
|
|
173
|
+
|
|
174
|
+
Before deploying, apply any pending migrations:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
cd "$APP_PATH" && npx prisma migrate deploy
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
This runs in production mode — it only applies pending migrations, never creates new ones. If this fails, stop the deploy and show the error. Common issues:
|
|
181
|
+
- DATABASE_URL not set or incorrect
|
|
182
|
+
- Database server unreachable
|
|
183
|
+
- Migration conflicts (rare, requires manual resolution)
|
|
184
|
+
|
|
185
|
+
### Step 3: Deploy
|
|
186
|
+
|
|
187
|
+
#### Docker mode
|
|
188
|
+
|
|
189
|
+
Build and restart the containers:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
cd "$APP_PATH" && docker compose build && docker compose up -d
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
If `docker compose` is not available, try `docker-compose` (older syntax):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
cd "$APP_PATH" && docker-compose build && docker-compose up -d
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Wait for the containers to start. Check container status:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
cd "$APP_PATH" && docker compose ps
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
All containers should show status "Up" or "running".
|
|
208
|
+
|
|
209
|
+
#### Vercel mode
|
|
210
|
+
|
|
211
|
+
Stage, commit, and push:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
cd "$APP_PATH" && git add -A && git commit -m "Deploy portal updates" && git push
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
If there are no changes to commit, just push:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
cd "$APP_PATH" && git push
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The push triggers Vercel's automatic deployment pipeline. Note that the deploy is async — the push returns immediately, but the actual deployment takes 1-3 minutes.
|
|
224
|
+
|
|
225
|
+
For Vercel, inform the user:
|
|
226
|
+
|
|
227
|
+
> "Pushed to remote. Vercel will build and deploy automatically. Check your Vercel dashboard for deploy status."
|
|
228
|
+
|
|
229
|
+
### Step 4: Post-deploy verification
|
|
230
|
+
|
|
231
|
+
#### Docker mode
|
|
232
|
+
|
|
233
|
+
Wait a few seconds for the app to start, then hit the health endpoint:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Expected: HTTP 200. Then check the response body:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
curl -s http://localhost:8080/api/health
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Expected: `{"status":"ok"}` or similar health response.
|
|
246
|
+
|
|
247
|
+
If the health check fails:
|
|
248
|
+
1. Check container logs: `cd "$APP_PATH" && docker compose logs --tail=50`
|
|
249
|
+
2. Check if the port is correct (might be 3000, 8080, or custom)
|
|
250
|
+
3. Report the error and suggest debugging steps
|
|
251
|
+
|
|
252
|
+
#### Vercel mode
|
|
253
|
+
|
|
254
|
+
If a production URL is known (from config or Vercel project settings), check it:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
curl -s -o /dev/null -w "%{http_code}" https://<production_url>/api/health
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
If the production URL is not known, skip the health check and inform the user:
|
|
261
|
+
|
|
262
|
+
> "Deploy triggered. Verify at your Vercel production URL once the build completes."
|
|
263
|
+
|
|
264
|
+
### Step 5: Deployment summary
|
|
265
|
+
|
|
266
|
+
Print a clear summary of what happened:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
Deploy complete!
|
|
270
|
+
|
|
271
|
+
Mode: docker
|
|
272
|
+
Migrations: 2 applied (or: up to date)
|
|
273
|
+
Type check: passed
|
|
274
|
+
Portals: 5 active (3 with credentials)
|
|
275
|
+
Health: OK (200)
|
|
276
|
+
|
|
277
|
+
App URL: http://localhost:8080
|
|
278
|
+
Login: http://localhost:8080/client
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
For Vercel:
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
Deploy triggered!
|
|
285
|
+
|
|
286
|
+
Mode: vercel
|
|
287
|
+
Migrations: up to date
|
|
288
|
+
Type check: passed
|
|
289
|
+
Commit: abc1234 "Deploy portal updates"
|
|
290
|
+
Portals: 5 active (3 with credentials)
|
|
291
|
+
|
|
292
|
+
Status: Building (check Vercel dashboard)
|
|
293
|
+
URL: https://your-app.vercel.app
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Step 6: Record deployment
|
|
297
|
+
|
|
298
|
+
Log the deployment for operational memory:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
echo '{"skill":"portal-deploy","key":"deploy","insight":"Deployed via <mode>. Migrations: <count>. Portals: <count> active.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Cloud Deploy Flow (when DEPLOY_MODE is "cloud")
|
|
307
|
+
|
|
308
|
+
This section runs instead of Steps 1-6 when `DEPLOY_MODE` is `"cloud"`. The cloud flow builds the app locally, packages the `.vercel/output` artifact, uploads that artifact to Showpane Cloud, and lets the control plane publish the hosted app. The OSS app should never need to reason about Vercel project IDs or call the Vercel API directly.
|
|
309
|
+
|
|
310
|
+
### Cloud Step 1: Pre-flight checks
|
|
311
|
+
|
|
312
|
+
#### 1a. Verify cloud credentials
|
|
313
|
+
|
|
314
|
+
Check that the preamble successfully read cloud config values:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
if [ -z "$CLOUD_API_TOKEN" ]; then
|
|
318
|
+
echo "ERROR: No API token found. Run 'showpane login' to authenticate."
|
|
319
|
+
exit 1
|
|
320
|
+
fi
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
If the token is missing, stop and tell the user to run `showpane login`.
|
|
324
|
+
|
|
325
|
+
#### 1b. Verify Showpane Cloud is reachable
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
curl -s -o /dev/null -w "%{http_code}" "$CLOUD_API_BASE/api/health"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Expected: HTTP 200. If Showpane Cloud is unreachable, stop and show the error. Token validity is checked by the deploy API itself — OSS should not call Vercel or depend on provider internals.
|
|
332
|
+
|
|
333
|
+
#### 1c. TypeScript type check
|
|
334
|
+
|
|
335
|
+
Same as the self-hosted flow:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
cd "$APP_PATH" && npx tsc --noEmit 2>&1
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
If type errors are found, display them and stop. Offer to fix simple issues (missing imports, typos).
|
|
342
|
+
|
|
343
|
+
#### 1d. Verify portals have credentials
|
|
344
|
+
|
|
345
|
+
Same as self-hosted Step 1b — run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
|
|
346
|
+
|
|
347
|
+
### Cloud Step 2: Build the app
|
|
348
|
+
|
|
349
|
+
Run a Next.js production build:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
cd "$APP_PATH" && npx next build
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
After the build completes, verify the output directory was created:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
ls -la "$APP_PATH/.vercel/output/"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Expected: the `.vercel/output/` directory exists and contains `config.json`, `static/`, and optionally `functions/`. If the build fails or the output directory is missing, show the build errors and stop.
|
|
362
|
+
|
|
363
|
+
The build typically takes 30-90 seconds depending on the number of portals.
|
|
364
|
+
|
|
365
|
+
### Cloud Step 3: Package the deploy artifact
|
|
366
|
+
|
|
367
|
+
Package the prebuilt output plus the traced runtime files as a single zip artifact that can be handed to Showpane Cloud. This bundle must include:
|
|
368
|
+
- `.vercel/output/**`
|
|
369
|
+
- traced `.next/server/**` and `node_modules/**` files referenced by the build output
|
|
370
|
+
- a sanitized `.env` stub so the traced runtime can resolve its expected path without leaking local secrets
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
ARTIFACT_PATH="/tmp/showpane-deploy-${CLOUD_ORG_SLUG:-portal}.zip"
|
|
374
|
+
rm -f "$ARTIFACT_PATH"
|
|
375
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/create-deploy-bundle.ts" --output "$ARTIFACT_PATH"
|
|
376
|
+
test -f "$ARTIFACT_PATH" || { echo "ERROR: Artifact zip was not created"; exit 1; }
|
|
377
|
+
echo "Artifact ready: $ARTIFACT_PATH"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Cloud Step 4: Export runtime data
|
|
381
|
+
|
|
382
|
+
Export the current local portal-runtime state so Showpane Cloud can sync credentials and portal metadata before publishing:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
RUNTIME_DATA_PATH="/tmp/showpane-runtime-${CLOUD_ORG_SLUG:-portal}.json"
|
|
386
|
+
rm -f "$RUNTIME_DATA_PATH"
|
|
387
|
+
if [ -n "$ORG_SLUG" ]; then
|
|
388
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-runtime-state.ts" --org-slug "$ORG_SLUG" > "$RUNTIME_DATA_PATH"
|
|
389
|
+
else
|
|
390
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-runtime-state.ts" > "$RUNTIME_DATA_PATH"
|
|
391
|
+
fi
|
|
392
|
+
test -f "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
|
|
393
|
+
echo "Runtime payload ready: $RUNTIME_DATA_PATH"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Cloud Step 5: Export file manifest
|
|
397
|
+
|
|
398
|
+
Export uploaded document metadata and checksums so Showpane Cloud can determine which files need syncing:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
FILE_MANIFEST_PATH="/tmp/showpane-files-${CLOUD_ORG_SLUG:-portal}.json"
|
|
402
|
+
rm -f "$FILE_MANIFEST_PATH"
|
|
403
|
+
if [ -n "$ORG_SLUG" ]; then
|
|
404
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-file-manifest.ts" --org-slug "$ORG_SLUG" > "$FILE_MANIFEST_PATH"
|
|
405
|
+
else
|
|
406
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/export-file-manifest.ts" > "$FILE_MANIFEST_PATH"
|
|
407
|
+
fi
|
|
408
|
+
test -f "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
|
|
409
|
+
echo "File manifest ready: $FILE_MANIFEST_PATH"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Cloud Step 6: Sync uploaded files
|
|
413
|
+
|
|
414
|
+
Ask Showpane Cloud which files are missing or stale, then upload only those file bytes:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
SYNC_PLAN_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/files/plan" \
|
|
418
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
419
|
+
-H "Content-Type: application/json" \
|
|
420
|
+
--data-binary @"$FILE_MANIFEST_PATH")
|
|
421
|
+
|
|
422
|
+
MISSING_COUNT=$(echo "$SYNC_PLAN_RESPONSE" | python3 -c "
|
|
423
|
+
import sys, json
|
|
424
|
+
d = json.load(sys.stdin)
|
|
425
|
+
if 'error' in d:
|
|
426
|
+
print('ERROR: ' + d['error'])
|
|
427
|
+
sys.exit(1)
|
|
428
|
+
print(len(d.get('missing', [])))
|
|
429
|
+
")
|
|
430
|
+
|
|
431
|
+
echo \"Files to sync: $MISSING_COUNT\"
|
|
432
|
+
|
|
433
|
+
if [ \"$MISSING_COUNT\" -gt 0 ]; then
|
|
434
|
+
TMP_SYNC_DIR=$(mktemp -d /tmp/showpane-file-sync.XXXXXX)
|
|
435
|
+
echo \"$SYNC_PLAN_RESPONSE\" | python3 -c '
|
|
436
|
+
import json, sys
|
|
437
|
+
for item in json.load(sys.stdin).get(\"missing\", []):
|
|
438
|
+
print(\"\\t\".join([
|
|
439
|
+
item[\"storagePath\"],
|
|
440
|
+
item[\"portalSlug\"],
|
|
441
|
+
item[\"filename\"],
|
|
442
|
+
item[\"mimeType\"],
|
|
443
|
+
str(item[\"size\"]),
|
|
444
|
+
item[\"uploadedBy\"],
|
|
445
|
+
item[\"uploadedAt\"],
|
|
446
|
+
item[\"checksum\"],
|
|
447
|
+
]))
|
|
448
|
+
' | while IFS=$'\t' read -r STORAGE_PATH PORTAL_SLUG FILE_NAME MIME_TYPE FILE_SIZE UPLOADED_BY UPLOADED_AT CHECKSUM; do
|
|
449
|
+
TMP_FILE="$TMP_SYNC_DIR/$CHECKSUM"
|
|
450
|
+
cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/materialize-file.ts" --storage-path "$STORAGE_PATH" --output "$TMP_FILE"
|
|
451
|
+
curl -s -X POST "$CLOUD_API_BASE/api/files/upload" \
|
|
452
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
453
|
+
-F "file=@$TMP_FILE;type=$MIME_TYPE" \
|
|
454
|
+
-F "storagePath=$STORAGE_PATH" \
|
|
455
|
+
-F "portalSlug=$PORTAL_SLUG" \
|
|
456
|
+
-F "filename=$FILE_NAME" \
|
|
457
|
+
-F "mimeType=$MIME_TYPE" \
|
|
458
|
+
-F "size=$FILE_SIZE" \
|
|
459
|
+
-F "uploadedBy=$UPLOADED_BY" \
|
|
460
|
+
-F "uploadedAt=$UPLOADED_AT" \
|
|
461
|
+
-F "checksum=$CHECKSUM" \
|
|
462
|
+
>/dev/null || exit 1
|
|
463
|
+
done
|
|
464
|
+
fi
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Cloud Step 7: Upload the artifact to Showpane Cloud
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
PORTAL_COUNT=$(cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" \
|
|
471
|
+
| python3 -c "import sys,json; print(len(json.load(sys.stdin).get('portals', [])))")
|
|
472
|
+
|
|
473
|
+
DEPLOY_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments" \
|
|
474
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
475
|
+
-F "artifact=@$ARTIFACT_PATH" \
|
|
476
|
+
-F "source=claude-portal-deploy" \
|
|
477
|
+
-F "app_path=$APP_PATH" \
|
|
478
|
+
-F "portalCount=$PORTAL_COUNT" \
|
|
479
|
+
-F "runtimeData=@$RUNTIME_DATA_PATH;type=application/json")
|
|
480
|
+
|
|
481
|
+
echo "$DEPLOY_RESPONSE" | python3 -c "
|
|
482
|
+
import sys, json
|
|
483
|
+
d = json.load(sys.stdin)
|
|
484
|
+
if 'error' in d:
|
|
485
|
+
print('ERROR: ' + str(d['error']))
|
|
486
|
+
sys.exit(1)
|
|
487
|
+
print('Deployment accepted')
|
|
488
|
+
print('ID: ' + d.get('deploymentId', d.get('id', 'unknown')))
|
|
489
|
+
print('Status: ' + d.get('status', 'unknown'))
|
|
490
|
+
"
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Extract the deployment ID from the response. If the API returns an error, show it and stop.
|
|
494
|
+
|
|
495
|
+
### Cloud Step 8: Wait for cloud publish to finish
|
|
496
|
+
|
|
497
|
+
Poll Showpane Cloud until the deployment reaches `live` or `failed`:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
DEPLOY_ID=$(echo "$DEPLOY_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deploymentId', d.get('id','')))")
|
|
501
|
+
|
|
502
|
+
echo "Waiting for deployment to go live..."
|
|
503
|
+
for i in $(seq 1 60); do
|
|
504
|
+
sleep 5
|
|
505
|
+
STATUS=$(curl -s \
|
|
506
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
507
|
+
"$CLOUD_API_BASE/api/deployments/$DEPLOY_ID" \
|
|
508
|
+
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))")
|
|
509
|
+
|
|
510
|
+
echo " Status: $STATUS"
|
|
511
|
+
|
|
512
|
+
if [ "$STATUS" = "live" ]; then
|
|
513
|
+
echo "Deployment is live!"
|
|
514
|
+
break
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
if [ "$STATUS" = "failed" ] || [ "$STATUS" = "unhealthy" ]; then
|
|
518
|
+
echo "ERROR: Deployment failed with status: $STATUS"
|
|
519
|
+
curl -s \
|
|
520
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
521
|
+
"$CLOUD_API_BASE/api/deployments/$DEPLOY_ID"
|
|
522
|
+
exit 1
|
|
523
|
+
fi
|
|
524
|
+
done
|
|
525
|
+
|
|
526
|
+
if [ "$STATUS" != "live" ]; then
|
|
527
|
+
echo "WARNING: Deployment did not become ready within 5 minutes."
|
|
528
|
+
echo "Check Showpane Cloud for status."
|
|
529
|
+
fi
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
The publish typically takes 15-60 seconds. Poll every 5 seconds for up to 5 minutes.
|
|
533
|
+
|
|
534
|
+
### Cloud Step 9: Post-deploy verification
|
|
535
|
+
|
|
536
|
+
Once the deployment is live, verify the portal is accessible:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
|
|
540
|
+
|
|
541
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL")
|
|
542
|
+
echo "Health check: $PORTAL_URL -> HTTP $HTTP_CODE"
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Expected: HTTP 200. If not 200, warn the user but do not treat it as a fatal error — DNS propagation or edge caching may cause a brief delay.
|
|
546
|
+
|
|
547
|
+
Also check the API health endpoint:
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/health"
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Cloud Step 10: Deployment summary
|
|
554
|
+
|
|
555
|
+
Print a clear summary:
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
Cloud deploy complete!
|
|
559
|
+
|
|
560
|
+
Mode: cloud
|
|
561
|
+
Type check: passed
|
|
562
|
+
Build: .vercel/output/ (N files)
|
|
563
|
+
Portals: N active
|
|
564
|
+
Status: live
|
|
565
|
+
Health: OK (200)
|
|
566
|
+
|
|
567
|
+
Portal URL: https://orgslug.showpane.com
|
|
568
|
+
Deploy ID: dep_xxxxxxxxxxxx
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Cloud Step 11: Record deployment
|
|
572
|
+
|
|
573
|
+
Log the cloud deployment for operational memory:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
echo '{"skill":"portal-deploy","key":"deploy","insight":"Cloud deploy to '$CLOUD_ORG_SLUG'.showpane.com. Migrations: <count>. Portals: <count> active. Deploy ID: '$DEPLOY_ID'.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Cloud Step 12: Clean up
|
|
580
|
+
|
|
581
|
+
Remove the temporary artifact:
|
|
582
|
+
|
|
583
|
+
```bash
|
|
584
|
+
rm -f "$ARTIFACT_PATH"
|
|
585
|
+
rm -f "$RUNTIME_DATA_PATH"
|
|
586
|
+
rm -f "$FILE_MANIFEST_PATH"
|
|
587
|
+
[ -n "${TMP_SYNC_DIR:-}" ] && rm -rf "$TMP_SYNC_DIR"
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## Post-Deploy Verification
|
|
593
|
+
|
|
594
|
+
After deployment succeeds (either self-hosted Steps 1-6 or Cloud Steps 1-12), automatically run these verification steps. Do not ask the user — just do them.
|
|
595
|
+
|
|
596
|
+
### Step V1: DNS/URL Check
|
|
597
|
+
Fetch the portal URL and verify it returns 200:
|
|
598
|
+
```bash
|
|
599
|
+
PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
|
|
600
|
+
echo "Verifying $PORTAL_URL..."
|
|
601
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
|
|
602
|
+
echo "HTTP status: $HTTP_CODE"
|
|
603
|
+
```
|
|
604
|
+
If not 200, warn but don't fail — DNS propagation may take time for cloud deploys.
|
|
605
|
+
|
|
606
|
+
### Step V2: Portal Content Check
|
|
607
|
+
If portals exist in the database, verify at least one portal login page loads:
|
|
608
|
+
```bash
|
|
609
|
+
# Get the first active portal slug
|
|
610
|
+
FIRST_SLUG=$(cd "$APP_PATH" && npx tsx -e "
|
|
611
|
+
const { prisma } = require('./src/lib/db');
|
|
612
|
+
prisma.clientPortal.findFirst({ where: { isActive: true }, select: { slug: true } })
|
|
613
|
+
.then(p => console.log(p?.slug || ''))
|
|
614
|
+
.finally(() => prisma.\$disconnect());
|
|
615
|
+
" 2>/dev/null)
|
|
616
|
+
if [ -n "$FIRST_SLUG" ]; then
|
|
617
|
+
echo "Checking portal: $PORTAL_URL/client/$FIRST_SLUG"
|
|
618
|
+
curl -s -o /dev/null -w "Portal login page: %{http_code}\n" "$PORTAL_URL/client/$FIRST_SLUG" 2>/dev/null
|
|
619
|
+
fi
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Step V3: SSL Check (cloud only)
|
|
623
|
+
For cloud deploys, verify SSL is working:
|
|
624
|
+
```bash
|
|
625
|
+
if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_PORTAL_URL" ]; then
|
|
626
|
+
echo "Checking SSL..."
|
|
627
|
+
SSL_OK=$(curl -s -o /dev/null -w "%{http_code}" "https://${CLOUD_ORG_SLUG}.showpane.com" 2>/dev/null)
|
|
628
|
+
echo "SSL status: $SSL_OK"
|
|
629
|
+
fi
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Step V4: Summary
|
|
633
|
+
Print a deployment summary:
|
|
634
|
+
```
|
|
635
|
+
✓ Deployed successfully
|
|
636
|
+
URL: [portal URL]
|
|
637
|
+
Portals: [count]
|
|
638
|
+
SSL: [ok/pending]
|
|
639
|
+
|
|
640
|
+
Next: /portal-status for ongoing monitoring
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
---
|
|
644
|
+
|
|
645
|
+
## Error Recovery
|
|
646
|
+
|
|
647
|
+
Deployments can fail at multiple points. Here is how to recover from each:
|
|
648
|
+
|
|
649
|
+
### Docker build failure
|
|
650
|
+
If `docker compose build` fails:
|
|
651
|
+
- Check the Dockerfile for syntax errors
|
|
652
|
+
- Check that all required files are present (especially `.env` and `prisma/schema.prisma`)
|
|
653
|
+
- Check available disk space — Docker builds can require significant space
|
|
654
|
+
- Try `docker compose build --no-cache` if a cached layer is stale
|
|
655
|
+
|
|
656
|
+
### Vercel push failure
|
|
657
|
+
If `git push` fails:
|
|
658
|
+
- **Authentication**: Check git credentials or SSH keys
|
|
659
|
+
- **Remote rejection**: The remote may have branch protections. Check if pushing to the correct branch.
|
|
660
|
+
- **Diverged history**: Someone else pushed since your last pull. Run `git pull --rebase` first.
|
|
661
|
+
|
|
662
|
+
### Health check failure
|
|
663
|
+
If the health endpoint returns non-200 after deploy:
|
|
664
|
+
- Check application logs: `docker compose logs --tail=100` (Docker) or Vercel function logs
|
|
665
|
+
- Common causes: missing environment variables, database connection issues, port conflicts
|
|
666
|
+
- For Docker, check if the container is actually running: `docker compose ps`
|
|
667
|
+
|
|
668
|
+
### Cloud: Token invalid or auth expired
|
|
669
|
+
If the Showpane Cloud API returns 401/403 during pre-flight or publish:
|
|
670
|
+
- **401 Unauthorized**: The access token is expired or revoked. Run `showpane login` to re-authenticate.
|
|
671
|
+
- **403 Forbidden**: The token does not belong to an org that can publish. Re-authenticate or finish cloud onboarding.
|
|
672
|
+
- **409 organization_required**: The user authenticated but has no org yet. Send them to Showpane Cloud checkout to start the trial, then re-run `showpane login`.
|
|
673
|
+
|
|
674
|
+
### Cloud: Artifact upload failure
|
|
675
|
+
If the POST to `/api/deployments` fails:
|
|
676
|
+
- **400 Bad Request**: The artifact is missing or malformed. Rebuild locally and retry.
|
|
677
|
+
- **413 Payload Too Large**: The build artifact is too large. Remove oversized static assets from the portal app.
|
|
678
|
+
- **422 Validation Error**: The cloud control plane rejected the deploy metadata. Show the response body directly.
|
|
679
|
+
|
|
680
|
+
### Cloud: File sync failure
|
|
681
|
+
If `POST /api/files/plan` or `POST /api/files/upload` fails:
|
|
682
|
+
- **409 Conflict**: Hosted file sync needs object storage configured in Showpane Cloud. Configure S3/R2-backed storage before deploying documents.
|
|
683
|
+
- **404 Not Found**: The target portal slug does not exist in the synced runtime state. Re-export runtime data and retry the deploy.
|
|
684
|
+
- **413 Payload Too Large**: A single uploaded file is too large for multipart upload. Move that document to shared storage and link it instead of embedding it in the portal.
|
|
685
|
+
- **5xx**: The cloud storage layer failed. Retry after verifying storage credentials.
|
|
686
|
+
|
|
687
|
+
### Cloud: Publish stuck or failed
|
|
688
|
+
If the deployment never reaches `live`:
|
|
689
|
+
- Check the response from `GET /api/deployments/$DEPLOY_ID` for an `error` field.
|
|
690
|
+
- Report the failure exactly as returned by Showpane Cloud.
|
|
691
|
+
- Do not tell the user to debug Vercel directly unless the cloud response explicitly points there.
|
|
692
|
+
|
|
693
|
+
## Completion
|
|
694
|
+
|
|
695
|
+
As a final step, log skill completion:
|
|
696
|
+
|
|
697
|
+
```bash
|
|
698
|
+
echo '{"skill":"portal-deploy","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/timeline.jsonl" 2>/dev/null
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## Conventions
|
|
702
|
+
|
|
703
|
+
- Always run pre-flight checks before deploying — never skip the type check
|
|
704
|
+
- Credential warnings are non-blocking — the user decides whether to continue
|
|
705
|
+
- Migrations run before the deploy, not after
|
|
706
|
+
- For Docker: verify the health endpoint after deploy
|
|
707
|
+
- For Vercel: the deploy is async — inform the user to check the dashboard
|
|
708
|
+
- Never force-push or reset git history during Vercel deploys
|
|
709
|
+
- If any pre-flight check fails (type errors, missing deploy config), stop and explain
|
|
710
|
+
- Show the full deployment summary with portal count, migration status, and health
|
|
711
|
+
- The deploy commit message is always "Deploy portal updates" — keep it simple and consistent
|
|
712
|
+
- If the user wants a custom commit message, they should commit manually before running deploy
|
|
713
|
+
- For Vercel deploys, the build typically takes 1-3 minutes — do not poll or wait, just inform the user
|
|
714
|
+
- Always run migrations before the build/push step, never after — the app code expects the latest schema
|
|
715
|
+
- If this is the first deploy, suggest running `/portal credentials` for all portals before deploying so clients can actually log in
|
|
716
|
+
- For Cloud deploys: build locally, upload the artifact to Showpane Cloud, and let the control plane publish it
|
|
717
|
+
- For Cloud deploys: always wait for the deployment to reach `live` before declaring success
|
|
718
|
+
- For Cloud deploys: the portal URL is `https://{org}.showpane.com` — verify it returns 200 after deploy
|
|
719
|
+
- For Cloud deploys: clean up the temporary artifact after deploy completes
|
|
720
|
+
- The CLI `showpane login` is auth only — org creation and billing live in Showpane Cloud checkout
|
|
721
|
+
- For Cloud deploys: OSS should never call Vercel directly or require project/provider details from the user
|