lemonade-host 0.1.0 → 0.1.1
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 -7
- package/bin/lemonade.mjs +3 -2
- package/package.json +1 -1
- package/src/commands.mjs +50 -28
package/README.md
CHANGED
|
@@ -22,20 +22,21 @@ and save it:
|
|
|
22
22
|
npx lemonade-host login
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
It asks for the token
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
It asks for the token — nothing else. A token is minted for one site, so it
|
|
26
|
+
already knows where to publish. The answer is written to `~/.lemonade` with
|
|
27
|
+
permissions only you can read. Or set `LEMONADE_TOKEN` in the environment
|
|
28
|
+
instead, which is what CI wants.
|
|
28
29
|
|
|
29
30
|
## Commands
|
|
30
31
|
|
|
31
32
|
| | |
|
|
32
33
|
|---|---|
|
|
33
|
-
| `lemonade deploy [dir]` | Zip a folder and publish it. Defaults to `.`, and prefers `dist/`, `build/`, `out/` or `public/` if one exists |
|
|
34
|
+
| `lemonade deploy [dir]` | Zip a folder and publish it. Defaults to `.`, and prefers `dist/`, `build/`, `out/` or `public/` if one exists. `--dir` does the same thing |
|
|
34
35
|
| `lemonade status` | The last few deploys for this site |
|
|
35
|
-
| `lemonade login` | Save a token
|
|
36
|
+
| `lemonade login` | Save a token to `~/.lemonade` |
|
|
36
37
|
| `lemonade whoami` | Which site the current token points at |
|
|
37
38
|
|
|
38
|
-
Useful flags: `--
|
|
39
|
+
Useful flags: `--dir`, `--token`, `--origin` (for staging), `--yes` (skip
|
|
39
40
|
the confirmation), `--json` (machine-readable output).
|
|
40
41
|
|
|
41
42
|
## A token reaches exactly one site
|
|
@@ -53,7 +54,6 @@ Revoke one any time from the same screen you minted it on.
|
|
|
53
54
|
- run: npx lemonade-host deploy ./dist --yes
|
|
54
55
|
env:
|
|
55
56
|
LEMONADE_TOKEN: ${{ secrets.LEMONADE_TOKEN }}
|
|
56
|
-
LEMONADE_SITE: ${{ secrets.LEMONADE_SITE }}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Exit code is 0 on a successful publish and non-zero on anything else, so a
|
package/bin/lemonade.mjs
CHANGED
|
@@ -23,7 +23,8 @@ const USAGE = `lemonade — publish a folder to Lemonade Host
|
|
|
23
23
|
|
|
24
24
|
Options
|
|
25
25
|
--token lm_… a deploy token (or set LEMONADE_TOKEN)
|
|
26
|
-
--
|
|
26
|
+
--dir FOLDER which folder to publish (same as the positional argument)
|
|
27
|
+
--site st_… which site — only needed if the token reaches more than one
|
|
27
28
|
--origin URL point at another instance, e.g. staging
|
|
28
29
|
--yes, -y do not ask before publishing
|
|
29
30
|
--json machine-readable output
|
|
@@ -36,7 +37,7 @@ deploy history. It cannot spend money, cancel anything, or see another site.
|
|
|
36
37
|
`;
|
|
37
38
|
|
|
38
39
|
const BOOLEAN = new Set(["yes", "y", "json", "include-hidden", "help", "h", "version", "v"]);
|
|
39
|
-
const VALUED = new Set(["token", "site", "origin"]);
|
|
40
|
+
const VALUED = new Set(["token", "site", "origin", "dir"]);
|
|
40
41
|
|
|
41
42
|
function parse(argv) {
|
|
42
43
|
const args = [];
|
package/package.json
CHANGED
package/src/commands.mjs
CHANGED
|
@@ -54,20 +54,33 @@ function requireAuth(flags) {
|
|
|
54
54
|
` It came from ${cfg.from.token}.`,
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const site = normalizeSite(cfg.site);
|
|
65
|
-
if (!site) {
|
|
57
|
+
// The site is OPTIONAL here. A token is minted for exactly one site, so
|
|
58
|
+
// the credential already implies it — asking for both was making people
|
|
59
|
+
// repeat something we know, and it made the documented promise ("set
|
|
60
|
+
// LEMONADE_TOKEN once and never touch it again") untrue. When it is
|
|
61
|
+
// absent, ask the server: `resolveSite` below.
|
|
62
|
+
const site = cfg.site ? normalizeSite(cfg.site) : null;
|
|
63
|
+
if (cfg.site && !site) {
|
|
66
64
|
throw new Refused(`"${cfg.site}" is not a site id — they look like st_….`);
|
|
67
65
|
}
|
|
68
66
|
return { ...cfg, site };
|
|
69
67
|
}
|
|
70
68
|
|
|
69
|
+
/**
|
|
70
|
+
* The site this run is about: the one configured, or the one the token was
|
|
71
|
+
* minted for. One round trip, and only when it is needed.
|
|
72
|
+
*/
|
|
73
|
+
async function resolveSite(cfg) {
|
|
74
|
+
if (cfg.site) return cfg.site;
|
|
75
|
+
const me = await call(cfg, `${cfg.origin}/api/deploy/whoami`);
|
|
76
|
+
if (!me.site) {
|
|
77
|
+
throw new Refused(
|
|
78
|
+
"That token did not resolve to a site.\n Mint a fresh one in your dashboard under Deploy tools.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return me.site;
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
/**
|
|
72
85
|
* Which folder to publish.
|
|
73
86
|
*
|
|
@@ -137,8 +150,10 @@ async function call(cfg, url, init = {}) {
|
|
|
137
150
|
/* ── deploy ──────────────────────────────────────────────────────────────── */
|
|
138
151
|
|
|
139
152
|
export async function deploy(args, flags) {
|
|
140
|
-
const
|
|
141
|
-
|
|
153
|
+
const base = requireAuth(flags);
|
|
154
|
+
// `--dir` is what the documentation has always shown; the positional form
|
|
155
|
+
// is what a shell user reaches for. Both work, and the flag wins.
|
|
156
|
+
const { dir, guessed } = chooseDir(flags.dir ?? args[0]);
|
|
142
157
|
|
|
143
158
|
const { files, skipped } = collect(dir, { includeHidden: flags.includeHidden });
|
|
144
159
|
if (files.length === 0) {
|
|
@@ -179,6 +194,8 @@ export async function deploy(args, flags) {
|
|
|
179
194
|
);
|
|
180
195
|
}
|
|
181
196
|
|
|
197
|
+
const cfg = { ...base, site: await resolveSite(base) };
|
|
198
|
+
|
|
182
199
|
if (!flags.yes && !flags.json && !(await confirm(`Publish to ${cfg.site}?`))) {
|
|
183
200
|
console.log("Nothing was published.");
|
|
184
201
|
return 1;
|
|
@@ -227,7 +244,8 @@ export async function deploy(args, flags) {
|
|
|
227
244
|
/* ── status ──────────────────────────────────────────────────────────────── */
|
|
228
245
|
|
|
229
246
|
export async function status(_args, flags) {
|
|
230
|
-
const
|
|
247
|
+
const base = requireAuth(flags);
|
|
248
|
+
const cfg = { ...base, site: await resolveSite(base) };
|
|
231
249
|
const body = await call(cfg, `${cfg.origin}/api/deploy/${cfg.site}/deployments`);
|
|
232
250
|
const rows = body.deployments ?? [];
|
|
233
251
|
|
|
@@ -254,15 +272,19 @@ export async function status(_args, flags) {
|
|
|
254
272
|
export async function whoami(_args, flags) {
|
|
255
273
|
const cfg = requireAuth(flags);
|
|
256
274
|
// Asking the SERVER rather than reading the config back: the question is
|
|
257
|
-
// "does this token still work, and what does it reach", and
|
|
258
|
-
//
|
|
259
|
-
await call(cfg, `${cfg.origin}/api/deploy
|
|
275
|
+
// "does this token still work, and what does it reach", and neither half
|
|
276
|
+
// can be answered locally.
|
|
277
|
+
const me = await call(cfg, `${cfg.origin}/api/deploy/whoami`);
|
|
260
278
|
if (flags.json) {
|
|
261
|
-
console.log(JSON.stringify(
|
|
279
|
+
console.log(JSON.stringify(me, null, 2));
|
|
262
280
|
return 0;
|
|
263
281
|
}
|
|
264
|
-
console.log(`Token is valid and reaches ${bold(
|
|
265
|
-
console.log(
|
|
282
|
+
console.log(`Token is valid and reaches ${bold(me.name)} (${me.site})`);
|
|
283
|
+
console.log(` ${me.url}`);
|
|
284
|
+
if (!me.serving) {
|
|
285
|
+
console.log(dim(" Not serving yet — this site has not been published from the dashboard."));
|
|
286
|
+
}
|
|
287
|
+
console.log(dim(` ${cfg.origin} · token from ${cfg.from.token}`));
|
|
266
288
|
return 0;
|
|
267
289
|
}
|
|
268
290
|
|
|
@@ -278,20 +300,20 @@ export async function login(_args, flags) {
|
|
|
278
300
|
if (!looksLikeToken(token)) {
|
|
279
301
|
throw new Refused("That does not look like a deploy token — they start with lm_.");
|
|
280
302
|
}
|
|
281
|
-
const siteRaw = flags.site ?? (await rl.question("Site id (st_…, or paste the URL): "));
|
|
282
|
-
const site = normalizeSite(siteRaw);
|
|
283
|
-
if (!site) throw new Refused("That is not a site id — they look like st_….");
|
|
284
|
-
|
|
285
303
|
const origin = (flags.origin ?? process.env.LEMONADE_ORIGIN ?? "https://app.lemonadehost.com").replace(/\/+$/, "");
|
|
286
304
|
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
305
|
+
// No site prompt. The token names its own site, so asking would be
|
|
306
|
+
// asking somebody to look up an id we are about to be told anyway —
|
|
307
|
+
// and this is also how the token is PROVED before it is written down.
|
|
308
|
+
// Saving an unverified token means the first real deploy is where you
|
|
309
|
+
// learn it was wrong, by which time the one place the raw value existed
|
|
310
|
+
// has usually been closed.
|
|
311
|
+
const probe = { token, origin, from: { token: "prompt", site: null } };
|
|
312
|
+
const me = await call(probe, `${origin}/api/deploy/whoami`);
|
|
292
313
|
|
|
293
|
-
const where = save({ token, site, origin });
|
|
314
|
+
const where = save({ token, site: me.site, origin });
|
|
294
315
|
console.log(`\n${green("Saved.")} ${dim(where)} (readable only by you)`);
|
|
316
|
+
console.log(`Publishing to ${bold(me.name)} — ${me.url}`);
|
|
295
317
|
console.log(dim("Now: lemonade deploy"));
|
|
296
318
|
return 0;
|
|
297
319
|
} finally {
|