showpane 0.4.9 → 0.4.11

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.
Files changed (49) hide show
  1. package/bundle/meta/scaffold-manifest.json +17 -24
  2. package/bundle/scaffold/VERSION +1 -1
  3. package/bundle/scaffold/__dot__env.example +3 -5
  4. package/bundle/scaffold/package.json +2 -1
  5. package/bundle/scaffold/prisma/schema.local.prisma +1 -1
  6. package/bundle/scaffold/prisma/seed.ts +6 -1
  7. package/bundle/scaffold/prisma.config.ts +5 -0
  8. package/bundle/scaffold/scripts/prisma-schema.mjs +1 -53
  9. package/bundle/scaffold/src/__tests__/client-portals.test.ts +4 -37
  10. package/bundle/scaffold/src/__tests__/deploy-bundle.test.ts +48 -0
  11. package/bundle/scaffold/src/app/api/client-auth/route.ts +1 -1
  12. package/bundle/scaffold/src/lib/client-portals.ts +8 -13
  13. package/bundle/scaffold/src/lib/deploy-bundle.ts +106 -0
  14. package/bundle/scaffold/src/lib/portal-contracts.ts +33 -0
  15. package/bundle/scaffold/src/lib/runtime-state.ts +2 -32
  16. package/bundle/scaffold/src/types/adm-zip.d.ts +15 -0
  17. package/bundle/toolchain/VERSION +1 -1
  18. package/bundle/toolchain/bin/create-deploy-bundle.ts +3 -72
  19. package/bundle/toolchain/bin/ensure-cloud-project-link.ts +73 -0
  20. package/bundle/toolchain/skills/portal-analytics/SKILL.md +2 -2
  21. package/bundle/toolchain/skills/portal-create/SKILL.md +2 -2
  22. package/bundle/toolchain/skills/portal-credentials/SKILL.md +3 -3
  23. package/bundle/toolchain/skills/portal-delete/SKILL.md +4 -4
  24. package/bundle/toolchain/skills/portal-deploy/SKILL.md +32 -264
  25. package/bundle/toolchain/skills/portal-dev/SKILL.md +15 -13
  26. package/bundle/toolchain/skills/portal-list/SKILL.md +2 -2
  27. package/bundle/toolchain/skills/portal-onboard/SKILL.md +2 -2
  28. package/bundle/toolchain/skills/portal-preview/SKILL.md +9 -23
  29. package/bundle/toolchain/skills/portal-setup/SKILL.md +19 -28
  30. package/bundle/toolchain/skills/portal-share/SKILL.md +3 -4
  31. package/bundle/toolchain/skills/portal-status/SKILL.md +2 -2
  32. package/bundle/toolchain/skills/portal-update/SKILL.md +2 -2
  33. package/bundle/toolchain/skills/portal-upgrade/SKILL.md +2 -2
  34. package/bundle/toolchain/skills/portal-verify/SKILL.md +21 -33
  35. package/bundle/toolchain/skills/shared/bin/check-portal-guard.sh +1 -5
  36. package/bundle/toolchain/skills/shared/preamble.md +2 -2
  37. package/dist/index.js +56 -10
  38. package/package.json +1 -1
  39. package/bundle/scaffold/docker/Caddyfile +0 -3
  40. package/bundle/scaffold/docker/Dockerfile +0 -30
  41. package/bundle/scaffold/docker-compose.yml +0 -53
  42. package/bundle/scaffold/prisma/migrations/20260408000000_init/migration.sql +0 -143
  43. package/bundle/scaffold/prisma/migrations/20260408010000_add_visitor_tracking/migration.sql +0 -6
  44. package/bundle/scaffold/prisma/migrations/20260409040000_add_portal_file_checksum/migration.sql +0 -2
  45. package/bundle/scaffold/prisma/migrations/migration_lock.toml +0 -3
  46. package/bundle/scaffold/prisma/schema.prisma +0 -128
  47. package/bundle/scaffold/scripts/backup.sh +0 -19
  48. package/bundle/scaffold/scripts/e2e-verify.sh +0 -487
  49. package/bundle/scaffold/scripts/restore.sh +0 -31
@@ -1,54 +1,10 @@
1
- import AdmZip from "adm-zip";
2
- import { readFileSync, readdirSync, statSync } from "node:fs";
3
- import path from "node:path";
1
+ import { createDeployBundle } from "../app/src/lib/deploy-bundle";
4
2
 
5
3
  function fail(message: string): never {
6
4
  console.error(JSON.stringify({ ok: false, error: message }));
7
5
  process.exit(1);
8
6
  }
9
7
 
10
- function walkFiles(dir: string, root: string, out: Set<string>) {
11
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
12
- const fullPath = path.join(dir, entry.name);
13
- if (entry.isDirectory()) {
14
- walkFiles(fullPath, root, out);
15
- continue;
16
- }
17
- out.add(path.relative(root, fullPath));
18
- }
19
- }
20
-
21
- function collectTracedFiles(appPath: string): Set<string> {
22
- const files = new Set<string>();
23
- const outputRoot = path.join(appPath, ".vercel", "output");
24
- const functionsRoot = path.join(outputRoot, "functions");
25
-
26
- walkFiles(outputRoot, appPath, files);
27
-
28
- const queue = [functionsRoot];
29
- while (queue.length > 0) {
30
- const current = queue.pop();
31
- if (!current) continue;
32
- for (const entry of readdirSync(current, { withFileTypes: true })) {
33
- const fullPath = path.join(current, entry.name);
34
- if (entry.isDirectory()) {
35
- queue.push(fullPath);
36
- continue;
37
- }
38
- if (entry.name !== ".vc-config.json") continue;
39
-
40
- const config = JSON.parse(readFileSync(fullPath, "utf8")) as {
41
- filePathMap?: Record<string, string>;
42
- };
43
- for (const relativePath of Object.values(config.filePathMap ?? {})) {
44
- files.add(relativePath);
45
- }
46
- }
47
- }
48
-
49
- return files;
50
- }
51
-
52
8
  async function main() {
53
9
  const args = process.argv.slice(2);
54
10
  const outputIndex = args.indexOf("--output");
@@ -59,33 +15,8 @@ async function main() {
59
15
  }
60
16
 
61
17
  const appPath = process.cwd();
62
- const outputRoot = path.join(appPath, ".vercel", "output");
63
- if (!statSync(outputRoot, { throwIfNoEntry: false })?.isDirectory()) {
64
- fail("Missing .vercel/output. Run a prebuilt Vercel build first.");
65
- }
66
-
67
- const zip = new AdmZip();
68
- const tracedFiles = collectTracedFiles(appPath);
69
-
70
- for (const relativePath of tracedFiles) {
71
- const normalized = relativePath.replace(/\\/g, "/");
72
-
73
- if (normalized === ".env" || normalized.startsWith(".env.")) {
74
- zip.addFile(normalized, Buffer.from("NODE_ENV=production\n"));
75
- continue;
76
- }
77
-
78
- const fullPath = path.join(appPath, relativePath);
79
- const stat = statSync(fullPath, { throwIfNoEntry: false });
80
- if (!stat?.isFile()) {
81
- continue;
82
- }
83
-
84
- zip.addLocalFile(fullPath, path.dirname(normalized), path.basename(normalized));
85
- }
86
-
87
- zip.writeZip(outputPath);
88
- console.log(JSON.stringify({ ok: true, outputPath, fileCount: tracedFiles.size }));
18
+ const { fileCount } = createDeployBundle(appPath, outputPath);
19
+ console.log(JSON.stringify({ ok: true, outputPath, fileCount }));
89
20
  }
90
21
 
91
22
  main().catch((error) => {
@@ -0,0 +1,73 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ type ShowpaneConfig = {
5
+ accessToken?: string;
6
+ };
7
+
8
+ type CloudProjectLink = {
9
+ projectId: string;
10
+ orgId: string;
11
+ projectName: string;
12
+ settings: {
13
+ createdAt: number;
14
+ framework: string;
15
+ devCommand: string | null;
16
+ installCommand: string | null;
17
+ buildCommand: string | null;
18
+ outputDirectory: string | null;
19
+ rootDirectory: string | null;
20
+ directoryListing: boolean;
21
+ nodeVersion: string;
22
+ };
23
+ };
24
+
25
+ function fail(message: string): never {
26
+ console.error(JSON.stringify({ ok: false, error: message }));
27
+ process.exit(1);
28
+ }
29
+
30
+ function getConfig(): ShowpaneConfig {
31
+ const configPath = path.join(process.env.HOME || "", ".showpane", "config.json");
32
+ if (!fs.existsSync(configPath)) {
33
+ fail("Showpane not configured");
34
+ }
35
+
36
+ return JSON.parse(fs.readFileSync(configPath, "utf8")) as ShowpaneConfig;
37
+ }
38
+
39
+ async function main() {
40
+ const config = getConfig();
41
+ if (!config.accessToken) {
42
+ fail("Missing cloud access token. Run showpane login.");
43
+ }
44
+
45
+ const apiBase = process.env.SHOWPANE_CLOUD_URL || "https://app.showpane.com";
46
+ const res = await fetch(`${apiBase}/api/cli/project-link`, {
47
+ headers: {
48
+ Authorization: `Bearer ${config.accessToken}`,
49
+ },
50
+ });
51
+
52
+ if (!res.ok) {
53
+ fail(`Could not fetch cloud project link (${res.status})`);
54
+ }
55
+
56
+ const projectLink = await res.json() as CloudProjectLink;
57
+ const vercelDir = path.join(process.cwd(), ".vercel");
58
+ fs.mkdirSync(vercelDir, { recursive: true });
59
+ fs.writeFileSync(
60
+ path.join(vercelDir, "project.json"),
61
+ `${JSON.stringify(projectLink, null, 2)}\n`,
62
+ );
63
+
64
+ console.log(JSON.stringify({
65
+ ok: true,
66
+ path: path.join(vercelDir, "project.json"),
67
+ projectId: projectLink.projectId,
68
+ }));
69
+ }
70
+
71
+ main().catch((error) => {
72
+ fail(error instanceof Error ? error.message : String(error));
73
+ });
@@ -17,7 +17,7 @@ if [ ! -f "$CONFIG" ]; then
17
17
  exit 1
18
18
  fi
19
19
  APP_PATH=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('app_path',''))" 2>/dev/null)
20
- DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','docker'))" 2>/dev/null)
20
+ DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','local'))" 2>/dev/null)
21
21
  ORG_SLUG=$(cat "$CONFIG" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
22
22
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
23
23
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
@@ -34,7 +34,7 @@ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
34
34
 
35
35
  # Predictive next-skill suggestion
36
36
  if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
37
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
37
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
38
38
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
39
39
  fi
40
40
 
@@ -19,7 +19,7 @@ if [ ! -f "$CONFIG" ]; then
19
19
  exit 1
20
20
  fi
21
21
  APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
22
- DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
22
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null)
23
23
  ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
24
24
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
25
25
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
@@ -36,7 +36,7 @@ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
36
36
 
37
37
  # Predictive next-skill suggestion
38
38
  if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
39
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
39
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
40
40
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
41
41
  fi
42
42
 
@@ -26,7 +26,7 @@ if [ ! -f "$CONFIG" ]; then
26
26
  exit 1
27
27
  fi
28
28
  APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
29
- DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
29
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null)
30
30
  ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
31
31
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
32
32
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
@@ -43,7 +43,7 @@ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
43
43
 
44
44
  # Predictive next-skill suggestion
45
45
  if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
46
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
46
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
47
47
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
48
48
  fi
49
49
 
@@ -128,7 +128,7 @@ The script returns JSON on stdout:
128
128
  If the script returns an error, handle it based on the error type:
129
129
 
130
130
  - `portal_not_found` — The portal slug doesn't exist in the database for this org. Suggest running `/portal create <slug>` first.
131
- - `database_error` — Connection or query failure. Check DATABASE_URL, verify the database is running, and check that migrations are up to date (`cd $APP_PATH && npx prisma migrate deploy`).
131
+ - `database_error` — Connection or query failure. Check DATABASE_URL and re-apply the local schema with `cd $APP_PATH && npm run prisma:db-push`.
132
132
  - `auth_secret_missing` — The AUTH_SECRET environment variable is not set in `$APP_PATH/.env`. The rotate-credentials script needs this to function. Suggest adding `AUTH_SECRET=<random-string>` to the .env file.
133
133
 
134
134
  If the script exits with a non-zero code but produces no JSON output, check stderr for Prisma or Node.js errors and relay them to the user.
@@ -27,7 +27,7 @@ if [ ! -f "$CONFIG" ]; then
27
27
  exit 1
28
28
  fi
29
29
  APP_PATH=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('app_path',''))" 2>/dev/null)
30
- DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','docker'))" 2>/dev/null)
30
+ DEPLOY_MODE=$(cat "$CONFIG" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('deploy_mode','local'))" 2>/dev/null)
31
31
  ORG_SLUG=$(cat "$CONFIG" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
32
32
  APP_PATH="${SHOWPANE_APP_PATH:-$APP_PATH}"
33
33
  if [ -f "$APP_PATH/.env" ]; then set -a && source "$APP_PATH/.env" && set +a; fi
@@ -44,7 +44,7 @@ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
44
44
 
45
45
  # Predictive next-skill suggestion
46
46
  if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
47
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
47
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
48
48
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
49
49
  fi
50
50
 
@@ -80,7 +80,7 @@ mention them unless they directly affect the current task.
80
80
  ## Safety Guard
81
81
 
82
82
  This skill has a PreToolUse guard that warns before destructive operations
83
- (database resets, file deletion, Vercel project removal). If the guard
83
+ (database resets and file deletion). If the guard
84
84
  triggers, confirm the action is intentional before proceeding.
85
85
 
86
86
  ## Overview
@@ -158,7 +158,7 @@ After the confirmation box, provide a brief explanation of the consequences:
158
158
  - "Clients with active sessions can still access the portal until their session expires (up to 7 days)."
159
159
  - "The page files (`page.tsx`, `*-client.tsx`) remain in the codebase. You can delete them from git manually if you want to clean up, or leave them for potential reactivation."
160
160
 
161
- If the deploy mode is `vercel` or `docker`, note: "The deactivation takes effect immediately -- no redeployment needed. The app checks the `isActive` flag at request time."
161
+ Note: "The deactivation takes effect immediately -- no redeployment needed. The app checks the `isActive` flag at request time."
162
162
 
163
163
  ### Step 6: Suggest next actions
164
164
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: portal-deploy
3
3
  description: |
4
- Deploy the Showpane portal app. Runs pre-flight checks, applies migrations, and deploys via Docker or Vercel.
4
+ Publish the Showpane portal app to Showpane Cloud. Runs pre-flight checks, builds the app locally, uploads the artifact, and waits for the hosted publish to go live.
5
5
  Trigger phrases: "portal deploy", "deploy portals", "push to production", "ship the portals". (showpane)
6
6
  allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, WebSearch]
7
7
  ---
@@ -19,7 +19,7 @@ if [ ! -f "$CONFIG" ]; then
19
19
  exit 1
20
20
  fi
21
21
  APP_PATH=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('app_path',''))" 2>/dev/null)
22
- DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','docker'))" 2>/dev/null)
22
+ DEPLOY_MODE=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('deploy_mode','local'))" 2>/dev/null)
23
23
  ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('orgSlug','') or d.get('org_slug',''))" 2>/dev/null)
24
24
  CLOUD_API_TOKEN=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('api_token', d.get('accessToken','')))" 2>/dev/null)
25
25
  CLOUD_ORG_SLUG=$(python3 -c "import json; d=json.load(open('$CONFIG')); print(d.get('cloud',d).get('org_slug', d.get('orgSlug','')))" 2>/dev/null)
@@ -40,7 +40,7 @@ LEARN_FILE="$HOME/.showpane/learnings.jsonl"
40
40
 
41
41
  # Predictive next-skill suggestion
42
42
  if [ -f "$HOME/.showpane/timeline.jsonl" ]; then
43
- _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//')
43
+ _RECENT=$(grep '"event":"completed"' "$HOME/.showpane/timeline.jsonl" 2>/dev/null | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',' | sed 's/,$//' || true)
44
44
  [ -n "$_RECENT" ] && echo "RECENT_SKILLS: $_RECENT"
45
45
  fi
46
46
 
@@ -75,237 +75,12 @@ mention them unless they directly affect the current task.
75
75
 
76
76
  ## Steps
77
77
 
78
- ### Step 0: Choose deployment target
78
+ This skill always publishes to Showpane Cloud.
79
79
 
80
- Check the `DEPLOY_MODE` variable from the preamble. Route accordingly:
80
+ - If `CLOUD_API_TOKEN` is missing, stop and tell the user to run `showpane login`.
81
+ - If `DEPLOY_MODE` is `local`, that is fine — local is the normal authoring mode. The deploy target is still Showpane Cloud.
81
82
 
82
- - If `DEPLOY_MODE` is `"cloud"` skip to **Cloud Deploy Flow** (after Step 6)
83
- - If `DEPLOY_MODE` is `"docker"` or `"vercel"` or unset — continue with the existing self-hosted deploy flow (Steps 1-6)
84
-
85
- If no deploy mode is configured, present the options:
86
-
87
- 1. **Self-host with Docker** (free) — Deploy with Docker Compose on your own server
88
- 2. **Showpane Cloud** ($29/month, 7-day free trial) — Managed hosting at {org}.showpane.com
89
-
90
- If the user chooses Cloud but has not run `showpane login` yet (no `CLOUD_API_TOKEN`), tell them:
91
-
92
- > "Run `showpane login` first to authenticate with Showpane Cloud, then re-run this deploy."
93
-
94
- ### Step 1: Pre-flight checks
95
-
96
- Run all checks before deploying. Any failure here should block the deploy.
97
-
98
- #### 1a. TypeScript type check
99
-
100
- ```bash
101
- cd "$APP_PATH" && npx tsc --noEmit 2>&1
102
- ```
103
-
104
- If type errors are found, display them clearly and stop. Do not deploy with type errors. Common type errors during portal development:
105
-
106
- - Missing imports (forgot to import an icon from lucide-react)
107
- - PortalShell prop mismatch (wrong type for contact, missing required prop)
108
- - Unused variables in tab content functions
109
-
110
- For simple issues (missing import, typo in a prop name), offer to fix them before retrying the deploy. For complex type errors in shared code, suggest the user investigate and fix manually.
111
-
112
- The type check typically takes 10-30 seconds depending on project size. If it takes longer than 60 seconds, it may indicate a problem with the TypeScript configuration.
113
-
114
- #### 1b. Verify all portals have credentials
115
-
116
- Run the list-portals script to check portal status:
117
-
118
- ```bash
119
- cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/list-portals.ts" --org-id <org_id>
120
- ```
121
-
122
- The output is a JSON array of portals. Check each portal for a `username` field. If any active portal lacks credentials, warn the user:
123
-
124
- ```
125
- WARNING: The following portals have no login credentials:
126
- - acme-health
127
- - new-client
128
-
129
- Clients won't be able to log in to these portals.
130
- Run /portal credentials <slug> to create credentials, or continue anyway?
131
- ```
132
-
133
- This is a warning, not a blocker — the user can choose to continue. Some portals may be works in progress that don't need credentials yet.
134
-
135
- #### 1c. Verify deployment config exists
136
-
137
- **For Docker mode:**
138
- ```bash
139
- ls "$APP_PATH/docker-compose.yml" 2>/dev/null || ls "$APP_PATH/compose.yml" 2>/dev/null
140
- ```
141
-
142
- **For Vercel mode:**
143
- ```bash
144
- ls "$APP_PATH/.vercel/project.json" 2>/dev/null || ls "$APP_PATH/vercel.json" 2>/dev/null
145
- ```
146
-
147
- If the config doesn't exist, inform the user and provide setup guidance:
148
- - Docker: "No docker-compose.yml found. Create one or switch deploy mode to vercel."
149
- - Vercel: "No Vercel config found. Run `npx vercel link` to connect your project."
150
-
151
- #### 1d. Check for uncommitted changes (Vercel mode only)
152
-
153
- For Vercel deploys, check if there are uncommitted changes:
154
-
155
- ```bash
156
- cd "$APP_PATH" && git status --porcelain
157
- ```
158
-
159
- If there are changes, show the user what will be committed as part of the deploy:
160
-
161
- ```bash
162
- cd "$APP_PATH" && git diff --stat
163
- ```
164
-
165
- List the changed files and ask the user to confirm. Pay attention to:
166
- - `.env` files — these should NEVER be committed. If `.env` appears in the changes, warn the user and exclude it.
167
- - Large binary files — these will slow down the deploy. Suggest adding them to `.gitignore`.
168
- - Files outside the portal directories — these may be unintended changes.
169
-
170
- If there are no changes to commit and the current branch is up to date with the remote, inform the user that there is nothing new to deploy.
171
-
172
- ### Step 2: Apply database migrations
173
-
174
- Before deploying, apply any pending migrations:
175
-
176
- ```bash
177
- cd "$APP_PATH" && npx prisma migrate deploy
178
- ```
179
-
180
- This runs in production mode — it only applies pending migrations, never creates new ones. If this fails, stop the deploy and show the error. Common issues:
181
- - DATABASE_URL not set or incorrect
182
- - Database server unreachable
183
- - Migration conflicts (rare, requires manual resolution)
184
-
185
- ### Step 3: Deploy
186
-
187
- #### Docker mode
188
-
189
- Build and restart the containers:
190
-
191
- ```bash
192
- cd "$APP_PATH" && docker compose build && docker compose up -d
193
- ```
194
-
195
- If `docker compose` is not available, try `docker-compose` (older syntax):
196
-
197
- ```bash
198
- cd "$APP_PATH" && docker-compose build && docker-compose up -d
199
- ```
200
-
201
- Wait for the containers to start. Check container status:
202
-
203
- ```bash
204
- cd "$APP_PATH" && docker compose ps
205
- ```
206
-
207
- All containers should show status "Up" or "running".
208
-
209
- #### Vercel mode
210
-
211
- Stage, commit, and push:
212
-
213
- ```bash
214
- cd "$APP_PATH" && git add -A && git commit -m "Deploy portal updates" && git push
215
- ```
216
-
217
- If there are no changes to commit, just push:
218
-
219
- ```bash
220
- cd "$APP_PATH" && git push
221
- ```
222
-
223
- The push triggers Vercel's automatic deployment pipeline. Note that the deploy is async — the push returns immediately, but the actual deployment takes 1-3 minutes.
224
-
225
- For Vercel, inform the user:
226
-
227
- > "Pushed to remote. Vercel will build and deploy automatically. Check your Vercel dashboard for deploy status."
228
-
229
- ### Step 4: Post-deploy verification
230
-
231
- #### Docker mode
232
-
233
- Wait a few seconds for the app to start, then hit the health endpoint:
234
-
235
- ```bash
236
- curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/health
237
- ```
238
-
239
- Expected: HTTP 200. Then check the response body:
240
-
241
- ```bash
242
- curl -s http://localhost:8080/api/health
243
- ```
244
-
245
- Expected: `{"status":"ok"}` or similar health response.
246
-
247
- If the health check fails:
248
- 1. Check container logs: `cd "$APP_PATH" && docker compose logs --tail=50`
249
- 2. Check if the port is correct (might be 3000, 8080, or custom)
250
- 3. Report the error and suggest debugging steps
251
-
252
- #### Vercel mode
253
-
254
- If a production URL is known (from config or Vercel project settings), check it:
255
-
256
- ```bash
257
- curl -s -o /dev/null -w "%{http_code}" https://<production_url>/api/health
258
- ```
259
-
260
- If the production URL is not known, skip the health check and inform the user:
261
-
262
- > "Deploy triggered. Verify at your Vercel production URL once the build completes."
263
-
264
- ### Step 5: Deployment summary
265
-
266
- Print a clear summary of what happened:
267
-
268
- ```
269
- Deploy complete!
270
-
271
- Mode: docker
272
- Migrations: 2 applied (or: up to date)
273
- Type check: passed
274
- Portals: 5 active (3 with credentials)
275
- Health: OK (200)
276
-
277
- App URL: http://localhost:8080
278
- Login: http://localhost:8080/client
279
- ```
280
-
281
- For Vercel:
282
-
283
- ```
284
- Deploy triggered!
285
-
286
- Mode: vercel
287
- Migrations: up to date
288
- Type check: passed
289
- Commit: abc1234 "Deploy portal updates"
290
- Portals: 5 active (3 with credentials)
291
-
292
- Status: Building (check Vercel dashboard)
293
- URL: https://your-app.vercel.app
294
- ```
295
-
296
- ### Step 6: Record deployment
297
-
298
- Log the deployment for operational memory:
299
-
300
- ```bash
301
- echo '{"skill":"portal-deploy","key":"deploy","insight":"Deployed via <mode>. Migrations: <count>. Portals: <count> active.","confidence":10,"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> "$HOME/.showpane/learnings.jsonl"
302
- ```
303
-
304
- ---
305
-
306
- ## Cloud Deploy Flow (when DEPLOY_MODE is "cloud")
307
-
308
- This section runs instead of Steps 1-6 when `DEPLOY_MODE` is `"cloud"`. The cloud flow builds the app locally, packages the `.vercel/output` artifact, uploads that artifact to Showpane Cloud, and lets the control plane publish the hosted app. The OSS app should never need to reason about Vercel project IDs or call the Vercel API directly.
83
+ The flow below builds the app locally, packages the `.vercel/output` artifact, uploads that artifact to Showpane Cloud, and lets the control plane publish the hosted app. The OSS app should never need to reason about provider project IDs or call provider APIs directly.
309
84
 
310
85
  ### Cloud Step 1: Pre-flight checks
311
86
 
@@ -328,11 +103,11 @@ If the token is missing, stop and tell the user to run `showpane login`.
328
103
  curl -s -o /dev/null -w "%{http_code}" "$CLOUD_API_BASE/api/health"
329
104
  ```
330
105
 
331
- Expected: HTTP 200. If Showpane Cloud is unreachable, stop and show the error. Token validity is checked by the deploy API itself — OSS should not call Vercel or depend on provider internals.
106
+ Expected: HTTP 200. If Showpane Cloud is unreachable, stop and show the error. Token validity is checked by the deploy API itself — OSS should not call provider internals directly.
332
107
 
333
108
  #### 1c. TypeScript type check
334
109
 
335
- Same as the self-hosted flow:
110
+ Run the standard type check:
336
111
 
337
112
  ```bash
338
113
  cd "$APP_PATH" && npx tsc --noEmit 2>&1
@@ -342,14 +117,22 @@ If type errors are found, display them and stop. Offer to fix simple issues (mis
342
117
 
343
118
  #### 1d. Verify portals have credentials
344
119
 
345
- Same as self-hosted Step 1b — run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
120
+ Run list-portals and warn about portals missing credentials. This is a warning, not a blocker.
346
121
 
347
122
  ### Cloud Step 2: Build the app
348
123
 
349
- Run a Next.js production build:
124
+ Before building, ensure the hidden local project-link metadata exists:
125
+
126
+ ```bash
127
+ if [ ! -f "$APP_PATH/.vercel/project.json" ]; then
128
+ cd "$APP_PATH" && NODE_PATH="$APP_PATH/node_modules" npx tsx --tsconfig "$APP_PATH/tsconfig.json" "$SKILL_DIR/bin/ensure-cloud-project-link.ts"
129
+ fi
130
+ ```
131
+
132
+ Run the cloud build command that produces the prebuilt artifact:
350
133
 
351
134
  ```bash
352
- cd "$APP_PATH" && npx next build
135
+ cd "$APP_PATH" && npm run cloud:build
353
136
  ```
354
137
 
355
138
  After the build completes, verify the output directory was created:
@@ -591,12 +374,12 @@ rm -f "$FILE_MANIFEST_PATH"
591
374
 
592
375
  ## Post-Deploy Verification
593
376
 
594
- After deployment succeeds (either self-hosted Steps 1-6 or Cloud Steps 1-12), automatically run these verification steps. Do not ask the user — just do them.
377
+ After deployment succeeds, automatically run these verification steps. Do not ask the user — just do them.
595
378
 
596
379
  ### Step V1: DNS/URL Check
597
380
  Fetch the portal URL and verify it returns 200:
598
381
  ```bash
599
- PORTAL_URL="${CLOUD_PORTAL_URL:-http://localhost:3000}"
382
+ PORTAL_URL="${CLOUD_PORTAL_URL:-https://$CLOUD_ORG_SLUG.showpane.com}"
600
383
  echo "Verifying $PORTAL_URL..."
601
384
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$PORTAL_URL" 2>/dev/null)
602
385
  echo "HTTP status: $HTTP_CODE"
@@ -646,24 +429,17 @@ Next: /portal-status for ongoing monitoring
646
429
 
647
430
  Deployments can fail at multiple points. Here is how to recover from each:
648
431
 
649
- ### Docker build failure
650
- If `docker compose build` fails:
651
- - Check the Dockerfile for syntax errors
652
- - Check that all required files are present (especially `.env` and `prisma/schema.prisma`)
653
- - Check available disk space Docker builds can require significant space
654
- - Try `docker compose build --no-cache` if a cached layer is stale
655
-
656
- ### Vercel push failure
657
- If `git push` fails:
658
- - **Authentication**: Check git credentials or SSH keys
659
- - **Remote rejection**: The remote may have branch protections. Check if pushing to the correct branch.
660
- - **Diverged history**: Someone else pushed since your last pull. Run `git pull --rebase` first.
432
+ ### Local build failure
433
+ If the local build fails:
434
+ - Check the type and build output directly
435
+ - Check that `.env` contains the expected local SQLite settings
436
+ - Check available disk space for the Next.js build and artifact zip
661
437
 
662
438
  ### Health check failure
663
439
  If the health endpoint returns non-200 after deploy:
664
- - Check application logs: `docker compose logs --tail=100` (Docker) or Vercel function logs
665
- - Common causes: missing environment variables, database connection issues, port conflicts
666
- - For Docker, check if the container is actually running: `docker compose ps`
440
+ - Check the deployment status from `GET /api/deployments/$DEPLOY_ID`
441
+ - Common causes: DNS propagation, hosted runtime config issues, or a bad artifact upload
442
+ - Report the exact hosted error if Showpane Cloud returns one
667
443
 
668
444
  ### Cloud: Token invalid or auth expired
669
445
  If the Showpane Cloud API returns 401/403 during pre-flight or publish:
@@ -688,7 +464,7 @@ If `POST /api/files/plan` or `POST /api/files/upload` fails:
688
464
  If the deployment never reaches `live`:
689
465
  - Check the response from `GET /api/deployments/$DEPLOY_ID` for an `error` field.
690
466
  - Report the failure exactly as returned by Showpane Cloud.
691
- - Do not tell the user to debug Vercel directly unless the cloud response explicitly points there.
467
+ - Do not tell the user to debug provider internals directly unless the cloud response explicitly points there.
692
468
 
693
469
  ## Completion
694
470
 
@@ -702,20 +478,12 @@ echo '{"skill":"portal-deploy","event":"completed","ts":"'$(date -u +%Y-%m-%dT%H
702
478
 
703
479
  - Always run pre-flight checks before deploying — never skip the type check
704
480
  - Credential warnings are non-blocking — the user decides whether to continue
705
- - Migrations run before the deploy, not after
706
- - For Docker: verify the health endpoint after deploy
707
- - For Vercel: the deploy is async — inform the user to check the dashboard
708
- - Never force-push or reset git history during Vercel deploys
709
481
  - If any pre-flight check fails (type errors, missing deploy config), stop and explain
710
482
  - Show the full deployment summary with portal count, migration status, and health
711
- - The deploy commit message is always "Deploy portal updates" — keep it simple and consistent
712
- - If the user wants a custom commit message, they should commit manually before running deploy
713
- - For Vercel deploys, the build typically takes 1-3 minutes — do not poll or wait, just inform the user
714
- - Always run migrations before the build/push step, never after — the app code expects the latest schema
715
483
  - If this is the first deploy, suggest running `/portal credentials` for all portals before deploying so clients can actually log in
716
484
  - For Cloud deploys: build locally, upload the artifact to Showpane Cloud, and let the control plane publish it
717
485
  - For Cloud deploys: always wait for the deployment to reach `live` before declaring success
718
486
  - For Cloud deploys: the portal URL is `https://{org}.showpane.com` — verify it returns 200 after deploy
719
487
  - For Cloud deploys: clean up the temporary artifact after deploy completes
720
488
  - The CLI `showpane login` is auth only — org creation and billing live in Showpane Cloud checkout
721
- - For Cloud deploys: OSS should never call Vercel directly or require project/provider details from the user
489
+ - For Cloud deploys: OSS should never call provider APIs directly or require provider details from the user