share-pages 0.2.0 → 0.3.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 +36 -0
- package/dist/{dist-BkIxqYQy.mjs → dist-LkxVliJx.mjs} +1 -1
- package/dist/{epub-BXQPqTFk-CSXSpn1h.mjs → epub-BXQPqTFk-DV2p5uyW.mjs} +2 -2
- package/dist/index.mjs +520 -101
- package/dist/{latex-DMo_3IW8-D8ylufbk.mjs → latex-DMo_3IW8-DdNgxe_1.mjs} +2 -2
- package/dist/{markdown-BWlCFeLJ-D_-ZvWdl.mjs → markdown-BWlCFeLJ-CCxfGkZu.mjs} +2 -2
- package/dist/{node-ZNJuj10Q.mjs → node-DVMkHtVp.mjs} +2 -2
- package/dist/{prepare-C56DezIS-DwhDVRlC.mjs → prepare-C56DezIS-pzIt9WD8.mjs} +3 -3
- package/package.json +10 -11
package/README.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Share Pages 的命令行客户端,用于发布和管理 HTML、目录、Markdown、LaTeX、EPUB 与 CSV 内容。
|
|
4
4
|
|
|
5
|
+
默认输出为适合人阅读的摘要;添加 `--json` 可获得稳定的单行 JSON。直接运行 `share-pages`,或在任意命令后添加 `--help`,即可看到按工作流组织的示例。
|
|
6
|
+
|
|
5
7
|
## 安装
|
|
6
8
|
|
|
7
9
|
需要 Node.js 22 或更高版本。
|
|
8
10
|
|
|
9
11
|
```sh
|
|
10
12
|
npm install --global share-pages
|
|
13
|
+
share-pages --version
|
|
14
|
+
share-pages
|
|
11
15
|
```
|
|
12
16
|
|
|
13
17
|
## 登录
|
|
@@ -16,6 +20,7 @@ npm install --global share-pages
|
|
|
16
20
|
|
|
17
21
|
```sh
|
|
18
22
|
share-pages login --server https://pages.example.com
|
|
23
|
+
share-pages status
|
|
19
24
|
```
|
|
20
25
|
|
|
21
26
|
自动化环境可通过 stdin 提供 Token:
|
|
@@ -35,15 +40,46 @@ share-pages publish ./README.md --id readme --access public
|
|
|
35
40
|
share-pages publish ./report.csv --id report --title "Report"
|
|
36
41
|
```
|
|
37
42
|
|
|
43
|
+
成功后 CLI 会给出页面地址、管理入口和下一次更新命令。更新已有页面必须显式添加 `--update`,避免误操作:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
share-pages publish ./README.md --id readme --update
|
|
47
|
+
```
|
|
48
|
+
|
|
38
49
|
发布前只做本地转换和校验:
|
|
39
50
|
|
|
40
51
|
```sh
|
|
41
52
|
share-pages publish ./site --dry-run --json
|
|
42
53
|
```
|
|
43
54
|
|
|
55
|
+
## 查询与恢复
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
share-pages pages list
|
|
59
|
+
share-pages pages get readme
|
|
60
|
+
share-pages versions list readme
|
|
61
|
+
share-pages stats readme
|
|
62
|
+
share-pages download readme --output readme-original.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
网络中断后,使用首次上传时打印的会话 ID 继续:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
share-pages publish ./site --id docs --resume <upload-id>
|
|
69
|
+
share-pages uploads <upload-id>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
删除本机凭据不会撤销远端 Token;敏感 Token 仍应在后台撤销:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
share-pages logout
|
|
76
|
+
```
|
|
77
|
+
|
|
44
78
|
查看全部命令:
|
|
45
79
|
|
|
46
80
|
```sh
|
|
47
81
|
share-pages --help
|
|
48
82
|
share-pages publish --help
|
|
49
83
|
```
|
|
84
|
+
|
|
85
|
+
完整参数、约束和示例均可通过 `share-pages help <命令>` 离线查看。
|
|
@@ -5447,4 +5447,4 @@ function escapeHtml(value) {
|
|
|
5447
5447
|
})[c] ?? c);
|
|
5448
5448
|
}
|
|
5449
5449
|
//#endregion
|
|
5450
|
-
export {
|
|
5450
|
+
export { defaultLimits as a, formats as c, slugSchema as d, validateManifestLimits as f, assert as i, manifestSchema as l, CHUNK_SIZE as n, digest as o, accessSchema as r, escapeHtml as s, AppError as t, safePath as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __commonJSMin } from "./rolldown-runtime-B4iAMlE-.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { c as unzipSafe, n as document, o as safeXMLText, r as encoder, s as sanitizeEPUB, t as decodeText } from "./prepare-C56DezIS-
|
|
2
|
+
import { i as assert, u as safePath } from "./dist-LkxVliJx.mjs";
|
|
3
|
+
import { c as unzipSafe, n as document, o as safeXMLText, r as encoder, s as sanitizeEPUB, t as decodeText } from "./prepare-C56DezIS-pzIt9WD8.mjs";
|
|
4
4
|
//#region ../../node_modules/.pnpm/@xmldom+xmldom@0.8.15/node_modules/@xmldom/xmldom/lib/conventions.js
|
|
5
5
|
var require_conventions = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
6
6
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,105 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { Command, CommanderError, Option } from "commander";
|
|
2
|
+
import { a as defaultLimits, c as formats, d as slugSchema, i as assert, n as CHUNK_SIZE, o as digest, r as accessSchema, t as AppError } from "./dist-LkxVliJx.mjs";
|
|
3
|
+
import { Command, CommanderError, Help, Option } from "commander";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { chmod, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
|
+
import { StringDecoder } from "node:string_decoder";
|
|
9
10
|
import { createWriteStream } from "node:fs";
|
|
10
11
|
import { Readable } from "node:stream";
|
|
11
12
|
import { pipeline } from "node:stream/promises";
|
|
13
|
+
//#region src/terminal.ts
|
|
14
|
+
const control = /[\x00-\x1f\x7f-\x9f]/g;
|
|
15
|
+
function terminalText(value, multiline = false) {
|
|
16
|
+
return String(value).replace(control, (character) => multiline && character === "\n" ? "\n" : " ");
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/errors.ts
|
|
20
|
+
const hints = {
|
|
21
|
+
LOGIN_REQUIRED: "先运行 share-pages login --server https://你的实例域名",
|
|
22
|
+
SERVER_REQUIRED: "添加 --server https://你的实例域名,或为已有 profile 重新登录",
|
|
23
|
+
INVALID_SERVER: "--server 只填写 origin,例如 https://pages.example.com",
|
|
24
|
+
INSECURE_SERVER: "远程实例必须使用 HTTPS;只有 localhost 可以使用 HTTP",
|
|
25
|
+
TOKEN_REQUIRED: "在后台创建发布 Token,然后重新登录",
|
|
26
|
+
INVALID_PAGE_ID: "页面 ID 使用 3–40 位小写字母、数字或连字符,并避开系统保留名称",
|
|
27
|
+
INVALID_PROFILE: "例如使用 default、production 或 team_docs",
|
|
28
|
+
INVALID_UPLOAD_ID: "复制上传开始时 CLI 输出的完整会话 ID",
|
|
29
|
+
INVALID_DATE: "例如使用 2026-09-10",
|
|
30
|
+
INVALID_DATE_RANGE: "交换起止日期,确保 --from 不晚于 --to",
|
|
31
|
+
INVALID_LIMIT: "将 --limit 设置为 1–100 的整数",
|
|
32
|
+
INVALID_TIMEOUT: "将 --timeout 设置为 1–3600 的整数秒数",
|
|
33
|
+
ID_REQUIRED: "添加 --id <已有页面 ID>,或去掉 --update 创建新页面",
|
|
34
|
+
TYPE_REQUIRED: "stdin 没有文件名;添加 --type markdown、html、latex、epub、csv 或 zip",
|
|
35
|
+
INVALID_INDEX_POLICY: "改用 --access public,或移除 --indexable",
|
|
36
|
+
INPUT_REQUIRED: "自动化环境请使用 --token-stdin、--password-file 或对应环境变量",
|
|
37
|
+
CONFIRMATION_REQUIRED: "确认目标无误后添加 --yes;永久删除无法恢复",
|
|
38
|
+
ID_EXISTS: "若要为已有页面创建新版本,请保持相同 --id 并添加 --update",
|
|
39
|
+
PAGE_NOT_FOUND: "运行 share-pages pages list 查找可用页面 ID",
|
|
40
|
+
VERSION_NOT_FOUND: "运行 share-pages versions list <页面 ID> 查找版本 ID",
|
|
41
|
+
RESUME_MISMATCH: "使用创建该会话时相同的文件和实例,或去掉 --resume 开始新上传",
|
|
42
|
+
INVALID_RESUME_STATE: "去掉 --resume 开始新上传;损坏状态不会影响服务端已有内容",
|
|
43
|
+
NETWORK_ERROR: "检查网络和实例地址后重试;已有上传会话可用 --resume 继续",
|
|
44
|
+
REQUEST_TIMEOUT: "可增大 --timeout,或用 --no-wait 后运行 share-pages uploads <上传 ID>",
|
|
45
|
+
OUTPUT_EXISTS: "换一个 --output 路径;CLI 不会覆盖已有文件",
|
|
46
|
+
INPUT_NOT_FOUND: "检查输入路径、--password-file 或 --resume 的值是否正确",
|
|
47
|
+
PERMISSION_DENIED: "检查文件权限以及当前目录是否可读写",
|
|
48
|
+
CONFIG_ERROR: "修复或移走配置文件后重新登录;默认位置为 ~/.config/share-pages/config.json",
|
|
49
|
+
INSECURE_SECRET_FILE: "秘密文件只能由当前用户读写,建议设置为 chmod 600",
|
|
50
|
+
INVALID_PASSWORD: "换用 8–256 位页面密码后重试",
|
|
51
|
+
PASSWORD_ACCESS_CONFLICT: "使用 --access password,或移除 --password-file",
|
|
52
|
+
EMPTY_PATCH: "运行 share-pages pages edit --help 查看可修改字段",
|
|
53
|
+
OUTPUT_CONFLICT: "只选择一种 stdout 输出格式后重试"
|
|
54
|
+
};
|
|
55
|
+
function systemError(error) {
|
|
56
|
+
const path = error.path ? `:${error.path}` : "";
|
|
57
|
+
if (error.code === "EEXIST") return new AppError("OUTPUT_EXISTS", `目标文件已存在${path}`);
|
|
58
|
+
if (error.code === "ENOENT") return new AppError("INPUT_NOT_FOUND", `找不到文件或目录${path}`);
|
|
59
|
+
if (error.code === "EACCES" || error.code === "EPERM") return new AppError("PERMISSION_DENIED", `没有文件访问权限${path}`, 403);
|
|
60
|
+
}
|
|
61
|
+
function isHelpExit(error) {
|
|
62
|
+
return error instanceof CommanderError && (error.exitCode === 0 || ["commander.help", "commander.helpDisplayed"].includes(error.code));
|
|
63
|
+
}
|
|
64
|
+
function normalizeError(error) {
|
|
65
|
+
if (error instanceof CommanderError) return {
|
|
66
|
+
code: "CLI_USAGE",
|
|
67
|
+
message: error.message.replace(/^error:\s*/i, "").trim().replace(/^unknown command /i, "未知命令 ").replace(/^unknown option /i, "未知选项 ").replace(/^missing required argument /i, "缺少必填参数 ").replace(/^too many arguments\./i, "参数过多。").replace(/\(Did you mean (.+)\?\)/i, "你可能想输入 $1。"),
|
|
68
|
+
status: 400,
|
|
69
|
+
hint: "运行 share-pages --help,或 share-pages <命令> --help 查看示例"
|
|
70
|
+
};
|
|
71
|
+
const resolved = (error instanceof AppError ? error : systemError(error)) ?? new AppError("CLI_ERROR", error instanceof Error ? error.message : "命令执行失败;请重试或使用 --help 检查参数");
|
|
72
|
+
return {
|
|
73
|
+
code: resolved.code,
|
|
74
|
+
message: resolved.message,
|
|
75
|
+
status: resolved.status,
|
|
76
|
+
fields: resolved.fields,
|
|
77
|
+
requestId: resolved.requestId,
|
|
78
|
+
hint: hints[resolved.code]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function exitCode(error) {
|
|
82
|
+
if (error.status === 401 || error.status === 403) return 3;
|
|
83
|
+
if (error.status === 409) return 4;
|
|
84
|
+
if (error.status >= 500) return 5;
|
|
85
|
+
return 2;
|
|
86
|
+
}
|
|
87
|
+
function printError(error, json) {
|
|
88
|
+
if (json) {
|
|
89
|
+
const { status: _, ...result } = error;
|
|
90
|
+
process.stdout.write(`${JSON.stringify({ error: result })}\n`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
process.stderr.write(`错误 [${terminalText(error.code)}]: ${terminalText(error.message, true)}\n`);
|
|
94
|
+
if (error.requestId) process.stderr.write(`请求 ID: ${terminalText(error.requestId)}\n`);
|
|
95
|
+
if (error.hint) process.stderr.write(`下一步: ${terminalText(error.hint)}\n`);
|
|
96
|
+
}
|
|
97
|
+
function wantsJSON(arguments_) {
|
|
98
|
+
const separator = arguments_.indexOf("--");
|
|
99
|
+
const options = separator === -1 ? arguments_ : arguments_.slice(0, separator);
|
|
100
|
+
return options.includes("--json") || options.includes("-j");
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
12
103
|
//#region ../../packages/sdk/dist/index.js
|
|
13
104
|
function checkAbort(signal) {
|
|
14
105
|
if (signal?.aborted) throw new AppError("ABORTED", "操作已取消,上传会话仍可恢复", 499);
|
|
@@ -320,6 +411,34 @@ function resumePath(id) {
|
|
|
320
411
|
if (!/^[a-z0-9-]+$/i.test(id)) throw new AppError("INVALID_UPLOAD_ID", "非法上传 ID");
|
|
321
412
|
return join(configDir, `upload-${id}.json`);
|
|
322
413
|
}
|
|
414
|
+
async function removeResume(id) {
|
|
415
|
+
await unlink(resumePath(id)).catch((error) => {
|
|
416
|
+
if (error.code !== "ENOENT") throw error;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/help.ts
|
|
421
|
+
const headings = /* @__PURE__ */ new Map([
|
|
422
|
+
["Usage:", "用法:"],
|
|
423
|
+
["Arguments:", "参数:"],
|
|
424
|
+
["Options:", "选项:"],
|
|
425
|
+
["Commands:", "命令:"],
|
|
426
|
+
["Global Options:", "全局选项:"]
|
|
427
|
+
]);
|
|
428
|
+
function configureHelp(command) {
|
|
429
|
+
command.helpOption("-h, --help", "显示命令帮助").helpCommand("help [command]", "显示指定命令的帮助").showSuggestionAfterError().showHelpAfterError(false).configureOutput({ writeErr: (value) => {
|
|
430
|
+
if (/^(用法|Usage):|^\n(?:示例|说明):/.test(value)) process.stdout.write(value);
|
|
431
|
+
} }).configureHelp({
|
|
432
|
+
helpWidth: Math.min(process.stdout.columns || 100, 120),
|
|
433
|
+
formatHelp(current, helper) {
|
|
434
|
+
return Help.prototype.formatHelp.call(helper, current, helper).replace(/^(Usage:|Arguments:|Options:|Commands:|Global Options:)/gm, (title) => headings.get(title) ?? title).replace(/\(choices: ([^)]+)\)/g, "(可选值: $1)").replace(/\(default: ([^)]+)\)/g, "(默认: $1)");
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
function examples(command, lines, notes = []) {
|
|
439
|
+
command.addHelpText("after", `\n示例:\n${lines.map((line) => ` ${line}`).join("\n")}${notes.length ? `\n\n说明:\n${notes.map((line) => ` ${line}`).join("\n")}` : ""}\n`);
|
|
440
|
+
return command;
|
|
441
|
+
}
|
|
323
442
|
//#endregion
|
|
324
443
|
//#region src/input.ts
|
|
325
444
|
async function stdinBytes(limit = 100 * 1024 ** 2) {
|
|
@@ -340,31 +459,57 @@ async function secret(prompt, interactive = true) {
|
|
|
340
459
|
process.stdin.resume();
|
|
341
460
|
return new Promise((resolve, reject) => {
|
|
342
461
|
let value = "";
|
|
462
|
+
const decoder = new StringDecoder("utf8");
|
|
343
463
|
const onData = (data) => {
|
|
344
|
-
for (const c of
|
|
464
|
+
for (const c of decoder.write(data)) {
|
|
345
465
|
if (c === "\r" || c === "\n") {
|
|
346
466
|
cleanup();
|
|
347
467
|
resolve(value);
|
|
348
468
|
return;
|
|
349
469
|
}
|
|
350
|
-
if (c === "") {
|
|
470
|
+
if (c === "" || c === "") {
|
|
351
471
|
cleanup();
|
|
352
472
|
reject(new AppError("CANCELLED", "操作取消"));
|
|
353
473
|
return;
|
|
354
474
|
}
|
|
355
|
-
if (c === "") value = value.slice(0, -1);
|
|
475
|
+
if (c === "\b" || c === "") value = value.slice(0, -1);
|
|
356
476
|
else if (c >= " ") value += c;
|
|
357
477
|
}
|
|
358
478
|
};
|
|
479
|
+
const onEnd = () => {
|
|
480
|
+
cleanup();
|
|
481
|
+
reject(new AppError("CANCELLED", "输入已结束,操作取消"));
|
|
482
|
+
};
|
|
483
|
+
const onError = (error) => {
|
|
484
|
+
cleanup();
|
|
485
|
+
reject(error);
|
|
486
|
+
};
|
|
359
487
|
function cleanup() {
|
|
360
488
|
process.stdin.off("data", onData);
|
|
489
|
+
process.stdin.off("end", onEnd);
|
|
490
|
+
process.stdin.off("error", onError);
|
|
361
491
|
process.stdin.setRawMode(false);
|
|
362
492
|
process.stdin.pause();
|
|
363
493
|
process.stderr.write("\n");
|
|
364
494
|
}
|
|
365
495
|
process.stdin.on("data", onData);
|
|
496
|
+
process.stdin.once("end", onEnd);
|
|
497
|
+
process.stdin.once("error", onError);
|
|
366
498
|
});
|
|
367
499
|
}
|
|
500
|
+
async function secretFile(path, label = "秘密文件") {
|
|
501
|
+
const file = await open(path, "r");
|
|
502
|
+
try {
|
|
503
|
+
const metadata = await file.stat();
|
|
504
|
+
if (!metadata.isFile()) throw new AppError("INVALID_SECRET_FILE", `${label}必须是普通文件`);
|
|
505
|
+
if (process.platform !== "win32" && (metadata.mode & 63) !== 0) throw new AppError("INSECURE_SECRET_FILE", `${label}权限过宽;请先运行 chmod 600 ${JSON.stringify(path)}`);
|
|
506
|
+
const value = (await file.readFile("utf8")).trimEnd();
|
|
507
|
+
if (!value) throw new AppError("EMPTY_SECRET_FILE", `${label}不能为空`);
|
|
508
|
+
return value;
|
|
509
|
+
} finally {
|
|
510
|
+
await file.close();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
368
513
|
async function confirm(message, yes, interactive) {
|
|
369
514
|
if (yes) return;
|
|
370
515
|
if (!interactive || !process.stdin.isTTY) throw new AppError("CONFIRMATION_REQUIRED", "自动化删除需要显式 --yes");
|
|
@@ -380,11 +525,11 @@ async function confirm(message, yes, interactive) {
|
|
|
380
525
|
}
|
|
381
526
|
//#endregion
|
|
382
527
|
//#region package.json
|
|
383
|
-
var version = "0.
|
|
528
|
+
var version = "0.3.0";
|
|
384
529
|
//#endregion
|
|
385
530
|
//#region src/shared.ts
|
|
386
531
|
function createCommand() {
|
|
387
|
-
return new Command().name("share-pages").description("
|
|
532
|
+
return new Command().name("share-pages").description("将 HTML、站点目录和文档发布到你自己的 Share Pages 实例").version(version, "-V, --version", "显示 CLI 版本").addOption(new Option("-j, --json", "以单行 JSON 输出结果和错误(适合脚本)")).addOption(new Option("-q, --quiet", "隐藏进度和操作提示")).addOption(new Option("--no-interactive", "禁止密码和删除确认等交互输入")).addOption(new Option("-p, --profile <name>", "使用指定的实例配置")).addOption(new Option("--server <url>", "临时覆盖实例地址")).exitOverride();
|
|
388
533
|
}
|
|
389
534
|
function options(command) {
|
|
390
535
|
const chain = [];
|
|
@@ -401,27 +546,75 @@ function stringOptions(options, keys) {
|
|
|
401
546
|
}
|
|
402
547
|
return values;
|
|
403
548
|
}
|
|
404
|
-
function output(value, o) {
|
|
549
|
+
function output(value, o, human) {
|
|
405
550
|
if (o.json) process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
406
|
-
else if (
|
|
551
|
+
else if (human) process.stdout.write(`${terminalText(human, true).trimEnd()}\n`);
|
|
552
|
+
else if (typeof value === "object" && value && "url" in value) process.stdout.write(`${terminalText(value.url)}\n`);
|
|
407
553
|
else process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
408
554
|
}
|
|
555
|
+
function note(message, o) {
|
|
556
|
+
if (!o.quiet) process.stderr.write(`${terminalText(message).trimEnd()}\n`);
|
|
557
|
+
}
|
|
409
558
|
async function client(o) {
|
|
410
559
|
return new SharePagesClient(await profile(o.profile, o.server));
|
|
411
560
|
}
|
|
412
561
|
//#endregion
|
|
562
|
+
//#region src/validation.ts
|
|
563
|
+
const choices = {
|
|
564
|
+
access: accessSchema.options,
|
|
565
|
+
format: formats,
|
|
566
|
+
pageState: [
|
|
567
|
+
"active",
|
|
568
|
+
"trash",
|
|
569
|
+
"purging",
|
|
570
|
+
"purged",
|
|
571
|
+
"all"
|
|
572
|
+
]
|
|
573
|
+
};
|
|
574
|
+
function pageId(value) {
|
|
575
|
+
const parsed = slugSchema.safeParse(value);
|
|
576
|
+
if (!parsed.success) throw new AppError("INVALID_PAGE_ID", parsed.error.issues[0]?.message ?? "页面 ID 无效");
|
|
577
|
+
return parsed.data;
|
|
578
|
+
}
|
|
579
|
+
function profileName(value) {
|
|
580
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,39}$/i.test(value)) throw new AppError("INVALID_PROFILE", "Profile 名称需为 1–40 位字母、数字、点、下划线或连字符");
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
function uploadId(value) {
|
|
584
|
+
if (!/^[a-z0-9-]{1,80}$/i.test(value)) throw new AppError("INVALID_UPLOAD_ID", "上传 ID 只能包含字母、数字和连字符");
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
function positiveSeconds(value) {
|
|
588
|
+
if (!/^\d+$/.test(value)) throw new AppError("INVALID_TIMEOUT", "--timeout 必须是 1–3600 之间的整数秒数");
|
|
589
|
+
const seconds = Number(value);
|
|
590
|
+
if (seconds < 1 || seconds > 3600) throw new AppError("INVALID_TIMEOUT", "--timeout 必须是 1–3600 之间的整数秒数");
|
|
591
|
+
return seconds;
|
|
592
|
+
}
|
|
593
|
+
function pageLimit(value) {
|
|
594
|
+
if (!/^\d+$/.test(value) || Number(value) < 1 || Number(value) > 100) throw new AppError("INVALID_LIMIT", "--limit 必须是 1–100 之间的整数");
|
|
595
|
+
return Number(value);
|
|
596
|
+
}
|
|
597
|
+
function isoDate(value) {
|
|
598
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
599
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(parsed.valueOf()) || parsed.toISOString().slice(0, 10) !== value) throw new AppError("INVALID_DATE", "日期必须使用 YYYY-MM-DD 格式");
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
function requirePatch(patch) {
|
|
603
|
+
if (!Object.keys(patch).length) throw new AppError("EMPTY_PATCH", "没有要修改的内容;请指定 --title、--access、--indexable 或 --password-file");
|
|
604
|
+
}
|
|
605
|
+
//#endregion
|
|
413
606
|
//#region src/accounts.ts
|
|
414
607
|
function registerAccounts(cli) {
|
|
415
|
-
cli.command("login").description("
|
|
416
|
-
const o = options(command), token = o.tokenStdin ? new TextDecoder().decode(await stdinBytes(1024)).trim() : process.env.SHARE_PAGES_TOKEN ?? await secret("发布 Token:", o.interactive);
|
|
417
|
-
if (!
|
|
608
|
+
examples(cli.command("login").description("验证并安全保存实例地址和发布 Token").helpGroup("开始使用").option("--token-stdin", "从 stdin 读取 Token(推荐用于自动化)").action(async (_local, command) => {
|
|
609
|
+
const o = options(command), config = await readConfig(), name = profileName(o.profile ?? config.active ?? "default"), existing = Object.hasOwn(config.profiles, name) ? config.profiles[name] : void 0, server = o.server ?? process.env.SHARE_PAGES_SERVER ?? existing?.server, token = o.tokenStdin ? new TextDecoder().decode(await stdinBytes(1024)).trim() : process.env.SHARE_PAGES_TOKEN ?? await secret("发布 Token:", o.interactive);
|
|
610
|
+
if (!server) throw new AppError("SERVER_REQUIRED", "首次登录需要 --server <url>");
|
|
611
|
+
if (!token) throw new AppError("TOKEN_REQUIRED", "发布 Token 不能为空", 401);
|
|
418
612
|
const sdk = new SharePagesClient({
|
|
419
|
-
server
|
|
613
|
+
server,
|
|
420
614
|
token
|
|
421
615
|
});
|
|
616
|
+
note(`正在验证 ${terminalText(sdk.server)}…`, o);
|
|
422
617
|
await sdk.request("/me");
|
|
423
|
-
const config = await readConfig();
|
|
424
|
-
const name = o.profile ?? "default";
|
|
425
618
|
config.profiles = {
|
|
426
619
|
...config.profiles,
|
|
427
620
|
[name]: {
|
|
@@ -435,81 +628,226 @@ function registerAccounts(cli) {
|
|
|
435
628
|
profile: name,
|
|
436
629
|
server: sdk.server,
|
|
437
630
|
status: "logged-in"
|
|
438
|
-
}, o);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
631
|
+
}, o, `已登录并启用 profile “${name}”\n实例: ${sdk.server}`);
|
|
632
|
+
note("下一步: share-pages publish <文件或目录> --id <页面 ID>", o);
|
|
633
|
+
}), [
|
|
634
|
+
"share-pages login --server https://pages.example.com",
|
|
635
|
+
"printf '%s' \"$SHARE_PAGES_TOKEN\" | share-pages login --server \"$SHARE_PAGES_SERVER\" --token-stdin --no-interactive",
|
|
636
|
+
"share-pages login --profile production # 更新已有 profile 的 Token"
|
|
637
|
+
]);
|
|
638
|
+
const profiles = cli.command("profiles").description("查看和切换本机保存的实例配置(永不显示 Token)").helpGroup("开始使用");
|
|
639
|
+
examples(profiles, [
|
|
640
|
+
"share-pages profiles list",
|
|
641
|
+
"share-pages profiles use production",
|
|
642
|
+
"share-pages status --profile production"
|
|
643
|
+
]);
|
|
644
|
+
examples(profiles.command("list").description("列出 profile 和当前实例").action(async (_o, command) => {
|
|
442
645
|
const o = options(command), c = await readConfig();
|
|
646
|
+
const items = Object.entries(c.profiles).map(([name, p]) => ({
|
|
647
|
+
name,
|
|
648
|
+
server: p.server
|
|
649
|
+
}));
|
|
443
650
|
output({
|
|
444
651
|
active: c.active,
|
|
445
|
-
profiles:
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
});
|
|
451
|
-
profiles.command("use <name>").action(async (name, _o, command) => {
|
|
652
|
+
profiles: items
|
|
653
|
+
}, o, items.length ? items.map((item) => `${item.name === c.active ? "*" : " "} ${terminalText(item.name)} ${terminalText(item.server)}`).join("\n") : "尚未保存 profile。");
|
|
654
|
+
if (!items.length) note("下一步: share-pages login --server https://你的实例域名", o);
|
|
655
|
+
}), ["share-pages profiles list", "share-pages profiles list --json"]);
|
|
656
|
+
examples(profiles.command("use <name>").description("切换默认 profile").action(async (name, _o, command) => {
|
|
452
657
|
const o = options(command), c = await readConfig();
|
|
453
|
-
|
|
454
|
-
c.
|
|
658
|
+
const key = profileName(name);
|
|
659
|
+
if (!Object.hasOwn(c.profiles, key)) throw new AppError("PROFILE_NOT_FOUND", "实例配置不存在");
|
|
660
|
+
c.active = key;
|
|
455
661
|
await writeConfig(c);
|
|
456
|
-
output({ active:
|
|
662
|
+
output({ active: key }, o, `已启用 profile “${key}”`);
|
|
663
|
+
}), ["share-pages profiles use production"]);
|
|
664
|
+
examples(cli.command("logout [name]").description("删除本机保存的一个 profile(不会撤销远端 Token)").helpGroup("开始使用").action(async (name, _local, command) => {
|
|
665
|
+
const o = options(command), config = await readConfig(), key = profileName(name ?? o.profile ?? config.active);
|
|
666
|
+
if (!Object.hasOwn(config.profiles, key)) throw new AppError("PROFILE_NOT_FOUND", "实例配置不存在");
|
|
667
|
+
const { [key]: _, ...remaining } = config.profiles;
|
|
668
|
+
config.profiles = remaining;
|
|
669
|
+
if (config.active === key) config.active = Object.keys(remaining)[0] ?? "default";
|
|
670
|
+
await writeConfig(config);
|
|
671
|
+
output({
|
|
672
|
+
profile: key,
|
|
673
|
+
status: "logged-out",
|
|
674
|
+
active: config.active
|
|
675
|
+
}, o, `已删除 profile “${key}”`);
|
|
676
|
+
note("安全提示: 如需让 Token 立即失效,请同时在后台撤销它。", o);
|
|
677
|
+
}), ["share-pages logout", "share-pages logout production"]);
|
|
678
|
+
examples(cli.command("status").description("检查当前 profile、网络、认证和服务端能力").helpGroup("开始使用").action(async (_local, command) => {
|
|
679
|
+
const o = options(command), config = await readConfig(), name = o.profile ?? config.active, sdk = await client(o);
|
|
680
|
+
note(`正在检查 ${terminalText(sdk.server)}…`, o);
|
|
681
|
+
const [actor, capabilities] = await Promise.all([sdk.request("/me"), sdk.capabilities()]);
|
|
682
|
+
const result = {
|
|
683
|
+
status: "ok",
|
|
684
|
+
profile: Object.hasOwn(config.profiles, name) ? name : null,
|
|
685
|
+
server: sdk.server,
|
|
686
|
+
actor: actor.kind,
|
|
687
|
+
scopes: actor.scopes,
|
|
688
|
+
pageIds: actor.pageIds,
|
|
689
|
+
prefixes: actor.prefixes,
|
|
690
|
+
formats: capabilities.formats,
|
|
691
|
+
apiVersion: capabilities.apiVersion,
|
|
692
|
+
contractVersion: capabilities.contractVersion
|
|
693
|
+
};
|
|
694
|
+
output(result, o, [
|
|
695
|
+
"连接与认证正常",
|
|
696
|
+
`Profile: ${terminalText(result.profile ?? "环境变量 / 临时参数")}`,
|
|
697
|
+
`实例: ${terminalText(result.server)}`,
|
|
698
|
+
`身份: ${terminalText(result.actor)}`,
|
|
699
|
+
`权限: ${terminalText(result.scopes.join(", ") || "无")}`,
|
|
700
|
+
`页面范围: ${terminalText(result.pageIds.length || result.prefixes.length ? [...result.pageIds, ...result.prefixes.map((prefix) => `${prefix}*`)].join(", ") : "全部(仍受 scope 限制)")}`,
|
|
701
|
+
`格式: ${terminalText(result.formats.join(", "))}`,
|
|
702
|
+
`API: v${terminalText(result.apiVersion)} · Contract ${terminalText(result.contractVersion)}`
|
|
703
|
+
].join("\n"));
|
|
704
|
+
}), ["share-pages status", "share-pages status --profile production --json"]);
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/presentation.ts
|
|
708
|
+
const date = (value) => {
|
|
709
|
+
if (!value || !Number.isFinite(value)) return "—";
|
|
710
|
+
try {
|
|
711
|
+
return new Date(value).toISOString();
|
|
712
|
+
} catch {
|
|
713
|
+
return "—";
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
const bytes = (value) => {
|
|
717
|
+
if (!Number.isFinite(value) || value < 0) return "—";
|
|
718
|
+
if (value < 1024) return `${value} B`;
|
|
719
|
+
if (value < 1024 ** 2) return `${(value / 1024).toFixed(1)} KiB`;
|
|
720
|
+
return `${(value / 1024 ** 2).toFixed(1)} MiB`;
|
|
721
|
+
};
|
|
722
|
+
function pagesList(result) {
|
|
723
|
+
if (!result.items.length) return "没有符合条件的页面。";
|
|
724
|
+
const rows = result.items.map((page) => `${terminalText(page.slug)} ${terminalText(page.title)}\n ${terminalText(page.format)} · ${terminalText(page.access)} · ${terminalText(page.state)} · ${date(page.updated_at)}`);
|
|
725
|
+
if (result.nextCursor) rows.push(`\n下一页: --cursor ${terminalText(result.nextCursor)}`);
|
|
726
|
+
return rows.join("\n");
|
|
727
|
+
}
|
|
728
|
+
function pageDetail(page) {
|
|
729
|
+
return [
|
|
730
|
+
`${terminalText(page.slug)} ${terminalText(page.title)}`,
|
|
731
|
+
`格式: ${terminalText(page.format)}`,
|
|
732
|
+
`访问: ${terminalText(page.access)}`,
|
|
733
|
+
`状态: ${terminalText(page.state)}`,
|
|
734
|
+
`收录: ${page.indexable ? "允许" : "禁止"}`,
|
|
735
|
+
`当前版本: ${terminalText(page.current_version_id ?? "尚未发布")}`,
|
|
736
|
+
`更新时间: ${date(page.updated_at)}`
|
|
737
|
+
].join("\n");
|
|
738
|
+
}
|
|
739
|
+
function versionsList(result) {
|
|
740
|
+
if (!result.items.length) return "该页面还没有版本。";
|
|
741
|
+
const rows = result.items.map((version) => `v${version.number} ${terminalText(version.id)}\n ${terminalText(version.state)} · ${bytes(version.bytes)} · ${date(version.created_at)}`);
|
|
742
|
+
if (result.nextCursor) rows.push(`\n下一页: --cursor ${terminalText(result.nextCursor)}`);
|
|
743
|
+
return rows.join("\n");
|
|
744
|
+
}
|
|
745
|
+
function statisticsSummary(result) {
|
|
746
|
+
const total = result.days.reduce((sum, day) => ({
|
|
747
|
+
pv: sum.pv + day.pv,
|
|
748
|
+
downloads: sum.downloads + day.downloads,
|
|
749
|
+
bytes: sum.bytes + day.bytes,
|
|
750
|
+
errors: sum.errors + day.errors
|
|
751
|
+
}), {
|
|
752
|
+
pv: 0,
|
|
753
|
+
downloads: 0,
|
|
754
|
+
bytes: 0,
|
|
755
|
+
errors: 0
|
|
756
|
+
});
|
|
757
|
+
const recent = result.days.slice(-7).map((day) => {
|
|
758
|
+
const uv = day.uv === null ? "不可用" : `${day.uv}${day.uvStatus === "exact" ? "" : "(估算)"}`;
|
|
759
|
+
return ` ${terminalText(day.day)} PV ${day.pv} · UV ${uv} · 下载 ${day.downloads}`;
|
|
457
760
|
});
|
|
761
|
+
const popular = result.popular.slice(0, 5).map((item) => ` ${terminalText(item.slug ?? item.id ?? item.scope)} PV ${item.pv} · 下载 ${item.downloads}`);
|
|
762
|
+
return [
|
|
763
|
+
`统计范围: ${terminalText(result.scope)}`,
|
|
764
|
+
`UTC 区间: ${terminalText(result.from)} 至 ${terminalText(result.to)}`,
|
|
765
|
+
`来源: ${terminalText(result.source)} · ${terminalText(result.cacheStatus)}`,
|
|
766
|
+
`汇总: PV ${total.pv} · 下载 ${total.downloads} · 流量 ${bytes(total.bytes)} · 错误 ${total.errors}`,
|
|
767
|
+
`访客日: ${result.visitorDays ?? "不可用"}`,
|
|
768
|
+
recent.length ? `最近 ${recent.length} 天:\n${recent.join("\n")}` : "该区间没有每日数据。",
|
|
769
|
+
popular.length ? `热门内容:\n${popular.join("\n")}` : void 0,
|
|
770
|
+
result.days.length > recent.length ? "完整逐日和分布数据请使用 --json 或 --csv。" : void 0
|
|
771
|
+
].filter(Boolean).join("\n");
|
|
458
772
|
}
|
|
459
773
|
//#endregion
|
|
460
774
|
//#region src/pages.ts
|
|
461
775
|
function registerPages(cli) {
|
|
462
|
-
const pages = cli.command("pages");
|
|
463
|
-
pages
|
|
776
|
+
const pages = cli.command("pages").description("查询、修改、删除和恢复页面").helpGroup("页面管理");
|
|
777
|
+
examples(pages, [
|
|
778
|
+
"share-pages pages list",
|
|
779
|
+
"share-pages pages get handbook",
|
|
780
|
+
"share-pages pages edit handbook --title \"团队手册\""
|
|
781
|
+
]);
|
|
782
|
+
examples(pages.command("list").description("列出页面;默认显示 active 页面").addOption(new Option("--state <state>", "按页面状态筛选").choices(choices.pageState)).option("--q <text>", "按页面 ID 或标题搜索").addOption(new Option("--format <format>", "按内容格式筛选").choices(choices.format)).addOption(new Option("--access <mode>", "按访问方式筛选").choices(choices.access)).option("--limit <count>", "每页返回 1–100 条", pageLimit, 30).option("--cursor <cursor>", "继续读取服务端返回的下一页").action(async (_local, command) => {
|
|
464
783
|
const o = options(command);
|
|
465
|
-
|
|
784
|
+
const params = stringOptions(o, [
|
|
466
785
|
"state",
|
|
467
786
|
"q",
|
|
468
787
|
"format",
|
|
469
788
|
"access",
|
|
470
789
|
"cursor"
|
|
471
|
-
])
|
|
472
|
-
|
|
473
|
-
|
|
790
|
+
]);
|
|
791
|
+
if (o.limit) params.limit = String(o.limit);
|
|
792
|
+
const result = await (await client(o)).listPages(params);
|
|
793
|
+
output(result, o, pagesList(result));
|
|
794
|
+
if (!result.items.length) note("提示: 使用 --state all 查看包括回收站在内的所有页面。", o);
|
|
795
|
+
}), [
|
|
796
|
+
"share-pages pages list",
|
|
797
|
+
"share-pages pages list --q handbook --format markdown",
|
|
798
|
+
"share-pages pages list --state trash --json"
|
|
799
|
+
]);
|
|
800
|
+
examples(pages.command("get").description("查看一个页面的元数据和当前版本").argument("<id>", "页面 ID", pageId).action(async (id, _local, command) => {
|
|
474
801
|
const o = options(command);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
802
|
+
const result = await (await client(o)).getPage(id);
|
|
803
|
+
output(result, o, pageDetail(result));
|
|
804
|
+
}), ["share-pages pages get handbook", "share-pages pages get handbook --json"]);
|
|
805
|
+
examples(pages.command("edit").description("修改页面标题、访问方式或收录策略").argument("<id>", "页面 ID", pageId).option("--title <title>", "设置新标题").addOption(new Option("--access <mode>", "设置访问方式").choices(choices.access)).addOption(new Option("--indexable <boolean>", "是否允许搜索引擎收录").choices(["true", "false"])).addOption(new Option("--password-file <path>", "从权限为 600 的文件读取新密码").implies({ access: "password" })).action(async (id, _local, command) => {
|
|
478
806
|
const o = options(command);
|
|
479
807
|
const patch = {};
|
|
480
|
-
if (o.
|
|
808
|
+
if (o.passwordFile && o.access !== "password") throw new AppError("PASSWORD_ACCESS_CONFLICT", "--password-file 只能用于 password 访问方式");
|
|
809
|
+
if (o.title !== void 0) patch.title = o.title;
|
|
481
810
|
if (o.access) patch.access = o.access;
|
|
482
|
-
if (o.indexable !== void 0)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
output(
|
|
488
|
-
|
|
489
|
-
|
|
811
|
+
if (o.indexable !== void 0) patch.indexable = o.indexable === "true";
|
|
812
|
+
if (o.access === "password") patch.password = o.passwordFile ? await secretFile(o.passwordFile, "页面密码文件") : await secret("页面密码(至少 8 位):", o.interactive);
|
|
813
|
+
if (typeof patch.password === "string" && (patch.password.length < 8 || patch.password.length > 256)) throw new AppError("INVALID_PASSWORD", "页面密码长度必须为 8–256 位");
|
|
814
|
+
requirePatch(patch);
|
|
815
|
+
const result = await (await client(o)).patchPage(id, patch);
|
|
816
|
+
output(result, o, pageDetail(result));
|
|
817
|
+
note(`已更新页面 ${id}`, o);
|
|
818
|
+
}), [
|
|
819
|
+
"share-pages pages edit handbook --title \"团队手册\"",
|
|
820
|
+
"share-pages pages edit handbook --access admin --indexable false",
|
|
821
|
+
"share-pages pages edit handbook --password-file ./private/password.txt"
|
|
822
|
+
]);
|
|
823
|
+
examples(pages.command("delete").description("将页面移入回收站,或永久删除").argument("<id>", "页面 ID", pageId).option("--purge", "永久删除页面和全部版本(不可恢复)").option("--yes", "跳过交互确认(自动化环境必需)").action(async (id, _local, command) => {
|
|
490
824
|
const o = options(command), sdk = await client(o);
|
|
491
825
|
const page = await sdk.getPage(id);
|
|
492
|
-
await confirm(`${o.purge ? "永久删除" : "移入回收站"}页面 ${id}(${page.title})及全部版本。`, !!o.yes, !!o.interactive);
|
|
493
|
-
output(await sdk.deletePage(id, o.purge), o);
|
|
494
|
-
});
|
|
495
|
-
pages.command("restore
|
|
826
|
+
await confirm(`${o.purge ? "永久删除" : "移入回收站"}页面 ${id}(${terminalText(page.title)})及全部版本。`, !!o.yes, !!o.interactive);
|
|
827
|
+
output(await sdk.deletePage(id, o.purge), o, o.purge ? `页面 ${id} 已进入永久清理队列。` : `页面 ${id} 已移入回收站。`);
|
|
828
|
+
}), ["share-pages pages delete handbook", "share-pages pages delete handbook --purge --yes --no-interactive"]);
|
|
829
|
+
examples(pages.command("restore").description("从回收站恢复页面").argument("<id>", "页面 ID", pageId).option("--replace-with <version>", "当前版本不可用时指定接替版本 ID").action(async (id, _local, command) => {
|
|
496
830
|
const o = options(command);
|
|
497
|
-
output(await (await client(o)).restorePage(id, o.replaceWith), o);
|
|
498
|
-
});
|
|
831
|
+
output(await (await client(o)).restorePage(id, o.replaceWith), o, `页面 ${id} 已恢复。`);
|
|
832
|
+
}), ["share-pages pages restore handbook", "share-pages pages restore handbook --replace-with <版本 UUID>"]);
|
|
499
833
|
}
|
|
500
834
|
//#endregion
|
|
501
835
|
//#region src/publish.ts
|
|
502
836
|
function registerPublish(cli) {
|
|
503
|
-
cli.command("publish
|
|
837
|
+
examples(cli.command("publish").description("检查、转换并发布文件、ZIP 或目录").helpGroup("发布与传输").argument("<input>", "文件、ZIP、目录,或 -(从 stdin 读取)").option("--id <id>", "设置 3–40 位稳定页面 ID", pageId).option("--update", "更新已有 ID").addOption(new Option("--type <format>", "覆盖自动识别的内容格式").choices(choices.format)).option("--title <title>", "覆盖从内容推导的页面标题").option("--entry <path>", "HTML/站点入口文件", "index.html").option("--spa", "找不到站点路径时回退到入口文件").addOption(new Option("--access <mode>", "访问方式(新页面默认 public)").choices(choices.access)).option("--indexable", "允许公开页面被搜索引擎收录").option("--encoding <encoding>", "Markdown/LaTeX/CSV 文本编码", "utf-8").option("--delimiter <delimiter>", "覆盖 CSV 分隔符自动识别").option("--no-header", "将 CSV 第一行视为数据而非表头").addOption(new Option("--password-file <path>", "从权限为 600 的文件读取页面密码").implies({ access: "password" })).addOption(new Option("--dry-run", "仅在本地转换和校验,不访问服务器").conflicts("resume")).addOption(new Option("--resume <upload-id>", "继续先前中断的上传").conflicts("dryRun")).option("--idempotency-key <key>", "为自动化重试提供稳定幂等键").option("--no-wait", "提交后返回处理中状态").option("--timeout <seconds>", "等待发布完成的秒数(1–3600)", positiveSeconds, 120).action(async (input, _local, command) => {
|
|
504
838
|
const o = options(command);
|
|
505
|
-
const { prepareNode, prepareBytes, ensureDOM } = await import("./node-
|
|
506
|
-
if (o.
|
|
839
|
+
const { prepareNode, prepareBytes, ensureDOM } = await import("./node-DVMkHtVp.mjs");
|
|
840
|
+
if (o.update && !o.id) throw new AppError("ID_REQUIRED", "--update 必须同时指定 --id");
|
|
841
|
+
if (o.passwordFile && o.access !== "password") throw new AppError("PASSWORD_ACCESS_CONFLICT", "--password-file 只能用于 password 访问方式");
|
|
842
|
+
if (o.indexable && o.access && o.access !== "public") throw new AppError("INVALID_INDEX_POLICY", "--indexable 只适用于 public 页面");
|
|
507
843
|
if (input === "-" && !o.type) throw new AppError("TYPE_REQUIRED", "stdin 需要显式 --type");
|
|
508
844
|
let sdk, limits = defaultLimits;
|
|
509
845
|
if (!o.dryRun) {
|
|
510
846
|
sdk = await client(o);
|
|
847
|
+
note(`正在检查实例限制: ${terminalText(sdk.server)}…`, o);
|
|
511
848
|
limits = (await sdk.capabilities()).limits;
|
|
512
849
|
}
|
|
850
|
+
note(o.dryRun ? "正在本地检查并转换内容…" : "正在检查并转换内容…", o);
|
|
513
851
|
const prepareOptions = {
|
|
514
852
|
type: o.type,
|
|
515
853
|
title: o.title,
|
|
@@ -526,21 +864,38 @@ function registerPublish(cli) {
|
|
|
526
864
|
prepared = await prepareBytes(`stdin.${o.type === "markdown" ? "md" : o.type === "latex" ? "tex" : o.type}`, await stdinBytes(limits.inputBytes), prepareOptions);
|
|
527
865
|
} else prepared = await prepareNode(input, prepareOptions);
|
|
528
866
|
if (o.dryRun) {
|
|
867
|
+
const totalBytes = prepared.manifest.files.reduce((sum, file) => sum + file.size, 0);
|
|
529
868
|
output({
|
|
530
869
|
status: "valid",
|
|
531
870
|
title: prepared.title,
|
|
532
871
|
manifest: prepared.manifest,
|
|
533
872
|
warnings: prepared.warnings
|
|
534
|
-
}, o
|
|
873
|
+
}, o, [
|
|
874
|
+
"本地检查通过(未访问服务器)",
|
|
875
|
+
`标题: ${terminalText(prepared.title)}`,
|
|
876
|
+
`格式: ${prepared.manifest.format}`,
|
|
877
|
+
`文件: ${prepared.manifest.files.length}`,
|
|
878
|
+
`总大小: ${totalBytes} bytes`,
|
|
879
|
+
`警告: ${prepared.warnings.length ? prepared.warnings.join(";") : "无"}`
|
|
880
|
+
].join("\n"));
|
|
535
881
|
return;
|
|
536
882
|
}
|
|
537
883
|
const manifestHash = await digest(JSON.stringify(prepared.manifest));
|
|
538
884
|
if (!sdk) throw new AppError("MISSING_CLIENT", "发布客户端尚未初始化");
|
|
539
885
|
if (o.resume) {
|
|
540
|
-
|
|
541
|
-
|
|
886
|
+
let saved;
|
|
887
|
+
try {
|
|
888
|
+
saved = JSON.parse(await readFile(resumePath(o.resume), "utf8"));
|
|
889
|
+
} catch (error) {
|
|
890
|
+
if (error.code) throw error;
|
|
891
|
+
throw new AppError("INVALID_RESUME_STATE", "续传状态文件已损坏,请开始新的上传");
|
|
892
|
+
}
|
|
893
|
+
if (!saved || typeof saved !== "object" || !("manifestHash" in saved) || !("server" in saved) || saved.manifestHash !== manifestHash || saved.server !== sdk.server) throw new AppError("RESUME_MISMATCH", "文件内容或实例与断点会话不一致");
|
|
542
894
|
}
|
|
543
|
-
const password = o.access === "password" ? o.passwordFile ?
|
|
895
|
+
const password = o.access === "password" ? o.passwordFile ? await secretFile(o.passwordFile, "页面密码文件") : await secret("页面密码(至少 8 位):", o.interactive) : void 0;
|
|
896
|
+
if (password && (password.length < 8 || password.length > 256)) throw new AppError("INVALID_PASSWORD", "页面密码长度必须为 8–256 位");
|
|
897
|
+
let sessionId = o.resume;
|
|
898
|
+
note(o.resume ? `正在恢复上传 ${o.resume}…` : "正在创建上传会话…", o);
|
|
544
899
|
const result = await sdk.publish(prepared, {
|
|
545
900
|
id: o.id,
|
|
546
901
|
update: !!o.update,
|
|
@@ -551,29 +906,47 @@ function registerPublish(cli) {
|
|
|
551
906
|
idempotencyKey: o.idempotencyKey,
|
|
552
907
|
uploadId: o.resume,
|
|
553
908
|
wait: o.wait,
|
|
554
|
-
timeoutMs:
|
|
909
|
+
timeoutMs: (o.timeout ?? 120) * 1e3,
|
|
555
910
|
onSession: async (s) => {
|
|
911
|
+
sessionId = s.id;
|
|
556
912
|
await savePrivate(resumePath(s.id), {
|
|
557
913
|
server: sdk?.server,
|
|
558
914
|
uploadId: s.id,
|
|
559
915
|
manifestHash
|
|
560
916
|
});
|
|
561
|
-
|
|
917
|
+
note(`上传会话: ${s.id}(网络中断后可使用 --resume ${s.id})`, o);
|
|
562
918
|
},
|
|
563
919
|
onProgress: (done, total) => {
|
|
564
920
|
if (!o.quiet && process.stderr.isTTY) process.stderr.write(`\r上传 ${total ? Math.floor(done / total * 100) : 100}%`);
|
|
565
921
|
}
|
|
566
922
|
});
|
|
923
|
+
if (result.status === "ready" && sessionId) await removeResume(sessionId);
|
|
567
924
|
if (!o.quiet && process.stderr.isTTY) process.stderr.write("\n");
|
|
568
|
-
output(result, o);
|
|
925
|
+
output(result, o, result.status === "ready" ? terminalText(result.url) : `上传 ${terminalText(result.id)}: ${terminalText(result.status)}`);
|
|
926
|
+
if (result.status === "ready") {
|
|
927
|
+
note(`已发布 ${terminalText(result.pageId)} · 版本 ${result.versionNumber}`, o);
|
|
928
|
+
note(`管理: ${terminalText(result.manageUrl)}`, o);
|
|
929
|
+
note(`下一次更新: share-pages publish ${terminalText(input)} --id ${result.pageId} --update`, o);
|
|
930
|
+
} else note(`下一步: share-pages uploads ${result.id}`, o);
|
|
569
931
|
if (result.status !== "ready") process.exitCode = 6;
|
|
570
|
-
})
|
|
932
|
+
}), [
|
|
933
|
+
"share-pages publish ./dist --id my-site",
|
|
934
|
+
"share-pages publish ./README.md --id project-readme --dry-run",
|
|
935
|
+
"share-pages publish ./README.md --id project-readme --update",
|
|
936
|
+
"printf '# Hello' | share-pages publish - --type markdown --id hello"
|
|
937
|
+
], [
|
|
938
|
+
"支持 html、zip、markdown、latex、epub、csv;目录按静态站点发布。",
|
|
939
|
+
"新建已有 ID 会失败;更新必须显式使用 --update,以避免误覆盖。",
|
|
940
|
+
"JSON 模式下结果只写 stdout,进度和提示写 stderr;添加 --quiet 可隐藏提示。"
|
|
941
|
+
]);
|
|
571
942
|
}
|
|
572
943
|
//#endregion
|
|
573
944
|
//#region src/stats.ts
|
|
574
945
|
function registerStats(cli) {
|
|
575
|
-
cli.command("stats
|
|
946
|
+
examples(cli.command("stats").description("查询全站、页面或版本的访问统计").helpGroup("观测与诊断").argument("[id]", "可选的页面 ID", pageId).option("--version <version>", "只查询指定版本 UUID").option("--from <date>", "UTC 起始日期(YYYY-MM-DD)", isoDate).option("--to <date>", "UTC 结束日期(YYYY-MM-DD)", isoDate).option("--csv", "以 CSV 输出到 stdout").action(async (id, _local, command) => {
|
|
576
947
|
const o = options(command), params = {};
|
|
948
|
+
if (o.csv && o.json) throw new AppError("OUTPUT_CONFLICT", "--csv 和 --json 不能同时使用");
|
|
949
|
+
if (o.from && o.to && o.from > o.to) throw new AppError("INVALID_DATE_RANGE", "--from 不能晚于 --to");
|
|
577
950
|
if (id) params.page = id;
|
|
578
951
|
for (const k of [
|
|
579
952
|
"version",
|
|
@@ -582,24 +955,45 @@ function registerStats(cli) {
|
|
|
582
955
|
]) if (o[k]) params[k] = o[k];
|
|
583
956
|
if (o.csv) params.format = "csv";
|
|
584
957
|
const data = await (await client(o)).stats(params);
|
|
585
|
-
if (data instanceof Response)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
958
|
+
if (data instanceof Response) {
|
|
959
|
+
const csv = await data.text();
|
|
960
|
+
process.stdout.write(csv.endsWith("\n") ? csv : `${csv}\n`);
|
|
961
|
+
} else output(data, o, statisticsSummary(data));
|
|
962
|
+
}), [
|
|
963
|
+
"share-pages stats --from 2026-09-01 --to 2026-09-07",
|
|
964
|
+
"share-pages stats handbook --json",
|
|
965
|
+
"share-pages stats handbook --csv > stats.csv"
|
|
966
|
+
]);
|
|
967
|
+
examples(cli.command("uploads").description("查询断点上传或异步发布任务的状态").helpGroup("观测与诊断").argument("<id>", "上传会话 ID", uploadId).action(async (id, _local, command) => {
|
|
589
968
|
const o = options(command);
|
|
590
|
-
|
|
591
|
-
|
|
969
|
+
const result = await (await client(o)).request(`/uploads/${encodeURIComponent(id)}`);
|
|
970
|
+
output(result, o, [
|
|
971
|
+
`上传 ${terminalText(result.id)}: ${terminalText(result.status)}`,
|
|
972
|
+
`版本: ${terminalText(result.versionId)}`,
|
|
973
|
+
result.result?.url ? `页面: ${terminalText(result.result.url)}` : void 0,
|
|
974
|
+
result.error ? `错误: ${terminalText(result.error.code)} · ${terminalText(result.error.message)}` : void 0,
|
|
975
|
+
result.missing.length ? `待上传分片文件: ${result.missing.length}` : void 0
|
|
976
|
+
].filter(Boolean).join("\n"));
|
|
977
|
+
}), ["share-pages uploads <上传会话 ID>", "share-pages uploads <上传会话 ID> --json"]);
|
|
592
978
|
}
|
|
593
979
|
//#endregion
|
|
594
980
|
//#region src/transfer.ts
|
|
595
981
|
function registerTransfer(cli) {
|
|
596
|
-
cli.command("download
|
|
597
|
-
const o = options(command)
|
|
982
|
+
examples(cli.command("download").description("下载页面当前版本或指定版本的原件").helpGroup("发布与传输").argument("<id>", "页面 ID", pageId).option("--version <version>", "下载指定版本 UUID(默认当前版本)").option("-o, --output <file>", "输出文件;使用 - 写入 stdout").action(async (id, _local, command) => {
|
|
983
|
+
const o = options(command);
|
|
984
|
+
if (o.output === "-" && o.json) throw new AppError("OUTPUT_CONFLICT", "--output - 输出原件字节,不能与 --json 同时使用");
|
|
985
|
+
const sdk = await client(o);
|
|
986
|
+
note(`正在下载 ${id}${o.version ? ` 的版本 ${terminalText(o.version)}` : ""}…`, o);
|
|
598
987
|
const version = o.version ?? (await sdk.getPage(id)).current_version_id;
|
|
599
988
|
if (!version) throw new AppError("NO_CURRENT_VERSION", "页面没有当前版");
|
|
600
989
|
const response = await sdk.download(id, String(version));
|
|
601
990
|
const filename = o.output ?? `${id}-${version}.source`;
|
|
602
991
|
if (!response.body) throw new AppError("EMPTY_DOWNLOAD", "下载内容为空");
|
|
992
|
+
if (filename === "-") {
|
|
993
|
+
note(`正在将 ${id} 的原件写入 stdout…`, o);
|
|
994
|
+
await pipeline(Readable.fromWeb(response.body), process.stdout);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
603
997
|
await pipeline(Readable.fromWeb(response.body), createWriteStream(filename, {
|
|
604
998
|
flags: "wx",
|
|
605
999
|
mode: 384
|
|
@@ -607,18 +1001,24 @@ function registerTransfer(cli) {
|
|
|
607
1001
|
output({
|
|
608
1002
|
file: filename,
|
|
609
1003
|
versionId: version
|
|
610
|
-
}, o);
|
|
611
|
-
})
|
|
1004
|
+
}, o, `原件已保存: ${terminalText(filename)}\n版本: ${terminalText(version)}`);
|
|
1005
|
+
}), [
|
|
1006
|
+
"share-pages download handbook --output handbook.md",
|
|
1007
|
+
"share-pages download handbook --version <版本 UUID> --output handbook-v1.md",
|
|
1008
|
+
"share-pages download handbook --output - > handbook.source"
|
|
1009
|
+
], ["CLI 使用独占创建模式,不会覆盖已有文件。"]);
|
|
612
1010
|
}
|
|
613
1011
|
//#endregion
|
|
614
1012
|
//#region src/versions.ts
|
|
615
1013
|
function registerVersions(cli) {
|
|
616
|
-
const versions = cli.command("versions");
|
|
617
|
-
versions
|
|
1014
|
+
const versions = cli.command("versions").description("查询、删除和恢复页面历史版本").helpGroup("页面管理");
|
|
1015
|
+
examples(versions, ["share-pages versions list handbook", "share-pages rollback handbook --to <版本 UUID>"]);
|
|
1016
|
+
examples(versions.command("list").description("按版本号倒序列出历史版本").argument("<id>", "页面 ID", pageId).option("--cursor <cursor>", "继续读取服务端返回的下一页").action(async (id, _local, command) => {
|
|
618
1017
|
const o = options(command);
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1018
|
+
const result = await (await client(o)).listVersions(id, o.cursor);
|
|
1019
|
+
output(result, o, versionsList(result));
|
|
1020
|
+
}), ["share-pages versions list handbook", "share-pages versions list handbook --json"]);
|
|
1021
|
+
examples(versions.command("delete").description("将历史版本移入回收站,或永久删除").argument("<id>", "页面 ID", pageId).argument("<version>", "版本 UUID").option("--replace-with <version>", "删除当前版时指定接替版本 UUID").option("--unpublish", "删除当前版且不指定接替版本时下线页面").option("--purge", "永久删除版本(不可恢复)").option("--yes", "跳过交互确认(自动化环境必需)").action(async (id, version, _local, command) => {
|
|
622
1022
|
const o = options(command), sdk = await client(o);
|
|
623
1023
|
await sdk.getPage(id);
|
|
624
1024
|
await confirm(`${o.purge ? "永久删除" : "移入回收站"} ${id} 的版本 ${version}。`, !!o.yes, !!o.interactive);
|
|
@@ -626,33 +1026,54 @@ function registerVersions(cli) {
|
|
|
626
1026
|
replaceWith: o.replaceWith,
|
|
627
1027
|
unpublish: o.unpublish,
|
|
628
1028
|
purge: o.purge
|
|
629
|
-
}), o);
|
|
630
|
-
});
|
|
631
|
-
versions.command("restore
|
|
1029
|
+
}), o, o.purge ? `版本 ${version} 已进入永久清理队列。` : `版本 ${version} 已移入回收站。`);
|
|
1030
|
+
}), ["share-pages versions delete handbook <版本 UUID>", "share-pages versions delete handbook <版本 UUID> --purge --yes --no-interactive"]);
|
|
1031
|
+
examples(versions.command("restore").description("恢复回收站中的历史版本(不切换当前版本)").argument("<id>", "页面 ID", pageId).argument("<version>", "版本 UUID").action(async (id, version, _local, command) => {
|
|
632
1032
|
const o = options(command);
|
|
633
|
-
output(await (await client(o)).restoreVersion(id, version), o);
|
|
634
|
-
});
|
|
635
|
-
cli.command("rollback
|
|
636
|
-
const o = options(command), result = await (await client(o)).rollback(id, o.to ?? "", {
|
|
637
|
-
|
|
1033
|
+
output(await (await client(o)).restoreVersion(id, version), o, `版本 ${version} 已恢复。`);
|
|
1034
|
+
}), ["share-pages versions restore handbook <版本 UUID>"]);
|
|
1035
|
+
examples(cli.command("rollback").description("以历史版本内容创建一个新的当前版本").helpGroup("页面管理").argument("<id>", "页面 ID", pageId).requiredOption("--to <version>", "要回滚到的版本 UUID").option("--idempotency-key <key>", "为自动化重试提供稳定幂等键").option("--no-wait", "提交后返回处理中状态").option("--timeout <seconds>", "等待完成的秒数(1–3600)", positiveSeconds, 120).action(async (id, _local, command) => {
|
|
1036
|
+
const o = options(command), result = await (await client(o)).rollback(id, o.to ?? "", {
|
|
1037
|
+
wait: o.wait,
|
|
1038
|
+
idempotencyKey: o.idempotencyKey,
|
|
1039
|
+
timeoutMs: (o.timeout ?? 120) * 1e3
|
|
1040
|
+
});
|
|
1041
|
+
output(result, o, result.status === "ready" ? result.url : `上传 ${result.id}: ${result.status}`);
|
|
1042
|
+
if (result.status === "ready") note(`已创建版本 ${result.versionNumber},当前页面已切换。`, o);
|
|
1043
|
+
else note(`下一步: share-pages uploads ${result.id}`, o);
|
|
638
1044
|
if (result.status !== "ready") process.exitCode = 6;
|
|
639
|
-
})
|
|
1045
|
+
}), [
|
|
1046
|
+
"share-pages versions list handbook",
|
|
1047
|
+
"share-pages rollback handbook --to <版本 UUID>",
|
|
1048
|
+
"share-pages rollback handbook --to <版本 UUID> --no-wait --json"
|
|
1049
|
+
]);
|
|
640
1050
|
}
|
|
641
1051
|
//#endregion
|
|
642
1052
|
//#region src/program.ts
|
|
643
1053
|
function buildCLI() {
|
|
644
1054
|
const cli = createCommand();
|
|
1055
|
+
configureHelp(cli);
|
|
645
1056
|
registerAccounts(cli);
|
|
646
1057
|
registerPublish(cli);
|
|
647
1058
|
registerPages(cli);
|
|
648
1059
|
registerVersions(cli);
|
|
649
1060
|
registerTransfer(cli);
|
|
650
1061
|
registerStats(cli);
|
|
1062
|
+
examples(cli, [
|
|
1063
|
+
"share-pages login --server https://pages.example.com",
|
|
1064
|
+
"share-pages publish ./dist --id my-site",
|
|
1065
|
+
"share-pages pages list",
|
|
1066
|
+
"share-pages status"
|
|
1067
|
+
], [
|
|
1068
|
+
"全局选项可以写在子命令前后。自动化环境建议添加 --json --no-interactive。",
|
|
1069
|
+
"环境变量:SHARE_PAGES_SERVER、SHARE_PAGES_TOKEN、SHARE_PAGES_CONFIG_DIR。",
|
|
1070
|
+
"完整说明:https://www.npmjs.com/package/share-pages,或运行 share-pages help <命令>。"
|
|
1071
|
+
]);
|
|
651
1072
|
const configure = (command) => {
|
|
652
1073
|
command.enablePositionalOptions();
|
|
653
1074
|
for (const option of cli.options) {
|
|
654
1075
|
if (option.long === "--version" || command.options.some((local) => local.long === option.long)) continue;
|
|
655
|
-
command.addOption(new Option(option.flags, option.description));
|
|
1076
|
+
command.addOption(new Option(option.flags, option.description).helpGroup("全局选项"));
|
|
656
1077
|
}
|
|
657
1078
|
for (const child of command.commands) configure(child);
|
|
658
1079
|
};
|
|
@@ -661,21 +1082,19 @@ function buildCLI() {
|
|
|
661
1082
|
}
|
|
662
1083
|
//#endregion
|
|
663
1084
|
//#region src/index.ts
|
|
1085
|
+
process.stdout.on("error", (error) => {
|
|
1086
|
+
if (error.code === "EPIPE") process.exitCode = 0;
|
|
1087
|
+
else throw error;
|
|
1088
|
+
});
|
|
664
1089
|
const cli = buildCLI();
|
|
665
1090
|
try {
|
|
666
1091
|
await cli.parseAsync();
|
|
667
1092
|
} catch (error) {
|
|
668
|
-
if (error
|
|
1093
|
+
if (isHelpExit(error)) process.exitCode = 0;
|
|
669
1094
|
else {
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
message: e.message,
|
|
674
|
-
fields: e.fields
|
|
675
|
-
};
|
|
676
|
-
if (process.argv.includes("--json")) process.stdout.write(`${JSON.stringify({ error: result })}\n`);
|
|
677
|
-
else process.stderr.write(`${e.code}: ${e.message}\n`);
|
|
678
|
-
process.exitCode = e.status === 401 || e.status === 403 ? 3 : e.status === 409 ? 4 : e.status >= 500 ? 5 : 2;
|
|
1095
|
+
const resolved = normalizeError(error);
|
|
1096
|
+
printError(resolved, wantsJSON(process.argv.slice(2)));
|
|
1097
|
+
process.exitCode = exitCode(resolved);
|
|
679
1098
|
}
|
|
680
1099
|
}
|
|
681
1100
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as document } from "./prepare-C56DezIS-
|
|
1
|
+
import { i as assert, s as escapeHtml, t as AppError } from "./dist-LkxVliJx.mjs";
|
|
2
|
+
import { n as document } from "./prepare-C56DezIS-pzIt9WD8.mjs";
|
|
3
3
|
//#region ../../packages/content/dist/latex-DMo_3IW8.js
|
|
4
4
|
async function latex(source, title) {
|
|
5
5
|
assert(!/\\(?:usepackage|RequirePackage|input|include|newcommand|renewcommand|def|bibliography|bibliographystyle|includegraphics|write18|catcode)\b/.test(source), "UNSUPPORTED_LATEX", "不支持任意宏包、宏定义、外部文件、图片或参考文献编译");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, t as __commonJSMin } from "./rolldown-runtime-B4iAMlE-.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { d as parse$4, l as walk, n as document$3 } from "./prepare-C56DezIS-
|
|
2
|
+
import { i as assert, s as escapeHtml } from "./dist-LkxVliJx.mjs";
|
|
3
|
+
import { d as parse$4, l as walk, n as document$3 } from "./prepare-C56DezIS-pzIt9WD8.mjs";
|
|
4
4
|
import minpath from "node:path";
|
|
5
5
|
import minproc from "node:process";
|
|
6
6
|
import { fileURLToPath as urlToPath } from "node:url";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as prepareBytes, i as finalize, u as zipSync } from "./prepare-C56DezIS-
|
|
1
|
+
import { a as defaultLimits, i as assert, u as safePath } from "./dist-LkxVliJx.mjs";
|
|
2
|
+
import { a as prepareBytes, i as finalize, u as zipSync } from "./prepare-C56DezIS-pzIt9WD8.mjs";
|
|
3
3
|
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
//#region ../../packages/content/dist/directory-Cz5DdsHB.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM } from "./rolldown-runtime-B4iAMlE-.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as defaultLimits, f as validateManifestLimits, i as assert, l as manifestSchema, o as digest, s as escapeHtml, t as AppError, u as safePath } from "./dist-LkxVliJx.mjs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
//#region ../../node_modules/.pnpm/parse5@8.0.1/node_modules/parse5/dist/common/unicode.js
|
|
@@ -8514,7 +8514,7 @@ async function prepareBytes(name, data, options = {}) {
|
|
|
8514
8514
|
if (type === "markdown" || type === "latex") {
|
|
8515
8515
|
assert(data.length <= limits.textBytes, "TEXT_TOO_LARGE", "文本文档超过 5 MiB 限额", 413);
|
|
8516
8516
|
const source = decodeText(data, options.encoding);
|
|
8517
|
-
files.set("index.html", encoder.encode(type === "markdown" ? await (await import("./markdown-BWlCFeLJ-
|
|
8517
|
+
files.set("index.html", encoder.encode(type === "markdown" ? await (await import("./markdown-BWlCFeLJ-CCxfGkZu.mjs")).markdown(source, title) : await (await import("./latex-DMo_3IW8-DdNgxe_1.mjs")).latex(source, title)));
|
|
8518
8518
|
} else if (type === "csv") {
|
|
8519
8519
|
assert(data.length <= limits.csvBytes, "CSV_TOO_LARGE", "CSV 超过字节限额", 413);
|
|
8520
8520
|
const source = decodeText(data, options.encoding);
|
|
@@ -8549,7 +8549,7 @@ async function prepareBytes(name, data, options = {}) {
|
|
|
8549
8549
|
header,
|
|
8550
8550
|
maxRows: limits.csvRows
|
|
8551
8551
|
})));
|
|
8552
|
-
} else if (type === "epub") files = await (await import("./epub-BXQPqTFk-
|
|
8552
|
+
} else if (type === "epub") files = await (await import("./epub-BXQPqTFk-DV2p5uyW.mjs")).prepareEPUB(data, title, limits, await digest(data));
|
|
8553
8553
|
for (const [path, bytes] of await loadViewerAssets(type)) files.set(path, bytes);
|
|
8554
8554
|
}
|
|
8555
8555
|
return finalize(name, data, files, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "share-pages",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Command-line client for publishing and managing Share Pages content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"share-pages",
|
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "pnpm --filter @share-pages/viewers build && tsdown && node scripts/copy-viewer-resources.mjs",
|
|
24
|
-
"prepublishOnly": "node scripts/check-publish-manifest.mjs && pnpm build && pnpm typecheck",
|
|
25
|
-
"typecheck": "tsc --noEmit"
|
|
26
|
-
},
|
|
27
22
|
"publishConfig": {
|
|
28
23
|
"access": "public"
|
|
29
24
|
},
|
|
@@ -32,9 +27,13 @@
|
|
|
32
27
|
"jsdom": "26.1.0"
|
|
33
28
|
},
|
|
34
29
|
"devDependencies": {
|
|
35
|
-
"
|
|
36
|
-
"@share-pages/contracts": "
|
|
37
|
-
"@share-pages/
|
|
38
|
-
"
|
|
30
|
+
"tsdown": "0.23.0",
|
|
31
|
+
"@share-pages/contracts": "0.1.0",
|
|
32
|
+
"@share-pages/content": "0.1.0",
|
|
33
|
+
"@share-pages/sdk": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "pnpm --filter @share-pages/viewers build && tsdown && node scripts/copy-viewer-resources.mjs",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
39
38
|
}
|
|
40
|
-
}
|
|
39
|
+
}
|