@hasna/shortlinks 0.2.5 → 0.2.7
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 +35 -0
- package/dist/cli/index.js +443 -236
- package/dist/index.js +21 -21
- package/dist/mcp/index.js +1 -1
- package/dist/serve/index.js +21 -21
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
33
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
34
|
var __require = import.meta.require;
|
|
35
35
|
|
|
36
|
-
// node_modules
|
|
36
|
+
// node_modules/commander/lib/error.js
|
|
37
37
|
var require_error = __commonJS((exports) => {
|
|
38
38
|
class CommanderError extends Error {
|
|
39
39
|
constructor(exitCode, code, message) {
|
|
@@ -57,7 +57,7 @@ var require_error = __commonJS((exports) => {
|
|
|
57
57
|
exports.InvalidArgumentError = InvalidArgumentError;
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
// node_modules
|
|
60
|
+
// node_modules/commander/lib/argument.js
|
|
61
61
|
var require_argument = __commonJS((exports) => {
|
|
62
62
|
var { InvalidArgumentError } = require_error();
|
|
63
63
|
|
|
@@ -136,7 +136,7 @@ var require_argument = __commonJS((exports) => {
|
|
|
136
136
|
exports.humanReadableArgName = humanReadableArgName;
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
// node_modules
|
|
139
|
+
// node_modules/commander/lib/help.js
|
|
140
140
|
var require_help = __commonJS((exports) => {
|
|
141
141
|
var { humanReadableArgName } = require_argument();
|
|
142
142
|
|
|
@@ -486,7 +486,7 @@ ${itemIndentStr}`);
|
|
|
486
486
|
exports.stripColor = stripColor;
|
|
487
487
|
});
|
|
488
488
|
|
|
489
|
-
// node_modules
|
|
489
|
+
// node_modules/commander/lib/option.js
|
|
490
490
|
var require_option = __commonJS((exports) => {
|
|
491
491
|
var { InvalidArgumentError } = require_error();
|
|
492
492
|
|
|
@@ -664,7 +664,7 @@ var require_option = __commonJS((exports) => {
|
|
|
664
664
|
exports.DualOptions = DualOptions;
|
|
665
665
|
});
|
|
666
666
|
|
|
667
|
-
// node_modules
|
|
667
|
+
// node_modules/commander/lib/suggestSimilar.js
|
|
668
668
|
var require_suggestSimilar = __commonJS((exports) => {
|
|
669
669
|
var maxDistance = 3;
|
|
670
670
|
function editDistance(a, b) {
|
|
@@ -737,7 +737,7 @@ var require_suggestSimilar = __commonJS((exports) => {
|
|
|
737
737
|
exports.suggestSimilar = suggestSimilar;
|
|
738
738
|
});
|
|
739
739
|
|
|
740
|
-
// node_modules
|
|
740
|
+
// node_modules/commander/lib/command.js
|
|
741
741
|
var require_command = __commonJS((exports) => {
|
|
742
742
|
var EventEmitter = __require("events").EventEmitter;
|
|
743
743
|
var childProcess = __require("child_process");
|
|
@@ -979,7 +979,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
979
979
|
this._exitCallback = (err) => {
|
|
980
980
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
981
981
|
throw err;
|
|
982
|
-
}
|
|
982
|
+
}
|
|
983
983
|
};
|
|
984
984
|
}
|
|
985
985
|
return this;
|
|
@@ -2047,7 +2047,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2047
2047
|
exports.useColor = useColor;
|
|
2048
2048
|
});
|
|
2049
2049
|
|
|
2050
|
-
// node_modules
|
|
2050
|
+
// node_modules/commander/index.js
|
|
2051
2051
|
var require_commander = __commonJS((exports) => {
|
|
2052
2052
|
var { Argument } = require_argument();
|
|
2053
2053
|
var { Command } = require_command();
|
|
@@ -2894,7 +2894,7 @@ async function upsertCloudflareDnsRecord(options) {
|
|
|
2894
2894
|
return { id: created.id, action: "created" };
|
|
2895
2895
|
}
|
|
2896
2896
|
|
|
2897
|
-
// node_modules/@hasna/events/dist/
|
|
2897
|
+
// node_modules/@hasna/events/dist/index.js
|
|
2898
2898
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
2899
2899
|
import { existsSync as existsSync3 } from "fs";
|
|
2900
2900
|
import { homedir as homedir2 } from "os";
|
|
@@ -3413,172 +3413,8 @@ function normalizeRetryPolicy(policy) {
|
|
|
3413
3413
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
3414
3414
|
};
|
|
3415
3415
|
}
|
|
3416
|
-
function parseJsonObject2(value, fallback) {
|
|
3417
|
-
if (!value)
|
|
3418
|
-
return fallback;
|
|
3419
|
-
const parsed = JSON.parse(value);
|
|
3420
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3421
|
-
throw new Error("Expected a JSON object");
|
|
3422
|
-
}
|
|
3423
|
-
return parsed;
|
|
3424
|
-
}
|
|
3425
|
-
function parseHeaders(values) {
|
|
3426
|
-
if (!values?.length)
|
|
3427
|
-
return;
|
|
3428
|
-
const headers = {};
|
|
3429
|
-
for (const value of values) {
|
|
3430
|
-
const separator = value.indexOf("=");
|
|
3431
|
-
if (separator === -1)
|
|
3432
|
-
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
3433
|
-
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
3434
|
-
}
|
|
3435
|
-
return headers;
|
|
3436
|
-
}
|
|
3437
|
-
function parseFilter(options) {
|
|
3438
|
-
const filter2 = {};
|
|
3439
|
-
if (options.source)
|
|
3440
|
-
filter2.source = options.source;
|
|
3441
|
-
if (options.type)
|
|
3442
|
-
filter2.type = options.type;
|
|
3443
|
-
if (options.subject)
|
|
3444
|
-
filter2.subject = options.subject;
|
|
3445
|
-
if (options.severity)
|
|
3446
|
-
filter2.severity = options.severity;
|
|
3447
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
3448
|
-
}
|
|
3449
|
-
function createClient(options) {
|
|
3450
|
-
if (options.createClient)
|
|
3451
|
-
return options.createClient();
|
|
3452
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
3453
|
-
}
|
|
3454
|
-
function print(value, json2, text) {
|
|
3455
|
-
if (json2)
|
|
3456
|
-
console.log(JSON.stringify(value, null, 2));
|
|
3457
|
-
else
|
|
3458
|
-
console.log(text);
|
|
3459
|
-
}
|
|
3460
|
-
function hasJsonOption(options) {
|
|
3461
|
-
return Boolean(options?.json || options?.opts?.().json || options?.optsWithGlobals?.().json || options?.parent?.opts?.().json || options?.parent?.optsWithGlobals?.().json);
|
|
3462
|
-
}
|
|
3463
|
-
function wantsJson(actionOptions, command) {
|
|
3464
|
-
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
3465
|
-
}
|
|
3466
|
-
function registerWebhookCommands(program, options) {
|
|
3467
|
-
const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
3468
|
-
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions, command) => {
|
|
3469
|
-
const timestamp = new Date().toISOString();
|
|
3470
|
-
const channel = {
|
|
3471
|
-
id: actionOptions.id,
|
|
3472
|
-
name: actionOptions.name,
|
|
3473
|
-
enabled: !actionOptions.disabled,
|
|
3474
|
-
transport: actionOptions.transport,
|
|
3475
|
-
filters: parseFilter(actionOptions),
|
|
3476
|
-
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
3477
|
-
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
3478
|
-
createdAt: timestamp,
|
|
3479
|
-
updatedAt: timestamp
|
|
3480
|
-
};
|
|
3481
|
-
if (actionOptions.transport === "webhook") {
|
|
3482
|
-
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
3483
|
-
} else if (actionOptions.transport === "command") {
|
|
3484
|
-
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
3485
|
-
} else {
|
|
3486
|
-
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
3487
|
-
}
|
|
3488
|
-
const saved = await createClient(options).addChannel(channel);
|
|
3489
|
-
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
3490
|
-
});
|
|
3491
|
-
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
3492
|
-
const channels = await createClient(options).listChannels();
|
|
3493
|
-
if (wantsJson(actionOptions, command)) {
|
|
3494
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
3495
|
-
return;
|
|
3496
|
-
}
|
|
3497
|
-
if (!channels.length) {
|
|
3498
|
-
console.log("No channels configured.");
|
|
3499
|
-
return;
|
|
3500
|
-
}
|
|
3501
|
-
for (const channel of channels) {
|
|
3502
|
-
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
3503
|
-
}
|
|
3504
|
-
});
|
|
3505
|
-
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
3506
|
-
const removed = await createClient(options).removeChannel(id);
|
|
3507
|
-
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
3508
|
-
});
|
|
3509
|
-
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
3510
|
-
const result = await createClient(options).testChannel(id, {
|
|
3511
|
-
source: options.source,
|
|
3512
|
-
type: actionOptions.type,
|
|
3513
|
-
subject: actionOptions.subject ?? id,
|
|
3514
|
-
message: actionOptions.message,
|
|
3515
|
-
data: parseJsonObject2(actionOptions.data, { test: true })
|
|
3516
|
-
});
|
|
3517
|
-
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
3518
|
-
});
|
|
3519
|
-
return webhooks;
|
|
3520
|
-
}
|
|
3521
|
-
function registerEventCommands(program, options) {
|
|
3522
|
-
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
3523
|
-
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions, command) => {
|
|
3524
|
-
const result = await createClient(options).emit({
|
|
3525
|
-
source: actionOptions.source ?? options.source,
|
|
3526
|
-
type,
|
|
3527
|
-
subject: actionOptions.subject,
|
|
3528
|
-
severity: actionOptions.severity,
|
|
3529
|
-
message: actionOptions.message,
|
|
3530
|
-
dedupeKey: actionOptions.dedupeKey,
|
|
3531
|
-
data: parseJsonObject2(actionOptions.data, {}),
|
|
3532
|
-
metadata: parseJsonObject2(actionOptions.metadata, {})
|
|
3533
|
-
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
3534
|
-
print(result, wantsJson(actionOptions, command), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
3535
|
-
});
|
|
3536
|
-
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
3537
|
-
let rows = await createClient(options).listEvents();
|
|
3538
|
-
if (actionOptions.source)
|
|
3539
|
-
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
3540
|
-
if (actionOptions.type)
|
|
3541
|
-
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
3542
|
-
if (actionOptions.limit)
|
|
3543
|
-
rows = rows.slice(-actionOptions.limit);
|
|
3544
|
-
if (wantsJson(actionOptions, command)) {
|
|
3545
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
3546
|
-
return;
|
|
3547
|
-
}
|
|
3548
|
-
if (!rows.length) {
|
|
3549
|
-
console.log("No events recorded.");
|
|
3550
|
-
return;
|
|
3551
|
-
}
|
|
3552
|
-
for (const event of rows)
|
|
3553
|
-
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
3554
|
-
});
|
|
3555
|
-
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
3556
|
-
const result = await createClient(options).replay({
|
|
3557
|
-
eventId: actionOptions.id,
|
|
3558
|
-
source: actionOptions.source,
|
|
3559
|
-
type: actionOptions.type,
|
|
3560
|
-
dryRun: actionOptions.dryRun
|
|
3561
|
-
});
|
|
3562
|
-
print(result, wantsJson(actionOptions, command), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
3563
|
-
});
|
|
3564
|
-
return events;
|
|
3565
|
-
}
|
|
3566
|
-
function registerEventsCommands(program, options) {
|
|
3567
|
-
registerWebhookCommands(program, options);
|
|
3568
|
-
registerEventCommands(program, options);
|
|
3569
|
-
}
|
|
3570
|
-
function parseNumber(value) {
|
|
3571
|
-
const parsed = Number(value);
|
|
3572
|
-
if (!Number.isFinite(parsed))
|
|
3573
|
-
throw new Error(`Expected a number, got ${value}`);
|
|
3574
|
-
return parsed;
|
|
3575
|
-
}
|
|
3576
|
-
function collectValues(value, previous) {
|
|
3577
|
-
previous.push(value);
|
|
3578
|
-
return previous;
|
|
3579
|
-
}
|
|
3580
3416
|
|
|
3581
|
-
// node_modules
|
|
3417
|
+
// node_modules/commander/esm.mjs
|
|
3582
3418
|
var import__ = __toESM(require_commander(), 1);
|
|
3583
3419
|
var {
|
|
3584
3420
|
program,
|
|
@@ -3594,7 +3430,7 @@ var {
|
|
|
3594
3430
|
Help
|
|
3595
3431
|
} = import__.default;
|
|
3596
3432
|
|
|
3597
|
-
// node_modules
|
|
3433
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
3598
3434
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
3599
3435
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
3600
3436
|
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
@@ -3771,7 +3607,7 @@ function assembleStyles() {
|
|
|
3771
3607
|
var ansiStyles = assembleStyles();
|
|
3772
3608
|
var ansi_styles_default = ansiStyles;
|
|
3773
3609
|
|
|
3774
|
-
// node_modules
|
|
3610
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
3775
3611
|
import process2 from "process";
|
|
3776
3612
|
import os from "os";
|
|
3777
3613
|
import tty from "tty";
|
|
@@ -3903,7 +3739,7 @@ var supportsColor = {
|
|
|
3903
3739
|
};
|
|
3904
3740
|
var supports_color_default = supportsColor;
|
|
3905
3741
|
|
|
3906
|
-
// node_modules
|
|
3742
|
+
// node_modules/chalk/source/utilities.js
|
|
3907
3743
|
function stringReplaceAll(string, substring, replacer) {
|
|
3908
3744
|
let index = string.indexOf(substring);
|
|
3909
3745
|
if (index === -1) {
|
|
@@ -3936,7 +3772,7 @@ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
|
3936
3772
|
return returnValue;
|
|
3937
3773
|
}
|
|
3938
3774
|
|
|
3939
|
-
// node_modules
|
|
3775
|
+
// node_modules/chalk/source/index.js
|
|
3940
3776
|
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
3941
3777
|
var GENERATOR = Symbol("GENERATOR");
|
|
3942
3778
|
var STYLER = Symbol("STYLER");
|
|
@@ -4089,7 +3925,7 @@ import { dirname as dirname3, join as join6 } from "path";
|
|
|
4089
3925
|
import { fileURLToPath } from "url";
|
|
4090
3926
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4091
3927
|
|
|
4092
|
-
// node_modules
|
|
3928
|
+
// node_modules/@hasna/contracts/dist/client/storage.js
|
|
4093
3929
|
var __defProp2 = Object.defineProperty;
|
|
4094
3930
|
var __returnValue = (v) => v;
|
|
4095
3931
|
function __exportSetter(name, newValue) {
|
|
@@ -10797,7 +10633,7 @@ var program2 = new Command;
|
|
|
10797
10633
|
function useJson(localOpts) {
|
|
10798
10634
|
return Boolean(localOpts?.json || program2.opts().json);
|
|
10799
10635
|
}
|
|
10800
|
-
function
|
|
10636
|
+
function print(data, localOpts, human) {
|
|
10801
10637
|
if (useJson(localOpts)) {
|
|
10802
10638
|
console.log(JSON.stringify(data, null, 2));
|
|
10803
10639
|
return;
|
|
@@ -10822,13 +10658,335 @@ async function withRuntimeStore(fn) {
|
|
|
10822
10658
|
await store.close();
|
|
10823
10659
|
}
|
|
10824
10660
|
}
|
|
10825
|
-
function formatLink(link) {
|
|
10826
|
-
return `${source_default.green(link.short_url || `${link.hostname}/${link.slug}`)} ${source_default.dim("->")} ${link.destination_url}`;
|
|
10827
|
-
}
|
|
10828
10661
|
function commandExists(command) {
|
|
10829
10662
|
const result = spawnSync3("which", [command], { encoding: "utf-8" });
|
|
10830
10663
|
return result.status === 0;
|
|
10831
10664
|
}
|
|
10665
|
+
var DEFAULT_HUMAN_LIMIT = 20;
|
|
10666
|
+
var DEFAULT_JSON_LIMIT = 100;
|
|
10667
|
+
var TEXT_LIMIT = 88;
|
|
10668
|
+
var EXTERNAL_OUTPUT_LIMIT = 20;
|
|
10669
|
+
var EXTERNAL_OUTPUT_WIDTH = 120;
|
|
10670
|
+
function parseLimit(value, fallback, label = "--limit") {
|
|
10671
|
+
if (value === undefined || value === null || value === "")
|
|
10672
|
+
return fallback;
|
|
10673
|
+
const parsed = Number(value);
|
|
10674
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
10675
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
10676
|
+
return parsed;
|
|
10677
|
+
}
|
|
10678
|
+
function humanLimit(opts) {
|
|
10679
|
+
return parseLimit(opts.limit, DEFAULT_HUMAN_LIMIT);
|
|
10680
|
+
}
|
|
10681
|
+
function jsonLimit(opts) {
|
|
10682
|
+
return opts.limit === undefined ? undefined : parseLimit(opts.limit, DEFAULT_JSON_LIMIT);
|
|
10683
|
+
}
|
|
10684
|
+
function truncateText(value, max = TEXT_LIMIT) {
|
|
10685
|
+
const text = value || "";
|
|
10686
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 3))}...` : text;
|
|
10687
|
+
}
|
|
10688
|
+
function yesNo(value) {
|
|
10689
|
+
return value ? "yes" : "no";
|
|
10690
|
+
}
|
|
10691
|
+
function printHint(message) {
|
|
10692
|
+
console.log(source_default.dim(message));
|
|
10693
|
+
}
|
|
10694
|
+
function printVerbose(data, opts) {
|
|
10695
|
+
if (!opts.verbose)
|
|
10696
|
+
return false;
|
|
10697
|
+
console.log(JSON.stringify(data, null, 2));
|
|
10698
|
+
return true;
|
|
10699
|
+
}
|
|
10700
|
+
function formatLink(link, maxDestinationLength = 72) {
|
|
10701
|
+
return `${source_default.green(link.short_url || `${link.hostname}/${link.slug}`)} ${source_default.dim("->")} ${truncateText(link.destination_url, maxDestinationLength)}`;
|
|
10702
|
+
}
|
|
10703
|
+
function printBoundedTextOutput(text, stream) {
|
|
10704
|
+
const lines = text.trim().split(/\r?\n/).filter(Boolean);
|
|
10705
|
+
let truncated = lines.length > EXTERNAL_OUTPUT_LIMIT;
|
|
10706
|
+
for (const line of lines.slice(0, EXTERNAL_OUTPUT_LIMIT)) {
|
|
10707
|
+
const output = truncateText(line, EXTERNAL_OUTPUT_WIDTH);
|
|
10708
|
+
if (output !== line)
|
|
10709
|
+
truncated = true;
|
|
10710
|
+
if (stream === "stderr")
|
|
10711
|
+
console.error(output);
|
|
10712
|
+
else
|
|
10713
|
+
console.log(output);
|
|
10714
|
+
}
|
|
10715
|
+
return truncated;
|
|
10716
|
+
}
|
|
10717
|
+
function printExternalCommandResult(result, opts, label) {
|
|
10718
|
+
if (opts.verbose) {
|
|
10719
|
+
if (result.stdout.trim())
|
|
10720
|
+
console.log(result.stdout.trim());
|
|
10721
|
+
if (result.stderr.trim())
|
|
10722
|
+
console.error(result.stderr.trim());
|
|
10723
|
+
return;
|
|
10724
|
+
}
|
|
10725
|
+
const stdoutTruncated = result.stdout.trim() ? printBoundedTextOutput(result.stdout, "stdout") : false;
|
|
10726
|
+
const stderrTruncated = result.stderr.trim() ? printBoundedTextOutput(result.stderr, "stderr") : false;
|
|
10727
|
+
if (stdoutTruncated || stderrTruncated)
|
|
10728
|
+
printHint(`Use --verbose or --json for full ${label} command output.`);
|
|
10729
|
+
}
|
|
10730
|
+
function printDomainSummary(domain) {
|
|
10731
|
+
console.log(`${domain.default_domain ? "*" : " "} ${domain.hostname} ${source_default.dim(domain.provider)} default=${yesNo(domain.default_domain)}`);
|
|
10732
|
+
if (domain.origin_url)
|
|
10733
|
+
console.log(` origin: ${truncateText(domain.origin_url)}`);
|
|
10734
|
+
if (domain.cloudflare_worker_name)
|
|
10735
|
+
console.log(` worker: ${domain.cloudflare_worker_name}`);
|
|
10736
|
+
printHint("Use `shortlinks domain get <hostname> --verbose` or `--json` for full details.");
|
|
10737
|
+
}
|
|
10738
|
+
function printLinkSummary(link) {
|
|
10739
|
+
console.log(formatLink(link));
|
|
10740
|
+
console.log(` slug: ${link.slug}`);
|
|
10741
|
+
console.log(` domain: ${link.hostname}`);
|
|
10742
|
+
console.log(` active: ${yesNo(link.active)}`);
|
|
10743
|
+
if (link.title)
|
|
10744
|
+
console.log(` title: ${truncateText(link.title)}`);
|
|
10745
|
+
if (link.expires_at)
|
|
10746
|
+
console.log(` expires: ${link.expires_at}`);
|
|
10747
|
+
printHint("Use `shortlinks link get <slug> --verbose` or `--json` for full details.");
|
|
10748
|
+
}
|
|
10749
|
+
function printStatsSummary(stats) {
|
|
10750
|
+
if ("link" in stats) {
|
|
10751
|
+
console.log(`${stats.link.short_url || `${stats.link.hostname}/${stats.link.slug}`} clicks=${stats.clicks}`);
|
|
10752
|
+
console.log(` destination: ${truncateText(stats.link.destination_url)}`);
|
|
10753
|
+
console.log(` last clicked: ${stats.last_clicked_at || "never"}`);
|
|
10754
|
+
const topReferrer = stats.top_referrers[0];
|
|
10755
|
+
const topAgent = stats.top_user_agents[0];
|
|
10756
|
+
if (topReferrer)
|
|
10757
|
+
console.log(` top referrer: ${truncateText(topReferrer.referer || "(direct)", 72)} (${topReferrer.clicks})`);
|
|
10758
|
+
if (topAgent)
|
|
10759
|
+
console.log(` top user agent: ${truncateText(topAgent.user_agent || "(unknown)", 72)} (${topAgent.clicks})`);
|
|
10760
|
+
printHint("Use `shortlinks stats <slug> --verbose` or `--json` for full stats.");
|
|
10761
|
+
return;
|
|
10762
|
+
}
|
|
10763
|
+
console.log(`domains=${stats.domains} links=${stats.links} clicks=${stats.clicks}`);
|
|
10764
|
+
}
|
|
10765
|
+
function printConfigSummary(data) {
|
|
10766
|
+
const config = data.config;
|
|
10767
|
+
console.log("shortlinks config");
|
|
10768
|
+
console.log(` path: ${data.path}`);
|
|
10769
|
+
console.log(` default domain: ${config.defaultDomain || "(unset)"}`);
|
|
10770
|
+
console.log(` public base URL: ${config.publicBaseUrl || "(unset)"}`);
|
|
10771
|
+
if (config.cloudflare) {
|
|
10772
|
+
console.log(` cloudflare worker: ${config.cloudflare.workerName || "(unset)"}`);
|
|
10773
|
+
console.log(` cloudflare origin: ${config.cloudflare.origin || "(unset)"}`);
|
|
10774
|
+
}
|
|
10775
|
+
printHint("Use `shortlinks config show --verbose` or `--json` for full config.");
|
|
10776
|
+
}
|
|
10777
|
+
function printCloudflarePlanSummary(plan) {
|
|
10778
|
+
console.log(`Cloudflare plan for ${plan.hostname}`);
|
|
10779
|
+
console.log(` CNAME: ${plan.hostname} -> ${plan.target} proxied=${yesNo(plan.proxied)}`);
|
|
10780
|
+
console.log(` worker: ${plan.workerName}`);
|
|
10781
|
+
console.log(` origin: ${truncateText(plan.origin)}`);
|
|
10782
|
+
console.log(` deploy: ${plan.wranglerCommand}`);
|
|
10783
|
+
printHint("Use `--verbose` or `--json` for the full DNS payload.");
|
|
10784
|
+
}
|
|
10785
|
+
function printCloudflareDnsSummary(result) {
|
|
10786
|
+
if (result && typeof result === "object" && "dnsRecord" in result) {
|
|
10787
|
+
printCloudflarePlanSummary(result);
|
|
10788
|
+
return;
|
|
10789
|
+
}
|
|
10790
|
+
const value = result;
|
|
10791
|
+
console.log(`Cloudflare DNS ${value.action || "updated"} ${value.id || ""}`.trim());
|
|
10792
|
+
printHint("Use `--json` for the full API result.");
|
|
10793
|
+
}
|
|
10794
|
+
function printLocalPlanSummary(plan) {
|
|
10795
|
+
console.log(`Local plan for ${plan.domain}`);
|
|
10796
|
+
console.log(` target: ${plan.targetHost}:${plan.port}`);
|
|
10797
|
+
console.log(` hosts: ${plan.hostsEntry}`);
|
|
10798
|
+
console.log(` cert: ${truncateText(plan.certPath)}`);
|
|
10799
|
+
console.log(` key: ${truncateText(plan.keyPath)}`);
|
|
10800
|
+
console.log(` machines: ${plan.machinesCommand}`);
|
|
10801
|
+
printHint("Use `--verbose` or `--json` for the full Caddy snippet.");
|
|
10802
|
+
}
|
|
10803
|
+
function printDoctorSummary(data) {
|
|
10804
|
+
const missingCommands = Object.entries(data.commands).filter(([, present]) => !present).map(([name]) => name);
|
|
10805
|
+
const presentEnv = Object.entries(data.environment).filter(([, present]) => present).map(([name]) => name);
|
|
10806
|
+
console.log(`${data.service} doctor`);
|
|
10807
|
+
console.log(` store: ${data.store}`);
|
|
10808
|
+
console.log(` db: ${data.db_exists ? "found" : "missing"} ${truncateText(data.db_path)}`);
|
|
10809
|
+
console.log(` stats: domains=${data.stats.domains} links=${data.stats.links} clicks=${data.stats.clicks}`);
|
|
10810
|
+
console.log(` commands: ${missingCommands.length ? `missing ${missingCommands.join(", ")}` : "ok"}`);
|
|
10811
|
+
console.log(` env: ${presentEnv.length ? presentEnv.join(", ") : "no optional env vars detected"}`);
|
|
10812
|
+
printHint("Use `shortlinks doctor --verbose` or `--json` for paths and full readiness data.");
|
|
10813
|
+
}
|
|
10814
|
+
function parseNumber(value) {
|
|
10815
|
+
if (value === undefined)
|
|
10816
|
+
return;
|
|
10817
|
+
const parsed = Number(value);
|
|
10818
|
+
if (!Number.isFinite(parsed))
|
|
10819
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
10820
|
+
return parsed;
|
|
10821
|
+
}
|
|
10822
|
+
function collectValues(value, previous = []) {
|
|
10823
|
+
return [...previous, value];
|
|
10824
|
+
}
|
|
10825
|
+
function parseJsonObject2(value, fallback = {}) {
|
|
10826
|
+
if (!value)
|
|
10827
|
+
return fallback;
|
|
10828
|
+
const parsed = JSON.parse(value);
|
|
10829
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10830
|
+
throw new Error("Expected a JSON object.");
|
|
10831
|
+
}
|
|
10832
|
+
return parsed;
|
|
10833
|
+
}
|
|
10834
|
+
function parseHeaders(values = []) {
|
|
10835
|
+
const headers = {};
|
|
10836
|
+
for (const value of values) {
|
|
10837
|
+
const index = value.indexOf("=");
|
|
10838
|
+
if (index <= 0)
|
|
10839
|
+
throw new Error(`Header must be name=value: ${value}`);
|
|
10840
|
+
headers[value.slice(0, index)] = value.slice(index + 1);
|
|
10841
|
+
}
|
|
10842
|
+
return headers;
|
|
10843
|
+
}
|
|
10844
|
+
function parseEventFilter(opts) {
|
|
10845
|
+
const filter = {};
|
|
10846
|
+
if (opts.type)
|
|
10847
|
+
filter.type = opts.type;
|
|
10848
|
+
if (opts.source)
|
|
10849
|
+
filter.source = opts.source;
|
|
10850
|
+
if (opts.subject)
|
|
10851
|
+
filter.subject = opts.subject;
|
|
10852
|
+
if (opts.severity)
|
|
10853
|
+
filter.severity = opts.severity;
|
|
10854
|
+
return Object.keys(filter).length ? [filter] : [];
|
|
10855
|
+
}
|
|
10856
|
+
function createEventsClient() {
|
|
10857
|
+
return new EventsClient;
|
|
10858
|
+
}
|
|
10859
|
+
function formatEventRow(event) {
|
|
10860
|
+
return `${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}${event.subject ? ` ${truncateText(event.subject, 48)}` : ""}`;
|
|
10861
|
+
}
|
|
10862
|
+
function formatChannelTarget(channel) {
|
|
10863
|
+
return channel.webhook?.url ?? channel.command?.command ?? channel.transport;
|
|
10864
|
+
}
|
|
10865
|
+
function registerCompactEventsCommands(program3) {
|
|
10866
|
+
const webhooks = program3.command("webhooks").description("Manage Hasna event webhook subscriptions");
|
|
10867
|
+
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Output JSON").action(async (target, opts) => {
|
|
10868
|
+
try {
|
|
10869
|
+
const transport = opts.transport;
|
|
10870
|
+
const channel = {
|
|
10871
|
+
id: opts.id,
|
|
10872
|
+
name: opts.name,
|
|
10873
|
+
enabled: !opts.disabled,
|
|
10874
|
+
transport,
|
|
10875
|
+
filters: parseEventFilter(opts),
|
|
10876
|
+
retry: opts.retryAttempts || opts.retryBackoffMs ? { maxAttempts: opts.retryAttempts, backoffMs: opts.retryBackoffMs } : undefined,
|
|
10877
|
+
redact: opts.redact?.length ? { paths: opts.redact } : undefined
|
|
10878
|
+
};
|
|
10879
|
+
if (transport === "webhook") {
|
|
10880
|
+
channel.webhook = { url: target, secret: opts.secret, headers: parseHeaders(opts.header), timeoutMs: opts.timeoutMs };
|
|
10881
|
+
} else if (transport === "command") {
|
|
10882
|
+
channel.command = { command: target, args: opts.arg ?? [], timeoutMs: opts.timeoutMs };
|
|
10883
|
+
} else {
|
|
10884
|
+
throw new Error(`Transport ${transport} is reserved for future use and cannot be added yet.`);
|
|
10885
|
+
}
|
|
10886
|
+
const saved = sanitizeChannelForOutput(await createEventsClient().addChannel(channel));
|
|
10887
|
+
print(saved, opts, () => console.log(`Added ${saved.transport} channel ${saved.id}`));
|
|
10888
|
+
} catch (error) {
|
|
10889
|
+
handleError(error);
|
|
10890
|
+
}
|
|
10891
|
+
});
|
|
10892
|
+
webhooks.command("list").description("List configured subscriptions").option("--limit <n>", "Maximum rows").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
10893
|
+
try {
|
|
10894
|
+
const channels = sanitizeChannelsForOutput(await createEventsClient().listChannels());
|
|
10895
|
+
const outputChannels = useJson(opts) && opts.limit === undefined ? channels : channels.slice(0, useJson(opts) ? parseLimit(opts.limit, channels.length) : humanLimit(opts));
|
|
10896
|
+
print(outputChannels, opts, () => {
|
|
10897
|
+
if (!channels.length) {
|
|
10898
|
+
console.log("No channels configured.");
|
|
10899
|
+
return;
|
|
10900
|
+
}
|
|
10901
|
+
for (const channel of outputChannels) {
|
|
10902
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${truncateText(formatChannelTarget(channel), 80)}`);
|
|
10903
|
+
}
|
|
10904
|
+
console.log(source_default.dim(`Showing ${outputChannels.length} of ${channels.length} channel(s).`));
|
|
10905
|
+
if (channels.length > outputChannels.length)
|
|
10906
|
+
printHint(`Use --limit ${channels.length} or --json for more.`);
|
|
10907
|
+
});
|
|
10908
|
+
} catch (error) {
|
|
10909
|
+
handleError(error);
|
|
10910
|
+
}
|
|
10911
|
+
});
|
|
10912
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Output JSON").action(async (id, opts) => {
|
|
10913
|
+
try {
|
|
10914
|
+
const removed = await createEventsClient().removeChannel(id);
|
|
10915
|
+
print({ removed }, opts, () => console.log(removed ? `Removed ${id}` : `Channel not found: ${id}`));
|
|
10916
|
+
} catch (error) {
|
|
10917
|
+
handleError(error);
|
|
10918
|
+
}
|
|
10919
|
+
});
|
|
10920
|
+
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Output JSON").action(async (id, opts) => {
|
|
10921
|
+
try {
|
|
10922
|
+
const result = await createEventsClient().testChannel(id, {
|
|
10923
|
+
source: "shortlinks",
|
|
10924
|
+
type: opts.type,
|
|
10925
|
+
subject: opts.subject ?? id,
|
|
10926
|
+
message: opts.message,
|
|
10927
|
+
data: parseJsonObject2(opts.data, { test: true })
|
|
10928
|
+
});
|
|
10929
|
+
print(result, opts, () => console.log(`${result.status}: ${result.channelId}`));
|
|
10930
|
+
} catch (error) {
|
|
10931
|
+
handleError(error);
|
|
10932
|
+
}
|
|
10933
|
+
});
|
|
10934
|
+
const events = program3.command("events").description("Emit, list, and replay Hasna events");
|
|
10935
|
+
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Output JSON").action(async (type, opts) => {
|
|
10936
|
+
try {
|
|
10937
|
+
const result = await createEventsClient().emit({
|
|
10938
|
+
source: opts.source ?? "shortlinks",
|
|
10939
|
+
type,
|
|
10940
|
+
subject: opts.subject,
|
|
10941
|
+
severity: opts.severity,
|
|
10942
|
+
message: opts.message,
|
|
10943
|
+
dedupeKey: opts.dedupeKey,
|
|
10944
|
+
data: parseJsonObject2(opts.data, {}),
|
|
10945
|
+
metadata: parseJsonObject2(opts.metadata, {})
|
|
10946
|
+
}, { deliver: opts.deliver, dedupe: opts.dedupe });
|
|
10947
|
+
print(result, opts, () => console.log(`${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`));
|
|
10948
|
+
} catch (error) {
|
|
10949
|
+
handleError(error);
|
|
10950
|
+
}
|
|
10951
|
+
});
|
|
10952
|
+
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Maximum rows").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
10953
|
+
try {
|
|
10954
|
+
let rows = await createEventsClient().listEvents();
|
|
10955
|
+
if (opts.source)
|
|
10956
|
+
rows = rows.filter((event) => event.source === opts.source);
|
|
10957
|
+
if (opts.type)
|
|
10958
|
+
rows = rows.filter((event) => event.type === opts.type);
|
|
10959
|
+
const limit = useJson(opts) ? jsonLimit(opts) : humanLimit(opts);
|
|
10960
|
+
const outputRows = limit === undefined ? rows : rows.slice(-limit);
|
|
10961
|
+
print(outputRows, opts, () => {
|
|
10962
|
+
if (!rows.length) {
|
|
10963
|
+
console.log("No events recorded.");
|
|
10964
|
+
return;
|
|
10965
|
+
}
|
|
10966
|
+
for (const event of outputRows)
|
|
10967
|
+
console.log(formatEventRow(event));
|
|
10968
|
+
console.log(source_default.dim(`Showing ${outputRows.length} of ${rows.length} event(s).`));
|
|
10969
|
+
if (rows.length > outputRows.length)
|
|
10970
|
+
printHint(`Use --limit ${rows.length} or --json for more.`);
|
|
10971
|
+
});
|
|
10972
|
+
} catch (error) {
|
|
10973
|
+
handleError(error);
|
|
10974
|
+
}
|
|
10975
|
+
});
|
|
10976
|
+
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Output JSON").action(async (opts) => {
|
|
10977
|
+
try {
|
|
10978
|
+
const result = await createEventsClient().replay({
|
|
10979
|
+
eventId: opts.id,
|
|
10980
|
+
source: opts.source,
|
|
10981
|
+
type: opts.type,
|
|
10982
|
+
dryRun: opts.dryRun
|
|
10983
|
+
});
|
|
10984
|
+
print(result, opts, () => console.log(`Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`));
|
|
10985
|
+
} catch (error) {
|
|
10986
|
+
handleError(error);
|
|
10987
|
+
}
|
|
10988
|
+
});
|
|
10989
|
+
}
|
|
10832
10990
|
program2.name("shortlinks").description("Shortlink manager with custom domains, click tracking, and Cloudflare helpers \u2014 local SQLite or self-hosted cloud /v1 API storage").version(getPackageVersion()).option("--db <path>", "SQLite database path (local mode only)").option("-j, --json", "Output JSON for agents and scripts");
|
|
10833
10991
|
program2.command("init").description("Initialize local shortlinks storage").option("--domain <hostname>", "Add a default shortlink domain").option("--public-base-url <url>", "Public URL base for generated links").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
10834
10992
|
try {
|
|
@@ -10856,7 +11014,7 @@ program2.command("init").description("Initialize local shortlinks storage").opti
|
|
|
10856
11014
|
stats: await store.totalStats()
|
|
10857
11015
|
};
|
|
10858
11016
|
});
|
|
10859
|
-
|
|
11017
|
+
print(result, opts, () => {
|
|
10860
11018
|
console.log(source_default.green("shortlinks initialized"));
|
|
10861
11019
|
console.log(` Data: ${result.data_dir}`);
|
|
10862
11020
|
console.log(` DB: ${result.db_path}`);
|
|
@@ -10868,9 +11026,13 @@ program2.command("init").description("Initialize local shortlinks storage").opti
|
|
|
10868
11026
|
}
|
|
10869
11027
|
});
|
|
10870
11028
|
var configCmd = program2.command("config").description("View and update local config");
|
|
10871
|
-
configCmd.command("show").description("Show local config").option("-j, --json", "Output JSON").action((opts) => {
|
|
11029
|
+
configCmd.command("show").description("Show local config").option("--verbose", "Show full config object").option("-j, --json", "Output JSON").action((opts) => {
|
|
10872
11030
|
const data = { path: getConfigPath(), config: loadConfig() };
|
|
10873
|
-
|
|
11031
|
+
print(data, opts, () => {
|
|
11032
|
+
if (printVerbose(data, opts))
|
|
11033
|
+
return;
|
|
11034
|
+
printConfigSummary(data);
|
|
11035
|
+
});
|
|
10874
11036
|
});
|
|
10875
11037
|
configCmd.command("set <key> <value>").description("Set config value: default-domain, public-base-url, cloudflare-account-id, cloudflare-worker-name, cloudflare-origin").option("-j, --json", "Output JSON").action((key, value, opts) => {
|
|
10876
11038
|
try {
|
|
@@ -10894,7 +11056,7 @@ configCmd.command("set <key> <value>").description("Set config value: default-do
|
|
|
10894
11056
|
default:
|
|
10895
11057
|
throw new Error(`Unknown config key: ${key}`);
|
|
10896
11058
|
}
|
|
10897
|
-
|
|
11059
|
+
print({ path: getConfigPath(), config }, opts, () => console.log(source_default.green(`Set ${key}.`)));
|
|
10898
11060
|
} catch (error) {
|
|
10899
11061
|
handleError(error);
|
|
10900
11062
|
}
|
|
@@ -10912,7 +11074,7 @@ domainCmd.command("add <hostname>").description("Add or update a custom domain")
|
|
|
10912
11074
|
originUrl: opts.origin,
|
|
10913
11075
|
notes: opts.notes
|
|
10914
11076
|
}));
|
|
10915
|
-
|
|
11077
|
+
print(domain, opts, () => {
|
|
10916
11078
|
console.log(source_default.green(`Domain ready: ${domain.hostname}`));
|
|
10917
11079
|
if (domain.default_domain)
|
|
10918
11080
|
console.log(source_default.dim("Default domain updated."));
|
|
@@ -10921,10 +11083,12 @@ domainCmd.command("add <hostname>").description("Add or update a custom domain")
|
|
|
10921
11083
|
handleError(error);
|
|
10922
11084
|
}
|
|
10923
11085
|
});
|
|
10924
|
-
domainCmd.command("list").description("List configured domains").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
11086
|
+
domainCmd.command("list").description("List configured domains").option("--limit <n>", "Maximum rows").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
10925
11087
|
try {
|
|
10926
|
-
const
|
|
10927
|
-
|
|
11088
|
+
const allDomains = await withRuntimeStore((store) => store.listDomains());
|
|
11089
|
+
const outputDomains = useJson(opts) && opts.limit === undefined ? allDomains : allDomains.slice(0, useJson(opts) ? parseLimit(opts.limit, allDomains.length) : humanLimit(opts));
|
|
11090
|
+
print(outputDomains, opts, () => {
|
|
11091
|
+
const domains = outputDomains;
|
|
10928
11092
|
if (domains.length === 0) {
|
|
10929
11093
|
console.log(source_default.dim("No domains configured."));
|
|
10930
11094
|
return;
|
|
@@ -10933,17 +11097,25 @@ domainCmd.command("list").description("List configured domains").option("-j, --j
|
|
|
10933
11097
|
const marker = domain.default_domain ? source_default.green("*") : " ";
|
|
10934
11098
|
console.log(`${marker} ${domain.hostname} ${source_default.dim(domain.provider)}`);
|
|
10935
11099
|
}
|
|
11100
|
+
console.log(source_default.dim(`Showing ${domains.length} of ${allDomains.length} domain(s).`));
|
|
11101
|
+
if (allDomains.length > domains.length)
|
|
11102
|
+
printHint(`Use --limit ${allDomains.length} or --json for more.`);
|
|
11103
|
+
printHint("Use `shortlinks domain get <hostname>` for details.");
|
|
10936
11104
|
});
|
|
10937
11105
|
} catch (error) {
|
|
10938
11106
|
handleError(error);
|
|
10939
11107
|
}
|
|
10940
11108
|
});
|
|
10941
|
-
domainCmd.command("get <hostname>").description("Show a configured domain").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
11109
|
+
domainCmd.command("get <hostname>").alias("show").description("Show a configured domain").option("--verbose", "Show full domain object").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
10942
11110
|
try {
|
|
10943
11111
|
const domain = await withRuntimeStore((store) => store.getDomain(hostname2));
|
|
10944
11112
|
if (!domain)
|
|
10945
11113
|
throw new Error("Domain not found.");
|
|
10946
|
-
|
|
11114
|
+
print(domain, opts, () => {
|
|
11115
|
+
if (printVerbose(domain, opts))
|
|
11116
|
+
return;
|
|
11117
|
+
printDomainSummary(domain);
|
|
11118
|
+
});
|
|
10947
11119
|
} catch (error) {
|
|
10948
11120
|
handleError(error);
|
|
10949
11121
|
}
|
|
@@ -10951,7 +11123,7 @@ domainCmd.command("get <hostname>").description("Show a configured domain").opti
|
|
|
10951
11123
|
domainCmd.command("remove <hostname>").alias("delete").alias("rm").description("Delete a domain and all of its links and clicks").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
10952
11124
|
try {
|
|
10953
11125
|
const domain = await withRuntimeStore((store) => store.deleteDomain(hostname2));
|
|
10954
|
-
|
|
11126
|
+
print({ deleted: true, hostname: domain.hostname }, opts, () => {
|
|
10955
11127
|
console.log(source_default.green(`Domain removed: ${domain.hostname}`));
|
|
10956
11128
|
console.log(source_default.dim("Its links and clicks were deleted."));
|
|
10957
11129
|
});
|
|
@@ -10959,7 +11131,7 @@ domainCmd.command("remove <hostname>").alias("delete").alias("rm").description("
|
|
|
10959
11131
|
handleError(error);
|
|
10960
11132
|
}
|
|
10961
11133
|
});
|
|
10962
|
-
domainCmd.command("setup <hostname>").description("Add a domain locally and optionally prepare Cloudflare DNS").option("--default", "Make this the default domain").option("--origin <url>", "Origin redirect server URL").option("--cloudflare", "Upsert Cloudflare CNAME record").option("--target <hostname>", "CNAME target for Cloudflare DNS").option("--zone-id <id>", "Cloudflare zone ID").option("--dry-run", "Show the Cloudflare plan without changing DNS").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
11134
|
+
domainCmd.command("setup <hostname>").description("Add a domain locally and optionally prepare Cloudflare DNS").option("--default", "Make this the default domain").option("--origin <url>", "Origin redirect server URL").option("--cloudflare", "Upsert Cloudflare CNAME record").option("--target <hostname>", "CNAME target for Cloudflare DNS").option("--zone-id <id>", "Cloudflare zone ID").option("--dry-run", "Show the Cloudflare plan without changing DNS").option("--verbose", "Show full setup result").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
10963
11135
|
try {
|
|
10964
11136
|
const result = await withRuntimeStore(async (store) => {
|
|
10965
11137
|
const domain = await store.addDomain({
|
|
@@ -10976,33 +11148,31 @@ domainCmd.command("setup <hostname>").description("Add a domain locally and opti
|
|
|
10976
11148
|
}) : null;
|
|
10977
11149
|
return { domain, cloudflare };
|
|
10978
11150
|
});
|
|
10979
|
-
|
|
11151
|
+
print(result, opts, () => {
|
|
11152
|
+
if (printVerbose(result, opts))
|
|
11153
|
+
return;
|
|
10980
11154
|
console.log(source_default.green(`Domain ready: ${result.domain.hostname}`));
|
|
10981
11155
|
if (result.cloudflare)
|
|
10982
|
-
|
|
11156
|
+
printCloudflareDnsSummary(result.cloudflare);
|
|
11157
|
+
if (!result.cloudflare)
|
|
11158
|
+
printHint("Use --cloudflare --dry-run to preview DNS changes.");
|
|
10983
11159
|
});
|
|
10984
11160
|
} catch (error) {
|
|
10985
11161
|
handleError(error);
|
|
10986
11162
|
}
|
|
10987
11163
|
});
|
|
10988
|
-
domainCmd.command("check <hostname>").description("Check domain availability through @hasna/domains").option("--dry-run", "Print the command without running it").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
11164
|
+
domainCmd.command("check <hostname>").description("Check domain availability through @hasna/domains").option("--dry-run", "Print the command without running it").option("--verbose", "Show full domains CLI output").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
10989
11165
|
const result = runDomains("check", hostname2, { dryRun: opts.dryRun });
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
console.log(result.stdout.trim());
|
|
10993
|
-
if (result.stderr.trim())
|
|
10994
|
-
console.error(result.stderr.trim());
|
|
11166
|
+
print(result, opts, () => {
|
|
11167
|
+
printExternalCommandResult(result, opts, "domains check");
|
|
10995
11168
|
if (result.status !== 0)
|
|
10996
11169
|
process.exit(result.status || 1);
|
|
10997
11170
|
});
|
|
10998
11171
|
});
|
|
10999
|
-
domainCmd.command("buy <hostname>").description("Buy a domain through @hasna/domains / Route 53").option("--dry-run", "Print the command without running it").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
11172
|
+
domainCmd.command("buy <hostname>").description("Buy a domain through @hasna/domains / Route 53").option("--dry-run", "Print the command without running it").option("--verbose", "Show full domains CLI output").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
11000
11173
|
const result = runDomains("buy", hostname2, { dryRun: opts.dryRun });
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
console.log(result.stdout.trim());
|
|
11004
|
-
if (result.stderr.trim())
|
|
11005
|
-
console.error(result.stderr.trim());
|
|
11174
|
+
print(result, opts, () => {
|
|
11175
|
+
printExternalCommandResult(result, opts, "domains buy");
|
|
11006
11176
|
if (result.status !== 0)
|
|
11007
11177
|
process.exit(result.status || 1);
|
|
11008
11178
|
});
|
|
@@ -11018,38 +11188,51 @@ async function createLinkAction(url, opts) {
|
|
|
11018
11188
|
expiresAt: opts.expires,
|
|
11019
11189
|
slugLength: opts.length ? Number(opts.length) : undefined
|
|
11020
11190
|
}));
|
|
11021
|
-
|
|
11191
|
+
print(link, opts, () => console.log(formatLink(link)));
|
|
11022
11192
|
} catch (error) {
|
|
11023
11193
|
handleError(error);
|
|
11024
11194
|
}
|
|
11025
11195
|
}
|
|
11026
11196
|
linkCmd.command("create <url>").description("Create a shortlink").option("--domain <hostname>", "Domain to use").option("--slug <slug>", "Custom slug").option("--title <title>", "Human title").option("--expires <date>", "Expiration date").option("--length <n>", "Generated slug length", "7").option("-j, --json", "Output JSON").action(createLinkAction);
|
|
11027
11197
|
program2.command("create <url>").description("Create a shortlink").option("--domain <hostname>", "Domain to use").option("--slug <slug>", "Custom slug").option("--title <title>", "Human title").option("--expires <date>", "Expiration date").option("--length <n>", "Generated slug length", "7").option("-j, --json", "Output JSON").action(createLinkAction);
|
|
11028
|
-
linkCmd.command("list").description("List shortlinks").option("--domain <hostname>", "Filter by domain").option("--active", "Only active links").option("--limit <n>", "Maximum rows"
|
|
11198
|
+
linkCmd.command("list").description("List shortlinks").option("--domain <hostname>", "Filter by domain").option("--active", "Only active links").option("--limit <n>", "Maximum rows").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
11029
11199
|
try {
|
|
11200
|
+
const requestedLimit = useJson(opts) ? jsonLimit(opts) : humanLimit(opts) + 1;
|
|
11030
11201
|
const links = await withRuntimeStore((store) => store.listLinks({
|
|
11031
11202
|
domain: opts.domain,
|
|
11032
11203
|
activeOnly: opts.active,
|
|
11033
|
-
limit:
|
|
11204
|
+
limit: requestedLimit
|
|
11034
11205
|
}));
|
|
11035
|
-
|
|
11206
|
+
print(links, opts, () => {
|
|
11036
11207
|
if (links.length === 0) {
|
|
11037
11208
|
console.log(source_default.dim("No links yet."));
|
|
11038
11209
|
return;
|
|
11039
11210
|
}
|
|
11040
|
-
|
|
11041
|
-
|
|
11211
|
+
const limit = humanLimit(opts);
|
|
11212
|
+
const displayed = links.slice(0, limit);
|
|
11213
|
+
for (const link of displayed) {
|
|
11214
|
+
const status = link.active ? "active" : "disabled";
|
|
11215
|
+
console.log(`${formatLink(link)} ${source_default.dim(status)}`);
|
|
11216
|
+
}
|
|
11217
|
+
console.log(source_default.dim(`Showing ${displayed.length}${links.length > displayed.length ? "+" : ""} link(s).`));
|
|
11218
|
+
if (links.length > displayed.length)
|
|
11219
|
+
printHint(`Use --limit ${limit * 2} or --json to see more rows.`);
|
|
11220
|
+
printHint("Use `shortlinks link get <slug>` for details.");
|
|
11042
11221
|
});
|
|
11043
11222
|
} catch (error) {
|
|
11044
11223
|
handleError(error);
|
|
11045
11224
|
}
|
|
11046
11225
|
});
|
|
11047
|
-
linkCmd.command("get <slug>").description("Show a shortlink").option("--domain <hostname>", "Domain to use").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11226
|
+
linkCmd.command("get <slug>").alias("show").description("Show a shortlink").option("--domain <hostname>", "Domain to use").option("--verbose", "Show full shortlink object").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11048
11227
|
try {
|
|
11049
11228
|
const link = await withRuntimeStore((store) => opts.domain ? store.getLink(opts.domain, slug) : store.getLink(slug));
|
|
11050
11229
|
if (!link)
|
|
11051
11230
|
throw new Error("Link not found.");
|
|
11052
|
-
|
|
11231
|
+
print(link, opts, () => {
|
|
11232
|
+
if (printVerbose(link, opts))
|
|
11233
|
+
return;
|
|
11234
|
+
printLinkSummary(link);
|
|
11235
|
+
});
|
|
11053
11236
|
} catch (error) {
|
|
11054
11237
|
handleError(error);
|
|
11055
11238
|
}
|
|
@@ -11057,7 +11240,7 @@ linkCmd.command("get <slug>").description("Show a shortlink").option("--domain <
|
|
|
11057
11240
|
linkCmd.command("disable <slug>").description("Disable a shortlink").option("--domain <hostname>", "Domain to use").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11058
11241
|
try {
|
|
11059
11242
|
const link = await withRuntimeStore((store) => opts.domain ? store.setLinkActive(opts.domain, slug, false) : store.setLinkActive(slug, false));
|
|
11060
|
-
|
|
11243
|
+
print(link, opts, () => console.log(source_default.green(`Disabled ${link.short_url}`)));
|
|
11061
11244
|
} catch (error) {
|
|
11062
11245
|
handleError(error);
|
|
11063
11246
|
}
|
|
@@ -11065,7 +11248,7 @@ linkCmd.command("disable <slug>").description("Disable a shortlink").option("--d
|
|
|
11065
11248
|
linkCmd.command("enable <slug>").description("Enable a shortlink").option("--domain <hostname>", "Domain to use").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11066
11249
|
try {
|
|
11067
11250
|
const link = await withRuntimeStore((store) => opts.domain ? store.setLinkActive(opts.domain, slug, true) : store.setLinkActive(slug, true));
|
|
11068
|
-
|
|
11251
|
+
print(link, opts, () => console.log(source_default.green(`Enabled ${link.short_url}`)));
|
|
11069
11252
|
} catch (error) {
|
|
11070
11253
|
handleError(error);
|
|
11071
11254
|
}
|
|
@@ -11073,7 +11256,7 @@ linkCmd.command("enable <slug>").description("Enable a shortlink").option("--dom
|
|
|
11073
11256
|
linkCmd.command("delete <slug>").description("Delete a shortlink").option("--domain <hostname>", "Domain to use").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11074
11257
|
try {
|
|
11075
11258
|
const link = await withRuntimeStore((store) => opts.domain ? store.deleteLink(opts.domain, slug) : store.deleteLink(slug));
|
|
11076
|
-
|
|
11259
|
+
print(link, opts, () => console.log(source_default.green(`Deleted ${link.short_url}`)));
|
|
11077
11260
|
} catch (error) {
|
|
11078
11261
|
handleError(error);
|
|
11079
11262
|
}
|
|
@@ -11083,19 +11266,23 @@ program2.command("resolve <slug>").description("Resolve a slug to its destinatio
|
|
|
11083
11266
|
const link = await withRuntimeStore((store) => opts.domain ? store.getLink(opts.domain, slug) : store.getLink(slug));
|
|
11084
11267
|
if (!link)
|
|
11085
11268
|
throw new Error("Link not found.");
|
|
11086
|
-
|
|
11269
|
+
print(link, opts, () => console.log(link.destination_url));
|
|
11087
11270
|
} catch (error) {
|
|
11088
11271
|
handleError(error);
|
|
11089
11272
|
}
|
|
11090
11273
|
});
|
|
11091
|
-
program2.command("stats [slug]").description("Show overall stats or stats for a shortlink").option("--domain <hostname>", "Domain to use").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11274
|
+
program2.command("stats [slug]").description("Show overall stats or stats for a shortlink").option("--domain <hostname>", "Domain to use").option("--verbose", "Show full stats object").option("-j, --json", "Output JSON").action(async (slug, opts) => {
|
|
11092
11275
|
try {
|
|
11093
11276
|
const result = await withRuntimeStore((store) => {
|
|
11094
11277
|
if (slug)
|
|
11095
11278
|
return opts.domain ? store.getStats(opts.domain, slug) : store.getStats(slug);
|
|
11096
11279
|
return store.totalStats();
|
|
11097
11280
|
});
|
|
11098
|
-
|
|
11281
|
+
print(result, opts, () => {
|
|
11282
|
+
if (printVerbose(result, opts))
|
|
11283
|
+
return;
|
|
11284
|
+
printStatsSummary(result);
|
|
11285
|
+
});
|
|
11099
11286
|
} catch (error) {
|
|
11100
11287
|
handleError(error);
|
|
11101
11288
|
}
|
|
@@ -11115,7 +11302,7 @@ program2.command("serve").description("Run the on-box redirect server that recor
|
|
|
11115
11302
|
}
|
|
11116
11303
|
});
|
|
11117
11304
|
var cfCmd = program2.command("cloudflare").description("Cloudflare DNS and Worker helpers");
|
|
11118
|
-
cfCmd.command("plan <hostname>").description("Print the Cloudflare setup plan").requiredOption("--target <hostname>", "CNAME target").option("--origin <url>", "Origin redirect server URL", process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com").option("--worker <name>", "Worker name", "shortlinks").option("--no-proxied", "Create unproxied DNS record").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
11305
|
+
cfCmd.command("plan <hostname>").description("Print the Cloudflare setup plan").requiredOption("--target <hostname>", "CNAME target").option("--origin <url>", "Origin redirect server URL", process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com").option("--worker <name>", "Worker name", "shortlinks").option("--no-proxied", "Create unproxied DNS record").option("--verbose", "Show full Cloudflare setup plan").option("-j, --json", "Output JSON").action((hostname2, opts) => {
|
|
11119
11306
|
try {
|
|
11120
11307
|
const plan = createCloudflarePlan({
|
|
11121
11308
|
hostname: hostname2,
|
|
@@ -11124,7 +11311,11 @@ cfCmd.command("plan <hostname>").description("Print the Cloudflare setup plan").
|
|
|
11124
11311
|
workerName: opts.worker,
|
|
11125
11312
|
proxied: opts.proxied
|
|
11126
11313
|
});
|
|
11127
|
-
|
|
11314
|
+
print(plan, opts, () => {
|
|
11315
|
+
if (printVerbose(plan, opts))
|
|
11316
|
+
return;
|
|
11317
|
+
printCloudflarePlanSummary(plan);
|
|
11318
|
+
});
|
|
11128
11319
|
} catch (error) {
|
|
11129
11320
|
handleError(error);
|
|
11130
11321
|
}
|
|
@@ -11132,7 +11323,7 @@ cfCmd.command("plan <hostname>").description("Print the Cloudflare setup plan").
|
|
|
11132
11323
|
cfCmd.command("worker").description("Write Cloudflare Worker files").option("--out-dir <dir>", "Output directory", "cloudflare").option("--worker <name>", "Worker name", "shortlinks").option("--origin <url>", "Origin redirect server URL", process.env.SHORTLINKS_ORIGIN || "https://shortlinks.example.com").option("-j, --json", "Output JSON").action((opts) => {
|
|
11133
11324
|
try {
|
|
11134
11325
|
const result = writeWorkerFiles({ outDir: opts.outDir, workerName: opts.worker, origin: opts.origin });
|
|
11135
|
-
|
|
11326
|
+
print(result, opts, () => {
|
|
11136
11327
|
console.log(source_default.green(`Wrote ${result.workerPath}`));
|
|
11137
11328
|
console.log(source_default.green(`Wrote ${result.wranglerPath}`));
|
|
11138
11329
|
});
|
|
@@ -11140,7 +11331,7 @@ cfCmd.command("worker").description("Write Cloudflare Worker files").option("--o
|
|
|
11140
11331
|
handleError(error);
|
|
11141
11332
|
}
|
|
11142
11333
|
});
|
|
11143
|
-
cfCmd.command("dns <hostname>").description("Create or update the Cloudflare CNAME record").requiredOption("--target <hostname>", "CNAME target").option("--zone-id <id>", "Cloudflare zone ID").option("--dry-run", "Show plan without changing DNS").option("--no-proxied", "Create unproxied DNS record").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
11334
|
+
cfCmd.command("dns <hostname>").description("Create or update the Cloudflare CNAME record").requiredOption("--target <hostname>", "CNAME target").option("--zone-id <id>", "Cloudflare zone ID").option("--dry-run", "Show plan without changing DNS").option("--no-proxied", "Create unproxied DNS record").option("--verbose", "Show full Cloudflare API result or dry-run plan").option("-j, --json", "Output JSON").action(async (hostname2, opts) => {
|
|
11144
11335
|
try {
|
|
11145
11336
|
const result = await upsertCloudflareDnsRecord({
|
|
11146
11337
|
hostname: hostname2,
|
|
@@ -11149,25 +11340,33 @@ cfCmd.command("dns <hostname>").description("Create or update the Cloudflare CNA
|
|
|
11149
11340
|
dryRun: opts.dryRun,
|
|
11150
11341
|
proxied: opts.proxied
|
|
11151
11342
|
});
|
|
11152
|
-
|
|
11343
|
+
print(result, opts, () => {
|
|
11344
|
+
if (printVerbose(result, opts))
|
|
11345
|
+
return;
|
|
11346
|
+
printCloudflareDnsSummary(result);
|
|
11347
|
+
});
|
|
11153
11348
|
} catch (error) {
|
|
11154
11349
|
handleError(error);
|
|
11155
11350
|
}
|
|
11156
11351
|
});
|
|
11157
11352
|
var localCmd = program2.command("local").description("Local domain setup helpers");
|
|
11158
|
-
localCmd.command("plan <domain>").description("Render hosts and reverse-proxy setup for a local shortlink domain").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("-j, --json", "Output JSON").action((domain, opts) => {
|
|
11353
|
+
localCmd.command("plan <domain>").description("Render hosts and reverse-proxy setup for a local shortlink domain").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("--verbose", "Show full local setup plan").option("-j, --json", "Output JSON").action((domain, opts) => {
|
|
11159
11354
|
try {
|
|
11160
11355
|
const plan = createLocalSetupPlan({
|
|
11161
11356
|
domain,
|
|
11162
11357
|
port: Number(opts.port),
|
|
11163
11358
|
targetHost: opts.targetHost
|
|
11164
11359
|
});
|
|
11165
|
-
|
|
11360
|
+
print(plan, opts, () => {
|
|
11361
|
+
if (printVerbose(plan, opts))
|
|
11362
|
+
return;
|
|
11363
|
+
printLocalPlanSummary(plan);
|
|
11364
|
+
});
|
|
11166
11365
|
} catch (error) {
|
|
11167
11366
|
handleError(error);
|
|
11168
11367
|
}
|
|
11169
11368
|
});
|
|
11170
|
-
localCmd.command("setup <domain>").description("Record local domain mapping with machines and print remaining sudo-only setup").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("--skip-machines", "Do not call machines dns add").option("-j, --json", "Output JSON").action((domain, opts) => {
|
|
11369
|
+
localCmd.command("setup <domain>").description("Record local domain mapping with machines and print remaining sudo-only setup").option("--port <port>", "Local redirect server port", "8787").option("--target-host <host>", "Local target host", "127.0.0.1").option("--skip-machines", "Do not call machines dns add").option("--verbose", "Show full local setup result").option("-j, --json", "Output JSON").action((domain, opts) => {
|
|
11171
11370
|
try {
|
|
11172
11371
|
const plan = createLocalSetupPlan({
|
|
11173
11372
|
domain,
|
|
@@ -11180,17 +11379,21 @@ localCmd.command("setup <domain>").description("Record local domain mapping with
|
|
|
11180
11379
|
targetHost: opts.targetHost
|
|
11181
11380
|
});
|
|
11182
11381
|
const result = { plan, machines };
|
|
11183
|
-
|
|
11382
|
+
print(result, opts, () => {
|
|
11184
11383
|
if (machines && machines.status !== 0) {
|
|
11185
11384
|
console.error(source_default.yellow(machines.stderr.trim() || "machines dns add failed"));
|
|
11186
11385
|
}
|
|
11187
|
-
|
|
11386
|
+
if (printVerbose(result, opts))
|
|
11387
|
+
return;
|
|
11388
|
+
printLocalPlanSummary(plan);
|
|
11389
|
+
if (machines)
|
|
11390
|
+
console.log(` machines status: ${machines.status ?? "not started"}`);
|
|
11188
11391
|
});
|
|
11189
11392
|
} catch (error) {
|
|
11190
11393
|
handleError(error);
|
|
11191
11394
|
}
|
|
11192
11395
|
});
|
|
11193
|
-
program2.command("doctor").description("Check local shortlinks tooling and integration readiness").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
11396
|
+
program2.command("doctor").description("Check local shortlinks tooling and integration readiness").option("--verbose", "Show full diagnostic object").option("-j, --json", "Output JSON").action(async (opts) => {
|
|
11194
11397
|
try {
|
|
11195
11398
|
const dbPath = getDatabasePath(program2.opts().db);
|
|
11196
11399
|
const data = await withRuntimeStore(async (store) => ({
|
|
@@ -11217,10 +11420,14 @@ program2.command("doctor").description("Check local shortlinks tooling and integ
|
|
|
11217
11420
|
shortlinks_origin_present: Boolean(process.env.SHORTLINKS_ORIGIN)
|
|
11218
11421
|
}
|
|
11219
11422
|
}));
|
|
11220
|
-
|
|
11423
|
+
print(data, opts, () => {
|
|
11424
|
+
if (printVerbose(data, opts))
|
|
11425
|
+
return;
|
|
11426
|
+
printDoctorSummary(data);
|
|
11427
|
+
});
|
|
11221
11428
|
} catch (error) {
|
|
11222
11429
|
handleError(error);
|
|
11223
11430
|
}
|
|
11224
11431
|
});
|
|
11225
|
-
|
|
11432
|
+
registerCompactEventsCommands(program2);
|
|
11226
11433
|
program2.parseAsync(process.argv).catch(handleError);
|