showpane 0.4.12 → 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-09T22:36:53.007Z",
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": "fe8e491e62fb2a84de52cdc1154d1451083f93bbccf1c5e65b42810d007eecc2",
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 mockedPrisma = vi.mocked(prisma);
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
- mockedPrisma.organization.findFirst.mockResolvedValue({
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(mockedPrisma.organization.findFirst).toHaveBeenCalledWith({
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
- mockedPrisma.organization.findFirst.mockResolvedValue(null);
41
+ mockedFindFirst.mockResolvedValue(null);
42
42
 
43
43
  const result = await resolveDefaultOrganizationId();
44
44
 
@@ -1,10 +1,97 @@
1
- import { createDeployBundle } from "../app/src/lib/deploy-bundle";
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 { fileCount } = createDeployBundle(appPath, outputPath);
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 2>&1
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
- if [ -n "$ORG_SLUG" ]; then
171
- 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"
172
- else
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
- if [ -n "$ORG_SLUG" ]; then
187
- 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"
188
- else
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
 
@@ -283,10 +277,15 @@ if [ -z "$DEPLOY_ID" ] || [ -z "$ARTIFACT_UPLOAD_URL" ]; then
283
277
  exit 1
284
278
  fi
285
279
 
286
- curl -s -X PUT "$ARTIFACT_UPLOAD_URL" \
280
+ UPLOAD_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$ARTIFACT_UPLOAD_URL" \
287
281
  -H "Content-Type: application/zip" \
288
282
  --data-binary @"$ARTIFACT_PATH" \
289
- >/dev/null
283
+ )
284
+
285
+ if [ "$UPLOAD_STATUS" != "200" ]; then
286
+ echo "ERROR: Artifact upload failed with HTTP $UPLOAD_STATUS"
287
+ exit 1
288
+ fi
290
289
  ```
291
290
 
292
291
  ### Cloud Step 9: Finalize the deployment
@@ -526,6 +525,7 @@ echo '{"skill":"portal-deploy","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H
526
525
  - If any pre-flight check fails (type errors, missing deploy config), stop and explain
527
526
  - Show the full deployment summary with portal count, migration status, and health
528
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
529
529
  - For Cloud deploys: build locally, upload the artifact to Showpane Cloud, and let the control plane publish it
530
530
  - For Cloud deploys: upload the artifact directly to storage using the presigned URL, not through the deployment function body
531
531
  - For Cloud deploys: always wait for the deployment to reach `live` before declaring success
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "showpane",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
4
4
  "description": "CLI for Showpane — AI-generated client portals",
5
5
  "type": "module",
6
6
  "bin": {