@micropage-sh/cli 2.6.1 → 2.6.2

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.
package/README.md CHANGED
@@ -32,6 +32,8 @@ micropage whoami # show the currently logged-in user and subscription
32
32
  micropage logout # clear the stored session
33
33
  ```
34
34
 
35
+ The CLI requires a paid plan (Pro or Pro+). Some features need a specific tier, such as deploy tokens (Pro+). The server enforces these limits. When a command needs a higher plan, the CLI prints the server's reason and an upgrade link.
36
+
35
37
  ## Project workflow
36
38
 
37
39
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micropage-sh/cli",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
4
4
  "description": "CLI for micropage.sh - create, sync, and publish microsites",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -607,6 +607,7 @@ async function deploy(projectUuid, deployToken, options = {}) {
607
607
  }
608
608
  }
609
609
  } catch (err) {
610
+ handleAuthError(err);
610
611
  console.error('Deploy failed:', err.message);
611
612
  process.exit(1);
612
613
  }
package/src/plan.js CHANGED
@@ -2,13 +2,37 @@
2
2
 
3
3
  const { SUPABASE_URL, SUPABASE_ANON_KEY } = require('./config');
4
4
 
5
+ const PRICING_URL = 'https://micropage.sh/pricing';
6
+
5
7
  const UPGRADE_MESSAGE = [
6
8
  'The micropage CLI is available on paid plans only.',
7
9
  'Your account is currently on the free plan.',
8
10
  '',
9
- 'Upgrade at: https://micropage.sh/pricing',
11
+ `Upgrade at: ${PRICING_URL}`,
10
12
  ].join('\n');
11
13
 
14
+ const TIER_LABELS = { pro: 'Pro', pro_plus: 'Pro+' };
15
+
16
+ // Message for a server refusal tagged PLAN_REQUIRED (see attachPlanRequired in
17
+ // supabase.js). Leads with the server's own explanation, then the upgrade hint.
18
+ function formatPlanRequiredMessage(err) {
19
+ const label = TIER_LABELS[err?.requiredTier];
20
+ const lines = [];
21
+ // Server messages written for the editor may end in a relative "Upgrade at /pricing.";
22
+ // drop it so the absolute link below is the only one.
23
+ const reason = (err?.message || '').replace(/\s*Upgrade at \S+$/i, '').trim();
24
+ if (reason) lines.push(reason, '');
25
+ lines.push(`This requires ${label ? `the ${label} plan` : 'a paid plan'}.`);
26
+ lines.push('');
27
+ lines.push(`Upgrade at: ${err?.upgradeUrl || PRICING_URL}`);
28
+ return lines.join('\n');
29
+ }
30
+
31
+ // The login gate below (and in whoami) is a product gate, not a security
32
+ // boundary: anyone can call the Supabase API directly with an editor session.
33
+ // The server enforces the per-feature gates (project limits via RLS, storage
34
+ // quota, custom domains, archives, posts, deploy-token creation and exchange)
35
+ // and answers with `code: "plan_required"` when a tier is missing.
12
36
  function isPaidTier(tier) {
13
37
  return tier === 'pro' || tier === 'pro_plus';
14
38
  }
@@ -37,4 +61,4 @@ async function getPlanTierWithToken(userId, accessToken) {
37
61
  }
38
62
  }
39
63
 
40
- module.exports = { UPGRADE_MESSAGE, isPaidTier, getPlanTierWithToken };
64
+ module.exports = { UPGRADE_MESSAGE, isPaidTier, getPlanTierWithToken, formatPlanRequiredMessage };
package/src/supabase.js CHANGED
@@ -13,6 +13,7 @@
13
13
 
14
14
  const { getSession, setSession, clearSession } = require('./auth');
15
15
  const { SUPABASE_URL, SUPABASE_ANON_KEY } = require('./config');
16
+ const { formatPlanRequiredMessage } = require('./plan');
16
17
 
17
18
  // ---------------------------------------------------------------------------
18
19
  // JWT helpers
@@ -97,6 +98,18 @@ async function getValidAccessToken() {
97
98
  // Base request
98
99
  // ---------------------------------------------------------------------------
99
100
 
101
+ // Server tier refusals carry `code: "plan_required"` in the JSON body, reusing
102
+ // 402/403. Tag the error so callers can tell "upgrade needed" apart from
103
+ // "not yours" / "invalid token", which share those statuses.
104
+ function attachPlanRequired(err, data) {
105
+ if (data && typeof data === 'object' && data.code === 'plan_required') {
106
+ err.code = 'PLAN_REQUIRED';
107
+ err.requiredTier = data.required_tier || null;
108
+ err.upgradeUrl = data.upgrade_url || null;
109
+ }
110
+ return err;
111
+ }
112
+
100
113
  async function requestWithToken(method, url, body, accessToken, extraHeaders = {}) {
101
114
  const headers = {
102
115
  'apikey': SUPABASE_ANON_KEY,
@@ -123,6 +136,7 @@ async function requestWithToken(method, url, body, accessToken, extraHeaders = {
123
136
  const err = new Error(msg);
124
137
  err.status = res.status;
125
138
  err.data = data;
139
+ attachPlanRequired(err, data);
126
140
  throw err;
127
141
  }
128
142
 
@@ -172,6 +186,7 @@ async function request(method, url, body, extraHeaders = {}) {
172
186
  err.status = res.status;
173
187
  err.data = data;
174
188
  if (res.status === 401) err.code = 'SESSION_EXPIRED';
189
+ attachPlanRequired(err, data);
175
190
  throw err;
176
191
  }
177
192
 
@@ -288,6 +303,10 @@ function handleAuthError(err) {
288
303
  console.error(err.message);
289
304
  process.exit(1);
290
305
  }
306
+ if (err.code === 'PLAN_REQUIRED') {
307
+ console.error(formatPlanRequiredMessage(err));
308
+ process.exit(1);
309
+ }
291
310
  }
292
311
 
293
312
  // ---------------------------------------------------------------------------
@@ -670,6 +689,7 @@ async function exchangeDeployTokenForAccessToken(deployTokenPlaintext, projectUu
670
689
  );
671
690
  err.status = res.status;
672
691
  err.data = data;
692
+ attachPlanRequired(err, data);
673
693
  throw err;
674
694
  }
675
695
  if (!data?.access_token) {
@@ -709,6 +729,7 @@ async function invokePublishBuild(accessToken, projectId, buildId) {
709
729
  );
710
730
  err.status = res.status;
711
731
  err.data = data;
732
+ attachPlanRequired(err, data);
712
733
  throw err;
713
734
  }
714
735
  return data;
@@ -719,6 +740,7 @@ module.exports = {
719
740
  fn,
720
741
  getUserInfo,
721
742
  handleAuthError,
743
+ attachPlanRequired,
722
744
  getValidAccessToken,
723
745
  getMaxDeployEventIdForBuild,
724
746
  getMaxDeployEventIdForProject,