@hasna/shortlinks 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +56 -251
- package/dist/index.js +16 -2
- package/dist/mcp/index.js +16 -2
- package/dist/serve/index.js +161 -197
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2894,7 +2894,7 @@ async function upsertCloudflareDnsRecord(options) {
|
|
|
2894
2894
|
return { id: created.id, action: "created" };
|
|
2895
2895
|
}
|
|
2896
2896
|
|
|
2897
|
-
// node_modules
|
|
2897
|
+
// node_modules/@hasna/events/dist/commander.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";
|
|
@@ -2911,83 +2911,29 @@ function getPathValue(input, path) {
|
|
|
2911
2911
|
return;
|
|
2912
2912
|
}, input);
|
|
2913
2913
|
}
|
|
2914
|
-
function
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
if (!values.some((item) => Object.is(item, value)))
|
|
2918
|
-
values.push(value);
|
|
2919
|
-
};
|
|
2920
|
-
if (path.includes(".") && path in input)
|
|
2921
|
-
push(input[path]);
|
|
2922
|
-
const nestedValue = getPathValue(input, path);
|
|
2923
|
-
if (nestedValue !== undefined || !path.includes("."))
|
|
2924
|
-
push(nestedValue);
|
|
2925
|
-
return values;
|
|
2926
|
-
}
|
|
2927
|
-
function wildcardToRegExp(pattern, options = {}) {
|
|
2928
|
-
let body = "";
|
|
2929
|
-
for (let index = 0;index < pattern.length; index += 1) {
|
|
2930
|
-
const char = pattern[index];
|
|
2931
|
-
if (char === "*") {
|
|
2932
|
-
if (pattern[index + 1] === "*") {
|
|
2933
|
-
body += ".*";
|
|
2934
|
-
index += 1;
|
|
2935
|
-
} else {
|
|
2936
|
-
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
2937
|
-
}
|
|
2938
|
-
} else {
|
|
2939
|
-
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
return new RegExp(`^${body}$`);
|
|
2914
|
+
function wildcardToRegExp(pattern) {
|
|
2915
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
2916
|
+
return new RegExp(`^${escaped}$`);
|
|
2943
2917
|
}
|
|
2944
|
-
function matchString(value, matcher
|
|
2918
|
+
function matchString(value, matcher) {
|
|
2945
2919
|
if (matcher === undefined)
|
|
2946
2920
|
return true;
|
|
2947
2921
|
if (value === undefined)
|
|
2948
2922
|
return false;
|
|
2949
2923
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
2950
|
-
return matchers.some((item) => wildcardToRegExp(item
|
|
2924
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
2951
2925
|
}
|
|
2952
2926
|
function matchRecord(input, matcher) {
|
|
2953
2927
|
if (!matcher)
|
|
2954
2928
|
return true;
|
|
2955
2929
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
2956
|
-
const
|
|
2957
|
-
|
|
2930
|
+
const actual = getPathValue(input, path);
|
|
2931
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
2932
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
2933
|
+
}
|
|
2934
|
+
return actual === expected;
|
|
2958
2935
|
});
|
|
2959
2936
|
}
|
|
2960
|
-
function matchField(actualValues, expected, path) {
|
|
2961
|
-
if (isNegativeMatcher(expected)) {
|
|
2962
|
-
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
2963
|
-
}
|
|
2964
|
-
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
2965
|
-
}
|
|
2966
|
-
function matchPositiveField(actual, expected, path) {
|
|
2967
|
-
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
2968
|
-
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
2969
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
2970
|
-
}));
|
|
2971
|
-
}
|
|
2972
|
-
if (Array.isArray(actual)) {
|
|
2973
|
-
return actual.some((item) => item === expected);
|
|
2974
|
-
}
|
|
2975
|
-
return actual === expected;
|
|
2976
|
-
}
|
|
2977
|
-
function stringCandidates(actual) {
|
|
2978
|
-
if (actual === undefined)
|
|
2979
|
-
return [];
|
|
2980
|
-
if (Array.isArray(actual)) {
|
|
2981
|
-
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
2982
|
-
}
|
|
2983
|
-
return [String(actual)];
|
|
2984
|
-
}
|
|
2985
|
-
function isPrimitiveFieldValue(value) {
|
|
2986
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
2987
|
-
}
|
|
2988
|
-
function isNegativeMatcher(value) {
|
|
2989
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
2990
|
-
}
|
|
2991
2937
|
function eventMatchesFilter(event, filter) {
|
|
2992
2938
|
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
2993
2939
|
}
|
|
@@ -3003,13 +2949,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
3003
2949
|
function getEventsDataDir(override) {
|
|
3004
2950
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
|
|
3005
2951
|
}
|
|
3006
|
-
function getActiveEventsDirEnv() {
|
|
3007
|
-
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
3008
|
-
return HASNA_EVENTS_DIR_ENV;
|
|
3009
|
-
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
3010
|
-
return HASNA_EVENTS_HOME_ENV;
|
|
3011
|
-
return null;
|
|
3012
|
-
}
|
|
3013
2952
|
|
|
3014
2953
|
class JsonEventsStore {
|
|
3015
2954
|
dataDir;
|
|
@@ -3122,52 +3061,6 @@ class JsonEventsStore {
|
|
|
3122
3061
|
});
|
|
3123
3062
|
}
|
|
3124
3063
|
}
|
|
3125
|
-
async function getEventsStatus(dataDir) {
|
|
3126
|
-
const store = new JsonEventsStore(dataDir);
|
|
3127
|
-
await store.init();
|
|
3128
|
-
const [channels, events, deliveries] = await Promise.all([
|
|
3129
|
-
store.listChannels(),
|
|
3130
|
-
store.listEvents(),
|
|
3131
|
-
store.listDeliveries()
|
|
3132
|
-
]);
|
|
3133
|
-
const transports = channels.reduce((counts, channel) => {
|
|
3134
|
-
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
3135
|
-
return counts;
|
|
3136
|
-
}, {});
|
|
3137
|
-
return {
|
|
3138
|
-
service: "events",
|
|
3139
|
-
schemaVersion: "1.0",
|
|
3140
|
-
dataDir: store.dataDir,
|
|
3141
|
-
env: {
|
|
3142
|
-
primary: HASNA_EVENTS_DIR_ENV,
|
|
3143
|
-
fallback: HASNA_EVENTS_HOME_ENV,
|
|
3144
|
-
active: getActiveEventsDirEnv()
|
|
3145
|
-
},
|
|
3146
|
-
files: {
|
|
3147
|
-
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
3148
|
-
events: statusFile(store.dataDir, "events.json", events.length),
|
|
3149
|
-
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
3150
|
-
},
|
|
3151
|
-
counts: {
|
|
3152
|
-
channels: channels.length,
|
|
3153
|
-
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
3154
|
-
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
3155
|
-
events: events.length,
|
|
3156
|
-
deliveries: deliveries.length
|
|
3157
|
-
},
|
|
3158
|
-
transports,
|
|
3159
|
-
safety: {
|
|
3160
|
-
includesEventPayloads: false,
|
|
3161
|
-
includesWebhookSecrets: false,
|
|
3162
|
-
listOutputsRedactSecrets: true,
|
|
3163
|
-
statusOutputIsMetadataOnly: true
|
|
3164
|
-
}
|
|
3165
|
-
};
|
|
3166
|
-
}
|
|
3167
|
-
function statusFile(dataDir, fileName, records) {
|
|
3168
|
-
const path = join4(dataDir, fileName);
|
|
3169
|
-
return { path, exists: existsSync3(path), records };
|
|
3170
|
-
}
|
|
3171
3064
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
3172
3065
|
function buildSignatureBase(timestamp, body) {
|
|
3173
3066
|
return `${timestamp}.${body}`;
|
|
@@ -3393,7 +3286,7 @@ class EventsClient {
|
|
|
3393
3286
|
}
|
|
3394
3287
|
return deliveries;
|
|
3395
3288
|
}
|
|
3396
|
-
async
|
|
3289
|
+
async testChannel(id, input = {}) {
|
|
3397
3290
|
const channel = await this.store.getChannel(id);
|
|
3398
3291
|
if (!channel)
|
|
3399
3292
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -3410,34 +3303,6 @@ class EventsClient {
|
|
|
3410
3303
|
time: input.time,
|
|
3411
3304
|
id: input.id
|
|
3412
3305
|
});
|
|
3413
|
-
const matched = channelMatchesEvent(channel, event);
|
|
3414
|
-
return {
|
|
3415
|
-
channelId: channel.id,
|
|
3416
|
-
matched,
|
|
3417
|
-
event,
|
|
3418
|
-
filters: channel.filters,
|
|
3419
|
-
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
3420
|
-
};
|
|
3421
|
-
}
|
|
3422
|
-
async testChannel(id, input = {}, options = {}) {
|
|
3423
|
-
const channel = await this.store.getChannel(id);
|
|
3424
|
-
if (!channel)
|
|
3425
|
-
throw new Error(`Channel not found: ${id}`);
|
|
3426
|
-
const match = await this.matchChannel(id, input);
|
|
3427
|
-
const event = match.event;
|
|
3428
|
-
if (options.honorFilters && !match.matched) {
|
|
3429
|
-
const timestamp = new Date().toISOString();
|
|
3430
|
-
const result2 = createDeliveryResult(event, channel, [{
|
|
3431
|
-
attempt: 1,
|
|
3432
|
-
status: "skipped",
|
|
3433
|
-
startedAt: timestamp,
|
|
3434
|
-
completedAt: timestamp,
|
|
3435
|
-
error: match.reason
|
|
3436
|
-
}]);
|
|
3437
|
-
result2.metadata = { reason: "filter_mismatch" };
|
|
3438
|
-
await this.store.appendDelivery(result2);
|
|
3439
|
-
return result2;
|
|
3440
|
-
}
|
|
3441
3306
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
3442
3307
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
3443
3308
|
await this.store.appendDelivery(result);
|
|
@@ -3548,76 +3413,6 @@ function normalizeRetryPolicy(policy) {
|
|
|
3548
3413
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
3549
3414
|
};
|
|
3550
3415
|
}
|
|
3551
|
-
function parseFieldMatchers(values, label, typed = false) {
|
|
3552
|
-
if (!values?.length)
|
|
3553
|
-
return;
|
|
3554
|
-
const result = {};
|
|
3555
|
-
for (const value of values) {
|
|
3556
|
-
const parsed = parseMatcherExpression(value, label);
|
|
3557
|
-
const path = parsed.path;
|
|
3558
|
-
if (path in result)
|
|
3559
|
-
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
3560
|
-
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
3561
|
-
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
3562
|
-
}
|
|
3563
|
-
return result;
|
|
3564
|
-
}
|
|
3565
|
-
function parseFilterOptions(options) {
|
|
3566
|
-
const filter2 = {};
|
|
3567
|
-
if (options.source)
|
|
3568
|
-
filter2.source = options.source;
|
|
3569
|
-
if (options.type)
|
|
3570
|
-
filter2.type = options.type;
|
|
3571
|
-
if (options.subject)
|
|
3572
|
-
filter2.subject = options.subject;
|
|
3573
|
-
if (options.severity)
|
|
3574
|
-
filter2.severity = options.severity;
|
|
3575
|
-
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
3576
|
-
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
3577
|
-
if (Object.keys(data).length > 0)
|
|
3578
|
-
filter2.data = data;
|
|
3579
|
-
if (Object.keys(metadata).length > 0)
|
|
3580
|
-
filter2.metadata = metadata;
|
|
3581
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
3582
|
-
}
|
|
3583
|
-
function mergeMatchers(...records) {
|
|
3584
|
-
const result = {};
|
|
3585
|
-
for (const record of records) {
|
|
3586
|
-
if (!record)
|
|
3587
|
-
continue;
|
|
3588
|
-
for (const [path, value] of Object.entries(record)) {
|
|
3589
|
-
if (path in result)
|
|
3590
|
-
throw new Error(`Duplicate filter path: ${path}`);
|
|
3591
|
-
result[path] = value;
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
return result;
|
|
3595
|
-
}
|
|
3596
|
-
function parseTypedMatcherValue(value, label) {
|
|
3597
|
-
const parsed = JSON.parse(value);
|
|
3598
|
-
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
3599
|
-
return parsed;
|
|
3600
|
-
}
|
|
3601
|
-
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
3602
|
-
}
|
|
3603
|
-
function parseMatcherExpression(value, label) {
|
|
3604
|
-
const negativeSeparator = value.indexOf("!=");
|
|
3605
|
-
if (negativeSeparator > 0) {
|
|
3606
|
-
return {
|
|
3607
|
-
path: value.slice(0, negativeSeparator),
|
|
3608
|
-
rawValue: value.slice(negativeSeparator + 2),
|
|
3609
|
-
negated: true
|
|
3610
|
-
};
|
|
3611
|
-
}
|
|
3612
|
-
const separator = value.indexOf("=");
|
|
3613
|
-
if (separator <= 0)
|
|
3614
|
-
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
3615
|
-
return {
|
|
3616
|
-
path: value.slice(0, separator),
|
|
3617
|
-
rawValue: value.slice(separator + 1),
|
|
3618
|
-
negated: false
|
|
3619
|
-
};
|
|
3620
|
-
}
|
|
3621
3416
|
function parseJsonObject2(value, fallback) {
|
|
3622
3417
|
if (!value)
|
|
3623
3418
|
return fallback;
|
|
@@ -3639,6 +3434,18 @@ function parseHeaders(values) {
|
|
|
3639
3434
|
}
|
|
3640
3435
|
return headers;
|
|
3641
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
|
+
}
|
|
3642
3449
|
function createClient(options) {
|
|
3643
3450
|
if (options.createClient)
|
|
3644
3451
|
return options.createClient();
|
|
@@ -3656,16 +3463,16 @@ function hasJsonOption(options) {
|
|
|
3656
3463
|
function wantsJson(actionOptions, command) {
|
|
3657
3464
|
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
3658
3465
|
}
|
|
3659
|
-
function
|
|
3660
|
-
const
|
|
3661
|
-
|
|
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) => {
|
|
3662
3469
|
const timestamp = new Date().toISOString();
|
|
3663
3470
|
const channel = {
|
|
3664
3471
|
id: actionOptions.id,
|
|
3665
3472
|
name: actionOptions.name,
|
|
3666
3473
|
enabled: !actionOptions.disabled,
|
|
3667
3474
|
transport: actionOptions.transport,
|
|
3668
|
-
filters:
|
|
3475
|
+
filters: parseFilter(actionOptions),
|
|
3669
3476
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
3670
3477
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
3671
3478
|
createdAt: timestamp,
|
|
@@ -3681,51 +3488,35 @@ function registerChannelCommands(program, options) {
|
|
|
3681
3488
|
const saved = await createClient(options).addChannel(channel);
|
|
3682
3489
|
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
3683
3490
|
});
|
|
3684
|
-
|
|
3685
|
-
const
|
|
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();
|
|
3686
3493
|
if (wantsJson(actionOptions, command)) {
|
|
3687
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(
|
|
3494
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
3688
3495
|
return;
|
|
3689
3496
|
}
|
|
3690
|
-
if (!
|
|
3497
|
+
if (!channels.length) {
|
|
3691
3498
|
console.log("No channels configured.");
|
|
3692
3499
|
return;
|
|
3693
3500
|
}
|
|
3694
|
-
for (const channel of
|
|
3501
|
+
for (const channel of channels) {
|
|
3695
3502
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
3696
3503
|
}
|
|
3697
3504
|
});
|
|
3698
|
-
|
|
3699
|
-
const status = await getEventsStatus(options.dataDir);
|
|
3700
|
-
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
3701
|
-
});
|
|
3702
|
-
channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
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) => {
|
|
3703
3506
|
const removed = await createClient(options).removeChannel(id);
|
|
3704
3507
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
3705
3508
|
});
|
|
3706
|
-
|
|
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) => {
|
|
3707
3510
|
const result = await createClient(options).testChannel(id, {
|
|
3708
|
-
source:
|
|
3511
|
+
source: options.source,
|
|
3709
3512
|
type: actionOptions.type,
|
|
3710
3513
|
subject: actionOptions.subject ?? id,
|
|
3711
3514
|
message: actionOptions.message,
|
|
3712
|
-
data: parseJsonObject2(actionOptions.data, { test: true })
|
|
3713
|
-
metadata: parseJsonObject2(actionOptions.metadata, {})
|
|
3714
|
-
}, { honorFilters: actionOptions.honorFilters });
|
|
3715
|
-
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
3716
|
-
});
|
|
3717
|
-
channels.command("match").description("Check whether a sample event matches one channel without delivering").argument("<id>", "Channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events match preview").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
3718
|
-
const result = await createClient(options).matchChannel(id, {
|
|
3719
|
-
source: actionOptions.source ?? options.source,
|
|
3720
|
-
type: actionOptions.type,
|
|
3721
|
-
subject: actionOptions.subject ?? id,
|
|
3722
|
-
message: actionOptions.message,
|
|
3723
|
-
data: parseJsonObject2(actionOptions.data, { test: true }),
|
|
3724
|
-
metadata: parseJsonObject2(actionOptions.metadata, {})
|
|
3515
|
+
data: parseJsonObject2(actionOptions.data, { test: true })
|
|
3725
3516
|
});
|
|
3726
|
-
print(result, wantsJson(actionOptions, command), `${result.
|
|
3517
|
+
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
3727
3518
|
});
|
|
3728
|
-
return
|
|
3519
|
+
return webhooks;
|
|
3729
3520
|
}
|
|
3730
3521
|
function registerEventCommands(program, options) {
|
|
3731
3522
|
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
@@ -3773,7 +3564,7 @@ function registerEventCommands(program, options) {
|
|
|
3773
3564
|
return events;
|
|
3774
3565
|
}
|
|
3775
3566
|
function registerEventsCommands(program, options) {
|
|
3776
|
-
|
|
3567
|
+
registerWebhookCommands(program, options);
|
|
3777
3568
|
registerEventCommands(program, options);
|
|
3778
3569
|
}
|
|
3779
3570
|
function parseNumber(value) {
|
|
@@ -10761,7 +10552,11 @@ class CloudShortlinksStore {
|
|
|
10761
10552
|
const domain = await this.getDomain(hostnameOrId);
|
|
10762
10553
|
if (!domain)
|
|
10763
10554
|
throw new Error("Domain not found.");
|
|
10764
|
-
|
|
10555
|
+
try {
|
|
10556
|
+
await this.transport.del(`/domains/${enc(domain.hostname)}`);
|
|
10557
|
+
} catch (error) {
|
|
10558
|
+
throw missingRouteError(error, "domain", `DELETE /domains/${domain.hostname}`);
|
|
10559
|
+
}
|
|
10765
10560
|
return domain;
|
|
10766
10561
|
}
|
|
10767
10562
|
async createLink(input) {
|
|
@@ -10815,7 +10610,11 @@ class CloudShortlinksStore {
|
|
|
10815
10610
|
const link = maybeSlug ? await this.getLink(domainOrSlug, maybeSlug) : await this.getLink(domainOrSlug);
|
|
10816
10611
|
if (!link)
|
|
10817
10612
|
throw new Error("Link not found.");
|
|
10818
|
-
|
|
10613
|
+
try {
|
|
10614
|
+
await this.transport.del(`/links/${enc(link.slug)}`, undefined, maybeSlug ? { query: { domain: domainOrSlug } } : {});
|
|
10615
|
+
} catch (error) {
|
|
10616
|
+
throw missingRouteError(error, "link", `DELETE /links/${link.slug}`);
|
|
10617
|
+
}
|
|
10819
10618
|
return link;
|
|
10820
10619
|
}
|
|
10821
10620
|
async recordClick(_link, _input = {}) {
|
|
@@ -10835,6 +10634,12 @@ class CloudShortlinksStore {
|
|
|
10835
10634
|
function isNotFound(error) {
|
|
10836
10635
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
10837
10636
|
}
|
|
10637
|
+
function missingRouteError(error, resource, request) {
|
|
10638
|
+
if (isNotFound(error)) {
|
|
10639
|
+
return new Error(`Cloud server rejected ${request} with 404 even though the ${resource} exists. ` + `The deployed shortlinks server predates the ${resource}-delete route \u2014 redeploy the ` + `cloud service (ECS) to a version that exposes it, then retry.`);
|
|
10640
|
+
}
|
|
10641
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
10642
|
+
}
|
|
10838
10643
|
|
|
10839
10644
|
// src/client-store.ts
|
|
10840
10645
|
class LocalStore {
|
package/dist/index.js
CHANGED
|
@@ -7659,7 +7659,11 @@ class CloudShortlinksStore {
|
|
|
7659
7659
|
const domain = await this.getDomain(hostnameOrId);
|
|
7660
7660
|
if (!domain)
|
|
7661
7661
|
throw new Error("Domain not found.");
|
|
7662
|
-
|
|
7662
|
+
try {
|
|
7663
|
+
await this.transport.del(`/domains/${enc(domain.hostname)}`);
|
|
7664
|
+
} catch (error) {
|
|
7665
|
+
throw missingRouteError(error, "domain", `DELETE /domains/${domain.hostname}`);
|
|
7666
|
+
}
|
|
7663
7667
|
return domain;
|
|
7664
7668
|
}
|
|
7665
7669
|
async createLink(input) {
|
|
@@ -7713,7 +7717,11 @@ class CloudShortlinksStore {
|
|
|
7713
7717
|
const link = maybeSlug ? await this.getLink(domainOrSlug, maybeSlug) : await this.getLink(domainOrSlug);
|
|
7714
7718
|
if (!link)
|
|
7715
7719
|
throw new Error("Link not found.");
|
|
7716
|
-
|
|
7720
|
+
try {
|
|
7721
|
+
await this.transport.del(`/links/${enc(link.slug)}`, undefined, maybeSlug ? { query: { domain: domainOrSlug } } : {});
|
|
7722
|
+
} catch (error) {
|
|
7723
|
+
throw missingRouteError(error, "link", `DELETE /links/${link.slug}`);
|
|
7724
|
+
}
|
|
7717
7725
|
return link;
|
|
7718
7726
|
}
|
|
7719
7727
|
async recordClick(_link, _input = {}) {
|
|
@@ -7733,6 +7741,12 @@ class CloudShortlinksStore {
|
|
|
7733
7741
|
function isNotFound(error) {
|
|
7734
7742
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
7735
7743
|
}
|
|
7744
|
+
function missingRouteError(error, resource, request) {
|
|
7745
|
+
if (isNotFound(error)) {
|
|
7746
|
+
return new Error(`Cloud server rejected ${request} with 404 even though the ${resource} exists. ` + `The deployed shortlinks server predates the ${resource}-delete route \u2014 redeploy the ` + `cloud service (ECS) to a version that exposes it, then retry.`);
|
|
7747
|
+
}
|
|
7748
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
7749
|
+
}
|
|
7736
7750
|
|
|
7737
7751
|
// src/client-store.ts
|
|
7738
7752
|
class LocalStore {
|
package/dist/mcp/index.js
CHANGED
|
@@ -6501,7 +6501,11 @@ class CloudShortlinksStore {
|
|
|
6501
6501
|
const domain = await this.getDomain(hostnameOrId);
|
|
6502
6502
|
if (!domain)
|
|
6503
6503
|
throw new Error("Domain not found.");
|
|
6504
|
-
|
|
6504
|
+
try {
|
|
6505
|
+
await this.transport.del(`/domains/${enc(domain.hostname)}`);
|
|
6506
|
+
} catch (error) {
|
|
6507
|
+
throw missingRouteError(error, "domain", `DELETE /domains/${domain.hostname}`);
|
|
6508
|
+
}
|
|
6505
6509
|
return domain;
|
|
6506
6510
|
}
|
|
6507
6511
|
async createLink(input) {
|
|
@@ -6555,7 +6559,11 @@ class CloudShortlinksStore {
|
|
|
6555
6559
|
const link = maybeSlug ? await this.getLink(domainOrSlug, maybeSlug) : await this.getLink(domainOrSlug);
|
|
6556
6560
|
if (!link)
|
|
6557
6561
|
throw new Error("Link not found.");
|
|
6558
|
-
|
|
6562
|
+
try {
|
|
6563
|
+
await this.transport.del(`/links/${enc(link.slug)}`, undefined, maybeSlug ? { query: { domain: domainOrSlug } } : {});
|
|
6564
|
+
} catch (error) {
|
|
6565
|
+
throw missingRouteError(error, "link", `DELETE /links/${link.slug}`);
|
|
6566
|
+
}
|
|
6559
6567
|
return link;
|
|
6560
6568
|
}
|
|
6561
6569
|
async recordClick(_link, _input = {}) {
|
|
@@ -6575,6 +6583,12 @@ class CloudShortlinksStore {
|
|
|
6575
6583
|
function isNotFound(error) {
|
|
6576
6584
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
6577
6585
|
}
|
|
6586
|
+
function missingRouteError(error, resource, request) {
|
|
6587
|
+
if (isNotFound(error)) {
|
|
6588
|
+
return new Error(`Cloud server rejected ${request} with 404 even though the ${resource} exists. ` + `The deployed shortlinks server predates the ${resource}-delete route \u2014 redeploy the ` + `cloud service (ECS) to a version that exposes it, then retry.`);
|
|
6589
|
+
}
|
|
6590
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
6591
|
+
}
|
|
6578
6592
|
|
|
6579
6593
|
// src/store.ts
|
|
6580
6594
|
import { createHash } from "crypto";
|