@micropage-sh/cli 2.6.1 → 2.6.3
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 +7 -5
- package/package.json +2 -2
- package/src/commands/projects.js +1 -0
- package/src/index.js +4 -4
- package/src/plan.js +26 -2
- package/src/supabase.js +22 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# micropage CLI
|
|
2
2
|
|
|
3
|
-
Command-line interface for [micropage.sh](https://
|
|
3
|
+
Command-line interface for [micropage.sh](https://micropage.sh): keep your `.page` files in git and create, edit, publish and manage your sites from the terminal.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -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
|
|
@@ -46,7 +48,7 @@ nano landing.page
|
|
|
46
48
|
# Push local content as a draft build
|
|
47
49
|
micropage push
|
|
48
50
|
|
|
49
|
-
# Publish the current draft (deploys
|
|
51
|
+
# Publish the current draft (deploys it live)
|
|
50
52
|
micropage publish
|
|
51
53
|
|
|
52
54
|
# Pull the latest remote build content to landing.page
|
|
@@ -113,7 +115,7 @@ See [`micropage posts`](#posts) below for the full command set.
|
|
|
113
115
|
|---|---|
|
|
114
116
|
| `micropage projects list [--json]` | List your projects |
|
|
115
117
|
| `micropage projects show [id] [--json]` | Show project + latest build details |
|
|
116
|
-
| `micropage projects create <name> [-d domain]` | Create a project and init local folder. Omit `-d` to let the API assign `{slugified-name}-{6 hex}` (unique
|
|
118
|
+
| `micropage projects create <name> [-d domain]` | Create a project and init local folder. Omit `-d` to let the API assign `{slugified-name}-{6 hex}` (unique `<slug>.micropage.sh` label, max 58 chars). Use `-d` only to override the slug. |
|
|
117
119
|
| `micropage projects fetch <uuid\|domain>` | Fetch an existing project and init local folder (includes storage → `./assets` sync) |
|
|
118
120
|
| `micropage projects pull` | Pull latest build raw content → `landing.page`, then sync `./assets` with project storage (1:1) |
|
|
119
121
|
| `micropage projects delete [-y]` | Delete project (remote + local `.micropage/`) |
|
|
@@ -130,7 +132,7 @@ When you create a new project, the CLI also scaffolds:
|
|
|
130
132
|
| Command | Description |
|
|
131
133
|
|---|---|
|
|
132
134
|
| `micropage push` | Merge local `.page` files and save as a draft build |
|
|
133
|
-
| `micropage publish` | Push then deploy
|
|
135
|
+
| `micropage publish` | Push then deploy the site live |
|
|
134
136
|
| `micropage builds list [--json]` | List builds for the current project |
|
|
135
137
|
| `micropage builds redeploy [version]` | Re-publish an older build as a new deploy |
|
|
136
138
|
| `micropage builds download [version] [-o file]` | Download a deployed build archive as a `.zip` file |
|
|
@@ -225,7 +227,7 @@ If the project folder contains a root-level `llms.txt`, its contents are publish
|
|
|
225
227
|
Each project folder has a `.micropage/project.json` file that stores:
|
|
226
228
|
- `projectId` — the Supabase project ID
|
|
227
229
|
- `buildId` — the ID of the last draft/deployed build (auto-updated by push/pull)
|
|
228
|
-
- `domain` — the
|
|
230
|
+
- `domain` — the project slug; the site lives at `<domain>.micropage.sh`
|
|
229
231
|
- `name` — the project name
|
|
230
232
|
|
|
231
233
|
Add `.micropage/` to your `.gitignore` if you share the content folder.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micropage-sh/cli",
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"description": "CLI
|
|
3
|
+
"version": "2.6.3",
|
|
4
|
+
"description": "Micropage CLI: keep your .page files in git and publish static sites from the terminal",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"micropage": "bin/micropage.js"
|
package/src/commands/projects.js
CHANGED
package/src/index.js
CHANGED
|
@@ -19,7 +19,7 @@ const program = new Command();
|
|
|
19
19
|
|
|
20
20
|
program
|
|
21
21
|
.name('micropage')
|
|
22
|
-
.description('CLI
|
|
22
|
+
.description('Micropage CLI: keep your .page files in git and publish static sites from the terminal')
|
|
23
23
|
.version(version);
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -65,7 +65,7 @@ projectCmd
|
|
|
65
65
|
.description('Create a new project and init local folder')
|
|
66
66
|
.option(
|
|
67
67
|
'-d, --domain <domain>',
|
|
68
|
-
'
|
|
68
|
+
'Choose the project slug, which sets its address <domain>.micropage.sh (default: server generates name-slug + 6 hex, max 58 chars)',
|
|
69
69
|
)
|
|
70
70
|
.action((name, opts) => projects.create(name, opts));
|
|
71
71
|
|
|
@@ -87,7 +87,7 @@ projectCmd
|
|
|
87
87
|
|
|
88
88
|
projectCmd
|
|
89
89
|
.command('publish')
|
|
90
|
-
.description('Push local content and publish
|
|
90
|
+
.description('Push local content and publish it live — alias for `micropage publish`')
|
|
91
91
|
.option('-w, --watch', 'Stream build/deploy events after triggering publish')
|
|
92
92
|
.action((opts) => builds.publish(opts));
|
|
93
93
|
|
|
@@ -152,7 +152,7 @@ program
|
|
|
152
152
|
|
|
153
153
|
program
|
|
154
154
|
.command('publish')
|
|
155
|
-
.description('Push local content and publish
|
|
155
|
+
.description('Push local content and publish it live')
|
|
156
156
|
.option('-w, --watch', 'Stream build/deploy events after triggering publish')
|
|
157
157
|
.action((opts) => builds.publish(opts));
|
|
158
158
|
|
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
|
-
|
|
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,
|