htmlhost-cli 2.8.0 → 2.9.0
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 +16 -0
- package/package.json +1 -1
- package/src/api.mjs +1 -0
- package/src/cli.mjs +20 -1
- package/src/commands/versions.mjs +130 -0
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# htmlhost
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/htmlhost-cli) [](https://htmlhost.co/docs/mcp)
|
|
4
|
+
|
|
3
5
|
Deploy HTML projects from the terminal. [htmlhost.co](https://htmlhost.co)
|
|
4
6
|
|
|
7
|
+
Want your AI assistant to deploy for you? The [htmlhost MCP server](https://www.npmjs.com/package/htmlhost-mcp) uses the same login as this CLI and works with Claude, Cursor, VS Code, Windsurf and Codex. [Setup guide →](https://htmlhost.co/docs/mcp)
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -162,6 +166,18 @@ Open a deployed site in the default browser.
|
|
|
162
166
|
### `htmlhost delete <slug>`
|
|
163
167
|
Delete a site. Use `--force` to skip confirmation.
|
|
164
168
|
|
|
169
|
+
### `htmlhost versions [slug]`
|
|
170
|
+
List a site's published versions, newest first: when, who, how it was published, and what changed. Every deploy and every save in the web editor is a version. Without a slug, uses the linked site. Add `--json` for machine-readable output.
|
|
171
|
+
|
|
172
|
+
### `htmlhost rollback [slug] [version]`
|
|
173
|
+
Make an earlier version live again. Without a version number, goes back to the version before the live one. The old version is published again as a new version, so you can switch back the same way. Needs the site owner to be on Pro. Use `--force` to skip confirmation, then `htmlhost pull` so your local files match.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
htmlhost versions # linked site
|
|
177
|
+
htmlhost rollback # back one version
|
|
178
|
+
htmlhost rollback my-site 12 # make v12 live again
|
|
179
|
+
```
|
|
180
|
+
|
|
165
181
|
### `htmlhost whoami`
|
|
166
182
|
Show current authenticated user and plan.
|
|
167
183
|
|
package/package.json
CHANGED
package/src/api.mjs
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { bold, dim, cyan, err } from "./ui.mjs";
|
|
|
5
5
|
import { ApiError } from "./api.mjs";
|
|
6
6
|
import { startUpdateCheck, applyUpdate } from "./update.mjs";
|
|
7
7
|
|
|
8
|
-
const VERSION = "2.
|
|
8
|
+
const VERSION = "2.9.0";
|
|
9
9
|
|
|
10
10
|
const HELP = `
|
|
11
11
|
${bold("htmlhost")} ${dim(`v${VERSION}`)} — deploy HTML from the terminal
|
|
@@ -19,6 +19,8 @@ const HELP = `
|
|
|
19
19
|
${cyan("htmlhost agents")} [dir] Teach your AI coding tool htmlhost (AGENTS.md)
|
|
20
20
|
${cyan("htmlhost recipients")} [slug] [cmd] Manage form notification recipients
|
|
21
21
|
${cyan("htmlhost list")} List your sites
|
|
22
|
+
${cyan("htmlhost versions")} [slug] List a site's published versions
|
|
23
|
+
${cyan("htmlhost rollback")} [slug] [ver] Make an earlier version live again (Pro)
|
|
22
24
|
${cyan("htmlhost delete")} <slug> Delete a site
|
|
23
25
|
${cyan("htmlhost login")} Authenticate with an API token
|
|
24
26
|
${cyan("htmlhost whoami")} Show current user
|
|
@@ -59,6 +61,11 @@ const HELP = `
|
|
|
59
61
|
${bold("Delete options:")}
|
|
60
62
|
--force, -f Skip confirmation prompt
|
|
61
63
|
|
|
64
|
+
${bold("Rollback:")}
|
|
65
|
+
No version: goes back to the version before the live one
|
|
66
|
+
--force, -f Skip confirmation prompt
|
|
67
|
+
Restoring publishes the old version again as a new version
|
|
68
|
+
|
|
62
69
|
${bold("How deploy works:")}
|
|
63
70
|
No args or dir: deploys entire folder as a multi-page site
|
|
64
71
|
Single file: deploys just that file (assets auto-inlined)
|
|
@@ -177,6 +184,18 @@ export async function run(argv) {
|
|
|
177
184
|
await deleteSite(args);
|
|
178
185
|
break;
|
|
179
186
|
}
|
|
187
|
+
case "versions":
|
|
188
|
+
case "history": {
|
|
189
|
+
const { versions } = await import("./commands/versions.mjs");
|
|
190
|
+
await versions(args, { json: jsonMode });
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "rollback":
|
|
194
|
+
case "restore": {
|
|
195
|
+
const { rollback } = await import("./commands/versions.mjs");
|
|
196
|
+
await rollback(args, { json: jsonMode });
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
180
199
|
case "open": {
|
|
181
200
|
const { open } = await import("./commands/open.mjs");
|
|
182
201
|
await open(args);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { get, post } from "../api.mjs";
|
|
2
|
+
import { ok, err, info, warn, dim, cyan, bold, confirm, formatBytes } from "../ui.mjs";
|
|
3
|
+
import { readLink } from "../link.mjs";
|
|
4
|
+
|
|
5
|
+
/** Explicit slug argument first, then the folder's .htmlhost link. */
|
|
6
|
+
function resolveSlug(positional) {
|
|
7
|
+
return positional[0] || readLink(process.cwd())?.site?.slug || null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function when(iso) {
|
|
11
|
+
const d = new Date(iso);
|
|
12
|
+
return d.toLocaleString("en-GB", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pad(str, width) {
|
|
16
|
+
const s = String(str);
|
|
17
|
+
const visible = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
18
|
+
return s + " ".repeat(Math.max(0, width - visible.length));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* htmlhost versions [slug] [--json]
|
|
23
|
+
* Lists a site's published versions, newest first.
|
|
24
|
+
*/
|
|
25
|
+
export async function versions(args, { json = false } = {}) {
|
|
26
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
27
|
+
const slug = resolveSlug(positional);
|
|
28
|
+
if (!slug) {
|
|
29
|
+
err("No site given and no .htmlhost link here.");
|
|
30
|
+
console.log(` Usage: ${cyan("htmlhost versions")} [slug]`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = await get(`/api/sites/${encodeURIComponent(slug)}/versions`);
|
|
35
|
+
if (json) {
|
|
36
|
+
console.log(JSON.stringify(data, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(` ${bold("Versions of")} ${cyan(`${data.slug}.htmlhost.co`)}`);
|
|
42
|
+
console.log();
|
|
43
|
+
if (!data.versions.length) {
|
|
44
|
+
console.log(dim(" No versions yet. Your next deploy will be the first."));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const v of data.versions) {
|
|
48
|
+
const tag = v.live ? cyan(" live") : "";
|
|
49
|
+
const name = v.label ? bold(v.label) : v.summary;
|
|
50
|
+
const by = v.createdBy ? ` · ${v.createdBy}` : "";
|
|
51
|
+
console.log(` ${pad(bold(`v${v.version}`), 6)}${tag} ${name}`);
|
|
52
|
+
console.log(dim(` ${when(v.createdAt)} · ${v.source}${by} · ${v.pageCount} page${v.pageCount === 1 ? "" : "s"}, ${formatBytes(v.totalBytes)}`));
|
|
53
|
+
}
|
|
54
|
+
console.log();
|
|
55
|
+
if (data.usable) {
|
|
56
|
+
console.log(dim(` Restore one with: htmlhost rollback ${data.slug} <version>`));
|
|
57
|
+
} else {
|
|
58
|
+
warn("Restoring earlier versions needs Pro: https://htmlhost.co/pricing");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* htmlhost rollback [slug] [version] [--force]
|
|
64
|
+
* Publishes an earlier version again. Without a version, goes back to the one
|
|
65
|
+
* before the live version.
|
|
66
|
+
*/
|
|
67
|
+
export async function rollback(args, { json = false } = {}) {
|
|
68
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
69
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
70
|
+
const isVersion = (s) => /^v?\d+$/i.test(s ?? "");
|
|
71
|
+
|
|
72
|
+
// `rollback 4`, `rollback my-site`, `rollback my-site 4`
|
|
73
|
+
let slugArg = null;
|
|
74
|
+
let versionArg = null;
|
|
75
|
+
if (positional.length >= 2) [slugArg, versionArg] = positional;
|
|
76
|
+
else if (isVersion(positional[0])) versionArg = positional[0];
|
|
77
|
+
else slugArg = positional[0] ?? null;
|
|
78
|
+
|
|
79
|
+
const slug = resolveSlug(slugArg ? [slugArg] : []);
|
|
80
|
+
if (!slug) {
|
|
81
|
+
err("No site given and no .htmlhost link here.");
|
|
82
|
+
console.log(` Usage: ${cyan("htmlhost rollback")} [slug] [version]`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data = await get(`/api/sites/${encodeURIComponent(slug)}/versions`);
|
|
87
|
+
if (!data.usable) {
|
|
88
|
+
err("Restoring earlier versions needs Pro: https://htmlhost.co/pricing");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const live = data.versions.find((v) => v.live);
|
|
92
|
+
let target;
|
|
93
|
+
if (versionArg) {
|
|
94
|
+
const n = Number(String(versionArg).replace(/^v/i, ""));
|
|
95
|
+
target = data.versions.find((v) => v.version === n);
|
|
96
|
+
if (!target) {
|
|
97
|
+
err(`Version ${n} isn't in this site's history.`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
target = data.versions.find((v) => !v.live && (!live || v.version < live.version));
|
|
102
|
+
if (!target) {
|
|
103
|
+
err("There's no earlier version to go back to.");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (target.live) {
|
|
108
|
+
info(`v${target.version} is already live.`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!force && !json) {
|
|
113
|
+
const yes = await confirm(` Make v${target.version} (${when(target.createdAt)}) live again on ${cyan(bold(`${data.slug}.htmlhost.co`))}?`);
|
|
114
|
+
if (!yes) {
|
|
115
|
+
console.log(" Cancelled.");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await post(`/api/sites/${encodeURIComponent(data.slug)}/versions/${target.version}/restore`, {});
|
|
121
|
+
if (json) {
|
|
122
|
+
console.log(JSON.stringify(result, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const url = result.url ? (String(result.url).startsWith("http") ? result.url : `https://${result.url}`) : `https://${data.slug}.htmlhost.co`;
|
|
126
|
+
ok(`Restored v${target.version} → ${cyan(url)}`);
|
|
127
|
+
if (readLink(process.cwd())?.site?.slug === data.slug) {
|
|
128
|
+
console.log(dim(` Your local files still have the newer version. Run ${cyan("htmlhost pull")} to match the site.`));
|
|
129
|
+
}
|
|
130
|
+
}
|