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-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|