showpane 0.4.23 → 0.4.25
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/bundle/meta/scaffold-manifest.json +3 -3
- package/bundle/scaffold/src/__tests__/portal-contracts.test.ts +20 -0
- package/bundle/scaffold/src/lib/portal-contracts.ts +2 -0
- package/bundle/toolchain/CLI_VERSION +1 -1
- package/bundle/toolchain/bin/deploy-to-cloud.ts +761 -0
- package/bundle/toolchain/bin/ensure-cloud-project-link.ts +2 -0
- package/bundle/toolchain/skills/portal-create/SKILL.md +9 -11
- package/bundle/toolchain/skills/portal-create/SKILL.md.tmpl +9 -11
- package/bundle/toolchain/skills/portal-deploy/SKILL.md +37 -325
- package/bundle/toolchain/skills/portal-deploy/SKILL.md.tmpl +37 -325
- package/bundle/toolchain/skills/portal-onboard/SKILL.md +9 -5
- package/bundle/toolchain/skills/portal-onboard/SKILL.md.tmpl +9 -5
- package/dist/index.js +125 -1
- package/package.json +1 -1
|
@@ -70,353 +70,65 @@ If type errors are found, display them and stop. Offer to fix simple issues (mis
|
|
|
70
70
|
|
|
71
71
|
Run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
|
|
72
72
|
|
|
73
|
-
### Cloud Step 2:
|
|
73
|
+
### Cloud Step 2: Run the canonical deploy command
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Use the built-in deploy command instead of reimplementing the staged cloud protocol in shell.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
fi
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Run the cloud build command that produces the prebuilt artifact:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
cd "$APP_PATH" && npm run cloud:build
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
After the build completes, verify the output directory was created:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
ls -la "$APP_PATH/.vercel/output/"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
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.
|
|
96
|
-
|
|
97
|
-
The build typically takes 30-90 seconds depending on the number of portals.
|
|
98
|
-
|
|
99
|
-
### Cloud Step 3: Package the deploy artifact
|
|
100
|
-
|
|
101
|
-
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:
|
|
102
|
-
- `.vercel/output/**`
|
|
103
|
-
- traced `.next/server/**` and `node_modules/**` files referenced by the build output
|
|
104
|
-
- a sanitized `.env` stub so the traced runtime can resolve its expected path without leaking local secrets
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
ARTIFACT_PATH="/tmp/showpane-deploy-${CLOUD_ORG_SLUG:-portal}.zip"
|
|
108
|
-
rm -f "$ARTIFACT_PATH"
|
|
109
|
-
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"
|
|
110
|
-
test -f "$ARTIFACT_PATH" || { echo "ERROR: Artifact zip was not created"; exit 1; }
|
|
111
|
-
echo "Artifact ready: $ARTIFACT_PATH"
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Cloud Step 4: Export runtime data
|
|
115
|
-
|
|
116
|
-
Export the current local portal-runtime state so Showpane Cloud can sync credentials and portal metadata before publishing:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
RUNTIME_DATA_PATH="/tmp/showpane-runtime-${CLOUD_ORG_SLUG:-portal}.json"
|
|
120
|
-
rm -f "$RUNTIME_DATA_PATH"
|
|
121
|
-
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" \
|
|
122
|
-
|| { echo "ERROR: Runtime payload export failed"; exit 1; }
|
|
123
|
-
test -s "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
|
|
124
|
-
echo "Runtime payload ready: $RUNTIME_DATA_PATH"
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Cloud Step 5: Export file manifest
|
|
128
|
-
|
|
129
|
-
Export uploaded document metadata and checksums so Showpane Cloud can determine which files need syncing:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
FILE_MANIFEST_PATH="/tmp/showpane-files-${CLOUD_ORG_SLUG:-portal}.json"
|
|
133
|
-
rm -f "$FILE_MANIFEST_PATH"
|
|
134
|
-
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" \
|
|
135
|
-
|| { echo "ERROR: File manifest export failed"; exit 1; }
|
|
136
|
-
test -s "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
|
|
137
|
-
echo "File manifest ready: $FILE_MANIFEST_PATH"
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Cloud Step 6: Sync uploaded files
|
|
141
|
-
|
|
142
|
-
Ask Showpane Cloud which files are missing or stale, then upload only those file bytes:
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
SYNC_PLAN_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/files/plan" \
|
|
146
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
147
|
-
-H "Content-Type: application/json" \
|
|
148
|
-
--data-binary @"$FILE_MANIFEST_PATH")
|
|
149
|
-
|
|
150
|
-
MISSING_COUNT=$(echo "$SYNC_PLAN_RESPONSE" | python3 -c "
|
|
151
|
-
import sys, json
|
|
152
|
-
d = json.load(sys.stdin)
|
|
153
|
-
if 'error' in d:
|
|
154
|
-
print('ERROR: ' + d['error'])
|
|
155
|
-
sys.exit(1)
|
|
156
|
-
print(len(d.get('missing', [])))
|
|
157
|
-
")
|
|
158
|
-
|
|
159
|
-
echo \"Files to sync: $MISSING_COUNT\"
|
|
160
|
-
|
|
161
|
-
if [ \"$MISSING_COUNT\" -gt 0 ]; then
|
|
162
|
-
TMP_SYNC_DIR=$(mktemp -d /tmp/showpane-file-sync.XXXXXX)
|
|
163
|
-
echo \"$SYNC_PLAN_RESPONSE\" | python3 -c '
|
|
164
|
-
import json, sys
|
|
165
|
-
for item in json.load(sys.stdin).get(\"missing\", []):
|
|
166
|
-
print(\"\\t\".join([
|
|
167
|
-
item[\"storagePath\"],
|
|
168
|
-
item[\"portalSlug\"],
|
|
169
|
-
item[\"filename\"],
|
|
170
|
-
item[\"mimeType\"],
|
|
171
|
-
str(item[\"size\"]),
|
|
172
|
-
item[\"uploadedBy\"],
|
|
173
|
-
item[\"uploadedAt\"],
|
|
174
|
-
item[\"checksum\"],
|
|
175
|
-
]))
|
|
176
|
-
' | while IFS=$'\t' read -r STORAGE_PATH PORTAL_SLUG FILE_NAME MIME_TYPE FILE_SIZE UPLOADED_BY UPLOADED_AT CHECKSUM; do
|
|
177
|
-
TMP_FILE="$TMP_SYNC_DIR/$CHECKSUM"
|
|
178
|
-
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"
|
|
179
|
-
curl -s -X POST "$CLOUD_API_BASE/api/files/upload" \
|
|
180
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
181
|
-
-F "file=@$TMP_FILE;type=$MIME_TYPE" \
|
|
182
|
-
-F "storagePath=$STORAGE_PATH" \
|
|
183
|
-
-F "portalSlug=$PORTAL_SLUG" \
|
|
184
|
-
-F "filename=$FILE_NAME" \
|
|
185
|
-
-F "mimeType=$MIME_TYPE" \
|
|
186
|
-
-F "size=$FILE_SIZE" \
|
|
187
|
-
-F "uploadedBy=$UPLOADED_BY" \
|
|
188
|
-
-F "uploadedAt=$UPLOADED_AT" \
|
|
189
|
-
-F "checksum=$CHECKSUM" \
|
|
190
|
-
>/dev/null || exit 1
|
|
191
|
-
done
|
|
192
|
-
fi
|
|
78
|
+
DEPLOY_JSON=$(cd "$APP_PATH" && SHOWPANE_APP_PATH="$APP_PATH" NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$SKILL_DIR/bin/tsconfig.json" "$SKILL_DIR/bin/deploy-to-cloud.ts" --app-path "$APP_PATH" --wait --json)
|
|
79
|
+
echo "$DEPLOY_JSON"
|
|
193
80
|
```
|
|
194
81
|
|
|
195
|
-
|
|
82
|
+
That command already owns:
|
|
83
|
+
- type check
|
|
84
|
+
- project-link bootstrap
|
|
85
|
+
- `cloud:build`
|
|
86
|
+
- artifact packaging
|
|
87
|
+
- runtime export
|
|
88
|
+
- optional file sync
|
|
89
|
+
- deployment init/upload/finalize
|
|
90
|
+
- polling to terminal state
|
|
91
|
+
- hosted verification
|
|
196
92
|
|
|
197
|
-
|
|
198
|
-
PORTAL_COUNT=$(cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" \
|
|
199
|
-
| python3 -c "import sys,json; print(len(json.load(sys.stdin).get('portals', [])))")
|
|
200
|
-
|
|
201
|
-
INIT_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments" \
|
|
202
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
203
|
-
-H "Content-Type: application/json" \
|
|
204
|
-
--data-binary '{}')
|
|
205
|
-
|
|
206
|
-
echo \"$INIT_RESPONSE\" | python3 -c "
|
|
207
|
-
import sys, json
|
|
208
|
-
d = json.load(sys.stdin)
|
|
209
|
-
if 'error' in d:
|
|
210
|
-
print('ERROR: ' + str(d['error']))
|
|
211
|
-
sys.exit(1)
|
|
212
|
-
print('Deployment initialized')
|
|
213
|
-
print('ID: ' + d.get('deploymentId', 'unknown'))
|
|
214
|
-
print('Status: ' + d.get('status', 'unknown'))
|
|
215
|
-
"
|
|
216
|
-
```
|
|
93
|
+
Parse the JSON and stop immediately if `ok` is false.
|
|
217
94
|
|
|
218
|
-
|
|
95
|
+
### Cloud Step 3: Summarize the result
|
|
219
96
|
|
|
220
|
-
|
|
97
|
+
Read these fields from the returned JSON:
|
|
98
|
+
- `deploymentId`
|
|
99
|
+
- `status`
|
|
100
|
+
- `liveUrl`
|
|
101
|
+
- `portalCount`
|
|
102
|
+
- `firstPortalSlug`
|
|
103
|
+
- `fileSyncCount`
|
|
104
|
+
- `verification.portalStatus`
|
|
105
|
+
- `verification.healthStatus`
|
|
221
106
|
|
|
222
|
-
|
|
223
|
-
DEPLOY_ID=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deploymentId',''))")
|
|
224
|
-
ARTIFACT_UPLOAD_URL=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('artifactUploadUrl',''))")
|
|
107
|
+
If `status` is not `live`, treat the deploy as failed.
|
|
225
108
|
|
|
226
|
-
|
|
227
|
-
echo "ERROR: Missing deploymentId or artifact upload URL"
|
|
228
|
-
exit 1
|
|
229
|
-
fi
|
|
230
|
-
|
|
231
|
-
UPLOAD_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$ARTIFACT_UPLOAD_URL" \
|
|
232
|
-
-H "Content-Type: application/zip" \
|
|
233
|
-
--data-binary @"$ARTIFACT_PATH" \
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
if [ "$UPLOAD_STATUS" != "200" ]; then
|
|
237
|
-
echo "ERROR: Artifact upload failed with HTTP $UPLOAD_STATUS"
|
|
238
|
-
exit 1
|
|
239
|
-
fi
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### Cloud Step 9: Finalize the deployment
|
|
243
|
-
|
|
244
|
-
```bash
|
|
245
|
-
FINALIZE_PAYLOAD="/tmp/showpane-deploy-finalize-${DEPLOY_ID}.json"
|
|
246
|
-
python3 - <<'PY' "$RUNTIME_DATA_PATH" "$PORTAL_COUNT" > "$FINALIZE_PAYLOAD"
|
|
247
|
-
import json, sys
|
|
248
|
-
runtime_path = sys.argv[1]
|
|
249
|
-
portal_count = int(sys.argv[2])
|
|
250
|
-
payload = {
|
|
251
|
-
"portalCount": portal_count,
|
|
252
|
-
"runtimeData": json.load(open(runtime_path)),
|
|
253
|
-
}
|
|
254
|
-
print(json.dumps(payload))
|
|
255
|
-
PY
|
|
256
|
-
|
|
257
|
-
FINALIZE_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID/finalize" \
|
|
258
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
259
|
-
-H "Content-Type: application/json" \
|
|
260
|
-
--data-binary @"$FINALIZE_PAYLOAD")
|
|
261
|
-
|
|
262
|
-
echo "$FINALIZE_RESPONSE" | python3 -c "
|
|
263
|
-
import sys, json
|
|
264
|
-
d = json.load(sys.stdin)
|
|
265
|
-
if 'error' in d:
|
|
266
|
-
print('ERROR: ' + str(d['error']))
|
|
267
|
-
sys.exit(1)
|
|
268
|
-
print('Deployment accepted')
|
|
269
|
-
print('ID: ' + d.get('deploymentId', 'unknown'))
|
|
270
|
-
print('Status: ' + d.get('status', 'unknown'))
|
|
271
|
-
"
|
|
272
|
-
```
|
|
109
|
+
Print a concise summary:
|
|
273
110
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
Poll Showpane Cloud until the deployment reaches `live` or `failed`:
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
echo "Waiting for deployment to go live..."
|
|
280
|
-
for i in $(seq 1 60); do
|
|
281
|
-
sleep 5
|
|
282
|
-
STATUS=$(curl -s \
|
|
283
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
284
|
-
"$CLOUD_API_BASE/api/deployments/$DEPLOY_ID" \
|
|
285
|
-
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))")
|
|
286
|
-
|
|
287
|
-
echo " Status: $STATUS"
|
|
288
|
-
|
|
289
|
-
if [ "$STATUS" = "live" ]; then
|
|
290
|
-
echo "Deployment is live!"
|
|
291
|
-
break
|
|
292
|
-
fi
|
|
293
|
-
|
|
294
|
-
if [ "$STATUS" = "failed" ] || [ "$STATUS" = "unhealthy" ]; then
|
|
295
|
-
echo "ERROR: Deployment failed with status: $STATUS"
|
|
296
|
-
curl -s \
|
|
297
|
-
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
298
|
-
"$CLOUD_API_BASE/api/deployments/$DEPLOY_ID"
|
|
299
|
-
exit 1
|
|
300
|
-
fi
|
|
301
|
-
done
|
|
302
|
-
|
|
303
|
-
if [ "$STATUS" != "live" ]; then
|
|
304
|
-
echo "WARNING: Deployment did not become ready within 5 minutes."
|
|
305
|
-
echo "Check Showpane Cloud for status."
|
|
306
|
-
fi
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
The publish typically takes 15-60 seconds. Poll every 5 seconds for up to 5 minutes.
|
|
310
|
-
|
|
311
|
-
### Cloud Step 11: Post-deploy verification
|
|
312
|
-
|
|
313
|
-
Once the deployment is live, verify the portal is accessible:
|
|
314
|
-
|
|
315
|
-
```bash
|
|
316
|
-
PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
|
|
317
|
-
|
|
318
|
-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL")
|
|
319
|
-
echo "Health check: $PORTAL_URL -> HTTP $HTTP_CODE"
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
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.
|
|
323
|
-
|
|
324
|
-
Also check the API health endpoint:
|
|
325
|
-
|
|
326
|
-
```bash
|
|
327
|
-
curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/health"
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Cloud Step 12: Deployment summary
|
|
331
|
-
|
|
332
|
-
Print a clear summary:
|
|
333
|
-
|
|
334
|
-
```
|
|
335
|
-
Cloud deploy complete!
|
|
111
|
+
```text
|
|
112
|
+
Cloud deploy complete
|
|
336
113
|
|
|
337
|
-
Mode: cloud
|
|
338
|
-
Type check: passed
|
|
339
|
-
Build: .vercel/output/ (N files)
|
|
340
|
-
Portals: N active
|
|
341
114
|
Status: live
|
|
342
|
-
Health: OK (200)
|
|
343
|
-
|
|
344
|
-
Portal URL: https://orgslug.showpane.com
|
|
345
115
|
Deploy ID: dep_xxxxxxxxxxxx
|
|
116
|
+
Live URL: https://orgslug.showpane.com
|
|
117
|
+
Portals: N
|
|
118
|
+
File sync: N
|
|
346
119
|
```
|
|
347
120
|
|
|
348
|
-
### Cloud Step
|
|
121
|
+
### Cloud Step 4: Record deployment
|
|
349
122
|
|
|
350
123
|
Log the cloud deployment for operational memory:
|
|
351
124
|
|
|
352
125
|
```bash
|
|
353
|
-
echo
|
|
126
|
+
DEPLOY_ID=$(echo "$DEPLOY_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deploymentId',''))")
|
|
127
|
+
PORTAL_COUNT=$(echo "$DEPLOY_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('portalCount',0))")
|
|
128
|
+
echo '{"skill":"portal-deploy","key":"deploy","insight":"Cloud deploy to '$CLOUD_ORG_SLUG'.showpane.com. Portals: '$PORTAL_COUNT' active. Deploy ID: '$DEPLOY_ID'.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
|
|
354
129
|
```
|
|
355
130
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Remove the temporary artifact:
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
rm -f "$ARTIFACT_PATH"
|
|
362
|
-
rm -f "$RUNTIME_DATA_PATH"
|
|
363
|
-
rm -f "$FILE_MANIFEST_PATH"
|
|
364
|
-
rm -f "$FINALIZE_PAYLOAD"
|
|
365
|
-
[ -n "${TMP_SYNC_DIR:-}" ] && rm -rf "$TMP_SYNC_DIR"
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## Post-Deploy Verification
|
|
371
|
-
|
|
372
|
-
After deployment succeeds, automatically run these verification steps. Do not ask the user — just do them.
|
|
373
|
-
|
|
374
|
-
### Step V1: DNS/URL Check
|
|
375
|
-
Fetch the portal URL and verify it returns 200:
|
|
376
|
-
```bash
|
|
377
|
-
PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
|
|
378
|
-
echo "Verifying $PORTAL_URL..."
|
|
379
|
-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
|
|
380
|
-
echo "HTTP status: $HTTP_CODE"
|
|
381
|
-
```
|
|
382
|
-
If not 200, warn but don't fail — DNS propagation may take time for cloud deploys.
|
|
383
|
-
|
|
384
|
-
### Step V2: Portal Content Check
|
|
385
|
-
If portals exist in the database, verify at least one portal login page loads:
|
|
386
|
-
```bash
|
|
387
|
-
# Get the first active portal slug
|
|
388
|
-
FIRST_SLUG=$(cd "$APP_PATH" && npx tsx -e "
|
|
389
|
-
const { prisma } = require('./src/lib/db');
|
|
390
|
-
prisma.clientPortal.findFirst({ where: { isActive: true }, select: { slug: true } })
|
|
391
|
-
.then(p => console.log(p?.slug || ''))
|
|
392
|
-
.finally(() => prisma.\$disconnect());
|
|
393
|
-
" 2>/dev/null)
|
|
394
|
-
if [ -n "$FIRST_SLUG" ]; then
|
|
395
|
-
echo "Checking portal: $PORTAL_URL/client/$FIRST_SLUG"
|
|
396
|
-
curl -s -o /dev/null -w "Portal login page: %{http_code}\n" "$PORTAL_URL/client/$FIRST_SLUG" 2>/dev/null
|
|
397
|
-
fi
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
### Step V3: SSL Check (cloud only)
|
|
401
|
-
For cloud deploys, verify SSL is working:
|
|
402
|
-
```bash
|
|
403
|
-
if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_PORTAL_URL" ]; then
|
|
404
|
-
echo "Checking SSL..."
|
|
405
|
-
SSL_OK=$(curl -s -o /dev/null -w "%{http_code}" "https://${CLOUD_ORG_SLUG}.showpane.com" 2>/dev/null)
|
|
406
|
-
echo "SSL status: $SSL_OK"
|
|
407
|
-
fi
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Step V4: Summary
|
|
411
|
-
Print a deployment summary:
|
|
412
|
-
```
|
|
413
|
-
✓ Deployed successfully
|
|
414
|
-
URL: [portal URL]
|
|
415
|
-
Portals: [count]
|
|
416
|
-
SSL: [ok/pending]
|
|
417
|
-
|
|
418
|
-
Next: /portal-status for ongoing monitoring
|
|
419
|
-
```
|
|
131
|
+
The deploy command already handles cleanup and hosted verification. Do not duplicate those steps here.
|
|
420
132
|
|
|
421
133
|
---
|
|
422
134
|
|
|
@@ -262,9 +262,8 @@ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PA
|
|
|
262
262
|
Use the returned `org.id` as the canonical org id for the rest of the create flow.
|
|
263
263
|
Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
are already known:
|
|
265
|
+
At this point, extra repo reorientation rarely changes the first draft. Use the
|
|
266
|
+
known inputs below and spend the time shaping the portal instead:
|
|
268
267
|
|
|
269
268
|
- workspace app path
|
|
270
269
|
- org slug
|
|
@@ -272,6 +271,9 @@ are already known:
|
|
|
272
271
|
- selected template
|
|
273
272
|
- optional transcript source
|
|
274
273
|
|
|
274
|
+
If you need structure or style, read only the selected template and the example
|
|
275
|
+
portal. Other portal skills are for later phases and usually just add latency here.
|
|
276
|
+
|
|
275
277
|
During this phase:
|
|
276
278
|
|
|
277
279
|
- suggest a slug if needed
|
|
@@ -384,8 +386,10 @@ afterthought unless the user explicitly says they want to stop at local preview.
|
|
|
384
386
|
|
|
385
387
|
If cloud auth is missing:
|
|
386
388
|
|
|
387
|
-
-
|
|
388
|
-
-
|
|
389
|
+
- run `showpane login` inline via Bash in the current session
|
|
390
|
+
- if you need to hand control to the user, tell them to run `! showpane login`
|
|
391
|
+
- let it handle sign-in, sign-up, and checkout if needed
|
|
392
|
+
- then resume publish from the checkpoint
|
|
389
393
|
|
|
390
394
|
If deploy returns `organization_required`:
|
|
391
395
|
|
|
@@ -169,9 +169,8 @@ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PA
|
|
|
169
169
|
Use the returned `org.id` as the canonical org id for the rest of the create flow.
|
|
170
170
|
Do not guess schema fields or discover the org via ad-hoc SQLite queries if this helper succeeds.
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
are already known:
|
|
172
|
+
At this point, extra repo reorientation rarely changes the first draft. Use the
|
|
173
|
+
known inputs below and spend the time shaping the portal instead:
|
|
175
174
|
|
|
176
175
|
- workspace app path
|
|
177
176
|
- org slug
|
|
@@ -179,6 +178,9 @@ are already known:
|
|
|
179
178
|
- selected template
|
|
180
179
|
- optional transcript source
|
|
181
180
|
|
|
181
|
+
If you need structure or style, read only the selected template and the example
|
|
182
|
+
portal. Other portal skills are for later phases and usually just add latency here.
|
|
183
|
+
|
|
182
184
|
During this phase:
|
|
183
185
|
|
|
184
186
|
- suggest a slug if needed
|
|
@@ -291,8 +293,10 @@ afterthought unless the user explicitly says they want to stop at local preview.
|
|
|
291
293
|
|
|
292
294
|
If cloud auth is missing:
|
|
293
295
|
|
|
294
|
-
-
|
|
295
|
-
-
|
|
296
|
+
- run `showpane login` inline via Bash in the current session
|
|
297
|
+
- if you need to hand control to the user, tell them to run `! showpane login`
|
|
298
|
+
- let it handle sign-in, sign-up, and checkout if needed
|
|
299
|
+
- then resume publish from the checkpoint
|
|
296
300
|
|
|
297
301
|
If deploy returns `organization_required`:
|
|
298
302
|
|
package/dist/index.js
CHANGED
|
@@ -67,6 +67,9 @@ function printCreateUsage() {
|
|
|
67
67
|
function printClaudeUsage() {
|
|
68
68
|
console.log("Usage: showpane claude [--project <name-or-path>] [--yes --name <company> --full-name <name> --work-email <email> [--website <domain>]] [--verbose]");
|
|
69
69
|
}
|
|
70
|
+
function printDeployUsage() {
|
|
71
|
+
console.log("Usage: showpane deploy [--wait] [--json]");
|
|
72
|
+
}
|
|
70
73
|
function printBanner() {
|
|
71
74
|
const banner = `
|
|
72
75
|
${BOLD}${WHITE} \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
@@ -736,6 +739,16 @@ function findFreePort(startPort) {
|
|
|
736
739
|
function getPackageRoot() {
|
|
737
740
|
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
738
741
|
}
|
|
742
|
+
function ensureCurrentToolchain(packageRoot2) {
|
|
743
|
+
const bundleRoot = getLocalBundleRoot(packageRoot2);
|
|
744
|
+
const showpaneVersion = getPackageVersion(packageRoot2);
|
|
745
|
+
const currentDeployScript = join(CURRENT_TOOLCHAIN_LINK, "bin", "deploy-to-cloud.ts");
|
|
746
|
+
const bundledDeployScript = join(bundleRoot, "toolchain", "bin", "deploy-to-cloud.ts");
|
|
747
|
+
if (existsSync(currentDeployScript) && existsSync(bundledDeployScript) && hashFile(currentDeployScript) === hashFile(bundledDeployScript)) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
syncToolchain(bundleRoot, showpaneVersion, false);
|
|
751
|
+
}
|
|
739
752
|
function getPackageVersion(packageRoot2) {
|
|
740
753
|
const packageJson = readJson(join(packageRoot2, "package.json"));
|
|
741
754
|
return packageJson.version;
|
|
@@ -1625,6 +1638,112 @@ async function login() {
|
|
|
1625
1638
|
error("Authentication timed out. Please try again.");
|
|
1626
1639
|
process.exit(1);
|
|
1627
1640
|
}
|
|
1641
|
+
function parseDeployArgs(args) {
|
|
1642
|
+
for (const arg of args) {
|
|
1643
|
+
if (arg === "--wait" || arg === "--json" || arg === "--help") {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
1647
|
+
}
|
|
1648
|
+
return {
|
|
1649
|
+
wait: args.includes("--wait"),
|
|
1650
|
+
json: args.includes("--json"),
|
|
1651
|
+
help: args.includes("--help")
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
async function deployProject(args) {
|
|
1655
|
+
const { wait, json, help } = parseDeployArgs(args);
|
|
1656
|
+
if (help) {
|
|
1657
|
+
printDeployUsage();
|
|
1658
|
+
process.exit(0);
|
|
1659
|
+
}
|
|
1660
|
+
const packageRoot2 = getPackageRoot();
|
|
1661
|
+
ensureCurrentToolchain(packageRoot2);
|
|
1662
|
+
ensureShowpaneShim();
|
|
1663
|
+
const config = readShowpaneConfig();
|
|
1664
|
+
const currentWorkspace = findWorkspaceRoot(process.cwd()) ?? (config.app_path ? findWorkspaceRoot(config.app_path) ?? resolve(config.app_path) : null);
|
|
1665
|
+
if (!currentWorkspace) {
|
|
1666
|
+
throw new Error("No Showpane workspace found. Run this inside a Showpane project, or set one up first.");
|
|
1667
|
+
}
|
|
1668
|
+
const scriptPath = join(CURRENT_TOOLCHAIN_LINK, "bin", "deploy-to-cloud.ts");
|
|
1669
|
+
if (!existsSync(scriptPath)) {
|
|
1670
|
+
throw new Error("Current Showpane toolchain is missing deploy-to-cloud.ts. Run `showpane sync`.");
|
|
1671
|
+
}
|
|
1672
|
+
const child = spawn(
|
|
1673
|
+
"npx",
|
|
1674
|
+
[
|
|
1675
|
+
"tsx",
|
|
1676
|
+
"--tsconfig",
|
|
1677
|
+
join(CURRENT_TOOLCHAIN_LINK, "bin", "tsconfig.json"),
|
|
1678
|
+
scriptPath,
|
|
1679
|
+
"--app-path",
|
|
1680
|
+
currentWorkspace,
|
|
1681
|
+
"--json",
|
|
1682
|
+
...wait ? ["--wait"] : []
|
|
1683
|
+
],
|
|
1684
|
+
{
|
|
1685
|
+
cwd: currentWorkspace,
|
|
1686
|
+
env: {
|
|
1687
|
+
...process.env,
|
|
1688
|
+
SHOWPANE_APP_PATH: currentWorkspace,
|
|
1689
|
+
SHOWPANE_CLOUD_URL: API_BASE
|
|
1690
|
+
},
|
|
1691
|
+
stdio: ["ignore", "pipe", "inherit"]
|
|
1692
|
+
}
|
|
1693
|
+
);
|
|
1694
|
+
let stdout = "";
|
|
1695
|
+
child.stdout?.on("data", (chunk) => {
|
|
1696
|
+
stdout += chunk.toString();
|
|
1697
|
+
});
|
|
1698
|
+
const exitCode = await new Promise((resolveExit, rejectExit) => {
|
|
1699
|
+
child.on("error", rejectExit);
|
|
1700
|
+
child.on("close", (code) => resolveExit(code ?? 1));
|
|
1701
|
+
});
|
|
1702
|
+
let parsed = null;
|
|
1703
|
+
if (stdout.trim()) {
|
|
1704
|
+
try {
|
|
1705
|
+
parsed = JSON.parse(stdout.trim());
|
|
1706
|
+
} catch {
|
|
1707
|
+
parsed = null;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (exitCode !== 0 || !parsed || !parsed.ok) {
|
|
1711
|
+
if (json && parsed && !parsed.ok) {
|
|
1712
|
+
process.stdout.write(`${JSON.stringify(parsed)}
|
|
1713
|
+
`);
|
|
1714
|
+
process.exit(exitCode || 1);
|
|
1715
|
+
}
|
|
1716
|
+
const message = parsed && !parsed.ok ? parsed.step ? `${parsed.step}: ${parsed.error}` : parsed.error : "Cloud deploy failed.";
|
|
1717
|
+
throw new Error(message);
|
|
1718
|
+
}
|
|
1719
|
+
config.deploy_mode = "cloud";
|
|
1720
|
+
if (parsed.liveUrl) {
|
|
1721
|
+
config.portalUrl = parsed.liveUrl;
|
|
1722
|
+
}
|
|
1723
|
+
updateWorkspaceFromConfig(config, currentWorkspace, {
|
|
1724
|
+
name: basename(currentWorkspace),
|
|
1725
|
+
deployMode: "cloud",
|
|
1726
|
+
orgSlug: typeof config.orgSlug === "string" ? config.orgSlug : ""
|
|
1727
|
+
});
|
|
1728
|
+
writeShowpaneConfig(config);
|
|
1729
|
+
if (json) {
|
|
1730
|
+
process.stdout.write(`${JSON.stringify(parsed)}
|
|
1731
|
+
`);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
console.log();
|
|
1735
|
+
green("Cloud deploy finished");
|
|
1736
|
+
console.log(` ${BOLD}Deploy ID:${RESET} ${parsed.deploymentId}`);
|
|
1737
|
+
console.log(` ${BOLD}Status:${RESET} ${parsed.status}`);
|
|
1738
|
+
console.log(` ${BOLD}URL:${RESET} ${parsed.liveUrl ?? "pending"}`);
|
|
1739
|
+
console.log(` ${BOLD}Portals:${RESET} ${parsed.portalCount}`);
|
|
1740
|
+
console.log(` ${BOLD}File sync:${RESET} ${parsed.fileSyncCount}`);
|
|
1741
|
+
if (parsed.verification.portalStatus !== null || parsed.verification.healthStatus !== null) {
|
|
1742
|
+
console.log(
|
|
1743
|
+
` ${BOLD}Verify:${RESET} portal=${parsed.verification.portalStatus ?? "n/a"} health=${parsed.verification.healthStatus ?? "n/a"}`
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1628
1747
|
var command = process.argv[2];
|
|
1629
1748
|
var packageRoot = getPackageRoot();
|
|
1630
1749
|
if (process.argv.includes("--version")) {
|
|
@@ -1633,7 +1752,7 @@ if (process.argv.includes("--version")) {
|
|
|
1633
1752
|
}
|
|
1634
1753
|
if (process.argv.length <= 2 && process.argv.includes("--help")) {
|
|
1635
1754
|
printCreateUsage();
|
|
1636
|
-
console.log("Commands: claude, login, projects, sync, upgrade");
|
|
1755
|
+
console.log("Commands: claude, deploy, login, projects, sync, upgrade");
|
|
1637
1756
|
process.exit(0);
|
|
1638
1757
|
}
|
|
1639
1758
|
if (command === "login") {
|
|
@@ -1657,6 +1776,11 @@ if (command === "login") {
|
|
|
1657
1776
|
error(String(err));
|
|
1658
1777
|
process.exit(1);
|
|
1659
1778
|
}
|
|
1779
|
+
} else if (command === "deploy") {
|
|
1780
|
+
deployProject(process.argv.slice(3)).catch((err) => {
|
|
1781
|
+
error(String(err));
|
|
1782
|
+
process.exit(1);
|
|
1783
|
+
});
|
|
1660
1784
|
} else if (command === "sync") {
|
|
1661
1785
|
syncCurrentToolchain().catch((err) => {
|
|
1662
1786
|
error(String(err));
|