persona-test-cli 0.1.3 → 0.1.4
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 +1 -1
- package/dist/commands/events.js +20 -4
- package/dist/index.js +27 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ authorize page loads decides the scope of the issued token.
|
|
|
39
39
|
| `whoami` | anyone | Show current identity + available commands |
|
|
40
40
|
| `campaigns list/create/edit/link/stats` | anyone | Share links — scoped to your own unless admin |
|
|
41
41
|
| `personas list/create/edit` | anyone | Ad-copy personas — scoped to your own unless admin |
|
|
42
|
-
| `events tail` | anyone | Poll new impression/click events |
|
|
42
|
+
| `events tail` | anyone | Poll new impression/click events (starts from now; `--since <iso>` to replay history first) |
|
|
43
43
|
| `users list/approve/reject` | **admin only** | Registration approval queue |
|
|
44
44
|
|
|
45
45
|
Run any command with `--help` for its full options and copy-pasteable examples.
|
package/dist/commands/events.js
CHANGED
|
@@ -2,8 +2,10 @@ import { apiRequest } from "../http.js";
|
|
|
2
2
|
import { requireSession } from "../session.js";
|
|
3
3
|
import { jsonMode } from "../output.js";
|
|
4
4
|
import { action } from "../output.js";
|
|
5
|
+
import { fail, EXIT_CODES } from "../errors.js";
|
|
5
6
|
export const EVENTS_TAIL_USAGE = ` persona-test-cli events tail
|
|
6
|
-
persona-test-cli events tail --slug summer --interval 3
|
|
7
|
+
persona-test-cli events tail --slug summer --interval 3
|
|
8
|
+
persona-test-cli events tail --since 2026-08-30T00:00:00Z # 回看历史,再接实时`;
|
|
7
9
|
const TAIL_USAGE = EVENTS_TAIL_USAGE;
|
|
8
10
|
function sleep(ms) {
|
|
9
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -12,17 +14,31 @@ export function eventsCommand(program) {
|
|
|
12
14
|
const cmd = program.command("events").description("查看展示/点击事件");
|
|
13
15
|
cmd
|
|
14
16
|
.command("tail")
|
|
15
|
-
.description("
|
|
17
|
+
.description("从现在开始轮询打印新的展示/点击事件,Ctrl+C 退出(要回看历史用 --since)")
|
|
16
18
|
.option("--slug <slug>", "只看某条分享链接的事件")
|
|
17
19
|
.option("--interval <seconds>", "轮询间隔秒数", "5")
|
|
20
|
+
.option("--since <iso>", "从这个 ISO 时间点开始回放,再接实时(默认从启动时刻开始)")
|
|
18
21
|
.option("--base-url <url>")
|
|
19
22
|
.addHelpText("after", `\n示例:\n${TAIL_USAGE}`)
|
|
20
23
|
.action(action(async (opts) => {
|
|
21
24
|
const { baseUrl, token } = requireSession(opts.baseUrl);
|
|
22
25
|
const intervalMs = Math.max(1, Number(opts.interval) || 5) * 1000;
|
|
23
|
-
|
|
26
|
+
// Default to "now", not the epoch: `tail` promises *new* events, and
|
|
27
|
+
// starting at 1970 made the first poll replay up to 50 historical
|
|
28
|
+
// events before showing anything live.
|
|
29
|
+
let since = new Date().toISOString();
|
|
30
|
+
if (opts.since) {
|
|
31
|
+
const parsed = new Date(opts.since);
|
|
32
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
33
|
+
fail(`--since 不是合法的时间: ${opts.since}(用 ISO 格式,如 2026-08-30T00:00:00Z)`, {
|
|
34
|
+
exitCode: EXIT_CODES.validation,
|
|
35
|
+
usage: TAIL_USAGE,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
since = parsed.toISOString();
|
|
39
|
+
}
|
|
24
40
|
if (!jsonMode) {
|
|
25
|
-
console.log(`轮询中(每 ${opts.interval} 秒)... Ctrl+C
|
|
41
|
+
console.log(`轮询中(每 ${opts.interval} 秒)... Ctrl+C 退出${opts.since ? ` — 回放起点 ${since}` : ""}`);
|
|
26
42
|
}
|
|
27
43
|
for (;;) {
|
|
28
44
|
const query = new URLSearchParams({ since, limit: "50" });
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { Command } from "commander";
|
|
5
|
+
import { Command, CommanderError } from "commander";
|
|
6
6
|
import { registerCommand } from "./commands/register.js";
|
|
7
7
|
import { loginCommand } from "./commands/login.js";
|
|
8
8
|
import { logoutCommand } from "./commands/logout.js";
|
|
@@ -12,7 +12,8 @@ import { personasCommand } from "./commands/personas.js";
|
|
|
12
12
|
import { eventsCommand } from "./commands/events.js";
|
|
13
13
|
import { usersCommand } from "./commands/users.js";
|
|
14
14
|
import { examplesCommand } from "./commands/examples.js";
|
|
15
|
-
import { jsonMode } from "./output.js";
|
|
15
|
+
import { jsonMode, printError } from "./output.js";
|
|
16
|
+
import { CliError, EXIT_CODES } from "./errors.js";
|
|
16
17
|
// Reads package.json's version whether running from source (cli/index.ts,
|
|
17
18
|
// package.json next to it) or from the built dist/index.js (package.json one
|
|
18
19
|
// directory up) — avoids a hardcoded version string drifting from package.json.
|
|
@@ -30,6 +31,17 @@ function readVersion() {
|
|
|
30
31
|
}
|
|
31
32
|
const version = readVersion();
|
|
32
33
|
const program = new Command();
|
|
34
|
+
// Commander hardcodes exit code 1 for every usage/argument problem (missing
|
|
35
|
+
// option, unknown flag, bad value) and writes the message to stderr itself.
|
|
36
|
+
// `examples` documents 3 = 参数校验失败, so take over both halves: exitOverride
|
|
37
|
+
// stops commander calling process.exit(1), and configureOutput silences its
|
|
38
|
+
// stderr write so every error flows through printError() — which honours --json
|
|
39
|
+
// and applies the documented exit code.
|
|
40
|
+
//
|
|
41
|
+
// MUST come before the subcommands are registered: commander copies
|
|
42
|
+
// _exitCallback and _outputConfiguration onto a child at addCommand() time,
|
|
43
|
+
// so declaring this at the bottom leaves every subcommand untouched.
|
|
44
|
+
program.exitOverride().configureOutput({ writeErr: () => { } });
|
|
33
45
|
program
|
|
34
46
|
.name("persona-test-cli")
|
|
35
47
|
.description("persona-test 运维 CLI —— admin 和已审核通过的普通用户都能用,普通用户只能看到/编辑自己创建的 campaigns/personas。\n" +
|
|
@@ -57,4 +69,16 @@ personasCommand(program);
|
|
|
57
69
|
eventsCommand(program);
|
|
58
70
|
usersCommand(program); // [仅 admin] — see the description on each of its subcommands
|
|
59
71
|
examplesCommand(program);
|
|
60
|
-
program.parseAsync(process.argv)
|
|
72
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
73
|
+
if (err instanceof CommanderError) {
|
|
74
|
+
// --help / -v travel through the throw path too, but they are normal exits.
|
|
75
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
|
|
76
|
+
return;
|
|
77
|
+
// Commander bakes its own "error: " prefix into some messages; printError
|
|
78
|
+
// adds ours, which would otherwise read "错误: error: unknown command ...".
|
|
79
|
+
const message = err.message.replace(/^error:\s*/i, "");
|
|
80
|
+
printError(new CliError(message, { exitCode: EXIT_CODES.validation }));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
printError(err);
|
|
84
|
+
});
|