showpane 0.4.22 → 0.4.24

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.
@@ -36,10 +36,9 @@ fi
36
36
  If the token is missing, stop and tell the user:
37
37
 
38
38
  - `showpane login` is the right next step
39
- - it opens browser auth for Showpane Cloud automatically
40
- - if they need an account first:
41
- - sign up: `https://app.showpane.com/sign-up`
42
- - sign in: `https://app.showpane.com/sign-in`
39
+ - it owns the whole browser flow now: sign up or sign in if needed, start checkout,
40
+ then return to CLI authorization automatically
41
+ - do not manually steer the user through separate sign-up, sign-in, or checkout steps
43
42
 
44
43
  If they are already inside Claude Code, explicitly suggest:
45
44
 
@@ -71,353 +70,65 @@ If type errors are found, display them and stop. Offer to fix simple issues (mis
71
70
 
72
71
  Run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
73
72
 
74
- ### Cloud Step 2: Build the app
73
+ ### Cloud Step 2: Run the canonical deploy command
75
74
 
76
- Before building, ensure the hidden local project-link metadata exists:
75
+ Use the built-in deploy command instead of reimplementing the staged cloud protocol in shell.
77
76
 
78
77
  ```bash
79
- if [ ! -f "$APP_PATH/.vercel/project.json" ]; then
80
- cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/ensure-cloud-project-link.ts"
81
- fi
82
- ```
83
-
84
- Run the cloud build command that produces the prebuilt artifact:
85
-
86
- ```bash
87
- cd "$APP_PATH" && npm run cloud:build
88
- ```
89
-
90
- After the build completes, verify the output directory was created:
91
-
92
- ```bash
93
- ls -la "$APP_PATH/.vercel/output/"
94
- ```
95
-
96
- 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.
97
-
98
- The build typically takes 30-90 seconds depending on the number of portals.
99
-
100
- ### Cloud Step 3: Package the deploy artifact
101
-
102
- 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:
103
- - `.vercel/output/**`
104
- - traced `.next/server/**` and `node_modules/**` files referenced by the build output
105
- - a sanitized `.env` stub so the traced runtime can resolve its expected path without leaking local secrets
106
-
107
- ```bash
108
- ARTIFACT_PATH="/tmp/showpane-deploy-${CLOUD_ORG_SLUG:-portal}.zip"
109
- rm -f "$ARTIFACT_PATH"
110
- 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"
111
- test -f "$ARTIFACT_PATH" || { echo "ERROR: Artifact zip was not created"; exit 1; }
112
- echo "Artifact ready: $ARTIFACT_PATH"
113
- ```
114
-
115
- ### Cloud Step 4: Export runtime data
116
-
117
- Export the current local portal-runtime state so Showpane Cloud can sync credentials and portal metadata before publishing:
118
-
119
- ```bash
120
- RUNTIME_DATA_PATH="/tmp/showpane-runtime-${CLOUD_ORG_SLUG:-portal}.json"
121
- rm -f "$RUNTIME_DATA_PATH"
122
- 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" \
123
- || { echo "ERROR: Runtime payload export failed"; exit 1; }
124
- test -s "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
125
- echo "Runtime payload ready: $RUNTIME_DATA_PATH"
126
- ```
127
-
128
- ### Cloud Step 5: Export file manifest
129
-
130
- Export uploaded document metadata and checksums so Showpane Cloud can determine which files need syncing:
131
-
132
- ```bash
133
- FILE_MANIFEST_PATH="/tmp/showpane-files-${CLOUD_ORG_SLUG:-portal}.json"
134
- rm -f "$FILE_MANIFEST_PATH"
135
- 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" \
136
- || { echo "ERROR: File manifest export failed"; exit 1; }
137
- test -s "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
138
- echo "File manifest ready: $FILE_MANIFEST_PATH"
139
- ```
140
-
141
- ### Cloud Step 6: Sync uploaded files
142
-
143
- Ask Showpane Cloud which files are missing or stale, then upload only those file bytes:
144
-
145
- ```bash
146
- SYNC_PLAN_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/files/plan" \
147
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
148
- -H "Content-Type: application/json" \
149
- --data-binary @"$FILE_MANIFEST_PATH")
150
-
151
- MISSING_COUNT=$(echo "$SYNC_PLAN_RESPONSE" | python3 -c "
152
- import sys, json
153
- d = json.load(sys.stdin)
154
- if 'error' in d:
155
- print('ERROR: ' + d['error'])
156
- sys.exit(1)
157
- print(len(d.get('missing', [])))
158
- ")
159
-
160
- echo \"Files to sync: $MISSING_COUNT\"
161
-
162
- if [ \"$MISSING_COUNT\" -gt 0 ]; then
163
- TMP_SYNC_DIR=$(mktemp -d /tmp/showpane-file-sync.XXXXXX)
164
- echo \"$SYNC_PLAN_RESPONSE\" | python3 -c '
165
- import json, sys
166
- for item in json.load(sys.stdin).get(\"missing\", []):
167
- print(\"\\t\".join([
168
- item[\"storagePath\"],
169
- item[\"portalSlug\"],
170
- item[\"filename\"],
171
- item[\"mimeType\"],
172
- str(item[\"size\"]),
173
- item[\"uploadedBy\"],
174
- item[\"uploadedAt\"],
175
- item[\"checksum\"],
176
- ]))
177
- ' | while IFS=$'\t' read -r STORAGE_PATH PORTAL_SLUG FILE_NAME MIME_TYPE FILE_SIZE UPLOADED_BY UPLOADED_AT CHECKSUM; do
178
- TMP_FILE="$TMP_SYNC_DIR/$CHECKSUM"
179
- 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"
180
- curl -s -X POST "$CLOUD_API_BASE/api/files/upload" \
181
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
182
- -F "file=@$TMP_FILE;type=$MIME_TYPE" \
183
- -F "storagePath=$STORAGE_PATH" \
184
- -F "portalSlug=$PORTAL_SLUG" \
185
- -F "filename=$FILE_NAME" \
186
- -F "mimeType=$MIME_TYPE" \
187
- -F "size=$FILE_SIZE" \
188
- -F "uploadedBy=$UPLOADED_BY" \
189
- -F "uploadedAt=$UPLOADED_AT" \
190
- -F "checksum=$CHECKSUM" \
191
- >/dev/null || exit 1
192
- done
193
- 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"
194
80
  ```
195
81
 
196
- ### Cloud Step 7: Start the staged deployment
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
197
92
 
198
- ```bash
199
- PORTAL_COUNT=$(cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" \
200
- | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('portals', [])))")
201
-
202
- INIT_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments" \
203
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
204
- -H "Content-Type: application/json" \
205
- --data-binary '{}')
206
-
207
- echo \"$INIT_RESPONSE\" | python3 -c "
208
- import sys, json
209
- d = json.load(sys.stdin)
210
- if 'error' in d:
211
- print('ERROR: ' + str(d['error']))
212
- sys.exit(1)
213
- print('Deployment initialized')
214
- print('ID: ' + d.get('deploymentId', 'unknown'))
215
- print('Status: ' + d.get('status', 'unknown'))
216
- "
217
- ```
93
+ Parse the JSON and stop immediately if `ok` is false.
218
94
 
219
- Extract the deployment ID and the presigned artifact upload URL from the response. If the API returns an error, show it and stop.
95
+ ### Cloud Step 3: Summarize the result
220
96
 
221
- ### Cloud Step 8: Upload the artifact directly to storage
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`
222
106
 
223
- ```bash
224
- DEPLOY_ID=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deploymentId',''))")
225
- 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.
226
108
 
227
- if [ -z "$DEPLOY_ID" ] || [ -z "$ARTIFACT_UPLOAD_URL" ]; then
228
- echo "ERROR: Missing deploymentId or artifact upload URL"
229
- exit 1
230
- fi
231
-
232
- UPLOAD_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$ARTIFACT_UPLOAD_URL" \
233
- -H "Content-Type: application/zip" \
234
- --data-binary @"$ARTIFACT_PATH" \
235
- )
236
-
237
- if [ "$UPLOAD_STATUS" != "200" ]; then
238
- echo "ERROR: Artifact upload failed with HTTP $UPLOAD_STATUS"
239
- exit 1
240
- fi
241
- ```
242
-
243
- ### Cloud Step 9: Finalize the deployment
244
-
245
- ```bash
246
- FINALIZE_PAYLOAD="/tmp/showpane-deploy-finalize-${DEPLOY_ID}.json"
247
- python3 - <<'PY' "$RUNTIME_DATA_PATH" "$PORTAL_COUNT" > "$FINALIZE_PAYLOAD"
248
- import json, sys
249
- runtime_path = sys.argv[1]
250
- portal_count = int(sys.argv[2])
251
- payload = {
252
- "portalCount": portal_count,
253
- "runtimeData": json.load(open(runtime_path)),
254
- }
255
- print(json.dumps(payload))
256
- PY
257
-
258
- FINALIZE_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID/finalize" \
259
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
260
- -H "Content-Type: application/json" \
261
- --data-binary @"$FINALIZE_PAYLOAD")
262
-
263
- echo "$FINALIZE_RESPONSE" | python3 -c "
264
- import sys, json
265
- d = json.load(sys.stdin)
266
- if 'error' in d:
267
- print('ERROR: ' + str(d['error']))
268
- sys.exit(1)
269
- print('Deployment accepted')
270
- print('ID: ' + d.get('deploymentId', 'unknown'))
271
- print('Status: ' + d.get('status', 'unknown'))
272
- "
273
- ```
109
+ Print a concise summary:
274
110
 
275
- ### Cloud Step 10: Wait for cloud publish to finish
276
-
277
- Poll Showpane Cloud until the deployment reaches `live` or `failed`:
278
-
279
- ```bash
280
- echo "Waiting for deployment to go live..."
281
- for i in $(seq 1 60); do
282
- sleep 5
283
- STATUS=$(curl -s \
284
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
285
- "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID" \
286
- | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))")
287
-
288
- echo " Status: $STATUS"
289
-
290
- if [ "$STATUS" = "live" ]; then
291
- echo "Deployment is live!"
292
- break
293
- fi
294
-
295
- if [ "$STATUS" = "failed" ] || [ "$STATUS" = "unhealthy" ]; then
296
- echo "ERROR: Deployment failed with status: $STATUS"
297
- curl -s \
298
- -H "Authorization: Bearer $CLOUD_API_TOKEN" \
299
- "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID"
300
- exit 1
301
- fi
302
- done
303
-
304
- if [ "$STATUS" != "live" ]; then
305
- echo "WARNING: Deployment did not become ready within 5 minutes."
306
- echo "Check Showpane Cloud for status."
307
- fi
308
- ```
309
-
310
- The publish typically takes 15-60 seconds. Poll every 5 seconds for up to 5 minutes.
311
-
312
- ### Cloud Step 11: Post-deploy verification
313
-
314
- Once the deployment is live, verify the portal is accessible:
315
-
316
- ```bash
317
- PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
318
-
319
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL")
320
- echo "Health check: $PORTAL_URL -> HTTP $HTTP_CODE"
321
- ```
322
-
323
- 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.
324
-
325
- Also check the API health endpoint:
326
-
327
- ```bash
328
- curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/health"
329
- ```
330
-
331
- ### Cloud Step 12: Deployment summary
332
-
333
- Print a clear summary:
334
-
335
- ```
336
- Cloud deploy complete!
111
+ ```text
112
+ Cloud deploy complete
337
113
 
338
- Mode: cloud
339
- Type check: passed
340
- Build: .vercel/output/ (N files)
341
- Portals: N active
342
114
  Status: live
343
- Health: OK (200)
344
-
345
- Portal URL: https://orgslug.showpane.com
346
115
  Deploy ID: dep_xxxxxxxxxxxx
116
+ Live URL: https://orgslug.showpane.com
117
+ Portals: N
118
+ File sync: N
347
119
  ```
348
120
 
349
- ### Cloud Step 13: Record deployment
121
+ ### Cloud Step 4: Record deployment
350
122
 
351
123
  Log the cloud deployment for operational memory:
352
124
 
353
125
  ```bash
354
- 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"
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"
355
129
  ```
356
130
 
357
- ### Cloud Step 14: Clean up
358
-
359
- Remove the temporary artifact:
360
-
361
- ```bash
362
- rm -f "$ARTIFACT_PATH"
363
- rm -f "$RUNTIME_DATA_PATH"
364
- rm -f "$FILE_MANIFEST_PATH"
365
- rm -f "$FINALIZE_PAYLOAD"
366
- [ -n "${TMP_SYNC_DIR:-}" ] && rm -rf "$TMP_SYNC_DIR"
367
- ```
368
-
369
- ---
370
-
371
- ## Post-Deploy Verification
372
-
373
- After deployment succeeds, automatically run these verification steps. Do not ask the user — just do them.
374
-
375
- ### Step V1: DNS/URL Check
376
- Fetch the portal URL and verify it returns 200:
377
- ```bash
378
- PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
379
- echo "Verifying $PORTAL_URL..."
380
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
381
- echo "HTTP status: $HTTP_CODE"
382
- ```
383
- If not 200, warn but don't fail — DNS propagation may take time for cloud deploys.
384
-
385
- ### Step V2: Portal Content Check
386
- If portals exist in the database, verify at least one portal login page loads:
387
- ```bash
388
- # Get the first active portal slug
389
- FIRST_SLUG=$(cd "$APP_PATH" && npx tsx -e "
390
- const { prisma } = require('./src/lib/db');
391
- prisma.clientPortal.findFirst({ where: { isActive: true }, select: { slug: true } })
392
- .then(p => console.log(p?.slug || ''))
393
- .finally(() => prisma.\$disconnect());
394
- " 2>/dev/null)
395
- if [ -n "$FIRST_SLUG" ]; then
396
- echo "Checking portal: $PORTAL_URL/client/$FIRST_SLUG"
397
- curl -s -o /dev/null -w "Portal login page: %{http_code}\n" "$PORTAL_URL/client/$FIRST_SLUG" 2>/dev/null
398
- fi
399
- ```
400
-
401
- ### Step V3: SSL Check (cloud only)
402
- For cloud deploys, verify SSL is working:
403
- ```bash
404
- if [ "$DEPLOY_MODE" = "cloud" ] && [ -n "$CLOUD_PORTAL_URL" ]; then
405
- echo "Checking SSL..."
406
- SSL_OK=$(curl -s -o /dev/null -w "%{http_code}" "https://${CLOUD_ORG_SLUG}.showpane.com" 2>/dev/null)
407
- echo "SSL status: $SSL_OK"
408
- fi
409
- ```
410
-
411
- ### Step V4: Summary
412
- Print a deployment summary:
413
- ```
414
- ✓ Deployed successfully
415
- URL: [portal URL]
416
- Portals: [count]
417
- SSL: [ok/pending]
418
-
419
- Next: /portal-status for ongoing monitoring
420
- ```
131
+ The deploy command already handles cleanup and hosted verification. Do not duplicate those steps here.
421
132
 
422
133
  ---
423
134
 
@@ -441,7 +152,7 @@ If the health endpoint returns non-200 after deploy:
441
152
  If the Showpane Cloud API returns 401/403 during pre-flight or publish:
442
153
  - **401 Unauthorized**: The access token is expired or revoked. Run `showpane login` to re-authenticate.
443
154
  - **403 Forbidden**: The token does not belong to an org that can publish. Re-authenticate or finish cloud onboarding.
444
- - **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`.
155
+ - **409 organization_required**: Tell the user to run `showpane login` again and let that browser flow continue. Do not manually redirect them into separate checkout steps from this skill.
445
156
  - **409 organization_not_ready**: The org exists, but billing or provisioning is not publish-ready. Surface the returned `reason`, `nextAction`, and settings/checkout URL directly instead of collapsing it into a generic deploy failure.
446
157
 
447
158
  ### Cloud: Artifact upload failure
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.22",
3
+ "version": "0.4.24",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {