peakurl 0.1.1 → 0.1.2
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/LICENSE +21 -0
- package/README.md +4 -1
- package/bin/peakurl.js +201 -129
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PeakURL Command-Line Interface and Abd Ur Rehman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
# PeakURL
|
|
1
|
+
# PeakURL - Command-Line Interface
|
|
2
2
|
|
|
3
3
|
The official command-line interface for PeakURL.
|
|
4
4
|
|
|
5
5
|
Use `peakurl` to create short links, inspect existing links, and manage your PeakURL account from the terminal.
|
|
6
6
|
|
|
7
|
+
Learn more in the full CLI docs: <https://peakurl.org/docs/cli>
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
Node.js 20 or later is required.
|
|
@@ -127,6 +129,7 @@ export PEAKURL_DISABLE_UPDATE_CHECK=1
|
|
|
127
129
|
## Links
|
|
128
130
|
|
|
129
131
|
- Website: <https://peakurl.org/>
|
|
132
|
+
- CLI docs: <https://peakurl.org/docs/cli>
|
|
130
133
|
- API docs: <https://peakurl.org/docs/api>
|
|
131
134
|
- npm package: <https://www.npmjs.com/package/peakurl>
|
|
132
135
|
- Issues: <https://github.com/PeakURL/PeakURL-CLI/issues>
|
package/bin/peakurl.js
CHANGED
|
@@ -13,7 +13,7 @@ var CliError = class extends Error {
|
|
|
13
13
|
this.exitCode = exitCode;
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
-
function
|
|
16
|
+
function wrapCliError(error) {
|
|
17
17
|
if (error instanceof CliError) {
|
|
18
18
|
return error;
|
|
19
19
|
}
|
|
@@ -24,6 +24,14 @@ function toCliError(error) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// src/lib/url.ts
|
|
27
|
+
function validateHttpUrl(parsed, label) {
|
|
28
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
29
|
+
throw new CliError(`${label} must use http or https.`);
|
|
30
|
+
}
|
|
31
|
+
if (parsed.username || parsed.password) {
|
|
32
|
+
throw new CliError(`${label} must not include embedded credentials.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
function normalizeBaseUrl(value) {
|
|
28
36
|
const input = value.trim();
|
|
29
37
|
if (!input) {
|
|
@@ -35,9 +43,7 @@ function normalizeBaseUrl(value) {
|
|
|
35
43
|
} catch {
|
|
36
44
|
throw new CliError(`Invalid base URL: ${value}`);
|
|
37
45
|
}
|
|
38
|
-
|
|
39
|
-
throw new CliError("PeakURL base URLs must use http or https.");
|
|
40
|
-
}
|
|
46
|
+
validateHttpUrl(parsed, "PeakURL base URL");
|
|
41
47
|
parsed.hash = "";
|
|
42
48
|
parsed.search = "";
|
|
43
49
|
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
@@ -62,8 +68,13 @@ function normalizeDestinationUrl(value) {
|
|
|
62
68
|
throw new CliError("A destination URL is required.");
|
|
63
69
|
}
|
|
64
70
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
const parsed = new URL(input);
|
|
72
|
+
validateHttpUrl(parsed, "Destination URL");
|
|
73
|
+
return parsed.toString();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof CliError) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
67
78
|
throw new CliError(`Invalid destination URL: ${value}`);
|
|
68
79
|
}
|
|
69
80
|
}
|
|
@@ -241,7 +252,7 @@ function defaultConfigPath() {
|
|
|
241
252
|
function defaultStatePath() {
|
|
242
253
|
return join(envPaths("peakurl", { suffix: "" }).config, STATE_FILENAME);
|
|
243
254
|
}
|
|
244
|
-
async function
|
|
255
|
+
async function prepareFileDirectory(filePath) {
|
|
245
256
|
const directory = dirname(filePath);
|
|
246
257
|
await mkdir(directory, { recursive: true, mode: 448 });
|
|
247
258
|
return directory;
|
|
@@ -301,7 +312,7 @@ var ConfigStore = class {
|
|
|
301
312
|
* @param config Normalized credential set to write.
|
|
302
313
|
*/
|
|
303
314
|
async save(config) {
|
|
304
|
-
const directory = await
|
|
315
|
+
const directory = await prepareFileDirectory(this.filePath);
|
|
305
316
|
await writeFile(this.filePath, `${JSON.stringify(config, null, 2)}
|
|
306
317
|
`, {
|
|
307
318
|
mode: 384
|
|
@@ -346,11 +357,16 @@ var StateStore = class {
|
|
|
346
357
|
* @param state State payload to save.
|
|
347
358
|
*/
|
|
348
359
|
async save(state) {
|
|
349
|
-
await
|
|
360
|
+
const directory = await prepareFileDirectory(this.filePath);
|
|
350
361
|
await writeFile(this.filePath, `${JSON.stringify(state, null, 2)}
|
|
351
362
|
`, {
|
|
352
363
|
mode: 384
|
|
353
364
|
});
|
|
365
|
+
try {
|
|
366
|
+
await chmod(directory, 448);
|
|
367
|
+
await chmod(this.filePath, 384);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
354
370
|
}
|
|
355
371
|
};
|
|
356
372
|
|
|
@@ -383,20 +399,126 @@ async function resolveStoredConfig(env, store = new ConfigStore()) {
|
|
|
383
399
|
};
|
|
384
400
|
}
|
|
385
401
|
|
|
402
|
+
// src/lib/output.ts
|
|
403
|
+
function writeStdout(message = "") {
|
|
404
|
+
process.stdout.write(`${message}
|
|
405
|
+
`);
|
|
406
|
+
}
|
|
407
|
+
function writeStderr(message = "") {
|
|
408
|
+
process.stderr.write(`${message}
|
|
409
|
+
`);
|
|
410
|
+
}
|
|
411
|
+
function writeNoticeBox(title, lines, target = "stderr") {
|
|
412
|
+
const contentLines = lines.length > 0 ? lines : [""];
|
|
413
|
+
const width = Math.max(
|
|
414
|
+
title.length,
|
|
415
|
+
...contentLines.map((line) => line.length)
|
|
416
|
+
);
|
|
417
|
+
const stream = target === "stdout" ? process.stdout : process.stderr;
|
|
418
|
+
const useTuiBox = stream.isTTY;
|
|
419
|
+
const border = useTuiBox ? {
|
|
420
|
+
topLeft: "\u250C",
|
|
421
|
+
topRight: "\u2510",
|
|
422
|
+
bottomLeft: "\u2514",
|
|
423
|
+
bottomRight: "\u2518",
|
|
424
|
+
horizontal: "\u2500",
|
|
425
|
+
vertical: "\u2502",
|
|
426
|
+
separatorLeft: "\u251C",
|
|
427
|
+
separatorRight: "\u2524"
|
|
428
|
+
} : {
|
|
429
|
+
topLeft: "+",
|
|
430
|
+
topRight: "+",
|
|
431
|
+
bottomLeft: "+",
|
|
432
|
+
bottomRight: "+",
|
|
433
|
+
horizontal: "-",
|
|
434
|
+
vertical: "|",
|
|
435
|
+
separatorLeft: "+",
|
|
436
|
+
separatorRight: "+"
|
|
437
|
+
};
|
|
438
|
+
const topBorder = `${border.topLeft}${border.horizontal.repeat(width + 2)}${border.topRight}`;
|
|
439
|
+
const separator = `${border.separatorLeft}${border.horizontal.repeat(width + 2)}${border.separatorRight}`;
|
|
440
|
+
const bottomBorder = `${border.bottomLeft}${border.horizontal.repeat(width + 2)}${border.bottomRight}`;
|
|
441
|
+
const writeLine = target === "stdout" ? writeStdout : writeStderr;
|
|
442
|
+
writeLine(topBorder);
|
|
443
|
+
writeLine(`${border.vertical} ${title.padEnd(width)} ${border.vertical}`);
|
|
444
|
+
writeLine(separator);
|
|
445
|
+
for (const line of contentLines) {
|
|
446
|
+
writeLine(
|
|
447
|
+
`${border.vertical} ${line.padEnd(width)} ${border.vertical}`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
writeLine(bottomBorder);
|
|
451
|
+
}
|
|
452
|
+
function formatTable(headers, rows, target = "stdout") {
|
|
453
|
+
const stream = target === "stdout" ? process.stdout : process.stderr;
|
|
454
|
+
const useTuiBox = stream.isTTY;
|
|
455
|
+
const border = useTuiBox ? {
|
|
456
|
+
topLeft: "\u250C",
|
|
457
|
+
topRight: "\u2510",
|
|
458
|
+
bottomLeft: "\u2514",
|
|
459
|
+
bottomRight: "\u2518",
|
|
460
|
+
horizontal: "\u2500",
|
|
461
|
+
vertical: "\u2502",
|
|
462
|
+
separatorLeft: "\u251C",
|
|
463
|
+
separatorRight: "\u2524",
|
|
464
|
+
topJunction: "\u252C",
|
|
465
|
+
middleJunction: "\u253C",
|
|
466
|
+
bottomJunction: "\u2534"
|
|
467
|
+
} : {
|
|
468
|
+
topLeft: "+",
|
|
469
|
+
topRight: "+",
|
|
470
|
+
bottomLeft: "+",
|
|
471
|
+
bottomRight: "+",
|
|
472
|
+
horizontal: "-",
|
|
473
|
+
vertical: "|",
|
|
474
|
+
separatorLeft: "+",
|
|
475
|
+
separatorRight: "+",
|
|
476
|
+
topJunction: "+",
|
|
477
|
+
middleJunction: "+",
|
|
478
|
+
bottomJunction: "+"
|
|
479
|
+
};
|
|
480
|
+
const widths = headers.map(
|
|
481
|
+
(header, index) => Math.max(
|
|
482
|
+
header.length,
|
|
483
|
+
...rows.map((row) => (row[index] ?? "").length)
|
|
484
|
+
)
|
|
485
|
+
);
|
|
486
|
+
const formatTableBorder = (left, join2, right) => `${left}${widths.map((width) => border.horizontal.repeat(width + 2)).join(join2)}${right}`;
|
|
487
|
+
const formatTableRow = (cells) => `${border.vertical}${cells.map((cell, index) => ` ${(cell ?? "").padEnd(widths[index])} `).join(border.vertical)}${border.vertical}`;
|
|
488
|
+
return [
|
|
489
|
+
formatTableBorder(border.topLeft, border.topJunction, border.topRight),
|
|
490
|
+
formatTableRow(headers),
|
|
491
|
+
formatTableBorder(
|
|
492
|
+
border.separatorLeft,
|
|
493
|
+
border.middleJunction,
|
|
494
|
+
border.separatorRight
|
|
495
|
+
),
|
|
496
|
+
...rows.map(formatTableRow),
|
|
497
|
+
formatTableBorder(
|
|
498
|
+
border.bottomLeft,
|
|
499
|
+
border.bottomJunction,
|
|
500
|
+
border.bottomRight
|
|
501
|
+
)
|
|
502
|
+
].join("\n");
|
|
503
|
+
}
|
|
504
|
+
function writeJson(value) {
|
|
505
|
+
writeStdout(JSON.stringify(value, null, 2));
|
|
506
|
+
}
|
|
507
|
+
|
|
386
508
|
// src/lib/links.ts
|
|
387
509
|
var LIST_KEYS = ["urls", "items", "results"];
|
|
388
|
-
function
|
|
510
|
+
function objectValue(value) {
|
|
389
511
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
390
512
|
}
|
|
391
|
-
function
|
|
513
|
+
function stringValue(value) {
|
|
392
514
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
393
515
|
}
|
|
394
|
-
function
|
|
516
|
+
function numberValue(value) {
|
|
395
517
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
396
518
|
}
|
|
397
|
-
function
|
|
519
|
+
function pickString(link, keys) {
|
|
398
520
|
for (const key of keys) {
|
|
399
|
-
const value =
|
|
521
|
+
const value = stringValue(link[key]);
|
|
400
522
|
if (value) {
|
|
401
523
|
return value;
|
|
402
524
|
}
|
|
@@ -407,31 +529,31 @@ function truncate(value, maxLength) {
|
|
|
407
529
|
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}\u2026` : value;
|
|
408
530
|
}
|
|
409
531
|
function extractListMeta(data) {
|
|
410
|
-
const record =
|
|
532
|
+
const record = objectValue(data);
|
|
411
533
|
if (!record) {
|
|
412
534
|
return null;
|
|
413
535
|
}
|
|
414
|
-
const meta =
|
|
536
|
+
const meta = objectValue(record.meta);
|
|
415
537
|
if (meta) {
|
|
416
538
|
return {
|
|
417
|
-
page:
|
|
418
|
-
limit:
|
|
419
|
-
totalItems:
|
|
420
|
-
totalPages:
|
|
539
|
+
page: numberValue(meta.page),
|
|
540
|
+
limit: numberValue(meta.limit),
|
|
541
|
+
totalItems: numberValue(meta.totalItems),
|
|
542
|
+
totalPages: numberValue(meta.totalPages)
|
|
421
543
|
};
|
|
422
544
|
}
|
|
423
545
|
return {
|
|
424
|
-
page:
|
|
425
|
-
limit:
|
|
426
|
-
totalItems:
|
|
427
|
-
totalPages:
|
|
546
|
+
page: numberValue(record.page),
|
|
547
|
+
limit: numberValue(record.limit),
|
|
548
|
+
totalItems: numberValue(record.total),
|
|
549
|
+
totalPages: numberValue(record.totalPages)
|
|
428
550
|
};
|
|
429
551
|
}
|
|
430
552
|
function extractLinks(data) {
|
|
431
553
|
if (Array.isArray(data)) {
|
|
432
554
|
return data;
|
|
433
555
|
}
|
|
434
|
-
const record =
|
|
556
|
+
const record = objectValue(data);
|
|
435
557
|
if (record) {
|
|
436
558
|
for (const key of LIST_KEYS) {
|
|
437
559
|
const value = record[key];
|
|
@@ -443,16 +565,16 @@ function extractLinks(data) {
|
|
|
443
565
|
return [];
|
|
444
566
|
}
|
|
445
567
|
function getLinkId(link) {
|
|
446
|
-
return
|
|
568
|
+
return pickString(link, ["id", "_id", "urlId"]) || (link.id !== void 0 ? String(link.id) : void 0);
|
|
447
569
|
}
|
|
448
570
|
function getLinkAlias(link) {
|
|
449
|
-
return
|
|
571
|
+
return pickString(link, ["alias", "shortCode", "slug", "code"]);
|
|
450
572
|
}
|
|
451
573
|
function getLinkShortUrl(link) {
|
|
452
|
-
return
|
|
574
|
+
return pickString(link, ["shortUrl", "shortLink", "shortURL", "url"]);
|
|
453
575
|
}
|
|
454
576
|
function getLinkDestination(link) {
|
|
455
|
-
return
|
|
577
|
+
return pickString(link, [
|
|
456
578
|
"destinationUrl",
|
|
457
579
|
"originalUrl",
|
|
458
580
|
"targetUrl",
|
|
@@ -468,14 +590,14 @@ function formatLinkDetails(link) {
|
|
|
468
590
|
["Alias", getLinkAlias(link)],
|
|
469
591
|
["Short URL", getLinkShortUrl(link)],
|
|
470
592
|
["Destination", getLinkDestination(link)],
|
|
471
|
-
["Title",
|
|
472
|
-
["Status",
|
|
593
|
+
["Title", stringValue(link.title)],
|
|
594
|
+
["Status", stringValue(link.status)],
|
|
473
595
|
[
|
|
474
596
|
"Clicks",
|
|
475
|
-
|
|
597
|
+
numberValue(link.clicks) === void 0 ? void 0 : String(link.clicks)
|
|
476
598
|
],
|
|
477
|
-
["Created",
|
|
478
|
-
["Updated",
|
|
599
|
+
["Created", stringValue(link.createdAt)],
|
|
600
|
+
["Updated", stringValue(link.updatedAt)]
|
|
479
601
|
].filter((entry) => Boolean(entry[1]));
|
|
480
602
|
return lines.map(([label, value]) => `${label}: ${value}`).join("\n");
|
|
481
603
|
}
|
|
@@ -483,23 +605,15 @@ function formatLinksTable(links) {
|
|
|
483
605
|
if (links.length === 0) {
|
|
484
606
|
return "No links found.";
|
|
485
607
|
}
|
|
486
|
-
const headers = ["ID", "
|
|
608
|
+
const headers = ["ID", "Alias", "Short URL", "Destination", "Status"];
|
|
487
609
|
const rows = links.map((link) => [
|
|
488
610
|
truncate(getLinkId(link) || "-", 18),
|
|
489
|
-
truncate(getLinkAlias(link) || "-",
|
|
611
|
+
truncate(getLinkAlias(link) || "-", 12),
|
|
490
612
|
truncate(getLinkShortUrl(link) || "-", 36),
|
|
491
613
|
truncate(getLinkDestination(link) || "-", 52),
|
|
492
|
-
truncate(
|
|
614
|
+
truncate(stringValue(link.status) || "-", 12)
|
|
493
615
|
]);
|
|
494
|
-
|
|
495
|
-
(header, index) => Math.max(header.length, ...rows.map((row) => row[index].length))
|
|
496
|
-
);
|
|
497
|
-
const renderRow = (row) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
|
|
498
|
-
return [
|
|
499
|
-
renderRow(headers),
|
|
500
|
-
renderRow(widths.map((width) => "-".repeat(width))),
|
|
501
|
-
...rows.map(renderRow)
|
|
502
|
-
].join("\n");
|
|
616
|
+
return formatTable(headers, rows);
|
|
503
617
|
}
|
|
504
618
|
function formatListSummary(data, count) {
|
|
505
619
|
const meta = extractListMeta(data);
|
|
@@ -515,60 +629,6 @@ function formatListSummary(data, count) {
|
|
|
515
629
|
return `${count} link${count === 1 ? "" : "s"} returned.`;
|
|
516
630
|
}
|
|
517
631
|
|
|
518
|
-
// src/lib/output.ts
|
|
519
|
-
function writeStdout(message = "") {
|
|
520
|
-
process.stdout.write(`${message}
|
|
521
|
-
`);
|
|
522
|
-
}
|
|
523
|
-
function writeStderr(message = "") {
|
|
524
|
-
process.stderr.write(`${message}
|
|
525
|
-
`);
|
|
526
|
-
}
|
|
527
|
-
function writeNoticeBox(title, lines, target = "stderr") {
|
|
528
|
-
const contentLines = lines.length > 0 ? lines : [""];
|
|
529
|
-
const width = Math.max(
|
|
530
|
-
title.length,
|
|
531
|
-
...contentLines.map((line) => line.length)
|
|
532
|
-
);
|
|
533
|
-
const stream = target === "stdout" ? process.stdout : process.stderr;
|
|
534
|
-
const useTuiBox = stream.isTTY;
|
|
535
|
-
const border = useTuiBox ? {
|
|
536
|
-
topLeft: "\u250C",
|
|
537
|
-
topRight: "\u2510",
|
|
538
|
-
bottomLeft: "\u2514",
|
|
539
|
-
bottomRight: "\u2518",
|
|
540
|
-
horizontal: "\u2500",
|
|
541
|
-
vertical: "\u2502",
|
|
542
|
-
separatorLeft: "\u251C",
|
|
543
|
-
separatorRight: "\u2524"
|
|
544
|
-
} : {
|
|
545
|
-
topLeft: "+",
|
|
546
|
-
topRight: "+",
|
|
547
|
-
bottomLeft: "+",
|
|
548
|
-
bottomRight: "+",
|
|
549
|
-
horizontal: "-",
|
|
550
|
-
vertical: "|",
|
|
551
|
-
separatorLeft: "+",
|
|
552
|
-
separatorRight: "+"
|
|
553
|
-
};
|
|
554
|
-
const topBorder = `${border.topLeft}${border.horizontal.repeat(width + 2)}${border.topRight}`;
|
|
555
|
-
const separator = `${border.separatorLeft}${border.horizontal.repeat(width + 2)}${border.separatorRight}`;
|
|
556
|
-
const bottomBorder = `${border.bottomLeft}${border.horizontal.repeat(width + 2)}${border.bottomRight}`;
|
|
557
|
-
const writeLine = target === "stdout" ? writeStdout : writeStderr;
|
|
558
|
-
writeLine(topBorder);
|
|
559
|
-
writeLine(`${border.vertical} ${title.padEnd(width)} ${border.vertical}`);
|
|
560
|
-
writeLine(separator);
|
|
561
|
-
for (const line of contentLines) {
|
|
562
|
-
writeLine(
|
|
563
|
-
`${border.vertical} ${line.padEnd(width)} ${border.vertical}`
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
writeLine(bottomBorder);
|
|
567
|
-
}
|
|
568
|
-
function writeJson(value) {
|
|
569
|
-
writeStdout(JSON.stringify(value, null, 2));
|
|
570
|
-
}
|
|
571
|
-
|
|
572
632
|
// src/commands/create.ts
|
|
573
633
|
function normalizeExpiresAt(value) {
|
|
574
634
|
if (!value) {
|
|
@@ -674,22 +734,22 @@ async function listCommand(options) {
|
|
|
674
734
|
}
|
|
675
735
|
|
|
676
736
|
// src/lib/users.ts
|
|
677
|
-
function
|
|
737
|
+
function stringValue2(value) {
|
|
678
738
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
679
739
|
}
|
|
680
740
|
function getUserLabel(user) {
|
|
681
|
-
const fullName = [
|
|
682
|
-
return fullName ||
|
|
741
|
+
const fullName = [stringValue2(user.firstName), stringValue2(user.lastName)].filter(Boolean).join(" ");
|
|
742
|
+
return fullName || stringValue2(user.username) || stringValue2(user.email) || String(user.id ?? "unknown");
|
|
683
743
|
}
|
|
684
744
|
function getQuietUserValue(user) {
|
|
685
|
-
return
|
|
745
|
+
return stringValue2(user.username) || stringValue2(user.email) || String(user.id ?? "");
|
|
686
746
|
}
|
|
687
747
|
function formatUserDetails(user) {
|
|
688
748
|
const lines = [
|
|
689
749
|
["Name", getUserLabel(user)],
|
|
690
|
-
["Username",
|
|
691
|
-
["Email",
|
|
692
|
-
["Role",
|
|
750
|
+
["Username", stringValue2(user.username)],
|
|
751
|
+
["Email", stringValue2(user.email)],
|
|
752
|
+
["Role", stringValue2(user.role)],
|
|
693
753
|
["ID", user.id === void 0 ? void 0 : String(user.id)]
|
|
694
754
|
].filter((entry) => Boolean(entry[1]));
|
|
695
755
|
return lines.map(([label, value]) => `${label}: ${value}`).join("\n");
|
|
@@ -701,7 +761,7 @@ async function loginCommand(options) {
|
|
|
701
761
|
const client = new PeakUrlApiClient(credentials);
|
|
702
762
|
const response = await client.whoami();
|
|
703
763
|
await new ConfigStore().save(credentials);
|
|
704
|
-
const
|
|
764
|
+
const responseBody = {
|
|
705
765
|
success: true,
|
|
706
766
|
message: `Saved credentials for ${credentials.baseUrl}.`,
|
|
707
767
|
data: {
|
|
@@ -711,7 +771,7 @@ async function loginCommand(options) {
|
|
|
711
771
|
timestamp: response.timestamp
|
|
712
772
|
};
|
|
713
773
|
if (options.json) {
|
|
714
|
-
writeJson(
|
|
774
|
+
writeJson(responseBody);
|
|
715
775
|
return;
|
|
716
776
|
}
|
|
717
777
|
if (options.quiet) {
|
|
@@ -727,15 +787,27 @@ var PACKAGE_NAME = "peakurl";
|
|
|
727
787
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
728
788
|
var CACHE_TTL_MS = 1e3 * 60 * 60 * 12;
|
|
729
789
|
var NOTICE_TTL_MS = 1e3 * 60 * 60 * 24;
|
|
730
|
-
function
|
|
790
|
+
function parseTime(value) {
|
|
731
791
|
if (!value) {
|
|
732
792
|
return null;
|
|
733
793
|
}
|
|
734
794
|
const parsed = Date.parse(value);
|
|
735
795
|
return Number.isNaN(parsed) ? null : parsed;
|
|
736
796
|
}
|
|
737
|
-
function
|
|
738
|
-
|
|
797
|
+
function getRegistryBaseUrl(env) {
|
|
798
|
+
const candidate = env.PEAKURL_NPM_REGISTRY_URL?.trim();
|
|
799
|
+
if (!candidate) {
|
|
800
|
+
return DEFAULT_REGISTRY_URL;
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const parsed = new URL(candidate);
|
|
804
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username || parsed.password) {
|
|
805
|
+
return DEFAULT_REGISTRY_URL;
|
|
806
|
+
}
|
|
807
|
+
return candidate;
|
|
808
|
+
} catch {
|
|
809
|
+
return DEFAULT_REGISTRY_URL;
|
|
810
|
+
}
|
|
739
811
|
}
|
|
740
812
|
function normalizeRegistryUrl(registryUrl) {
|
|
741
813
|
return registryUrl.replace(/\/+$/, "");
|
|
@@ -811,11 +883,11 @@ function compareSemver(left, right) {
|
|
|
811
883
|
}
|
|
812
884
|
return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease);
|
|
813
885
|
}
|
|
814
|
-
function
|
|
886
|
+
function getUpdateInstallCommand() {
|
|
815
887
|
return `npm install -g ${PACKAGE_NAME}@latest`;
|
|
816
888
|
}
|
|
817
|
-
async function
|
|
818
|
-
const registryUrl = normalizeRegistryUrl(
|
|
889
|
+
async function fetchLatestPackageVersion(env) {
|
|
890
|
+
const registryUrl = normalizeRegistryUrl(getRegistryBaseUrl(env));
|
|
819
891
|
const url = `${registryUrl}/${PACKAGE_NAME}/latest`;
|
|
820
892
|
const controller = new AbortController();
|
|
821
893
|
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
@@ -837,7 +909,7 @@ async function fetchLatestVersion(env) {
|
|
|
837
909
|
clearTimeout(timeout);
|
|
838
910
|
}
|
|
839
911
|
}
|
|
840
|
-
async function
|
|
912
|
+
async function getCachedUpdateState(store) {
|
|
841
913
|
const state = await store.load();
|
|
842
914
|
return state.update ?? {};
|
|
843
915
|
}
|
|
@@ -848,15 +920,15 @@ async function saveUpdateState(store, update) {
|
|
|
848
920
|
update
|
|
849
921
|
});
|
|
850
922
|
}
|
|
851
|
-
async function
|
|
923
|
+
async function getLatestPackageVersion(env, options) {
|
|
852
924
|
const store = options?.store ?? new StateStore();
|
|
853
|
-
const updateState = await
|
|
854
|
-
const lastCheckedAt =
|
|
925
|
+
const updateState = await getCachedUpdateState(store);
|
|
926
|
+
const lastCheckedAt = parseTime(updateState.lastCheckedAt);
|
|
855
927
|
const now = Date.now();
|
|
856
928
|
if (!options?.forceRefresh && updateState.latestVersion && lastCheckedAt !== null && now - lastCheckedAt < CACHE_TTL_MS) {
|
|
857
929
|
return updateState.latestVersion;
|
|
858
930
|
}
|
|
859
|
-
const latestVersion = await
|
|
931
|
+
const latestVersion = await fetchLatestPackageVersion(env);
|
|
860
932
|
if (!latestVersion) {
|
|
861
933
|
return updateState.latestVersion ?? null;
|
|
862
934
|
}
|
|
@@ -868,7 +940,7 @@ async function resolveLatestVersion(env, options) {
|
|
|
868
940
|
return latestVersion;
|
|
869
941
|
}
|
|
870
942
|
async function getUpdateStatus(currentVersion, env, options) {
|
|
871
|
-
const resolvedLatestVersion = await
|
|
943
|
+
const resolvedLatestVersion = await getLatestPackageVersion(env, {
|
|
872
944
|
forceRefresh: options?.forceRefresh,
|
|
873
945
|
store: options?.store
|
|
874
946
|
});
|
|
@@ -882,7 +954,7 @@ async function getUpdateStatus(currentVersion, env, options) {
|
|
|
882
954
|
currentVersion,
|
|
883
955
|
latestVersion,
|
|
884
956
|
isOutdated: compareSemver(currentVersion, latestVersion) < 0,
|
|
885
|
-
installCommand:
|
|
957
|
+
installCommand: getUpdateInstallCommand()
|
|
886
958
|
};
|
|
887
959
|
}
|
|
888
960
|
function writeUpdateNotice(status) {
|
|
@@ -899,14 +971,14 @@ async function maybeShowUpdateNotice(options) {
|
|
|
899
971
|
return;
|
|
900
972
|
}
|
|
901
973
|
const store = new StateStore();
|
|
902
|
-
const updateState = await
|
|
974
|
+
const updateState = await getCachedUpdateState(store);
|
|
903
975
|
const status = await getUpdateStatus(options.currentVersion, options.env, {
|
|
904
976
|
store
|
|
905
977
|
});
|
|
906
978
|
if (!status.isOutdated) {
|
|
907
979
|
return;
|
|
908
980
|
}
|
|
909
|
-
const lastNotifiedAt =
|
|
981
|
+
const lastNotifiedAt = parseTime(updateState.lastNotifiedAt);
|
|
910
982
|
const alreadyNotifiedForVersion = updateState.lastNotifiedVersion === status.latestVersion;
|
|
911
983
|
if (alreadyNotifiedForVersion && lastNotifiedAt !== null && Date.now() - lastNotifiedAt < NOTICE_TTL_MS) {
|
|
912
984
|
return;
|
|
@@ -924,7 +996,7 @@ async function updateCommand(options, currentVersion) {
|
|
|
924
996
|
const status = await getUpdateStatus(currentVersion, process.env, {
|
|
925
997
|
forceRefresh: true
|
|
926
998
|
});
|
|
927
|
-
const
|
|
999
|
+
const responseBody = {
|
|
928
1000
|
success: true,
|
|
929
1001
|
message: status.isOutdated ? `A newer PeakURL CLI version is available (${status.latestVersion}).` : `PeakURL CLI ${status.currentVersion} is up to date.`,
|
|
930
1002
|
data: {
|
|
@@ -937,7 +1009,7 @@ async function updateCommand(options, currentVersion) {
|
|
|
937
1009
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
938
1010
|
};
|
|
939
1011
|
if (options.json) {
|
|
940
|
-
writeJson(
|
|
1012
|
+
writeJson(responseBody);
|
|
941
1013
|
return;
|
|
942
1014
|
}
|
|
943
1015
|
if (!status.isOutdated) {
|
|
@@ -988,7 +1060,7 @@ function parsePositiveInteger(label) {
|
|
|
988
1060
|
return parsed;
|
|
989
1061
|
};
|
|
990
1062
|
}
|
|
991
|
-
async function
|
|
1063
|
+
async function getCliVersion() {
|
|
992
1064
|
const packageJson = new URL("../package.json", import.meta.url);
|
|
993
1065
|
const content = await readFile2(packageJson, "utf8");
|
|
994
1066
|
const parsed = JSON.parse(content);
|
|
@@ -996,7 +1068,7 @@ async function readVersion() {
|
|
|
996
1068
|
}
|
|
997
1069
|
async function main() {
|
|
998
1070
|
const program = new Command();
|
|
999
|
-
const version = await
|
|
1071
|
+
const version = await getCliVersion();
|
|
1000
1072
|
program.name("peakurl").description("PeakURL command-line interface").version(version).showHelpAfterError().showSuggestionAfterError().addHelpText(
|
|
1001
1073
|
"after",
|
|
1002
1074
|
`
|
|
@@ -1044,7 +1116,7 @@ Examples:
|
|
|
1044
1116
|
if (error instanceof CommanderError) {
|
|
1045
1117
|
process.exit(error.exitCode);
|
|
1046
1118
|
}
|
|
1047
|
-
const cliError =
|
|
1119
|
+
const cliError = wrapCliError(error);
|
|
1048
1120
|
writeStderr(cliError.message);
|
|
1049
1121
|
process.exit(cliError.exitCode);
|
|
1050
1122
|
}
|