@vantienkhai/shippage-cli 0.1.1 → 0.2.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 +10 -5
- package/dist/index.js +114 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,20 +10,25 @@ npm i -g @vantienkhai/shippage-cli
|
|
|
10
10
|
|
|
11
11
|
## Authenticate
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Run browser login once:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
shippage login
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The CLI opens Shippage in your browser, asks you to confirm a short code, then
|
|
20
|
+
saves a long-lived API key to `~/.config/shippage/config.json`. Environment
|
|
21
|
+
variables still override the saved config:
|
|
15
22
|
|
|
16
23
|
```bash
|
|
17
24
|
export SHIPPAGE_API_URL="https://shippage.vantienkhai-uet.workers.dev"
|
|
18
25
|
export SHIPPAGE_TOKEN="<your spg_ API key>"
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
> API key secrets are shown only once. Store them securely and revoke unused
|
|
22
|
-
> keys from the dashboard.
|
|
23
|
-
|
|
24
28
|
## Commands
|
|
25
29
|
|
|
26
30
|
```bash
|
|
31
|
+
shippage login
|
|
27
32
|
shippage publish ./out/article.html --title "AI Search Glossary" --slug ai-search-glossary --visibility public
|
|
28
33
|
shippage bulk "out/**/*.html" --visibility unlisted
|
|
29
34
|
shippage list
|
package/dist/index.js
CHANGED
|
@@ -21,17 +21,58 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
|
21
21
|
|
|
22
22
|
// src/index.ts
|
|
23
23
|
import "dotenv/config";
|
|
24
|
-
import
|
|
25
|
-
import
|
|
24
|
+
import { spawn } from "child_process";
|
|
25
|
+
import fs2 from "fs/promises";
|
|
26
|
+
import path2 from "path";
|
|
26
27
|
import { Command } from "commander";
|
|
27
28
|
import chalk from "chalk";
|
|
28
29
|
import { globby } from "globby";
|
|
30
|
+
|
|
31
|
+
// src/config.ts
|
|
32
|
+
import fs from "fs/promises";
|
|
33
|
+
import os from "os";
|
|
34
|
+
import path from "path";
|
|
35
|
+
function parseCliConfig(value) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(value);
|
|
38
|
+
return __spreadValues(__spreadValues({}, typeof parsed.apiUrl === "string" ? { apiUrl: parsed.apiUrl } : {}), typeof parsed.token === "string" ? { token: parsed.token } : {});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function mergeCliConfig(current, update) {
|
|
44
|
+
return __spreadValues(__spreadValues({}, current), update);
|
|
45
|
+
}
|
|
46
|
+
function cliConfigPath() {
|
|
47
|
+
const root = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
48
|
+
return path.join(root, "shippage", "config.json");
|
|
49
|
+
}
|
|
50
|
+
async function readCliConfig() {
|
|
51
|
+
try {
|
|
52
|
+
return parseCliConfig(await fs.readFile(cliConfigPath(), "utf8"));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function writeCliConfig(update) {
|
|
58
|
+
const configPath = cliConfigPath();
|
|
59
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true, mode: 448 });
|
|
60
|
+
const config = mergeCliConfig(await readCliConfig(), update);
|
|
61
|
+
await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
62
|
+
`, { mode: 384 });
|
|
63
|
+
await fs.chmod(configPath, 384);
|
|
64
|
+
return configPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/index.ts
|
|
29
68
|
var program = new Command();
|
|
69
|
+
var DEFAULT_API_URL = "https://shippage.vantienkhai-uet.workers.dev";
|
|
70
|
+
var storedConfig = {};
|
|
30
71
|
function apiUrl() {
|
|
31
|
-
return (process.env.SHIPPAGE_API_URL ||
|
|
72
|
+
return (process.env.SHIPPAGE_API_URL || storedConfig.apiUrl || DEFAULT_API_URL).replace(/\/$/, "");
|
|
32
73
|
}
|
|
33
74
|
function apiToken() {
|
|
34
|
-
return process.env.SHIPPAGE_TOKEN;
|
|
75
|
+
return process.env.SHIPPAGE_TOKEN || storedConfig.token;
|
|
35
76
|
}
|
|
36
77
|
async function requestJson(endpoint, init = {}) {
|
|
37
78
|
const headers = new Headers(init.headers);
|
|
@@ -47,12 +88,12 @@ async function requestJson(endpoint, init = {}) {
|
|
|
47
88
|
return payload;
|
|
48
89
|
}
|
|
49
90
|
async function publishFile(filePath, options) {
|
|
50
|
-
const absolute =
|
|
51
|
-
const buffer = await
|
|
91
|
+
const absolute = path2.resolve(filePath);
|
|
92
|
+
const buffer = await fs2.readFile(absolute);
|
|
52
93
|
const form = new FormData();
|
|
53
|
-
const extension =
|
|
94
|
+
const extension = path2.extname(filePath).toLowerCase();
|
|
54
95
|
const type = extension === ".md" || extension === ".mdx" ? "text/markdown" : extension === ".txt" ? "text/plain" : "text/html";
|
|
55
|
-
form.set("file", new Blob([buffer], { type }),
|
|
96
|
+
form.set("file", new Blob([buffer], { type }), path2.basename(filePath));
|
|
56
97
|
if (options.title) form.set("title", options.title);
|
|
57
98
|
if (options.description) form.set("description", options.description);
|
|
58
99
|
if (options.slug) form.set("slug", options.slug);
|
|
@@ -63,13 +104,46 @@ async function publishFile(filePath, options) {
|
|
|
63
104
|
body: form
|
|
64
105
|
});
|
|
65
106
|
}
|
|
66
|
-
program.name("shippage").description("CLI for Shippage fast HTML publishing").version("0.
|
|
67
|
-
program.command("login").description("
|
|
68
|
-
|
|
69
|
-
if (options.token)
|
|
70
|
-
|
|
71
|
-
console.log(
|
|
107
|
+
program.name("shippage").description("CLI for Shippage fast HTML publishing").version("0.2.0");
|
|
108
|
+
program.command("login").description("Open a browser and save a long-lived Shippage login").option("--token <token>", "Shippage API key").option("--api-url <url>", "Shippage API URL").option("--no-open", "Do not open the browser automatically").action(async (options) => {
|
|
109
|
+
const loginApiUrl = (options.apiUrl || apiUrl()).replace(/\/$/, "");
|
|
110
|
+
if (options.token) {
|
|
111
|
+
const configPath = await writeCliConfig({ apiUrl: loginApiUrl, token: options.token });
|
|
112
|
+
console.log(chalk.green(`Saved Shippage credentials to ${configPath}`));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const response = await fetch(`${loginApiUrl}/api/cli-auth/device`, { method: "POST" });
|
|
116
|
+
const request = await response.json();
|
|
117
|
+
if (!response.ok || !request.deviceCode || !request.userCode || !request.verificationUrl || !request.expiresAt) {
|
|
118
|
+
throw new Error(request.error || "Unable to start browser login.");
|
|
119
|
+
}
|
|
120
|
+
console.log(`Open: ${request.verificationUrl}`);
|
|
121
|
+
console.log(`Confirm code: ${chalk.bold(request.userCode)}`);
|
|
122
|
+
if (options.open) {
|
|
123
|
+
try {
|
|
124
|
+
await openBrowser(request.verificationUrl);
|
|
125
|
+
console.log(chalk.dim("Opened your browser. Waiting for authorization..."));
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.log(chalk.yellow("Could not open a browser automatically. Open the URL above."));
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.log(chalk.dim("Waiting for authorization..."));
|
|
131
|
+
}
|
|
132
|
+
const interval = Math.max(1, request.interval || 2) * 1e3;
|
|
133
|
+
const expiresAt = new Date(request.expiresAt).getTime();
|
|
134
|
+
while (Date.now() < expiresAt) {
|
|
135
|
+
await sleep(interval);
|
|
136
|
+
const pollResponse = await fetch(`${loginApiUrl}/api/cli-auth/device?device_code=${encodeURIComponent(request.deviceCode)}`);
|
|
137
|
+
const poll = await pollResponse.json();
|
|
138
|
+
if (pollResponse.status === 202 || poll.status === "pending") continue;
|
|
139
|
+
if (!pollResponse.ok || !poll.token) {
|
|
140
|
+
throw new Error(poll.error || "Browser login failed.");
|
|
141
|
+
}
|
|
142
|
+
const configPath = await writeCliConfig({ apiUrl: poll.apiUrl || loginApiUrl, token: poll.token });
|
|
143
|
+
console.log(chalk.green(`Logged in. Credentials saved to ${configPath}`));
|
|
144
|
+
return;
|
|
72
145
|
}
|
|
146
|
+
throw new Error("Browser login expired. Run shippage login again.");
|
|
73
147
|
});
|
|
74
148
|
program.command("publish").argument("<file>", "HTML, Markdown, or text file").option("--title <title>", "Page title").option("--description <description>", "Page meta description").option("--slug <slug>", "Custom path slug").option("--domain <domain>", "Verified custom domain").option("--visibility <visibility>", "public, unlisted, or private", "unlisted").action(async (file, options) => {
|
|
75
149
|
const result = await publishFile(file, options);
|
|
@@ -87,7 +161,7 @@ program.command("bulk").argument("<pattern>", "Glob pattern, for example out/**/
|
|
|
87
161
|
}
|
|
88
162
|
for (const file of files) {
|
|
89
163
|
const result = await publishFile(file, __spreadProps(__spreadValues({}, options), {
|
|
90
|
-
title:
|
|
164
|
+
title: path2.basename(file, path2.extname(file))
|
|
91
165
|
}));
|
|
92
166
|
console.log(`${chalk.green("Published")} ${file} -> ${result.shareUrl}`);
|
|
93
167
|
}
|
|
@@ -152,7 +226,28 @@ domain.command("remove").argument("<domain>", "Domain name").action(async (domai
|
|
|
152
226
|
});
|
|
153
227
|
console.log(chalk.green(`Removed ${domainName}`));
|
|
154
228
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
229
|
+
void bootstrap();
|
|
230
|
+
async function bootstrap() {
|
|
231
|
+
try {
|
|
232
|
+
storedConfig = await readCliConfig();
|
|
233
|
+
await program.parseAsync();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(chalk.red(error instanceof Error ? error.message : "Command failed"));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function sleep(milliseconds) {
|
|
240
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
241
|
+
}
|
|
242
|
+
function openBrowser(url) {
|
|
243
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
244
|
+
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
245
|
+
return new Promise((resolve, reject) => {
|
|
246
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
247
|
+
child.once("error", reject);
|
|
248
|
+
child.once("spawn", () => {
|
|
249
|
+
child.unref();
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|