showpane 0.4.11 → 0.4.13
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"generatedAt": "2026-04-09T23:30:19.010Z",
|
|
4
4
|
"scaffoldVersion": "0.2.4",
|
|
5
5
|
"files": {
|
|
6
6
|
".env.example": "ed105f2bdcd1888a98181d55e3c9f7d6eff3ae9c3e2366c2e777a12e3caddfa7",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"scripts/prisma-db-push.mjs": "76ac85fe65b5dc3d9cc7432e44618fcc84b7443574c8d88198d01f13ac23c040",
|
|
20
20
|
"scripts/prisma-generate.mjs": "d371e63388fa39f963b7c3c7cb8f87e0d9cd43cbf69d254b999108e29b8738c8",
|
|
21
21
|
"scripts/prisma-schema.mjs": "0a86cc1b5f84120948aed8f97a84f2d5b173f91a43ea34ad6767441894121d83",
|
|
22
|
-
"src/__tests__/client-portals.test.ts": "
|
|
22
|
+
"src/__tests__/client-portals.test.ts": "9c3236bf0f7190b7d5ba9082287dcb29bc00d28dd63782a89505125ead06c624",
|
|
23
23
|
"src/__tests__/deploy-bundle.test.ts": "abd3216170f306c09df6abb0d2afad966a5741e8859f25a310a0a09693d37609",
|
|
24
24
|
"src/__tests__/portal-contracts.test.ts": "80066377d3281786c2bb9ecc857514124e094a2e66dca2fb08ded994c25fa2bc",
|
|
25
25
|
"src/app/(portal)/client/[slug]/page.tsx": "4f2f9253b2ad5d37a0f13759db52c786ae9c401f50fae9431da1417e9736e000",
|
|
@@ -16,7 +16,7 @@ vi.mock("@/lib/db", () => ({
|
|
|
16
16
|
import { resolveDefaultOrganizationId } from "@/lib/client-portals";
|
|
17
17
|
import { prisma } from "@/lib/db";
|
|
18
18
|
|
|
19
|
-
const
|
|
19
|
+
const mockedFindFirst = vi.mocked(prisma.organization.findFirst);
|
|
20
20
|
|
|
21
21
|
describe("resolveDefaultOrganizationId", () => {
|
|
22
22
|
beforeEach(() => {
|
|
@@ -24,21 +24,21 @@ describe("resolveDefaultOrganizationId", () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it("falls back to first org in DB", async () => {
|
|
27
|
-
|
|
27
|
+
mockedFindFirst.mockResolvedValue({
|
|
28
28
|
id: "local-org-1",
|
|
29
29
|
} as never);
|
|
30
30
|
|
|
31
31
|
const result = await resolveDefaultOrganizationId();
|
|
32
32
|
|
|
33
33
|
expect(result).toBe("local-org-1");
|
|
34
|
-
expect(
|
|
34
|
+
expect(mockedFindFirst).toHaveBeenCalledWith({
|
|
35
35
|
select: { id: true },
|
|
36
36
|
orderBy: { createdAt: "asc" },
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it("returns null when no orgs exist in DB", async () => {
|
|
41
|
-
|
|
41
|
+
mockedFindFirst.mockResolvedValue(null);
|
|
42
42
|
|
|
43
43
|
const result = await resolveDefaultOrganizationId();
|
|
44
44
|
|
|
@@ -1,10 +1,97 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AdmZip from "adm-zip";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
statSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
2
10
|
|
|
3
11
|
function fail(message: string): never {
|
|
4
12
|
console.error(JSON.stringify({ ok: false, error: message }));
|
|
5
13
|
process.exit(1);
|
|
6
14
|
}
|
|
7
15
|
|
|
16
|
+
function walkFiles(dir: string, root: string, out: Set<string>) {
|
|
17
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
18
|
+
const fullPath = path.join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
walkFiles(fullPath, root, out);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
out.add(path.relative(root, fullPath));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectTracedFiles(appPath: string): Set<string> {
|
|
28
|
+
const files = new Set<string>();
|
|
29
|
+
const outputRoot = path.join(appPath, ".vercel", "output");
|
|
30
|
+
const functionsRoot = path.join(outputRoot, "functions");
|
|
31
|
+
|
|
32
|
+
walkFiles(outputRoot, appPath, files);
|
|
33
|
+
|
|
34
|
+
const queue = [functionsRoot];
|
|
35
|
+
while (queue.length > 0) {
|
|
36
|
+
const current = queue.pop();
|
|
37
|
+
if (!current) continue;
|
|
38
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
39
|
+
const fullPath = path.join(current, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
queue.push(fullPath);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (entry.name !== ".vc-config.json") continue;
|
|
45
|
+
|
|
46
|
+
const config = JSON.parse(readFileSync(fullPath, "utf8")) as {
|
|
47
|
+
filePathMap?: Record<string, string>;
|
|
48
|
+
};
|
|
49
|
+
for (const relativePath of Object.values(config.filePathMap ?? {})) {
|
|
50
|
+
files.add(relativePath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return files;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addPathToZip(zip: AdmZip, sourcePath: string, zipPath: string): number {
|
|
59
|
+
const zipPathPosix = zipPath.replace(/\\/g, "/");
|
|
60
|
+
|
|
61
|
+
if (zipPathPosix === ".env" || zipPathPosix.startsWith(".env.")) {
|
|
62
|
+
zip.addFile(zipPathPosix, Buffer.from("NODE_ENV=production\n"));
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const entry = lstatSync(sourcePath, { throwIfNoEntry: false });
|
|
67
|
+
if (!entry) {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (entry.isSymbolicLink()) {
|
|
72
|
+
return addPathToZip(zip, realpathSync(sourcePath), zipPathPosix);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
let count = 0;
|
|
77
|
+
for (const child of readdirSync(sourcePath, { withFileTypes: true })) {
|
|
78
|
+
count += addPathToZip(
|
|
79
|
+
zip,
|
|
80
|
+
path.join(sourcePath, child.name),
|
|
81
|
+
path.posix.join(zipPathPosix, child.name),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return count;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!entry.isFile() && !statSync(sourcePath, { throwIfNoEntry: false })?.isFile()) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
zip.addLocalFile(sourcePath, path.posix.dirname(zipPathPosix), path.posix.basename(zipPathPosix));
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
8
95
|
async function main() {
|
|
9
96
|
const args = process.argv.slice(2);
|
|
10
97
|
const outputIndex = args.indexOf("--output");
|
|
@@ -15,7 +102,20 @@ async function main() {
|
|
|
15
102
|
}
|
|
16
103
|
|
|
17
104
|
const appPath = process.cwd();
|
|
18
|
-
const
|
|
105
|
+
const outputRoot = path.join(appPath, ".vercel", "output");
|
|
106
|
+
if (!statSync(outputRoot, { throwIfNoEntry: false })?.isDirectory()) {
|
|
107
|
+
fail("Missing .vercel/output. Run `npm run cloud:build` first.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const zip = new AdmZip();
|
|
111
|
+
const tracedFiles = collectTracedFiles(appPath);
|
|
112
|
+
let fileCount = 0;
|
|
113
|
+
|
|
114
|
+
for (const relativePath of tracedFiles) {
|
|
115
|
+
fileCount += addPathToZip(zip, path.join(appPath, relativePath), relativePath);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
zip.writeZip(outputPath);
|
|
19
119
|
console.log(JSON.stringify({ ok: true, outputPath, fileCount }));
|
|
20
120
|
}
|
|
21
121
|
|
|
@@ -110,7 +110,7 @@ Expected: HTTP 200. If Showpane Cloud is unreachable, stop and show the error. T
|
|
|
110
110
|
Run the standard type check:
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
|
-
cd "$APP_PATH" && npx tsc --noEmit
|
|
113
|
+
cd "$APP_PATH" && npx tsc --noEmit
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
If type errors are found, display them and stop. Offer to fix simple issues (missing imports, typos).
|
|
@@ -167,12 +167,9 @@ Export the current local portal-runtime state so Showpane Cloud can sync credent
|
|
|
167
167
|
```bash
|
|
168
168
|
RUNTIME_DATA_PATH="/tmp/showpane-runtime-${CLOUD_ORG_SLUG:-portal}.json"
|
|
169
169
|
rm -f "$RUNTIME_DATA_PATH"
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
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"
|
|
174
|
-
fi
|
|
175
|
-
test -f "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
|
|
170
|
+
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" \
|
|
171
|
+
|| { echo "ERROR: Runtime payload export failed"; exit 1; }
|
|
172
|
+
test -s "$RUNTIME_DATA_PATH" || { echo "ERROR: Runtime payload was not created"; exit 1; }
|
|
176
173
|
echo "Runtime payload ready: $RUNTIME_DATA_PATH"
|
|
177
174
|
```
|
|
178
175
|
|
|
@@ -183,12 +180,9 @@ Export uploaded document metadata and checksums so Showpane Cloud can determine
|
|
|
183
180
|
```bash
|
|
184
181
|
FILE_MANIFEST_PATH="/tmp/showpane-files-${CLOUD_ORG_SLUG:-portal}.json"
|
|
185
182
|
rm -f "$FILE_MANIFEST_PATH"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
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"
|
|
190
|
-
fi
|
|
191
|
-
test -f "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
|
|
183
|
+
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" \
|
|
184
|
+
|| { echo "ERROR: File manifest export failed"; exit 1; }
|
|
185
|
+
test -s "$FILE_MANIFEST_PATH" || { echo "ERROR: File manifest was not created"; exit 1; }
|
|
192
186
|
echo "File manifest ready: $FILE_MANIFEST_PATH"
|
|
193
187
|
```
|
|
194
188
|
|
|
@@ -247,41 +241,90 @@ for item in json.load(sys.stdin).get(\"missing\", []):
|
|
|
247
241
|
fi
|
|
248
242
|
```
|
|
249
243
|
|
|
250
|
-
### Cloud Step 7:
|
|
244
|
+
### Cloud Step 7: Start the staged deployment
|
|
251
245
|
|
|
252
246
|
```bash
|
|
253
247
|
PORTAL_COUNT=$(cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" \
|
|
254
248
|
| python3 -c "import sys,json; print(len(json.load(sys.stdin).get('portals', [])))")
|
|
255
249
|
|
|
256
|
-
|
|
250
|
+
INIT_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments" \
|
|
257
251
|
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-F "app_path=$APP_PATH" \
|
|
261
|
-
-F "portalCount=$PORTAL_COUNT" \
|
|
262
|
-
-F "runtimeData=@$RUNTIME_DATA_PATH;type=application/json")
|
|
252
|
+
-H "Content-Type: application/json" \
|
|
253
|
+
--data-binary '{}')
|
|
263
254
|
|
|
264
|
-
echo "$
|
|
255
|
+
echo \"$INIT_RESPONSE\" | python3 -c "
|
|
265
256
|
import sys, json
|
|
266
257
|
d = json.load(sys.stdin)
|
|
267
258
|
if 'error' in d:
|
|
268
259
|
print('ERROR: ' + str(d['error']))
|
|
269
260
|
sys.exit(1)
|
|
270
|
-
print('Deployment
|
|
271
|
-
print('ID: ' + d.get('deploymentId',
|
|
261
|
+
print('Deployment initialized')
|
|
262
|
+
print('ID: ' + d.get('deploymentId', 'unknown'))
|
|
272
263
|
print('Status: ' + d.get('status', 'unknown'))
|
|
273
264
|
"
|
|
274
265
|
```
|
|
275
266
|
|
|
276
|
-
Extract the deployment ID from the response. If the API returns an error, show it and stop.
|
|
267
|
+
Extract the deployment ID and the presigned artifact upload URL from the response. If the API returns an error, show it and stop.
|
|
277
268
|
|
|
278
|
-
### Cloud Step 8:
|
|
269
|
+
### Cloud Step 8: Upload the artifact directly to storage
|
|
279
270
|
|
|
280
|
-
|
|
271
|
+
```bash
|
|
272
|
+
DEPLOY_ID=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('deploymentId',''))")
|
|
273
|
+
ARTIFACT_UPLOAD_URL=$(echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('artifactUploadUrl',''))")
|
|
274
|
+
|
|
275
|
+
if [ -z "$DEPLOY_ID" ] || [ -z "$ARTIFACT_UPLOAD_URL" ]; then
|
|
276
|
+
echo "ERROR: Missing deploymentId or artifact upload URL"
|
|
277
|
+
exit 1
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
UPLOAD_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$ARTIFACT_UPLOAD_URL" \
|
|
281
|
+
-H "Content-Type: application/zip" \
|
|
282
|
+
--data-binary @"$ARTIFACT_PATH" \
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if [ "$UPLOAD_STATUS" != "200" ]; then
|
|
286
|
+
echo "ERROR: Artifact upload failed with HTTP $UPLOAD_STATUS"
|
|
287
|
+
exit 1
|
|
288
|
+
fi
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Cloud Step 9: Finalize the deployment
|
|
281
292
|
|
|
282
293
|
```bash
|
|
283
|
-
|
|
294
|
+
FINALIZE_PAYLOAD="/tmp/showpane-deploy-finalize-${DEPLOY_ID}.json"
|
|
295
|
+
python3 - <<'PY' "$RUNTIME_DATA_PATH" "$PORTAL_COUNT" > "$FINALIZE_PAYLOAD"
|
|
296
|
+
import json, sys
|
|
297
|
+
runtime_path = sys.argv[1]
|
|
298
|
+
portal_count = int(sys.argv[2])
|
|
299
|
+
payload = {
|
|
300
|
+
"portalCount": portal_count,
|
|
301
|
+
"runtimeData": json.load(open(runtime_path)),
|
|
302
|
+
}
|
|
303
|
+
print(json.dumps(payload))
|
|
304
|
+
PY
|
|
305
|
+
|
|
306
|
+
FINALIZE_RESPONSE=$(curl -s -X POST "$CLOUD_API_BASE/api/deployments/$DEPLOY_ID/finalize" \
|
|
307
|
+
-H "Authorization: Bearer $CLOUD_API_TOKEN" \
|
|
308
|
+
-H "Content-Type: application/json" \
|
|
309
|
+
--data-binary @"$FINALIZE_PAYLOAD")
|
|
310
|
+
|
|
311
|
+
echo "$FINALIZE_RESPONSE" | python3 -c "
|
|
312
|
+
import sys, json
|
|
313
|
+
d = json.load(sys.stdin)
|
|
314
|
+
if 'error' in d:
|
|
315
|
+
print('ERROR: ' + str(d['error']))
|
|
316
|
+
sys.exit(1)
|
|
317
|
+
print('Deployment accepted')
|
|
318
|
+
print('ID: ' + d.get('deploymentId', 'unknown'))
|
|
319
|
+
print('Status: ' + d.get('status', 'unknown'))
|
|
320
|
+
"
|
|
321
|
+
```
|
|
284
322
|
|
|
323
|
+
### Cloud Step 10: Wait for cloud publish to finish
|
|
324
|
+
|
|
325
|
+
Poll Showpane Cloud until the deployment reaches `live` or `failed`:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
285
328
|
echo "Waiting for deployment to go live..."
|
|
286
329
|
for i in $(seq 1 60); do
|
|
287
330
|
sleep 5
|
|
@@ -314,7 +357,7 @@ fi
|
|
|
314
357
|
|
|
315
358
|
The publish typically takes 15-60 seconds. Poll every 5 seconds for up to 5 minutes.
|
|
316
359
|
|
|
317
|
-
### Cloud Step
|
|
360
|
+
### Cloud Step 11: Post-deploy verification
|
|
318
361
|
|
|
319
362
|
Once the deployment is live, verify the portal is accessible:
|
|
320
363
|
|
|
@@ -333,7 +376,7 @@ Also check the API health endpoint:
|
|
|
333
376
|
curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL/api/health"
|
|
334
377
|
```
|
|
335
378
|
|
|
336
|
-
### Cloud Step
|
|
379
|
+
### Cloud Step 12: Deployment summary
|
|
337
380
|
|
|
338
381
|
Print a clear summary:
|
|
339
382
|
|
|
@@ -351,7 +394,7 @@ Cloud deploy complete!
|
|
|
351
394
|
Deploy ID: dep_xxxxxxxxxxxx
|
|
352
395
|
```
|
|
353
396
|
|
|
354
|
-
### Cloud Step
|
|
397
|
+
### Cloud Step 13: Record deployment
|
|
355
398
|
|
|
356
399
|
Log the cloud deployment for operational memory:
|
|
357
400
|
|
|
@@ -359,7 +402,7 @@ Log the cloud deployment for operational memory:
|
|
|
359
402
|
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"
|
|
360
403
|
```
|
|
361
404
|
|
|
362
|
-
### Cloud Step
|
|
405
|
+
### Cloud Step 14: Clean up
|
|
363
406
|
|
|
364
407
|
Remove the temporary artifact:
|
|
365
408
|
|
|
@@ -367,6 +410,7 @@ Remove the temporary artifact:
|
|
|
367
410
|
rm -f "$ARTIFACT_PATH"
|
|
368
411
|
rm -f "$RUNTIME_DATA_PATH"
|
|
369
412
|
rm -f "$FILE_MANIFEST_PATH"
|
|
413
|
+
rm -f "$FINALIZE_PAYLOAD"
|
|
370
414
|
[ -n "${TMP_SYNC_DIR:-}" ] && rm -rf "$TMP_SYNC_DIR"
|
|
371
415
|
```
|
|
372
416
|
|
|
@@ -448,9 +492,9 @@ If the Showpane Cloud API returns 401/403 during pre-flight or publish:
|
|
|
448
492
|
- **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`.
|
|
449
493
|
|
|
450
494
|
### Cloud: Artifact upload failure
|
|
451
|
-
If the
|
|
452
|
-
- **400 Bad Request**: The
|
|
453
|
-
- **
|
|
495
|
+
If the deployment init or finalize call fails:
|
|
496
|
+
- **400 Bad Request**: The runtime payload is missing or malformed. Rebuild locally and retry.
|
|
497
|
+
- **409 Conflict**: The deployment was not initialized, the artifact was not uploaded yet, or the org has no hosted project provisioned.
|
|
454
498
|
- **422 Validation Error**: The cloud control plane rejected the deploy metadata. Show the response body directly.
|
|
455
499
|
|
|
456
500
|
### Cloud: File sync failure
|
|
@@ -481,7 +525,9 @@ echo '{"skill":"portal-deploy","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H
|
|
|
481
525
|
- If any pre-flight check fails (type errors, missing deploy config), stop and explain
|
|
482
526
|
- Show the full deployment summary with portal count, migration status, and health
|
|
483
527
|
- If this is the first deploy, suggest running `/portal credentials` for all portals before deploying so clients can actually log in
|
|
528
|
+
- For Cloud deploys: use the local workspace's current SQLite org for runtime exports, not the cloud org slug from config
|
|
484
529
|
- For Cloud deploys: build locally, upload the artifact to Showpane Cloud, and let the control plane publish it
|
|
530
|
+
- For Cloud deploys: upload the artifact directly to storage using the presigned URL, not through the deployment function body
|
|
485
531
|
- For Cloud deploys: always wait for the deployment to reach `live` before declaring success
|
|
486
532
|
- For Cloud deploys: the portal URL is `https://{org}.showpane.com` — verify it returns 200 after deploy
|
|
487
533
|
- For Cloud deploys: clean up the temporary artifact after deploy completes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "showpane",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "CLI for Showpane
|
|
3
|
+
"version": "0.4.13",
|
|
4
|
+
"description": "CLI for Showpane — AI-generated client portals",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"showpane": "./dist/index.js"
|