@micropage-sh/cli 2.0.0 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micropage-sh/cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "CLI for micropage.sh - create, sync, and publish microsites",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -2,9 +2,10 @@
2
2
 
3
3
  const http = require('http');
4
4
  const { randomBytes } = require('crypto');
5
- const { setSession, getSession } = require('../auth');
5
+ const { setSession, getSession, clearSession } = require('../auth');
6
6
  const { getUserInfo, getValidAccessToken } = require('../supabase');
7
7
  const { APP_URL, SUPABASE_URL, SUPABASE_ANON_KEY } = require('../config');
8
+ const { UPGRADE_MESSAGE, isPaidTier, getPlanTierWithToken } = require('../plan');
8
9
 
9
10
  // Find an available ephemeral port by briefly binding to port 0.
10
11
  function getFreePort() {
@@ -164,11 +165,24 @@ async function run(options = {}) {
164
165
  const existing = getSession();
165
166
  if (existing) {
166
167
  try {
167
- await getValidAccessToken();
168
- const email = existing.user?.email || 'unknown';
169
- console.log(`Already logged in as ${email}.`);
170
- console.log('Use --force to re-authenticate.');
171
- return;
168
+ const accessToken = await getValidAccessToken();
169
+ const planTier = await getPlanTierWithToken(existing.user?.id, accessToken);
170
+ if (!isPaidTier(planTier)) {
171
+ clearSession();
172
+ console.error(UPGRADE_MESSAGE);
173
+ process.exit(1);
174
+ }
175
+ const displayName = existing.user?.email
176
+ || existing.user?.user_metadata?.full_name
177
+ || existing.user?.user_metadata?.user_name
178
+ || existing.user?.user_metadata?.name
179
+ || existing.user?.id;
180
+ if (displayName) {
181
+ console.log(`Already logged in as ${displayName}.`);
182
+ console.log('Use --force to re-authenticate.');
183
+ process.exit(0);
184
+ }
185
+ // No identifiable display name on the cached user — fall through to a fresh login.
172
186
  } catch {
173
187
  // Token expired or refresh failed — fall through to re-login
174
188
  }
@@ -240,10 +254,27 @@ async function run(options = {}) {
240
254
  server.close();
241
255
 
242
256
  const user = await getUserInfo(access_token);
257
+ if (!user?.id) {
258
+ console.error('Login failed: could not load user profile.');
259
+ process.exit(1);
260
+ }
261
+
262
+ const planTier = await getPlanTierWithToken(user.id, access_token);
263
+ if (!isPaidTier(planTier)) {
264
+ // Free-tier (or unconfirmed) users get no session at all.
265
+ console.error(UPGRADE_MESSAGE);
266
+ process.exit(1);
267
+ }
268
+
243
269
  setSession({ access_token, refresh_token, user });
244
270
 
245
- const displayName = user?.email || user?.user_metadata?.user_name || 'unknown';
271
+ const displayName = user.email
272
+ || user.user_metadata?.full_name
273
+ || user.user_metadata?.user_name
274
+ || user.user_metadata?.name
275
+ || user.id;
246
276
  console.log(`Logged in as ${displayName}`);
277
+ process.exit(0);
247
278
  } catch (err) {
248
279
  clearTimeout(timeout);
249
280
  server.close();
@@ -9,8 +9,16 @@ function run() {
9
9
  return;
10
10
  }
11
11
  clearSession();
12
- const email = session.user?.email || 'unknown';
13
- console.log(`Logged out (was: ${email}).`);
12
+ const displayName = session.user?.email
13
+ || session.user?.user_metadata?.full_name
14
+ || session.user?.user_metadata?.user_name
15
+ || session.user?.user_metadata?.name
16
+ || session.user?.id;
17
+ if (displayName) {
18
+ console.log(`Logged out (was: ${displayName}).`);
19
+ } else {
20
+ console.log('Logged out.');
21
+ }
14
22
  }
15
23
 
16
24
  module.exports = { run };
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- const { getSession } = require('../auth');
3
+ const { getSession, clearSession } = require('../auth');
4
4
  const { db, getUserInfo, getValidAccessToken, handleAuthError } = require('../supabase');
5
+ const { UPGRADE_MESSAGE, isPaidTier } = require('../plan');
5
6
 
6
7
  async function run() {
7
8
  const session = getSession();
@@ -25,35 +26,41 @@ async function run() {
25
26
  process.exit(1);
26
27
  }
27
28
 
28
- console.log('User ID: ', user.id);
29
- console.log('Email: ', user.email || '-');
30
- const name =
31
- user.user_metadata?.full_name ||
32
- user.user_metadata?.user_name ||
33
- user.user_metadata?.name ||
34
- '-';
35
- console.log('Name: ', name);
36
- if (user.app_metadata?.provider) {
37
- console.log('Auth via: ', user.app_metadata.provider);
38
- }
39
-
40
- // Billing / subscription overview (best-effort; do not fail whoami if this breaks).
29
+ // Gate access on plan first — free-tier accounts get no session and no detail dump.
41
30
  let planTier = 'free';
42
- let subscription = null;
43
31
  try {
44
32
  const customer = await db
45
33
  .from('customers')
46
34
  .select('plan_tier')
47
35
  .eq('user_id', user.id)
48
36
  .single();
49
- if (customer?.plan_tier) {
37
+ if (customer?.plan_tier === 'pro' || customer?.plan_tier === 'pro_plus') {
50
38
  planTier = customer.plan_tier;
51
39
  }
52
40
  } catch (err) {
53
41
  handleAuthError(err);
54
- console.error('Warning: failed to load plan info:', err.message);
42
+ // If we cannot determine plan, default to free → deny CLI access below.
55
43
  }
56
44
 
45
+ if (!isPaidTier(planTier)) {
46
+ clearSession();
47
+ console.error(UPGRADE_MESSAGE);
48
+ process.exit(1);
49
+ }
50
+
51
+ console.log('User ID: ', user.id);
52
+ console.log('Email: ', user.email || '-');
53
+ const name =
54
+ user.user_metadata?.full_name ||
55
+ user.user_metadata?.user_name ||
56
+ user.user_metadata?.name ||
57
+ '-';
58
+ console.log('Name: ', name);
59
+ if (user.app_metadata?.provider) {
60
+ console.log('Auth via: ', user.app_metadata.provider);
61
+ }
62
+
63
+ let subscription = null;
57
64
  try {
58
65
  const subs = await db
59
66
  .from('subscriptions')
package/src/index.js CHANGED
@@ -18,7 +18,7 @@ const program = new Command();
18
18
  program
19
19
  .name('micropage')
20
20
  .description('CLI for micropage.sh – create, sync, and publish microsites')
21
- .version('2.0.0');
21
+ .version('2.0.2');
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Authentication
package/src/plan.js ADDED
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const { SUPABASE_URL, SUPABASE_ANON_KEY } = require('./config');
4
+
5
+ const UPGRADE_MESSAGE = [
6
+ 'The micropage CLI is available on paid plans only.',
7
+ 'Your account is currently on the free plan.',
8
+ '',
9
+ 'Upgrade at: https://micropage.sh/pricing',
10
+ ].join('\n');
11
+
12
+ function isPaidTier(tier) {
13
+ return tier === 'pro' || tier === 'pro_plus';
14
+ }
15
+
16
+ // Look up plan_tier using a bearer token directly so this can be called inside
17
+ // `login` before a session is persisted. Defaults to 'free' on any failure —
18
+ // CLI access is denied when we cannot confirm a paid tier.
19
+ async function getPlanTierWithToken(userId, accessToken) {
20
+ try {
21
+ const url = `${SUPABASE_URL}/rest/v1/customers?select=plan_tier&user_id=eq.${encodeURIComponent(userId)}&limit=1`;
22
+ const res = await fetch(url, {
23
+ headers: {
24
+ apikey: SUPABASE_ANON_KEY,
25
+ Authorization: `Bearer ${accessToken}`,
26
+ },
27
+ });
28
+ if (!res.ok) return 'free';
29
+ const data = await res.json();
30
+ const row = Array.isArray(data) ? data[0] : null;
31
+ if (row?.plan_tier === 'pro' || row?.plan_tier === 'pro_plus') {
32
+ return row.plan_tier;
33
+ }
34
+ return 'free';
35
+ } catch {
36
+ return 'free';
37
+ }
38
+ }
39
+
40
+ module.exports = { UPGRADE_MESSAGE, isPaidTier, getPlanTierWithToken };