peakurl 0.2.0 → 0.3.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 +85 -15
- package/bin/peakurl.js +1313 -132
- package/package.json +1 -1
package/bin/peakurl.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFile as
|
|
4
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
5
5
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
6
6
|
|
|
7
|
+
// src/commands/links.ts
|
|
8
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
9
|
+
import { dirname as dirname2, resolve } from "path";
|
|
10
|
+
|
|
7
11
|
// src/lib/errors.ts
|
|
8
12
|
var CliError = class extends Error {
|
|
9
13
|
exitCode;
|
|
@@ -34,28 +38,30 @@ function validateHttpUrl(parsed, label) {
|
|
|
34
38
|
throw new CliError(`${label} must not include embedded credentials.`);
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
|
-
function
|
|
41
|
+
function getApiBaseUrl(value) {
|
|
38
42
|
const input = value.trim();
|
|
39
43
|
if (!input) {
|
|
40
|
-
throw new CliError("A PeakURL base URL is required.");
|
|
44
|
+
throw new CliError("A PeakURL API base URL is required.");
|
|
41
45
|
}
|
|
42
46
|
let parsed;
|
|
43
47
|
try {
|
|
44
48
|
parsed = new URL(input);
|
|
45
49
|
} catch {
|
|
46
|
-
throw new CliError(`Invalid base URL: ${value}`);
|
|
50
|
+
throw new CliError(`Invalid API base URL: ${value}`);
|
|
47
51
|
}
|
|
48
|
-
validateHttpUrl(parsed, "PeakURL base URL");
|
|
52
|
+
validateHttpUrl(parsed, "PeakURL API base URL");
|
|
49
53
|
parsed.hash = "";
|
|
50
54
|
parsed.search = "";
|
|
51
55
|
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
if (!/\/api\/v1$/i.test(pathname)) {
|
|
57
|
+
throw new CliError("PeakURL API base URL must end with /api/v1.");
|
|
58
|
+
}
|
|
59
|
+
return `${parsed.origin}${pathname}`;
|
|
54
60
|
}
|
|
55
|
-
function buildApiUrl(
|
|
56
|
-
const cleanBaseUrl =
|
|
61
|
+
function buildApiUrl(apiBaseUrl, path, query) {
|
|
62
|
+
const cleanBaseUrl = getApiBaseUrl(apiBaseUrl);
|
|
57
63
|
const cleanPath = path.replace(/^\/+/, "");
|
|
58
|
-
const url = new URL(
|
|
64
|
+
const url = new URL(cleanPath, `${cleanBaseUrl}/`);
|
|
59
65
|
for (const [key, value] of Object.entries(query ?? {})) {
|
|
60
66
|
if (value === void 0 || value === "") {
|
|
61
67
|
continue;
|
|
@@ -80,6 +86,22 @@ function normalizeDestinationUrl(value) {
|
|
|
80
86
|
throw new CliError(`Invalid destination URL: ${value}`);
|
|
81
87
|
}
|
|
82
88
|
}
|
|
89
|
+
function normalizeWebhookUrl(value) {
|
|
90
|
+
const input = value.trim();
|
|
91
|
+
if (!input) {
|
|
92
|
+
throw new CliError("A webhook URL is required.");
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const parsed = new URL(input);
|
|
96
|
+
validateHttpUrl(parsed, "Webhook URL");
|
|
97
|
+
return parsed.toString();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error instanceof CliError) {
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
throw new CliError(`Invalid webhook URL: ${value}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
83
105
|
|
|
84
106
|
// src/api/client.ts
|
|
85
107
|
function isApiResponse(value) {
|
|
@@ -87,17 +109,17 @@ function isApiResponse(value) {
|
|
|
87
109
|
value && typeof value === "object" && "success" in value && "message" in value && "timestamp" in value
|
|
88
110
|
);
|
|
89
111
|
}
|
|
90
|
-
function networkError(
|
|
112
|
+
function networkError(apiBaseUrl, error) {
|
|
91
113
|
if (error instanceof Error && error.message) {
|
|
92
|
-
return `Could not reach PeakURL at ${
|
|
114
|
+
return `Could not reach PeakURL at ${apiBaseUrl}. ${error.message}`;
|
|
93
115
|
}
|
|
94
|
-
return `Could not reach PeakURL at ${
|
|
116
|
+
return `Could not reach PeakURL at ${apiBaseUrl}.`;
|
|
95
117
|
}
|
|
96
118
|
var ApiClient = class {
|
|
97
119
|
/**
|
|
98
120
|
* Creates a client bound to one resolved credential set.
|
|
99
121
|
*
|
|
100
|
-
* @param config
|
|
122
|
+
* @param config Explicit API base URL plus bearer API key.
|
|
101
123
|
*/
|
|
102
124
|
constructor(config) {
|
|
103
125
|
this.config = config;
|
|
@@ -114,6 +136,14 @@ var ApiClient = class {
|
|
|
114
136
|
whoami() {
|
|
115
137
|
return this.request("GET", "users/me");
|
|
116
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Loads the current system status snapshot for the authenticated site.
|
|
141
|
+
*
|
|
142
|
+
* @returns API response envelope containing system status sections.
|
|
143
|
+
*/
|
|
144
|
+
getStatus() {
|
|
145
|
+
return this.request("GET", "system/status");
|
|
146
|
+
}
|
|
117
147
|
/**
|
|
118
148
|
* Creates a short URL.
|
|
119
149
|
*
|
|
@@ -135,6 +165,29 @@ var ApiClient = class {
|
|
|
135
165
|
listUrls(query) {
|
|
136
166
|
return this.request("GET", "urls", void 0, query);
|
|
137
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Exports the full accessible link dataset for the authenticated user.
|
|
170
|
+
*
|
|
171
|
+
* @param query Optional search and sort values.
|
|
172
|
+
* @returns API response envelope containing the full export payload.
|
|
173
|
+
*/
|
|
174
|
+
exportUrls(query) {
|
|
175
|
+
return this.request(
|
|
176
|
+
"GET",
|
|
177
|
+
"urls/export",
|
|
178
|
+
void 0,
|
|
179
|
+
query
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Imports multiple short links in one bulk request.
|
|
184
|
+
*
|
|
185
|
+
* @param payload Request body accepted by `POST /api/v1/urls/bulk`.
|
|
186
|
+
* @returns API response envelope containing created rows plus row errors.
|
|
187
|
+
*/
|
|
188
|
+
importUrls(payload) {
|
|
189
|
+
return this.request("POST", "urls/bulk", payload);
|
|
190
|
+
}
|
|
138
191
|
/**
|
|
139
192
|
* Loads a single short URL by identifier or alias.
|
|
140
193
|
*
|
|
@@ -164,6 +217,35 @@ var ApiClient = class {
|
|
|
164
217
|
`urls/${encodeURIComponent(id)}`
|
|
165
218
|
);
|
|
166
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Lists outbound webhooks for the authenticated user.
|
|
222
|
+
*
|
|
223
|
+
* @returns API response envelope containing webhook rows.
|
|
224
|
+
*/
|
|
225
|
+
listWebhooks() {
|
|
226
|
+
return this.request("GET", "webhooks");
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Creates one outbound webhook subscription.
|
|
230
|
+
*
|
|
231
|
+
* @param payload Request body accepted by `POST /api/v1/webhooks`.
|
|
232
|
+
* @returns API response envelope containing the created webhook.
|
|
233
|
+
*/
|
|
234
|
+
createWebhook(payload) {
|
|
235
|
+
return this.request("POST", "webhooks", payload);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Deletes one webhook by its stable row ID.
|
|
239
|
+
*
|
|
240
|
+
* @param id Webhook identifier returned by the list/create endpoints.
|
|
241
|
+
* @returns API response envelope containing the deletion result.
|
|
242
|
+
*/
|
|
243
|
+
deleteWebhook(id) {
|
|
244
|
+
return this.request(
|
|
245
|
+
"DELETE",
|
|
246
|
+
`webhooks/${encodeURIComponent(id)}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
167
249
|
/**
|
|
168
250
|
* Performs one authenticated API request and normalizes the response.
|
|
169
251
|
*
|
|
@@ -175,7 +257,7 @@ var ApiClient = class {
|
|
|
175
257
|
* @throws {CliError} When the network request fails or the API returns an error.
|
|
176
258
|
*/
|
|
177
259
|
async request(method, path, body, query) {
|
|
178
|
-
const url = buildApiUrl(this.config.
|
|
260
|
+
const url = buildApiUrl(this.config.apiBaseUrl, path, query);
|
|
179
261
|
let response;
|
|
180
262
|
try {
|
|
181
263
|
response = await fetch(url, {
|
|
@@ -188,7 +270,7 @@ var ApiClient = class {
|
|
|
188
270
|
body: body ? JSON.stringify(body) : void 0
|
|
189
271
|
});
|
|
190
272
|
} catch (error) {
|
|
191
|
-
throw new CliError(networkError(this.config.
|
|
273
|
+
throw new CliError(networkError(this.config.apiBaseUrl, error), 1, {
|
|
192
274
|
cause: error instanceof Error ? error : void 0
|
|
193
275
|
});
|
|
194
276
|
}
|
|
@@ -273,11 +355,12 @@ var ConfigStore = class {
|
|
|
273
355
|
try {
|
|
274
356
|
const content = await readFile(this.filePath, "utf8");
|
|
275
357
|
const parsed = JSON.parse(content);
|
|
276
|
-
|
|
358
|
+
const apiBaseUrl = typeof parsed?.apiBaseUrl === "string" ? parsed.apiBaseUrl : typeof parsed?.baseUrl === "string" ? parsed.baseUrl : void 0;
|
|
359
|
+
if (typeof apiBaseUrl !== "string" || typeof parsed?.apiKey !== "string") {
|
|
277
360
|
throw new CliError(`Invalid config file: ${this.filePath}`);
|
|
278
361
|
}
|
|
279
362
|
return {
|
|
280
|
-
|
|
363
|
+
apiBaseUrl,
|
|
281
364
|
apiKey: parsed.apiKey
|
|
282
365
|
};
|
|
283
366
|
} catch (error) {
|
|
@@ -388,42 +471,158 @@ var StateStore = class {
|
|
|
388
471
|
|
|
389
472
|
// src/lib/auth.ts
|
|
390
473
|
var AUTH_REQUIRED_MESSAGE = "PeakURL credentials are not configured.";
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
[
|
|
474
|
+
var EXAMPLE_BASE_URL = "https://example.com/api/v1";
|
|
475
|
+
var EXAMPLE_API_KEY = "YOUR_API_KEY";
|
|
476
|
+
function authRows(commandName) {
|
|
477
|
+
const retryCommand = commandName ? `peakurl ${commandName}` : "peakurl whoami";
|
|
478
|
+
const rows2 = [
|
|
479
|
+
[
|
|
480
|
+
"Save credentials",
|
|
481
|
+
`peakurl login --base-url ${EXAMPLE_BASE_URL}
|
|
482
|
+
--api-key ${EXAMPLE_API_KEY}`,
|
|
483
|
+
"Regular use on this machine"
|
|
484
|
+
],
|
|
485
|
+
[
|
|
486
|
+
"Set environment variables",
|
|
487
|
+
`PEAKURL_BASE_URL=${EXAMPLE_BASE_URL}
|
|
488
|
+
PEAKURL_API_KEY=${EXAMPLE_API_KEY}`,
|
|
489
|
+
"CI, scripts, or one-off use"
|
|
490
|
+
],
|
|
491
|
+
["Then run", retryCommand, "After completing one of the steps above"]
|
|
397
492
|
];
|
|
493
|
+
return rows2;
|
|
398
494
|
}
|
|
399
495
|
function getLoginConfig(input, env) {
|
|
400
|
-
const
|
|
496
|
+
const apiBaseUrl = input.baseUrl?.trim() || env.PEAKURL_BASE_URL?.trim();
|
|
401
497
|
const apiKey = input.apiKey?.trim() || env.PEAKURL_API_KEY?.trim();
|
|
402
|
-
if (!
|
|
498
|
+
if (!apiBaseUrl || !apiKey) {
|
|
403
499
|
throw new CliError(
|
|
404
500
|
"Missing credentials. Provide --base-url and --api-key, or set PEAKURL_BASE_URL and PEAKURL_API_KEY."
|
|
405
501
|
);
|
|
406
502
|
}
|
|
407
503
|
return {
|
|
408
|
-
|
|
504
|
+
apiBaseUrl: getApiBaseUrl(apiBaseUrl),
|
|
409
505
|
apiKey
|
|
410
506
|
};
|
|
411
507
|
}
|
|
412
508
|
async function getAuthConfig(env, store = new ConfigStore()) {
|
|
413
509
|
const saved = await store.load();
|
|
414
|
-
const
|
|
510
|
+
const apiBaseUrl = env.PEAKURL_BASE_URL?.trim() || saved?.apiBaseUrl;
|
|
415
511
|
const apiKey = env.PEAKURL_API_KEY?.trim() || saved?.apiKey;
|
|
416
|
-
if (!
|
|
512
|
+
if (!apiBaseUrl || !apiKey) {
|
|
417
513
|
throw new CliError(AUTH_REQUIRED_MESSAGE, 1, {
|
|
418
514
|
kind: "auth_required"
|
|
419
515
|
});
|
|
420
516
|
}
|
|
421
517
|
return {
|
|
422
|
-
|
|
518
|
+
apiBaseUrl: getApiBaseUrl(apiBaseUrl),
|
|
423
519
|
apiKey
|
|
424
520
|
};
|
|
425
521
|
}
|
|
426
522
|
|
|
523
|
+
// src/lib/exports.ts
|
|
524
|
+
var EXPORT_HEADERS = [
|
|
525
|
+
"url",
|
|
526
|
+
"alias",
|
|
527
|
+
"title",
|
|
528
|
+
"password",
|
|
529
|
+
"expires",
|
|
530
|
+
"short_url",
|
|
531
|
+
"clicks",
|
|
532
|
+
"unique_clicks",
|
|
533
|
+
"created_at"
|
|
534
|
+
];
|
|
535
|
+
function text(value) {
|
|
536
|
+
return typeof value === "string" ? value : "";
|
|
537
|
+
}
|
|
538
|
+
function csvValue(value) {
|
|
539
|
+
const content = value == null ? "" : String(value);
|
|
540
|
+
if (/[",\r\n]/.test(content)) {
|
|
541
|
+
return `"${content.replace(/"/g, '""')}"`;
|
|
542
|
+
}
|
|
543
|
+
return content;
|
|
544
|
+
}
|
|
545
|
+
function xmlValue(value) {
|
|
546
|
+
return String(value ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
547
|
+
}
|
|
548
|
+
function aliasValue(link) {
|
|
549
|
+
return text(link.alias) || text(link.shortCode);
|
|
550
|
+
}
|
|
551
|
+
function parseFileFormat(value) {
|
|
552
|
+
const format = value.trim().toLowerCase();
|
|
553
|
+
if (format === "csv" || format === "json" || format === "xml") {
|
|
554
|
+
return format;
|
|
555
|
+
}
|
|
556
|
+
throw new CliError(
|
|
557
|
+
`Unsupported file format: ${value}. Use csv, json, or xml.`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
function getFileFormatFromPath(filePath) {
|
|
561
|
+
const value = filePath.trim().toLowerCase();
|
|
562
|
+
if (value.endsWith(".csv")) {
|
|
563
|
+
return "csv";
|
|
564
|
+
}
|
|
565
|
+
if (value.endsWith(".json")) {
|
|
566
|
+
return "json";
|
|
567
|
+
}
|
|
568
|
+
if (value.endsWith(".xml")) {
|
|
569
|
+
return "xml";
|
|
570
|
+
}
|
|
571
|
+
return void 0;
|
|
572
|
+
}
|
|
573
|
+
function getExportFileName(format) {
|
|
574
|
+
return `peakurl-links.${format}`;
|
|
575
|
+
}
|
|
576
|
+
function buildExportRows(links) {
|
|
577
|
+
return links.map((link) => ({
|
|
578
|
+
url: text(link.destinationUrl),
|
|
579
|
+
alias: aliasValue(link),
|
|
580
|
+
title: text(link.title),
|
|
581
|
+
password: "",
|
|
582
|
+
expires: text(link.expiresAt),
|
|
583
|
+
short_url: text(link.shortUrl),
|
|
584
|
+
clicks: typeof link.clicks === "number" ? link.clicks : "",
|
|
585
|
+
unique_clicks: typeof link.uniqueClicks === "number" ? link.uniqueClicks : "",
|
|
586
|
+
created_at: text(link.createdAt)
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
function serializeLinkExport(links, format) {
|
|
590
|
+
const rows2 = buildExportRows(links);
|
|
591
|
+
if (format === "json") {
|
|
592
|
+
return JSON.stringify(rows2, null, 2);
|
|
593
|
+
}
|
|
594
|
+
if (format === "xml") {
|
|
595
|
+
const body = rows2.map(
|
|
596
|
+
(row2) => ` <url>
|
|
597
|
+
<destinationUrl>${xmlValue(row2.url)}</destinationUrl>
|
|
598
|
+
<alias>${xmlValue(row2.alias)}</alias>
|
|
599
|
+
<title>${xmlValue(row2.title)}</title>
|
|
600
|
+
<password>${xmlValue(row2.password)}</password>
|
|
601
|
+
<expiresAt>${xmlValue(row2.expires)}</expiresAt>
|
|
602
|
+
<shortUrl>${xmlValue(row2.short_url)}</shortUrl>
|
|
603
|
+
<clicks>${xmlValue(row2.clicks)}</clicks>
|
|
604
|
+
<uniqueClicks>${xmlValue(row2.unique_clicks)}</uniqueClicks>
|
|
605
|
+
<createdAt>${xmlValue(row2.created_at)}</createdAt>
|
|
606
|
+
</url>`
|
|
607
|
+
).join("\n");
|
|
608
|
+
return `<urls>
|
|
609
|
+
${body}
|
|
610
|
+
</urls>
|
|
611
|
+
`;
|
|
612
|
+
}
|
|
613
|
+
const lines = [
|
|
614
|
+
EXPORT_HEADERS.join(","),
|
|
615
|
+
...rows2.map(
|
|
616
|
+
(row2) => EXPORT_HEADERS.map((key) => csvValue(row2[key])).join(",")
|
|
617
|
+
)
|
|
618
|
+
];
|
|
619
|
+
return `${lines.join("\n")}
|
|
620
|
+
`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/lib/imports.ts
|
|
624
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
625
|
+
|
|
427
626
|
// src/lib/output.ts
|
|
428
627
|
function writeStdout(message = "") {
|
|
429
628
|
process.stdout.write(`${message}
|
|
@@ -474,7 +673,7 @@ function writeNoticeBox(title, lines, target = "stderr") {
|
|
|
474
673
|
}
|
|
475
674
|
writeLine(bottomBorder);
|
|
476
675
|
}
|
|
477
|
-
function formatTable(headers,
|
|
676
|
+
function formatTable(headers, rows2, target = "stdout") {
|
|
478
677
|
const stream = target === "stdout" ? process.stdout : process.stderr;
|
|
479
678
|
const useTuiBox = stream.isTTY;
|
|
480
679
|
const border = useTuiBox ? {
|
|
@@ -502,14 +701,26 @@ function formatTable(headers, rows, target = "stdout") {
|
|
|
502
701
|
middleJunction: "+",
|
|
503
702
|
bottomJunction: "+"
|
|
504
703
|
};
|
|
704
|
+
const getLines = (value) => (value ?? "").split("\n");
|
|
505
705
|
const widths = headers.map(
|
|
506
706
|
(header, index) => Math.max(
|
|
507
707
|
header.length,
|
|
508
|
-
...
|
|
708
|
+
...rows2.flatMap(
|
|
709
|
+
(row2) => getLines(row2[index]).map((line) => line.length)
|
|
710
|
+
)
|
|
509
711
|
)
|
|
510
712
|
);
|
|
511
713
|
const formatTableBorder = (left, join2, right) => `${left}${widths.map((width) => border.horizontal.repeat(width + 2)).join(join2)}${right}`;
|
|
512
|
-
const formatTableRow = (cells) =>
|
|
714
|
+
const formatTableRow = (cells) => {
|
|
715
|
+
const linesByCell = cells.map(getLines);
|
|
716
|
+
const rowHeight = Math.max(...linesByCell.map((lines) => lines.length));
|
|
717
|
+
return Array.from(
|
|
718
|
+
{ length: rowHeight },
|
|
719
|
+
(_value, rowIndex) => `${border.vertical}${linesByCell.map(
|
|
720
|
+
(lines, cellIndex) => ` ${(lines[rowIndex] ?? "").padEnd(widths[cellIndex])} `
|
|
721
|
+
).join(border.vertical)}${border.vertical}`
|
|
722
|
+
).join("\n");
|
|
723
|
+
};
|
|
513
724
|
return [
|
|
514
725
|
formatTableBorder(border.topLeft, border.topJunction, border.topRight),
|
|
515
726
|
formatTableRow(headers),
|
|
@@ -518,7 +729,7 @@ function formatTable(headers, rows, target = "stdout") {
|
|
|
518
729
|
border.middleJunction,
|
|
519
730
|
border.separatorRight
|
|
520
731
|
),
|
|
521
|
-
...
|
|
732
|
+
...rows2.map(formatTableRow),
|
|
522
733
|
formatTableBorder(
|
|
523
734
|
border.bottomLeft,
|
|
524
735
|
border.bottomJunction,
|
|
@@ -530,6 +741,261 @@ function writeJson(value) {
|
|
|
530
741
|
writeStdout(JSON.stringify(value, null, 2));
|
|
531
742
|
}
|
|
532
743
|
|
|
744
|
+
// src/lib/imports.ts
|
|
745
|
+
function text2(value) {
|
|
746
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
747
|
+
}
|
|
748
|
+
function normalizeHeader(value) {
|
|
749
|
+
return value.replace(/^\uFEFF/, "").trim().toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
750
|
+
}
|
|
751
|
+
function parseCsvRows(text6) {
|
|
752
|
+
const rows2 = [];
|
|
753
|
+
const source = text6.replace(/^\uFEFF/, "");
|
|
754
|
+
let row2 = [];
|
|
755
|
+
let value = "";
|
|
756
|
+
let inQuotes = false;
|
|
757
|
+
const pushRow = () => {
|
|
758
|
+
row2.push(value);
|
|
759
|
+
if (row2.some((cell) => cell.trim() !== "")) {
|
|
760
|
+
rows2.push(row2);
|
|
761
|
+
}
|
|
762
|
+
row2 = [];
|
|
763
|
+
value = "";
|
|
764
|
+
};
|
|
765
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
766
|
+
const char = source[index];
|
|
767
|
+
if (char === '"') {
|
|
768
|
+
if (inQuotes && source[index + 1] === '"') {
|
|
769
|
+
value += '"';
|
|
770
|
+
index += 1;
|
|
771
|
+
} else {
|
|
772
|
+
inQuotes = !inQuotes;
|
|
773
|
+
}
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (char === "," && !inQuotes) {
|
|
777
|
+
row2.push(value);
|
|
778
|
+
value = "";
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if ((char === "\n" || char === "\r") && !inQuotes) {
|
|
782
|
+
if (char === "\r" && source[index + 1] === "\n") {
|
|
783
|
+
index += 1;
|
|
784
|
+
}
|
|
785
|
+
pushRow();
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
value += char;
|
|
789
|
+
}
|
|
790
|
+
if (value.length > 0 || row2.length > 0) {
|
|
791
|
+
pushRow();
|
|
792
|
+
}
|
|
793
|
+
return rows2;
|
|
794
|
+
}
|
|
795
|
+
function extractAlias(value) {
|
|
796
|
+
const input = value.trim();
|
|
797
|
+
if (!input) {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const url = new URL(input);
|
|
802
|
+
const pathname = url.pathname.replace(/^\/+|\/+$/g, "");
|
|
803
|
+
return pathname ? decodeURIComponent(pathname.split("/").pop() || "") : void 0;
|
|
804
|
+
} catch {
|
|
805
|
+
const pathname = input.replace(/^\/+|\/+$/g, "");
|
|
806
|
+
return pathname ? pathname.split("/").pop() || void 0 : void 0;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function decodeXml(value) {
|
|
810
|
+
return value.replace(/'/g, "'").replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&");
|
|
811
|
+
}
|
|
812
|
+
function normalizeImportRow(value) {
|
|
813
|
+
const destinationUrl = text2(value.destinationUrl) || text2(value.url) || text2(value.destination);
|
|
814
|
+
if (!destinationUrl) {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
const alias = text2(value.alias) || text2(value.shortCode) || text2(value.shortcode) || text2(value.code) || extractAlias(
|
|
818
|
+
text2(value.shortUrl) || text2(value.short_url) || text2(value.shortLink) || text2(value.shortlink) || ""
|
|
819
|
+
);
|
|
820
|
+
return {
|
|
821
|
+
destinationUrl,
|
|
822
|
+
...alias ? { alias } : {},
|
|
823
|
+
...text2(value.title) ? { title: text2(value.title) } : {},
|
|
824
|
+
...text2(value.password) ? { password: text2(value.password) } : {},
|
|
825
|
+
...text2(value.status) ? { status: text2(value.status) } : {},
|
|
826
|
+
...text2(value.expiresAt) || text2(value.expires) ? { expiresAt: text2(value.expiresAt) || text2(value.expires) } : {},
|
|
827
|
+
...text2(value.utmSource) ? { utmSource: text2(value.utmSource) } : {},
|
|
828
|
+
...text2(value.utmMedium) ? { utmMedium: text2(value.utmMedium) } : {},
|
|
829
|
+
...text2(value.utmCampaign) ? { utmCampaign: text2(value.utmCampaign) } : {},
|
|
830
|
+
...text2(value.utmTerm) ? { utmTerm: text2(value.utmTerm) } : {},
|
|
831
|
+
...text2(value.utmContent) ? { utmContent: text2(value.utmContent) } : {}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function parseJson(text6) {
|
|
835
|
+
const parsed = JSON.parse(text6);
|
|
836
|
+
const items = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.urls) ? parsed.urls ?? [] : [];
|
|
837
|
+
return items.map(
|
|
838
|
+
(item) => item && typeof item === "object" ? normalizeImportRow(item) : null
|
|
839
|
+
).filter((item) => Boolean(item));
|
|
840
|
+
}
|
|
841
|
+
function parseCsv(text6) {
|
|
842
|
+
const rows2 = parseCsvRows(text6);
|
|
843
|
+
if (rows2.length < 2) {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
const headers = rows2[0].map((header) => normalizeHeader(header));
|
|
847
|
+
const links = [];
|
|
848
|
+
for (let index = 1; index < rows2.length; index += 1) {
|
|
849
|
+
const row2 = rows2[index];
|
|
850
|
+
const entry = {};
|
|
851
|
+
headers.forEach((header, column) => {
|
|
852
|
+
const value = row2[column]?.trim();
|
|
853
|
+
if (!value) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (header === "url" || header === "destinationurl" || header === "destination") {
|
|
857
|
+
entry.destinationUrl = value;
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (header === "alias" || header === "shortcode" || header === "code") {
|
|
861
|
+
entry.alias = value;
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (header === "shorturl" || header === "shortlink") {
|
|
865
|
+
entry.alias = entry.alias || extractAlias(value);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (header === "password") {
|
|
869
|
+
entry.password = value;
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (header === "expires" || header === "expiresat") {
|
|
873
|
+
entry.expiresAt = value;
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (header === "title") {
|
|
877
|
+
entry.title = value;
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (header === "status") {
|
|
881
|
+
entry.status = value;
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (header === "utmsource") {
|
|
885
|
+
entry.utmSource = value;
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (header === "utmmedium") {
|
|
889
|
+
entry.utmMedium = value;
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (header === "utmcampaign") {
|
|
893
|
+
entry.utmCampaign = value;
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (header === "utmterm") {
|
|
897
|
+
entry.utmTerm = value;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (header === "utmcontent") {
|
|
901
|
+
entry.utmContent = value;
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
const link = normalizeImportRow(entry);
|
|
905
|
+
if (link) {
|
|
906
|
+
links.push(link);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return links;
|
|
910
|
+
}
|
|
911
|
+
function parseXml(text6) {
|
|
912
|
+
const entries = Array.from(
|
|
913
|
+
text6.matchAll(/<(url|item)\b[^>]*>([\s\S]*?)<\/\1>/gi),
|
|
914
|
+
(match) => match[2]
|
|
915
|
+
);
|
|
916
|
+
return entries.map((entry) => {
|
|
917
|
+
const getValue = (tag) => {
|
|
918
|
+
const match = new RegExp(
|
|
919
|
+
`<${tag}>([\\s\\S]*?)</${tag}>`,
|
|
920
|
+
"i"
|
|
921
|
+
).exec(entry);
|
|
922
|
+
return match ? decodeXml(match[1].trim()) : void 0;
|
|
923
|
+
};
|
|
924
|
+
return normalizeImportRow({
|
|
925
|
+
destinationUrl: getValue("destinationUrl"),
|
|
926
|
+
url: getValue("url"),
|
|
927
|
+
alias: getValue("alias"),
|
|
928
|
+
shortCode: getValue("shortCode"),
|
|
929
|
+
shortUrl: getValue("shortUrl"),
|
|
930
|
+
password: getValue("password"),
|
|
931
|
+
expiresAt: getValue("expiresAt"),
|
|
932
|
+
expires: getValue("expires"),
|
|
933
|
+
title: getValue("title"),
|
|
934
|
+
status: getValue("status"),
|
|
935
|
+
utmSource: getValue("utmSource"),
|
|
936
|
+
utmMedium: getValue("utmMedium"),
|
|
937
|
+
utmCampaign: getValue("utmCampaign"),
|
|
938
|
+
utmTerm: getValue("utmTerm"),
|
|
939
|
+
utmContent: getValue("utmContent")
|
|
940
|
+
});
|
|
941
|
+
}).filter((item) => Boolean(item));
|
|
942
|
+
}
|
|
943
|
+
function getImportFormat(filePath, value) {
|
|
944
|
+
if (value) {
|
|
945
|
+
return parseFileFormat(value);
|
|
946
|
+
}
|
|
947
|
+
const format = getFileFormatFromPath(filePath);
|
|
948
|
+
if (format) {
|
|
949
|
+
return format;
|
|
950
|
+
}
|
|
951
|
+
throw new CliError(
|
|
952
|
+
"Could not determine the import file format. Use a .csv, .json, or .xml file, or pass --format."
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
async function readImportRows(filePath, format) {
|
|
956
|
+
let textContent = "";
|
|
957
|
+
try {
|
|
958
|
+
textContent = await readFile2(filePath, "utf8");
|
|
959
|
+
} catch (error) {
|
|
960
|
+
throw new CliError(`Could not read import file ${filePath}.`, 1, {
|
|
961
|
+
cause: error instanceof Error ? error : void 0
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
try {
|
|
965
|
+
const rows2 = format === "json" ? parseJson(textContent) : format === "xml" ? parseXml(textContent) : parseCsv(textContent);
|
|
966
|
+
if (rows2.length === 0) {
|
|
967
|
+
throw new CliError(`No import rows were found in ${filePath}.`);
|
|
968
|
+
}
|
|
969
|
+
return rows2;
|
|
970
|
+
} catch (error) {
|
|
971
|
+
if (error instanceof CliError) {
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
974
|
+
throw new CliError(
|
|
975
|
+
`Could not parse ${filePath} as ${format.toUpperCase()}.`,
|
|
976
|
+
1,
|
|
977
|
+
{
|
|
978
|
+
cause: error instanceof Error ? error : void 0
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function formatImportErrors(errors) {
|
|
984
|
+
return formatTable(
|
|
985
|
+
["Destination", "Alias", "Error"],
|
|
986
|
+
errors.map((item) => [
|
|
987
|
+
item.destinationUrl || "-",
|
|
988
|
+
item.alias || "-",
|
|
989
|
+
item.error || "Unknown error"
|
|
990
|
+
])
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
function formatImportSummary(data) {
|
|
994
|
+
const created = data.results?.length ?? 0;
|
|
995
|
+
const errors = data.errors?.length ?? 0;
|
|
996
|
+
return `${created} link${created === 1 ? "" : "s"} created. ${errors} error${errors === 1 ? "" : "s"}.`;
|
|
997
|
+
}
|
|
998
|
+
|
|
533
999
|
// src/lib/links.ts
|
|
534
1000
|
var LIST_KEYS = ["urls", "items", "results"];
|
|
535
1001
|
function asObject(value) {
|
|
@@ -631,14 +1097,14 @@ function formatLinksTable(links) {
|
|
|
631
1097
|
return "No links found.";
|
|
632
1098
|
}
|
|
633
1099
|
const headers = ["ID", "Alias", "Short URL", "Destination", "Status"];
|
|
634
|
-
const
|
|
1100
|
+
const rows2 = links.map((link) => [
|
|
635
1101
|
truncate(getLinkId(link) || "-", 18),
|
|
636
1102
|
truncate(getLinkAlias(link) || "-", 12),
|
|
637
1103
|
truncate(getLinkShortUrl(link) || "-", 36),
|
|
638
1104
|
truncate(getLinkDestination(link) || "-", 52),
|
|
639
1105
|
truncate(asString(link.status) || "-", 12)
|
|
640
1106
|
]);
|
|
641
|
-
return formatTable(headers,
|
|
1107
|
+
return formatTable(headers, rows2);
|
|
642
1108
|
}
|
|
643
1109
|
function formatListSummary(data, count) {
|
|
644
1110
|
const meta = getListMeta(data);
|
|
@@ -654,6 +1120,323 @@ function formatListSummary(data, count) {
|
|
|
654
1120
|
return `${count} link${count === 1 ? "" : "s"} returned.`;
|
|
655
1121
|
}
|
|
656
1122
|
|
|
1123
|
+
// src/lib/status.ts
|
|
1124
|
+
function text3(value) {
|
|
1125
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
function integer(value) {
|
|
1128
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
if (typeof value === "string" && value.trim()) {
|
|
1132
|
+
const parsed = Number(value);
|
|
1133
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1134
|
+
}
|
|
1135
|
+
return void 0;
|
|
1136
|
+
}
|
|
1137
|
+
function flag(value) {
|
|
1138
|
+
if (typeof value === "boolean") {
|
|
1139
|
+
return value;
|
|
1140
|
+
}
|
|
1141
|
+
if (value === 1 || value === "1") {
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
if (value === 0 || value === "0") {
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
return void 0;
|
|
1148
|
+
}
|
|
1149
|
+
function yesNo(value, yes = "Yes", no = "No") {
|
|
1150
|
+
const normalized = flag(value);
|
|
1151
|
+
if (normalized === void 0) {
|
|
1152
|
+
return void 0;
|
|
1153
|
+
}
|
|
1154
|
+
return normalized ? yes : no;
|
|
1155
|
+
}
|
|
1156
|
+
function formatState(value) {
|
|
1157
|
+
const normalized = text3(value)?.toLowerCase();
|
|
1158
|
+
if (!normalized) {
|
|
1159
|
+
return void 0;
|
|
1160
|
+
}
|
|
1161
|
+
switch (normalized) {
|
|
1162
|
+
case "ok":
|
|
1163
|
+
return "Good";
|
|
1164
|
+
case "warning":
|
|
1165
|
+
return "Warning";
|
|
1166
|
+
case "error":
|
|
1167
|
+
return "Error";
|
|
1168
|
+
default:
|
|
1169
|
+
return normalized;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function formatCount(value) {
|
|
1173
|
+
const normalized = integer(value);
|
|
1174
|
+
return normalized === void 0 ? text3(value) : String(normalized);
|
|
1175
|
+
}
|
|
1176
|
+
function formatSize(value) {
|
|
1177
|
+
const bytes = integer(value);
|
|
1178
|
+
if (bytes === void 0) {
|
|
1179
|
+
return text3(value);
|
|
1180
|
+
}
|
|
1181
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
1182
|
+
let size = bytes;
|
|
1183
|
+
let unitIndex = 0;
|
|
1184
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
1185
|
+
size /= 1024;
|
|
1186
|
+
unitIndex += 1;
|
|
1187
|
+
}
|
|
1188
|
+
const amount = size >= 10 || unitIndex === 0 ? size.toFixed(0) : size.toFixed(1);
|
|
1189
|
+
return `${amount} ${units[unitIndex]}`;
|
|
1190
|
+
}
|
|
1191
|
+
function formatSeconds(value) {
|
|
1192
|
+
const normalized = integer(value);
|
|
1193
|
+
return normalized === void 0 ? text3(value) : `${normalized} seconds`;
|
|
1194
|
+
}
|
|
1195
|
+
function wrapText(value, width = 68) {
|
|
1196
|
+
const lines = [];
|
|
1197
|
+
for (const line of value.split("\n")) {
|
|
1198
|
+
if (line.length <= width) {
|
|
1199
|
+
lines.push(line);
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
for (let index = 0; index < line.length; index += width) {
|
|
1203
|
+
lines.push(line.slice(index, index + width));
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return lines.join("\n");
|
|
1207
|
+
}
|
|
1208
|
+
function row(label, value, width) {
|
|
1209
|
+
const normalized = typeof value === "string" ? value : typeof value === "number" ? String(value) : void 0;
|
|
1210
|
+
if (!normalized) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
return [label, wrapText(normalized, width)];
|
|
1214
|
+
}
|
|
1215
|
+
function rows(values) {
|
|
1216
|
+
return values.filter((value) => Boolean(value));
|
|
1217
|
+
}
|
|
1218
|
+
function getSummary(status2) {
|
|
1219
|
+
const summary = status2.summary ?? {};
|
|
1220
|
+
const checks = status2.checks ?? [];
|
|
1221
|
+
if (summary.okCount !== void 0 && summary.warningCount !== void 0 && summary.errorCount !== void 0 && summary.totalChecks !== void 0) {
|
|
1222
|
+
return summary;
|
|
1223
|
+
}
|
|
1224
|
+
let okCount = 0;
|
|
1225
|
+
let warningCount = 0;
|
|
1226
|
+
let errorCount = 0;
|
|
1227
|
+
for (const check of checks) {
|
|
1228
|
+
switch (text3(check.status)?.toLowerCase()) {
|
|
1229
|
+
case "error":
|
|
1230
|
+
errorCount += 1;
|
|
1231
|
+
break;
|
|
1232
|
+
case "warning":
|
|
1233
|
+
warningCount += 1;
|
|
1234
|
+
break;
|
|
1235
|
+
case "ok":
|
|
1236
|
+
okCount += 1;
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
overall: summary.overall ?? (errorCount > 0 ? "error" : warningCount > 0 ? "warning" : "ok"),
|
|
1242
|
+
okCount: summary.okCount ?? okCount,
|
|
1243
|
+
warningCount: summary.warningCount ?? warningCount,
|
|
1244
|
+
errorCount: summary.errorCount ?? errorCount,
|
|
1245
|
+
totalChecks: summary.totalChecks ?? checks.length
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function summaryRows(status2) {
|
|
1249
|
+
const summary = getSummary(status2);
|
|
1250
|
+
return rows([
|
|
1251
|
+
row("Overall", formatState(summary.overall)),
|
|
1252
|
+
row("Generated at", text3(status2.generatedAt)),
|
|
1253
|
+
row("Total checks", formatCount(summary.totalChecks)),
|
|
1254
|
+
row("Good checks", formatCount(summary.okCount)),
|
|
1255
|
+
row("Warnings", formatCount(summary.warningCount)),
|
|
1256
|
+
row("Errors", formatCount(summary.errorCount))
|
|
1257
|
+
]);
|
|
1258
|
+
}
|
|
1259
|
+
function siteRows(site) {
|
|
1260
|
+
if (!site) {
|
|
1261
|
+
return [];
|
|
1262
|
+
}
|
|
1263
|
+
return rows([
|
|
1264
|
+
row("Name", text3(site.name)),
|
|
1265
|
+
row("URL", text3(site.url)),
|
|
1266
|
+
row("Version", text3(site.version)),
|
|
1267
|
+
row("Environment", text3(site.environment)),
|
|
1268
|
+
row("Install type", text3(site.installType)),
|
|
1269
|
+
row("Locale", text3(site.locale)),
|
|
1270
|
+
row("HTML lang", text3(site.htmlLang)),
|
|
1271
|
+
row(
|
|
1272
|
+
"Language",
|
|
1273
|
+
text3(site.languageNativeName) || text3(site.languageLabel)
|
|
1274
|
+
),
|
|
1275
|
+
row("Installed languages", formatCount(site.installedLanguagesCount)),
|
|
1276
|
+
row("Default locale", text3(site.defaultLocale)),
|
|
1277
|
+
row("Debug", yesNo(site.debugEnabled, "Enabled", "Disabled"))
|
|
1278
|
+
]);
|
|
1279
|
+
}
|
|
1280
|
+
function serverRows(server) {
|
|
1281
|
+
if (!server) {
|
|
1282
|
+
return [];
|
|
1283
|
+
}
|
|
1284
|
+
return rows([
|
|
1285
|
+
row("PHP version", text3(server.phpVersion)),
|
|
1286
|
+
row("PHP SAPI", text3(server.phpSapi)),
|
|
1287
|
+
row("Server software", text3(server.serverSoftware), 56),
|
|
1288
|
+
row("Operating system", text3(server.operatingSystem)),
|
|
1289
|
+
row("Timezone", text3(server.timezone)),
|
|
1290
|
+
row("Memory limit", text3(server.memoryLimit)),
|
|
1291
|
+
row("Max execution time", formatSeconds(server.maxExecutionTime)),
|
|
1292
|
+
row("Upload max filesize", text3(server.uploadMaxFilesize)),
|
|
1293
|
+
row("Post max size", text3(server.postMaxSize)),
|
|
1294
|
+
row("Intl extension", yesNo(server.extensions?.intl)),
|
|
1295
|
+
row("cURL extension", yesNo(server.extensions?.curl)),
|
|
1296
|
+
row("ZipArchive", yesNo(server.extensions?.zip))
|
|
1297
|
+
]);
|
|
1298
|
+
}
|
|
1299
|
+
function databaseRows(database) {
|
|
1300
|
+
if (!database) {
|
|
1301
|
+
return [];
|
|
1302
|
+
}
|
|
1303
|
+
return rows([
|
|
1304
|
+
row("Connected", yesNo(database.connected)),
|
|
1305
|
+
row("Server type", text3(database.serverType)),
|
|
1306
|
+
row("Version", text3(database.version)),
|
|
1307
|
+
row("Host", text3(database.host)),
|
|
1308
|
+
row("Port", formatCount(database.port)),
|
|
1309
|
+
row("Name", text3(database.name)),
|
|
1310
|
+
row("Charset", text3(database.charset)),
|
|
1311
|
+
row("Prefix", text3(database.prefix)),
|
|
1312
|
+
row("Schema version", formatCount(database.schemaVersion)),
|
|
1313
|
+
row(
|
|
1314
|
+
"Required schema version",
|
|
1315
|
+
formatCount(database.requiredSchemaVersion)
|
|
1316
|
+
),
|
|
1317
|
+
row("Schema compatible", yesNo(database.schemaCompatible)),
|
|
1318
|
+
row("Schema upgrade required", yesNo(database.schemaUpgradeRequired)),
|
|
1319
|
+
row("Schema issues", formatCount(database.schemaIssuesCount)),
|
|
1320
|
+
row("Last upgraded", text3(database.schemaLastUpgradedAt)),
|
|
1321
|
+
row("Last error", text3(database.schemaLastError), 56)
|
|
1322
|
+
]);
|
|
1323
|
+
}
|
|
1324
|
+
function storageRows(storage) {
|
|
1325
|
+
if (!storage) {
|
|
1326
|
+
return [];
|
|
1327
|
+
}
|
|
1328
|
+
return rows([
|
|
1329
|
+
row("Content directory", text3(storage.contentDirectory), 60),
|
|
1330
|
+
row("Content exists", yesNo(storage.contentExists)),
|
|
1331
|
+
row("Content writable", yesNo(storage.contentWritable)),
|
|
1332
|
+
row("Content size", formatSize(storage.contentDirectorySizeBytes)),
|
|
1333
|
+
row("Languages directory", text3(storage.languagesDirectory), 60),
|
|
1334
|
+
row("Languages exists", yesNo(storage.languagesDirectoryExists)),
|
|
1335
|
+
row("Languages readable", yesNo(storage.languagesDirectoryReadable)),
|
|
1336
|
+
row("Languages size", formatSize(storage.languagesDirectorySizeBytes)),
|
|
1337
|
+
row("Config path", text3(storage.configPath), 60),
|
|
1338
|
+
row("Config exists", yesNo(storage.configExists)),
|
|
1339
|
+
row("Config size", formatSize(storage.configSizeBytes)),
|
|
1340
|
+
row("Debug log path", text3(storage.debugLogPath), 60),
|
|
1341
|
+
row("Debug log exists", yesNo(storage.debugLogExists)),
|
|
1342
|
+
row("Debug log readable", yesNo(storage.debugLogReadable)),
|
|
1343
|
+
row("Debug log size", formatSize(storage.debugLogSizeBytes)),
|
|
1344
|
+
row("App directory", text3(storage.appDirectory), 60),
|
|
1345
|
+
row("App writable", yesNo(storage.appWritable)),
|
|
1346
|
+
row("App size", formatSize(storage.appDirectorySizeBytes)),
|
|
1347
|
+
row("Release root", text3(storage.releaseRoot), 60),
|
|
1348
|
+
row("Release size", formatSize(storage.releaseRootSizeBytes))
|
|
1349
|
+
]);
|
|
1350
|
+
}
|
|
1351
|
+
function mailRows(mail) {
|
|
1352
|
+
if (!mail) {
|
|
1353
|
+
return [];
|
|
1354
|
+
}
|
|
1355
|
+
return rows([
|
|
1356
|
+
row("Driver", text3(mail.driver)),
|
|
1357
|
+
row("Transport", yesNo(mail.transportReady, "Ready", "Not ready")),
|
|
1358
|
+
row("From email", text3(mail.fromEmail)),
|
|
1359
|
+
row("From name", text3(mail.fromName)),
|
|
1360
|
+
row("SMTP host", text3(mail.smtpHost)),
|
|
1361
|
+
row("SMTP port", text3(mail.smtpPort)),
|
|
1362
|
+
row("SMTP encryption", text3(mail.smtpEncryption)),
|
|
1363
|
+
row("SMTP auth", yesNo(mail.smtpAuth)),
|
|
1364
|
+
row("Configuration", text3(mail.configurationLabel)),
|
|
1365
|
+
row("Configuration path", text3(mail.configurationPath), 60)
|
|
1366
|
+
]);
|
|
1367
|
+
}
|
|
1368
|
+
function locationRows(location) {
|
|
1369
|
+
if (!location) {
|
|
1370
|
+
return [];
|
|
1371
|
+
}
|
|
1372
|
+
return rows([
|
|
1373
|
+
row(
|
|
1374
|
+
"Analytics",
|
|
1375
|
+
yesNo(location.locationAnalyticsReady, "Ready", "Not ready")
|
|
1376
|
+
),
|
|
1377
|
+
row("Last downloaded", text3(location.lastDownloadedAt)),
|
|
1378
|
+
row("Database updated", text3(location.databaseUpdatedAt)),
|
|
1379
|
+
row("Database size", formatSize(location.databaseSizeBytes)),
|
|
1380
|
+
row("Credentials configured", yesNo(location.credentialsConfigured)),
|
|
1381
|
+
row("Account ID", text3(location.accountId)),
|
|
1382
|
+
row("Database path", text3(location.databasePath), 60),
|
|
1383
|
+
row("Database readable", yesNo(location.databaseReadable)),
|
|
1384
|
+
row("Download command", text3(location.downloadCommand), 60)
|
|
1385
|
+
]);
|
|
1386
|
+
}
|
|
1387
|
+
function dataRows(data) {
|
|
1388
|
+
if (!data) {
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
return rows([
|
|
1392
|
+
row("Users", formatCount(data.users)),
|
|
1393
|
+
row("Links", formatCount(data.links)),
|
|
1394
|
+
row("Clicks", formatCount(data.clicks)),
|
|
1395
|
+
row("Sessions", formatCount(data.sessions)),
|
|
1396
|
+
row("API keys", formatCount(data.apiKeys)),
|
|
1397
|
+
row("Webhooks", formatCount(data.webhooks)),
|
|
1398
|
+
row("Audit events", formatCount(data.auditEvents)),
|
|
1399
|
+
row("Managed tables", formatCount(data.managedTables))
|
|
1400
|
+
]);
|
|
1401
|
+
}
|
|
1402
|
+
function checksTable(checks) {
|
|
1403
|
+
if (checks.length === 0) {
|
|
1404
|
+
return void 0;
|
|
1405
|
+
}
|
|
1406
|
+
const rows2 = checks.map((check) => [
|
|
1407
|
+
wrapText(text3(check.label) || "Check", 24),
|
|
1408
|
+
formatState(check.status) || "Unknown",
|
|
1409
|
+
wrapText(text3(check.description) || "Not available", 56)
|
|
1410
|
+
]);
|
|
1411
|
+
return formatTable(["Check", "Status", "Description"], rows2);
|
|
1412
|
+
}
|
|
1413
|
+
function section(title, rows2) {
|
|
1414
|
+
if (rows2.length === 0) {
|
|
1415
|
+
return void 0;
|
|
1416
|
+
}
|
|
1417
|
+
return `${title}
|
|
1418
|
+
${formatTable(["Field", "Value"], rows2)}`;
|
|
1419
|
+
}
|
|
1420
|
+
function getStatusValue(status2) {
|
|
1421
|
+
return text3(getSummary(status2).overall) || "unknown";
|
|
1422
|
+
}
|
|
1423
|
+
function formatStatusReport(status2) {
|
|
1424
|
+
const checks = checksTable(status2.checks ?? []);
|
|
1425
|
+
const sections = [
|
|
1426
|
+
section("Summary", summaryRows(status2)),
|
|
1427
|
+
checks ? `Checks
|
|
1428
|
+
${checks}` : void 0,
|
|
1429
|
+
section("Site", siteRows(status2.site)),
|
|
1430
|
+
section("Server", serverRows(status2.server)),
|
|
1431
|
+
section("Database", databaseRows(status2.database)),
|
|
1432
|
+
section("Storage", storageRows(status2.storage)),
|
|
1433
|
+
section("Mail", mailRows(status2.mail)),
|
|
1434
|
+
section("Location", locationRows(status2.location)),
|
|
1435
|
+
section("Data", dataRows(status2.data))
|
|
1436
|
+
].filter((value) => Boolean(value));
|
|
1437
|
+
return sections.length > 0 ? sections.join("\n\n") : "No system status fields returned.";
|
|
1438
|
+
}
|
|
1439
|
+
|
|
657
1440
|
// src/lib/update.ts
|
|
658
1441
|
var PACKAGE_NAME = "peakurl";
|
|
659
1442
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
@@ -756,7 +1539,7 @@ function compareSemver(left, right) {
|
|
|
756
1539
|
return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease);
|
|
757
1540
|
}
|
|
758
1541
|
function getUpdateInstallCommand() {
|
|
759
|
-
return `npm
|
|
1542
|
+
return `npm i -g ${PACKAGE_NAME}@latest`;
|
|
760
1543
|
}
|
|
761
1544
|
async function fetchLatestVersion(env) {
|
|
762
1545
|
const registryUrl = normalizeRegistryUrl(getRegistryBaseUrl(env));
|
|
@@ -829,10 +1612,10 @@ async function getUpdateStatus(currentVersion, env, options) {
|
|
|
829
1612
|
installCommand: getUpdateInstallCommand()
|
|
830
1613
|
};
|
|
831
1614
|
}
|
|
832
|
-
function showUpdateNotice(
|
|
1615
|
+
function showUpdateNotice(status2) {
|
|
833
1616
|
writeNoticeBox("Update Available", [
|
|
834
|
-
`${PACKAGE_NAME} ${
|
|
835
|
-
`Run: ${
|
|
1617
|
+
`${PACKAGE_NAME} ${status2.currentVersion} -> ${status2.latestVersion}`,
|
|
1618
|
+
`Run: ${status2.installCommand}`
|
|
836
1619
|
]);
|
|
837
1620
|
}
|
|
838
1621
|
async function checkUpdates(options) {
|
|
@@ -844,51 +1627,157 @@ async function checkUpdates(options) {
|
|
|
844
1627
|
}
|
|
845
1628
|
const store = new StateStore();
|
|
846
1629
|
const updateState = await getUpdateState(store);
|
|
847
|
-
const
|
|
1630
|
+
const status2 = await getUpdateStatus(options.currentVersion, options.env, {
|
|
848
1631
|
store
|
|
849
1632
|
});
|
|
850
|
-
if (!
|
|
1633
|
+
if (!status2.isOutdated) {
|
|
851
1634
|
return;
|
|
852
1635
|
}
|
|
853
1636
|
const lastNotifiedAt = parseTime(updateState.lastNotifiedAt);
|
|
854
|
-
const alreadyNotifiedForVersion = updateState.lastNotifiedVersion ===
|
|
1637
|
+
const alreadyNotifiedForVersion = updateState.lastNotifiedVersion === status2.latestVersion;
|
|
855
1638
|
if (alreadyNotifiedForVersion && lastNotifiedAt !== null && Date.now() - lastNotifiedAt < NOTICE_TTL_MS) {
|
|
856
1639
|
return;
|
|
857
1640
|
}
|
|
858
1641
|
await saveUpdateState(store, {
|
|
859
1642
|
...updateState,
|
|
860
1643
|
lastNotifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
861
|
-
lastNotifiedVersion:
|
|
1644
|
+
lastNotifiedVersion: status2.latestVersion
|
|
862
1645
|
});
|
|
863
|
-
showUpdateNotice(
|
|
1646
|
+
showUpdateNotice(status2);
|
|
864
1647
|
}
|
|
865
1648
|
|
|
866
1649
|
// src/lib/users.ts
|
|
867
|
-
function
|
|
1650
|
+
function text4(value) {
|
|
868
1651
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
869
1652
|
}
|
|
870
1653
|
function userLabel(user) {
|
|
871
|
-
const fullName = [
|
|
872
|
-
return fullName ||
|
|
1654
|
+
const fullName = [text4(user.firstName), text4(user.lastName)].filter((value) => Boolean(value)).join(" ");
|
|
1655
|
+
return fullName || text4(user.username) || text4(user.email) || String(user.id ?? "unknown");
|
|
873
1656
|
}
|
|
874
1657
|
function userValue(user) {
|
|
875
|
-
return
|
|
1658
|
+
return text4(user.username) || text4(user.email) || String(user.id ?? "");
|
|
876
1659
|
}
|
|
877
1660
|
function userTable(user) {
|
|
878
|
-
const
|
|
1661
|
+
const rows2 = [
|
|
879
1662
|
["Name", userLabel(user)],
|
|
880
|
-
["Username",
|
|
881
|
-
["Email",
|
|
882
|
-
["Role",
|
|
1663
|
+
["Username", text4(user.username)],
|
|
1664
|
+
["Email", text4(user.email)],
|
|
1665
|
+
["Role", text4(user.role)],
|
|
883
1666
|
["ID", user.id === void 0 ? void 0 : String(user.id)]
|
|
884
1667
|
].filter((entry) => Boolean(entry[1]));
|
|
885
|
-
if (
|
|
1668
|
+
if (rows2.length === 0) {
|
|
886
1669
|
return "No user fields returned.";
|
|
887
1670
|
}
|
|
888
|
-
return formatTable(["Field", "Value"],
|
|
1671
|
+
return formatTable(["Field", "Value"], rows2);
|
|
889
1672
|
}
|
|
890
1673
|
|
|
891
|
-
// src/
|
|
1674
|
+
// src/lib/webhooks.ts
|
|
1675
|
+
var WEBHOOK_EVENTS = [
|
|
1676
|
+
{
|
|
1677
|
+
id: "link.created",
|
|
1678
|
+
label: "Link Created",
|
|
1679
|
+
description: "Send a delivery when a short link is created."
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
id: "link.clicked",
|
|
1683
|
+
label: "Link Clicked",
|
|
1684
|
+
description: "Send a delivery when a visitor clicks a short link."
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
id: "link.updated",
|
|
1688
|
+
label: "Link Updated",
|
|
1689
|
+
description: "Send a delivery when a short link is updated."
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
id: "link.deleted",
|
|
1693
|
+
label: "Link Deleted",
|
|
1694
|
+
description: "Send a delivery when a short link is deleted."
|
|
1695
|
+
}
|
|
1696
|
+
];
|
|
1697
|
+
function text5(value) {
|
|
1698
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1699
|
+
}
|
|
1700
|
+
function textList(value) {
|
|
1701
|
+
return Array.isArray(value) ? value.map((item) => text5(item)).filter((item) => Boolean(item)) : [];
|
|
1702
|
+
}
|
|
1703
|
+
function truncate2(value, maxLength) {
|
|
1704
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
|
|
1705
|
+
}
|
|
1706
|
+
function parseWebhookEvents(value, previous = []) {
|
|
1707
|
+
const allowed = new Set(WEBHOOK_EVENTS.map((event) => event.id));
|
|
1708
|
+
const values = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
1709
|
+
if (values.length === 0) {
|
|
1710
|
+
throw new CliError("A webhook event value is required.");
|
|
1711
|
+
}
|
|
1712
|
+
for (const event of values) {
|
|
1713
|
+
if (!allowed.has(event)) {
|
|
1714
|
+
throw new CliError(
|
|
1715
|
+
`Unknown webhook event: ${event}. Run \`peakurl webhook events\` to see the supported values.`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return [...previous, ...values];
|
|
1720
|
+
}
|
|
1721
|
+
function getWebhookId(webhook) {
|
|
1722
|
+
return text5(webhook.id) || (webhook.id !== void 0 ? String(webhook.id) : void 0);
|
|
1723
|
+
}
|
|
1724
|
+
function getWebhookUrl(webhook) {
|
|
1725
|
+
return text5(webhook.url);
|
|
1726
|
+
}
|
|
1727
|
+
function getWebhookEvents(webhook) {
|
|
1728
|
+
return textList(webhook.events);
|
|
1729
|
+
}
|
|
1730
|
+
function getQuietWebhookValue(webhook) {
|
|
1731
|
+
return getWebhookId(webhook) || getWebhookUrl(webhook) || "";
|
|
1732
|
+
}
|
|
1733
|
+
function formatWebhookEventsTable() {
|
|
1734
|
+
return formatTable(
|
|
1735
|
+
["Event", "Label", "Description"],
|
|
1736
|
+
WEBHOOK_EVENTS.map((event) => [
|
|
1737
|
+
event.id,
|
|
1738
|
+
event.label,
|
|
1739
|
+
event.description
|
|
1740
|
+
])
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
function formatWebhooksTable(webhooks) {
|
|
1744
|
+
if (webhooks.length === 0) {
|
|
1745
|
+
return "No webhooks found.";
|
|
1746
|
+
}
|
|
1747
|
+
return formatTable(
|
|
1748
|
+
["ID", "URL", "Events", "Status", "Secret"],
|
|
1749
|
+
webhooks.map((webhook) => [
|
|
1750
|
+
truncate2(getWebhookId(webhook) || "-", 18),
|
|
1751
|
+
truncate2(getWebhookUrl(webhook) || "-", 42),
|
|
1752
|
+
truncate2(getWebhookEvents(webhook).join(", ") || "-", 30),
|
|
1753
|
+
webhook.isActive === false ? "inactive" : "active",
|
|
1754
|
+
truncate2(text5(webhook.secretHint) || "-", 18)
|
|
1755
|
+
])
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
function formatWebhookDetails(webhook) {
|
|
1759
|
+
const rows2 = [
|
|
1760
|
+
["ID", getWebhookId(webhook)],
|
|
1761
|
+
["URL", getWebhookUrl(webhook)],
|
|
1762
|
+
[
|
|
1763
|
+
"Events",
|
|
1764
|
+
getWebhookEvents(webhook).length > 0 ? getWebhookEvents(webhook).join(", ") : void 0
|
|
1765
|
+
],
|
|
1766
|
+
["Status", webhook.isActive === false ? "inactive" : "active"],
|
|
1767
|
+
["Secret", text5(webhook.secret)],
|
|
1768
|
+
["Secret Hint", text5(webhook.secretHint)],
|
|
1769
|
+
["Created", text5(webhook.createdAt)]
|
|
1770
|
+
].filter((entry) => Boolean(entry[1]));
|
|
1771
|
+
if (rows2.length === 0) {
|
|
1772
|
+
return "No webhook fields returned.";
|
|
1773
|
+
}
|
|
1774
|
+
return formatTable(["Field", "Value"], rows2);
|
|
1775
|
+
}
|
|
1776
|
+
function formatWebhooksSummary(webhooks) {
|
|
1777
|
+
return `${webhooks.length} webhook${webhooks.length === 1 ? "" : "s"} returned.`;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/commands/links.ts
|
|
892
1781
|
function normalizeExpiresAt(value) {
|
|
893
1782
|
if (!value) {
|
|
894
1783
|
return void 0;
|
|
@@ -898,7 +1787,7 @@ function normalizeExpiresAt(value) {
|
|
|
898
1787
|
}
|
|
899
1788
|
return value;
|
|
900
1789
|
}
|
|
901
|
-
async function
|
|
1790
|
+
async function createLink(destinationUrl, options) {
|
|
902
1791
|
const config = await getAuthConfig(process.env);
|
|
903
1792
|
const response = await new ApiClient(config).createUrl({
|
|
904
1793
|
destinationUrl: normalizeDestinationUrl(destinationUrl),
|
|
@@ -924,47 +1813,73 @@ async function createCommand(destinationUrl, options) {
|
|
|
924
1813
|
writeStdout(response.message);
|
|
925
1814
|
writeStdout(formatLinkDetails(response.data));
|
|
926
1815
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1816
|
+
async function importLinks(filePath, options) {
|
|
1817
|
+
const format = getImportFormat(filePath, options.format);
|
|
1818
|
+
const urls = await readImportRows(filePath, format);
|
|
930
1819
|
const config = await getAuthConfig(process.env);
|
|
931
|
-
const
|
|
932
|
-
const
|
|
933
|
-
const
|
|
934
|
-
if (!resolvedId) {
|
|
935
|
-
throw new CliError(
|
|
936
|
-
"PeakURL returned a link record without an ID, so the CLI cannot delete it safely."
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
const response = await client.deleteUrl(resolvedId);
|
|
1820
|
+
const response = await new ApiClient(config).importUrls({ urls });
|
|
1821
|
+
const links = extractLinks(response.data?.results ?? []);
|
|
1822
|
+
const errors = response.data?.errors ?? [];
|
|
940
1823
|
if (options.json) {
|
|
941
1824
|
writeJson(response);
|
|
942
1825
|
return;
|
|
943
1826
|
}
|
|
944
1827
|
if (options.quiet) {
|
|
1828
|
+
for (const link of links) {
|
|
1829
|
+
const value = getQuietLinkValue(link);
|
|
1830
|
+
if (value) {
|
|
1831
|
+
writeStdout(value);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
945
1834
|
return;
|
|
946
1835
|
}
|
|
947
1836
|
writeStdout(response.message);
|
|
1837
|
+
if (links.length > 0) {
|
|
1838
|
+
writeStdout(formatLinksTable(links));
|
|
1839
|
+
} else {
|
|
1840
|
+
writeStdout("No links were created.");
|
|
1841
|
+
}
|
|
1842
|
+
if (errors.length > 0) {
|
|
1843
|
+
writeStdout(formatImportErrors(errors));
|
|
1844
|
+
}
|
|
1845
|
+
writeStdout(formatImportSummary(response.data ?? {}));
|
|
948
1846
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1847
|
+
async function exportLinks(options) {
|
|
1848
|
+
if (options.stdout && options.output) {
|
|
1849
|
+
throw new CliError("Use either --stdout or --output, not both.");
|
|
1850
|
+
}
|
|
1851
|
+
const format = options.format ? parseFileFormat(options.format) : options.output ? getFileFormatFromPath(options.output) || "csv" : "csv";
|
|
952
1852
|
const config = await getAuthConfig(process.env);
|
|
953
|
-
const response = await new ApiClient(config).
|
|
954
|
-
|
|
955
|
-
|
|
1853
|
+
const response = await new ApiClient(config).exportUrls({
|
|
1854
|
+
search: options.search,
|
|
1855
|
+
sortBy: options.sortBy,
|
|
1856
|
+
sortOrder: options.sortOrder
|
|
1857
|
+
});
|
|
1858
|
+
const links = extractLinks(response.data);
|
|
1859
|
+
const content = serializeLinkExport(links, format);
|
|
1860
|
+
if (options.stdout) {
|
|
1861
|
+
process.stdout.write(content);
|
|
956
1862
|
return;
|
|
957
1863
|
}
|
|
1864
|
+
const filePath = resolve(options.output || getExportFileName(format));
|
|
1865
|
+
try {
|
|
1866
|
+
await mkdir2(dirname2(filePath), { recursive: true });
|
|
1867
|
+
await writeFile2(filePath, content, "utf8");
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
throw new CliError(`Could not write export file ${filePath}.`, 1, {
|
|
1870
|
+
cause: error instanceof Error ? error : void 0
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
958
1873
|
if (options.quiet) {
|
|
959
|
-
writeStdout(
|
|
1874
|
+
writeStdout(filePath);
|
|
960
1875
|
return;
|
|
961
1876
|
}
|
|
962
1877
|
writeStdout(response.message);
|
|
963
|
-
writeStdout(
|
|
1878
|
+
writeStdout(
|
|
1879
|
+
`Saved ${links.length} link${links.length === 1 ? "" : "s"} to ${filePath}.`
|
|
1880
|
+
);
|
|
964
1881
|
}
|
|
965
|
-
|
|
966
|
-
// src/commands/list.ts
|
|
967
|
-
async function listCommand(options) {
|
|
1882
|
+
async function listLinks(options) {
|
|
968
1883
|
const config = await getAuthConfig(process.env);
|
|
969
1884
|
const response = await new ApiClient(config).listUrls({
|
|
970
1885
|
page: options.page,
|
|
@@ -991,18 +1906,52 @@ async function listCommand(options) {
|
|
|
991
1906
|
writeStdout(formatLinksTable(links));
|
|
992
1907
|
writeStdout(formatListSummary(response.data, links.length));
|
|
993
1908
|
}
|
|
1909
|
+
async function getLink(idOrAlias, options) {
|
|
1910
|
+
const config = await getAuthConfig(process.env);
|
|
1911
|
+
const response = await new ApiClient(config).getUrl(idOrAlias);
|
|
1912
|
+
if (options.json) {
|
|
1913
|
+
writeJson(response);
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (options.quiet) {
|
|
1917
|
+
writeStdout(getQuietLinkValue(response.data));
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
writeStdout(response.message);
|
|
1921
|
+
writeStdout(formatLinkDetails(response.data));
|
|
1922
|
+
}
|
|
1923
|
+
async function deleteLink(idOrAlias, options) {
|
|
1924
|
+
const config = await getAuthConfig(process.env);
|
|
1925
|
+
const client = new ApiClient(config);
|
|
1926
|
+
const lookupResponse = await client.getUrl(idOrAlias);
|
|
1927
|
+
const resolvedId = getLinkId(lookupResponse.data);
|
|
1928
|
+
if (!resolvedId) {
|
|
1929
|
+
throw new CliError(
|
|
1930
|
+
"PeakURL returned a link record without an ID, so the CLI cannot delete it safely."
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
const response = await client.deleteUrl(resolvedId);
|
|
1934
|
+
if (options.json) {
|
|
1935
|
+
writeJson(response);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
if (options.quiet) {
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
writeStdout(response.message);
|
|
1942
|
+
}
|
|
994
1943
|
|
|
995
1944
|
// src/commands/login.ts
|
|
996
|
-
async function
|
|
1945
|
+
async function login(options) {
|
|
997
1946
|
const credentials = getLoginConfig(options, process.env);
|
|
998
1947
|
const client = new ApiClient(credentials);
|
|
999
1948
|
const response = await client.whoami();
|
|
1000
1949
|
await new ConfigStore().save(credentials);
|
|
1001
1950
|
const responseBody = {
|
|
1002
1951
|
success: true,
|
|
1003
|
-
message: `Saved credentials for ${credentials.
|
|
1952
|
+
message: `Saved credentials for ${credentials.apiBaseUrl}.`,
|
|
1004
1953
|
data: {
|
|
1005
|
-
|
|
1954
|
+
apiBaseUrl: credentials.apiBaseUrl,
|
|
1006
1955
|
user: response.data
|
|
1007
1956
|
},
|
|
1008
1957
|
timestamp: response.timestamp
|
|
@@ -1014,7 +1963,7 @@ async function loginCommand(options) {
|
|
|
1014
1963
|
if (options.quiet) {
|
|
1015
1964
|
return;
|
|
1016
1965
|
}
|
|
1017
|
-
writeStdout(`Saved credentials for ${credentials.
|
|
1966
|
+
writeStdout(`Saved credentials for ${credentials.apiBaseUrl}`);
|
|
1018
1967
|
writeStdout(`Authenticated as ${userLabel(response.data)}`);
|
|
1019
1968
|
writeStdout(userTable(response.data));
|
|
1020
1969
|
}
|
|
@@ -1023,18 +1972,18 @@ async function loginCommand(options) {
|
|
|
1023
1972
|
function hasEnvConfig(env) {
|
|
1024
1973
|
return Boolean(env.PEAKURL_BASE_URL?.trim() || env.PEAKURL_API_KEY?.trim());
|
|
1025
1974
|
}
|
|
1026
|
-
async function
|
|
1975
|
+
async function logout(options) {
|
|
1027
1976
|
const store = new ConfigStore();
|
|
1028
1977
|
const saved = await store.load();
|
|
1029
1978
|
const removed = await store.clear();
|
|
1030
1979
|
const envConfig = hasEnvConfig(process.env);
|
|
1031
|
-
const message = removed && saved?.
|
|
1980
|
+
const message = removed && saved?.apiBaseUrl ? `Logged out. Removed saved credentials for ${saved.apiBaseUrl}.` : removed ? "Logged out. Removed saved PeakURL credentials." : "Already logged out. No saved PeakURL credentials were found.";
|
|
1032
1981
|
const responseBody = {
|
|
1033
1982
|
success: true,
|
|
1034
1983
|
message,
|
|
1035
1984
|
data: {
|
|
1036
1985
|
removed,
|
|
1037
|
-
|
|
1986
|
+
apiBaseUrl: saved?.apiBaseUrl,
|
|
1038
1987
|
envCredentialsActive: envConfig
|
|
1039
1988
|
},
|
|
1040
1989
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1054,19 +2003,35 @@ async function logoutCommand(options) {
|
|
|
1054
2003
|
}
|
|
1055
2004
|
}
|
|
1056
2005
|
|
|
2006
|
+
// src/commands/status.ts
|
|
2007
|
+
async function status(options) {
|
|
2008
|
+
const config = await getAuthConfig(process.env);
|
|
2009
|
+
const response = await new ApiClient(config).getStatus();
|
|
2010
|
+
if (options.json) {
|
|
2011
|
+
writeJson(response);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
if (options.quiet) {
|
|
2015
|
+
writeStdout(getStatusValue(response.data));
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
writeStdout(response.message);
|
|
2019
|
+
writeStdout(formatStatusReport(response.data));
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1057
2022
|
// src/commands/update.ts
|
|
1058
|
-
async function
|
|
1059
|
-
const
|
|
2023
|
+
async function checkUpdate(options, currentVersion) {
|
|
2024
|
+
const status2 = await getUpdateStatus(currentVersion, process.env, {
|
|
1060
2025
|
forceRefresh: true
|
|
1061
2026
|
});
|
|
1062
2027
|
const responseBody = {
|
|
1063
2028
|
success: true,
|
|
1064
|
-
message:
|
|
2029
|
+
message: status2.isOutdated ? `A newer PeakURL CLI version is available (${status2.latestVersion}).` : `PeakURL CLI ${status2.currentVersion} is up to date.`,
|
|
1065
2030
|
data: {
|
|
1066
|
-
currentVersion:
|
|
1067
|
-
latestVersion:
|
|
1068
|
-
isOutdated:
|
|
1069
|
-
installCommand:
|
|
2031
|
+
currentVersion: status2.currentVersion,
|
|
2032
|
+
latestVersion: status2.latestVersion,
|
|
2033
|
+
isOutdated: status2.isOutdated,
|
|
2034
|
+
installCommand: status2.installCommand,
|
|
1070
2035
|
checkOnly: Boolean(options.check)
|
|
1071
2036
|
},
|
|
1072
2037
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1075,28 +2040,108 @@ async function updateCommand(options, currentVersion) {
|
|
|
1075
2040
|
writeJson(responseBody);
|
|
1076
2041
|
return;
|
|
1077
2042
|
}
|
|
1078
|
-
if (!
|
|
2043
|
+
if (!status2.isOutdated) {
|
|
1079
2044
|
if (!options.quiet) {
|
|
1080
|
-
writeStdout(`PeakURL CLI ${
|
|
2045
|
+
writeStdout(`PeakURL CLI ${status2.currentVersion} is up to date.`);
|
|
1081
2046
|
}
|
|
1082
2047
|
return;
|
|
1083
2048
|
}
|
|
1084
2049
|
if (options.quiet) {
|
|
1085
|
-
writeStdout(
|
|
2050
|
+
writeStdout(status2.installCommand);
|
|
1086
2051
|
return;
|
|
1087
2052
|
}
|
|
1088
2053
|
writeNoticeBox(
|
|
1089
2054
|
"Update Available",
|
|
1090
2055
|
[
|
|
1091
|
-
`peakurl ${
|
|
1092
|
-
`Run: ${
|
|
2056
|
+
`peakurl ${status2.currentVersion} -> ${status2.latestVersion}`,
|
|
2057
|
+
`Run: ${status2.installCommand}`
|
|
1093
2058
|
],
|
|
1094
2059
|
"stdout"
|
|
1095
2060
|
);
|
|
1096
2061
|
}
|
|
1097
2062
|
|
|
2063
|
+
// src/commands/webhooks.ts
|
|
2064
|
+
async function listWebhooks(options) {
|
|
2065
|
+
const config = await getAuthConfig(process.env);
|
|
2066
|
+
const response = await new ApiClient(config).listWebhooks();
|
|
2067
|
+
if (options.json) {
|
|
2068
|
+
writeJson(response);
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (options.quiet) {
|
|
2072
|
+
for (const webhook of response.data ?? []) {
|
|
2073
|
+
const value = getQuietWebhookValue(webhook);
|
|
2074
|
+
if (value) {
|
|
2075
|
+
writeStdout(value);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
writeStdout(response.message);
|
|
2081
|
+
writeStdout(formatWebhooksTable(response.data ?? []));
|
|
2082
|
+
writeStdout(formatWebhooksSummary(response.data ?? []));
|
|
2083
|
+
}
|
|
2084
|
+
async function createWebhook(url, options) {
|
|
2085
|
+
const events = Array.from(new Set(options.event ?? []));
|
|
2086
|
+
if (events.length === 0) {
|
|
2087
|
+
throw new CliError(
|
|
2088
|
+
"At least one webhook event is required. Use `--event <event>` and run `peakurl webhook events` to see the supported values."
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
const config = await getAuthConfig(process.env);
|
|
2092
|
+
const response = await new ApiClient(config).createWebhook({
|
|
2093
|
+
url: normalizeWebhookUrl(url),
|
|
2094
|
+
events
|
|
2095
|
+
});
|
|
2096
|
+
if (options.json) {
|
|
2097
|
+
writeJson(response);
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (options.quiet) {
|
|
2101
|
+
writeStdout(getQuietWebhookValue(response.data));
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
writeStdout(response.message);
|
|
2105
|
+
writeStdout(formatWebhookDetails(response.data));
|
|
2106
|
+
if (response.data.secret) {
|
|
2107
|
+
writeStdout("Save the signing secret now. PeakURL only shows it once.");
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
async function deleteWebhook(id, options) {
|
|
2111
|
+
const config = await getAuthConfig(process.env);
|
|
2112
|
+
const response = await new ApiClient(config).deleteWebhook(id.trim());
|
|
2113
|
+
if (options.json) {
|
|
2114
|
+
writeJson(response);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
if (options.quiet) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
writeStdout(response.message);
|
|
2121
|
+
}
|
|
2122
|
+
async function listWebhookEvents(options) {
|
|
2123
|
+
const response = {
|
|
2124
|
+
success: true,
|
|
2125
|
+
message: "Webhook events loaded.",
|
|
2126
|
+
data: WEBHOOK_EVENTS,
|
|
2127
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2128
|
+
};
|
|
2129
|
+
if (options.json) {
|
|
2130
|
+
writeJson(response);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
if (options.quiet) {
|
|
2134
|
+
for (const event of WEBHOOK_EVENTS) {
|
|
2135
|
+
writeStdout(event.id);
|
|
2136
|
+
}
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
writeStdout(response.message);
|
|
2140
|
+
writeStdout(formatWebhookEventsTable());
|
|
2141
|
+
}
|
|
2142
|
+
|
|
1098
2143
|
// src/commands/whoami.ts
|
|
1099
|
-
async function
|
|
2144
|
+
async function whoami(options) {
|
|
1100
2145
|
const config = await getAuthConfig(process.env);
|
|
1101
2146
|
const response = await new ApiClient(config).whoami();
|
|
1102
2147
|
if (options.json) {
|
|
@@ -1123,27 +2168,59 @@ function parseNumber(label) {
|
|
|
1123
2168
|
return parsed;
|
|
1124
2169
|
};
|
|
1125
2170
|
}
|
|
2171
|
+
function addExamples(command, lines) {
|
|
2172
|
+
return command.addHelpText(
|
|
2173
|
+
"after",
|
|
2174
|
+
`
|
|
2175
|
+
Examples:
|
|
2176
|
+
${lines.map((line) => ` ${line}`).join("\n")}
|
|
2177
|
+
|
|
2178
|
+
Documentation:
|
|
2179
|
+
https://peakurl.org/docs/cli`
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
1126
2182
|
async function getCliVersion() {
|
|
1127
2183
|
const packageJson = new URL("../package.json", import.meta.url);
|
|
1128
|
-
const content = await
|
|
2184
|
+
const content = await readFile3(packageJson, "utf8");
|
|
1129
2185
|
const parsed = JSON.parse(content);
|
|
1130
2186
|
return parsed.version || "0.0.0";
|
|
1131
2187
|
}
|
|
2188
|
+
function getRetryCommandName(argv) {
|
|
2189
|
+
const first = argv[2]?.trim();
|
|
2190
|
+
if (!first || first.startsWith("-")) {
|
|
2191
|
+
return void 0;
|
|
2192
|
+
}
|
|
2193
|
+
if (first === "webhook" || first === "webhooks") {
|
|
2194
|
+
const second = argv[3]?.trim();
|
|
2195
|
+
if (second && !second.startsWith("-")) {
|
|
2196
|
+
return `${first} ${second}`;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return first;
|
|
2200
|
+
}
|
|
1132
2201
|
async function main() {
|
|
1133
2202
|
const program = new Command();
|
|
1134
2203
|
const version = await getCliVersion();
|
|
1135
|
-
program.name("peakurl").description("PeakURL
|
|
2204
|
+
program.name("peakurl").description("Manage your PeakURL site from the terminal.").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").version(version, "-V, --version", "Show CLI version").showHelpAfterError().showSuggestionAfterError().addHelpText(
|
|
1136
2205
|
"after",
|
|
1137
2206
|
`
|
|
1138
|
-
|
|
1139
|
-
peakurl login --base-url https://
|
|
1140
|
-
peakurl whoami
|
|
1141
|
-
peakurl
|
|
1142
|
-
|
|
2207
|
+
Get Started:
|
|
2208
|
+
peakurl login --base-url https://example.com/api/v1 --api-key YOUR_API_KEY
|
|
2209
|
+
peakurl whoami
|
|
2210
|
+
peakurl create https://example.com/docs --alias docs
|
|
2211
|
+
|
|
2212
|
+
Common Commands:
|
|
2213
|
+
peakurl status
|
|
1143
2214
|
peakurl list --limit 10
|
|
2215
|
+
peakurl import ./links.csv
|
|
2216
|
+
peakurl export --format csv
|
|
2217
|
+
peakurl webhook list
|
|
1144
2218
|
peakurl update --check
|
|
1145
|
-
|
|
1146
|
-
|
|
2219
|
+
|
|
2220
|
+
Documentation:
|
|
2221
|
+
https://peakurl.org/docs/cli
|
|
2222
|
+
|
|
2223
|
+
Run 'peakurl <command> --help' for command-specific flags and examples.`
|
|
1147
2224
|
).exitOverride();
|
|
1148
2225
|
program.hook("preAction", async (_command, actionCommand) => {
|
|
1149
2226
|
const options = actionCommand.optsWithGlobals();
|
|
@@ -1154,27 +2231,120 @@ Examples:
|
|
|
1154
2231
|
env: process.env
|
|
1155
2232
|
});
|
|
1156
2233
|
});
|
|
1157
|
-
|
|
1158
|
-
"Save
|
|
1159
|
-
|
|
1160
|
-
"
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
)
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
"--
|
|
1176
|
-
|
|
1177
|
-
|
|
2234
|
+
addExamples(
|
|
2235
|
+
program.command("login").summary("Save API credentials").description(
|
|
2236
|
+
"Save PeakURL credentials after verifying them with GET /users/me."
|
|
2237
|
+
).helpOption("-h, --help", "Show help").option(
|
|
2238
|
+
"--base-url <url>",
|
|
2239
|
+
"PeakURL API base URL, for example https://example.com/api/v1"
|
|
2240
|
+
).option("--api-key <token>", "PeakURL API key to store").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(login),
|
|
2241
|
+
[
|
|
2242
|
+
"peakurl login --base-url https://example.com/api/v1 --api-key YOUR_API_KEY",
|
|
2243
|
+
"peakurl login --json"
|
|
2244
|
+
]
|
|
2245
|
+
);
|
|
2246
|
+
addExamples(
|
|
2247
|
+
program.command("whoami").summary("Show the current account").description("Show the current authenticated PeakURL user.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print a minimal identity value").action(whoami),
|
|
2248
|
+
["peakurl whoami", "peakurl whoami --json"]
|
|
2249
|
+
);
|
|
2250
|
+
addExamples(
|
|
2251
|
+
program.command("logout").summary("Remove saved credentials").description("Remove saved PeakURL credentials from this device.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(logout),
|
|
2252
|
+
["peakurl logout", "peakurl logout --json"]
|
|
2253
|
+
);
|
|
2254
|
+
addExamples(
|
|
2255
|
+
program.command("status").summary("Show site system status").description("Show the current PeakURL system status snapshot.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print only the overall health value").action(status),
|
|
2256
|
+
["peakurl status", "peakurl status --json", "peakurl status --quiet"]
|
|
2257
|
+
);
|
|
2258
|
+
addExamples(
|
|
2259
|
+
program.command("create").summary("Create a short link").description("Create a PeakURL short link.").helpOption("-h, --help", "Show help").argument("<url>", "Destination URL to shorten").option("--alias <alias>", "Custom alias for the short link").option("--title <title>", "Title to store with the short link").option("--password <password>", "Password-protect the short link").option(
|
|
2260
|
+
"--status <status>",
|
|
2261
|
+
"Link status, for example active or paused"
|
|
2262
|
+
).option(
|
|
2263
|
+
"--expires-at <iso>",
|
|
2264
|
+
"Expiration timestamp in ISO-8601 format"
|
|
2265
|
+
).option("--utm-source <value>", "UTM source").option("--utm-medium <value>", "UTM medium").option("--utm-campaign <value>", "UTM campaign").option("--utm-term <value>", "UTM term").option("--utm-content <value>", "UTM content").option("--json", "Print machine-readable output").option("--quiet", "Print only the created short URL").action(createLink),
|
|
2266
|
+
[
|
|
2267
|
+
"peakurl create https://example.com/docs --alias docs",
|
|
2268
|
+
'peakurl create https://example.com/launch --title "Launch Page"',
|
|
2269
|
+
"peakurl create https://example.com --json"
|
|
2270
|
+
]
|
|
2271
|
+
);
|
|
2272
|
+
addExamples(
|
|
2273
|
+
program.command("import").summary("Import links from a file").description(
|
|
2274
|
+
"Import multiple short links from a local CSV, JSON, or XML file."
|
|
2275
|
+
).helpOption("-h, --help", "Show help").argument("<file>", "Path to the import file").option("--format <format>", "File format: csv, json, or xml").option("--json", "Print machine-readable output").option("--quiet", "Print only the created short URLs").action(importLinks),
|
|
2276
|
+
["peakurl import ./links.csv", "peakurl import ./links.xml --json"]
|
|
2277
|
+
);
|
|
2278
|
+
addExamples(
|
|
2279
|
+
program.command("export").summary("Export links to a file").description(
|
|
2280
|
+
"Export accessible links as a local CSV, JSON, or XML file."
|
|
2281
|
+
).helpOption("-h, --help", "Show help").option("--format <format>", "File format: csv, json, or xml").option("--output <path>", "Write the export to a specific file").option("--stdout", "Write the raw export content to stdout").option("--search <query>", "Search term").option("--sort-by <field>", "Sort field").option(
|
|
2282
|
+
"--sort-order <order>",
|
|
2283
|
+
"Sort order, for example asc or desc"
|
|
2284
|
+
).option("--quiet", "Print only the saved export path").action(exportLinks),
|
|
2285
|
+
[
|
|
2286
|
+
"peakurl export --format csv",
|
|
2287
|
+
"peakurl export --format json --stdout",
|
|
2288
|
+
"peakurl export --format xml --output ./backups/links.xml"
|
|
2289
|
+
]
|
|
2290
|
+
);
|
|
2291
|
+
addExamples(
|
|
2292
|
+
program.command("list").summary("List short links").description("List PeakURL short links.").helpOption("-h, --help", "Show help").option("--page <number>", "Page number", parseNumber("page")).option("--limit <number>", "Page size", parseNumber("limit")).option("--search <query>", "Search term").option("--sort-by <field>", "Sort field").option(
|
|
2293
|
+
"--sort-order <order>",
|
|
2294
|
+
"Sort order, for example asc or desc"
|
|
2295
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print a minimal per-link value").action(listLinks),
|
|
2296
|
+
[
|
|
2297
|
+
"peakurl list",
|
|
2298
|
+
"peakurl list --limit 25 --page 1",
|
|
2299
|
+
"peakurl list --search launch --json"
|
|
2300
|
+
]
|
|
2301
|
+
);
|
|
2302
|
+
addExamples(
|
|
2303
|
+
program.command("get").summary("Show one short link").description("Fetch a single PeakURL short link by id or alias.").helpOption("-h, --help", "Show help").argument("<id-or-alias>", "Link identifier or alias").option("--json", "Print machine-readable output").option("--quiet", "Print only the short URL").action(getLink),
|
|
2304
|
+
["peakurl get docs", "peakurl get url_123 --json"]
|
|
2305
|
+
);
|
|
2306
|
+
addExamples(
|
|
2307
|
+
program.command("delete").summary("Delete one short link").description("Delete a PeakURL short link by id or alias.").helpOption("-h, --help", "Show help").argument("<id-or-alias>", "Link identifier or alias").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(deleteLink),
|
|
2308
|
+
["peakurl delete docs", "peakurl delete url_123 --quiet"]
|
|
2309
|
+
);
|
|
2310
|
+
addExamples(
|
|
2311
|
+
program.command("update").summary("Check for CLI updates").description(
|
|
2312
|
+
"Check for a newer CLI version and print the npm command to install it."
|
|
2313
|
+
).helpOption("-h, --help", "Show help").option(
|
|
2314
|
+
"--check",
|
|
2315
|
+
"Alias for checking update status without changing anything"
|
|
2316
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print minimal output").action((options) => checkUpdate(options, version)),
|
|
2317
|
+
["peakurl update", "peakurl update --check", "peakurl update --json"]
|
|
2318
|
+
);
|
|
2319
|
+
const webhook = program.command("webhook").alias("webhooks").summary("Manage webhooks").helpOption("-h, --help", "Show help").description("Manage outbound webhook integrations.");
|
|
2320
|
+
addExamples(webhook, [
|
|
2321
|
+
"peakurl webhook list",
|
|
2322
|
+
"peakurl webhook create https://example.com/api/webhooks/peakurl --event link.clicked",
|
|
2323
|
+
"peakurl webhook events"
|
|
2324
|
+
]);
|
|
2325
|
+
addExamples(
|
|
2326
|
+
webhook.command("list").summary("List webhooks").description("List outbound webhooks.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print minimal webhook identifiers").action(listWebhooks),
|
|
2327
|
+
["peakurl webhook list", "peakurl webhook list --json"]
|
|
2328
|
+
);
|
|
2329
|
+
addExamples(
|
|
2330
|
+
webhook.command("create").summary("Create a webhook").description("Create an outbound webhook.").helpOption("-h, --help", "Show help").argument("<url>", "Webhook endpoint URL").option(
|
|
2331
|
+
"--event <event>",
|
|
2332
|
+
"Webhook event id, for example link.clicked",
|
|
2333
|
+
parseWebhookEvents
|
|
2334
|
+
).option("--json", "Print machine-readable output").option("--quiet", "Print only the created webhook ID").action(createWebhook),
|
|
2335
|
+
[
|
|
2336
|
+
"peakurl webhook create https://example.com/api/webhooks/peakurl --event link.clicked",
|
|
2337
|
+
"peakurl webhook create https://example.com/api/webhooks/peakurl --event link.clicked --event link.created"
|
|
2338
|
+
]
|
|
2339
|
+
);
|
|
2340
|
+
addExamples(
|
|
2341
|
+
webhook.command("delete").summary("Delete a webhook").description("Delete an outbound webhook by id.").helpOption("-h, --help", "Show help").argument("<id>", "Webhook identifier").option("--json", "Print machine-readable output").option("--quiet", "Suppress success output").action(deleteWebhook),
|
|
2342
|
+
["peakurl webhook delete webhook_123"]
|
|
2343
|
+
);
|
|
2344
|
+
addExamples(
|
|
2345
|
+
webhook.command("events").summary("List supported events").description("List the webhook events supported by the CLI.").helpOption("-h, --help", "Show help").option("--json", "Print machine-readable output").option("--quiet", "Print only event ids").action(listWebhookEvents),
|
|
2346
|
+
["peakurl webhook events", "peakurl webhook events --json"]
|
|
2347
|
+
);
|
|
1178
2348
|
try {
|
|
1179
2349
|
await program.parseAsync(process.argv);
|
|
1180
2350
|
} catch (error) {
|
|
@@ -1183,8 +2353,19 @@ Examples:
|
|
|
1183
2353
|
}
|
|
1184
2354
|
const cliError = ensureCliError(error);
|
|
1185
2355
|
if (cliError.kind === "auth_required") {
|
|
1186
|
-
|
|
1187
|
-
writeStderr(
|
|
2356
|
+
const commandName = getRetryCommandName(process.argv);
|
|
2357
|
+
writeStderr("Authentication required.");
|
|
2358
|
+
writeStderr("PeakURL could not find credentials for this command.");
|
|
2359
|
+
writeStderr(
|
|
2360
|
+
"Use one of the first two steps below, then run the last command."
|
|
2361
|
+
);
|
|
2362
|
+
writeStderr(
|
|
2363
|
+
formatTable(
|
|
2364
|
+
["Step", "Command", "Notes"],
|
|
2365
|
+
authRows(commandName),
|
|
2366
|
+
"stderr"
|
|
2367
|
+
)
|
|
2368
|
+
);
|
|
1188
2369
|
} else {
|
|
1189
2370
|
writeStderr(cliError.message);
|
|
1190
2371
|
}
|