@seedcord/core 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{Bus-BvF4VShN.mjs → Bus-yJGE6Fs2.mjs} +111 -92
  2. package/dist/Bus-yJGE6Fs2.mjs.map +1 -0
  3. package/dist/Pluggable-BI_LQuPm.mjs +280 -0
  4. package/dist/Pluggable-BI_LQuPm.mjs.map +1 -0
  5. package/dist/Pluggable-bFCmT4oB.d.mts +148 -0
  6. package/dist/{Plugin-D9ykXA5g.mjs → Plugin-B0SRHzMX.mjs} +7 -3
  7. package/dist/Plugin-B0SRHzMX.mjs.map +1 -0
  8. package/dist/{Plugin-CQ9IEj5X.d.mts → Plugin-BW2JH2_h.d.mts} +2 -2
  9. package/dist/{cursor-BANzac53.d.mts → cursor-CO_MTgdZ.d.mts} +8 -252
  10. package/dist/{cursor-CuoA9cRh.mjs → cursor-CaT051ii.mjs} +307 -425
  11. package/dist/cursor-CaT051ii.mjs.map +1 -0
  12. package/dist/{index-rt8rZg4d.d.mts → index-CvneCObW.d.mts} +2 -2
  13. package/dist/index.d.mts +78 -19
  14. package/dist/index.mjs +223 -21
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal.index.d.mts +13 -6
  17. package/dist/internal.index.mjs +8 -455
  18. package/dist/internal.index.mjs.map +1 -1
  19. package/dist/node-internal.index.d.mts +6 -133
  20. package/dist/node-internal.index.mjs +16 -473
  21. package/dist/node-internal.index.mjs.map +1 -1
  22. package/dist/node.index.d.mts +2 -0
  23. package/dist/node.index.mjs +256 -0
  24. package/dist/node.index.mjs.map +1 -0
  25. package/dist/{phases-hBxMCD2V.d.mts → phases-C5Steaa2.d.mts} +151 -119
  26. package/dist/plugin.index.d.mts +1 -1
  27. package/dist/plugin.index.mjs +1 -1
  28. package/dist/publishDefault-D826XXDQ.mjs +14 -0
  29. package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
  30. package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-BQmbFm-S.mjs} +10 -2
  31. package/dist/slashRouteLeaves-BQmbFm-S.mjs.map +1 -0
  32. package/dist/{subscribers-wnMyNYZd.mjs → subscribers-DVa-Pr45.mjs} +3 -3
  33. package/dist/{subscribers-wnMyNYZd.mjs.map → subscribers-DVa-Pr45.mjs.map} +1 -1
  34. package/package.json +19 -12
  35. package/dist/Bus-BvF4VShN.mjs.map +0 -1
  36. package/dist/Plugin-D9ykXA5g.mjs.map +0 -1
  37. package/dist/cursor-CuoA9cRh.mjs.map +0 -1
  38. package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
@@ -1,55 +1,9 @@
1
- import { Logger } from "@seedcord/logger";
2
1
  import { APIApplicationCommand } from "discord-api-types/v10";
2
+ import { Logger } from "@seedcord/logger";
3
3
  import { TypedEventEmitter } from "@seedcord/event-emitter";
4
4
  import { REST } from "@discordjs/rest";
5
- import { Config, EventFrequency, IRateLimiter, RenderContext, ReplyResponse, TypedExclude } from "@seedcord/types";
6
5
  import { UUID } from "node:crypto";
7
- //#region src/metadataKeys.d.ts
8
- declare enum InteractionRoutes {
9
- Slash = "slash",
10
- Button = "button",
11
- Modal = "modal",
12
- StringMenu = "stringMenu",
13
- UserMenu = "userMenu",
14
- RoleMenu = "roleMenu",
15
- ChannelMenu = "channelMenu",
16
- MentionableMenu = "mentionableMenu",
17
- MessageContextMenu = "messageContextMenu",
18
- UserContextMenu = "userContextMenu",
19
- Autocomplete = "autocomplete"
20
- }
21
- declare const InteractionMetadataKey: unique symbol;
22
- declare const InteractionRouteKeys: Record<InteractionRoutes, symbol>;
23
- declare const MiddlewareMetadataKey: unique symbol;
24
- declare const EventMetadataKey: unique symbol;
25
- declare const CommandMetadataKey: unique symbol;
26
- declare const GatedMetadataKey: unique symbol;
27
- declare const SubscribeMetadataKey: unique symbol;
28
- declare const WebhookUrlMetadataKey: unique symbol;
29
- //#endregion
30
- //#region src/subscribers/publishDefault.d.ts
31
- /** @internal PublishableKey excludes this key so publish() never emits it externally. */
32
- declare const PublishDefault: unique symbol;
33
- //#endregion
34
- //#region src/reply/AckTrace.d.ts
35
- declare class AckTrace extends Error {
36
- constructor(method: ReplyMethod);
37
- }
38
- //#endregion
39
- //#region src/reply/ackLegality.d.ts
40
- type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';
41
- type ReplyMethod = 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';
42
- declare function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void;
43
- declare function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp';
44
- //#endregion
45
- //#region src/reply/responseReport.d.ts
46
- interface ReplyTelemetry {
47
- readonly bus: Bus;
48
- readonly interactionId: string;
49
- }
50
- type WriteMethod = ReplyMethod | 'respond';
51
- declare function reportedWrite<Result>(telemetry: ReplyTelemetry, routeId: string, method: WriteMethod, write: () => Promise<Result>): Promise<Result>;
52
- //#endregion
6
+ import { Config, EventFrequency, IRateLimiter, RenderContext, ReplyResponse, TypedExclude } from "@seedcord/types";
53
7
  //#region src/stops/Notice.d.ts
54
8
  /**
55
9
  * Base class for a user-facing refusal or a reported fault.
@@ -104,8 +58,9 @@ declare abstract class Notice extends Error {
104
58
  */
105
59
  ephemeral: boolean;
106
60
  /**
107
- * A short one-line reason. When every arm of an `or` gate refuses and each refusal sets this, `or`
108
- * lists them as the reply.
61
+ * A short one-line reason. When every arm of an {@link or} gate refuses and each one sets this, `or`
62
+ * throws a refusal listing them under `You need to meet at least one of these:`. Write a verb phrase that
63
+ * finishes that line, for example, "be the bot owner".
109
64
  */
110
65
  summary?: string;
111
66
  protected constructor(message: string, options?: ErrorOptions);
@@ -116,6 +71,59 @@ declare abstract class Notice extends Error {
116
71
  abstract render(ctx: RenderContext): ReplyResponse;
117
72
  }
118
73
  //#endregion
74
+ //#region src/metadataKeys.d.ts
75
+ declare enum InteractionRoutes {
76
+ Slash = "slash",
77
+ Button = "button",
78
+ Modal = "modal",
79
+ StringMenu = "stringMenu",
80
+ UserMenu = "userMenu",
81
+ RoleMenu = "roleMenu",
82
+ ChannelMenu = "channelMenu",
83
+ MentionableMenu = "mentionableMenu",
84
+ MessageContextMenu = "messageContextMenu",
85
+ UserContextMenu = "userContextMenu",
86
+ Autocomplete = "autocomplete"
87
+ }
88
+ declare const InteractionMetadataKey: unique symbol;
89
+ declare const InteractionRouteKeys: Record<InteractionRoutes, symbol>;
90
+ declare const MiddlewareMetadataKey: unique symbol;
91
+ declare const EventMetadataKey: unique symbol;
92
+ declare const CommandMetadataKey: unique symbol;
93
+ declare const GatedMetadataKey: unique symbol;
94
+ declare const SubscribeMetadataKey: unique symbol;
95
+ declare const WebhookUrlMetadataKey: unique symbol;
96
+ //#endregion
97
+ //#region src/subscribers/publishDefault.d.ts
98
+ /** @internal PublishableKey excludes this key so publish() never emits it externally. */
99
+ declare const PublishDefault: unique symbol;
100
+ //#endregion
101
+ //#region src/subscribers/slots.d.ts
102
+ declare const RegisterDefaults: unique symbol;
103
+ declare const VerifyWebhooks: unique symbol;
104
+ declare const RegisteredCount: unique symbol;
105
+ declare const RegisterSubscriber: unique symbol;
106
+ declare const UnregisterSubscriber: unique symbol;
107
+ //#endregion
108
+ //#region src/reply/AckTrace.d.ts
109
+ declare class AckTrace extends Error {
110
+ constructor(method: ReplyMethod);
111
+ }
112
+ //#endregion
113
+ //#region src/reply/ackLegality.d.ts
114
+ type AckState = 'unacked' | 'deferred-reply' | 'deferred-update' | 'replied';
115
+ type ReplyMethod = 'reply' | 'defer' | 'deferUpdate' | 'update' | 'followUp' | 'edit' | 'delete' | 'send' | 'showModal';
116
+ declare function checkAckLegality(method: ReplyMethod, state: AckState, routeId: string, ackedBy?: AckTrace): void;
117
+ declare function sendTarget(state: AckState): 'reply' | 'edit' | 'followUp';
118
+ //#endregion
119
+ //#region src/reply/responseReport.d.ts
120
+ interface ReplyTelemetry {
121
+ readonly bus: Bus;
122
+ readonly interactionId: string;
123
+ }
124
+ type WriteMethod = ReplyMethod | 'respond';
125
+ declare function reportedWrite<Result>(telemetry: ReplyTelemetry, routeId: string, method: WriteMethod, write: () => Promise<Result>): Promise<Result>;
126
+ //#endregion
119
127
  //#region src/subscribers/types/Subscriptions.d.ts
120
128
  /**
121
129
  * How a dispatch finished. The thrown value's type sets this, wherever it was thrown. A `Silence` and a
@@ -123,8 +131,6 @@ declare abstract class Notice extends Error {
123
131
  * `Fault`, is `failed`, as is any other throw.
124
132
  */
125
133
  type DispatchOutcome = 'handled' | 'refused' | 'failed';
126
- /** Whether a write through the reply surface reached Discord. */
127
- type ResponseOutcome = 'sent' | 'failed';
128
134
  /**
129
135
  * Where a reported fault came from.
130
136
  */
@@ -133,36 +139,69 @@ type FaultSource = InteractionFaultSource | EventFaultSource;
133
139
  * A fault that came from a discord interaction, JSON-safe for a webhook payload.
134
140
  */
135
141
  interface InteractionFaultSource {
136
- kind: 'interaction';
137
- interactionKind: 'slash' | 'context-menu' | 'button' | 'select' | 'modal';
142
+ readonly kind: 'interaction';
143
+ readonly interactionKind: 'slash' | 'context-menu' | 'button' | 'select' | 'modal';
138
144
  /** Slash or context-menu command name, null for component and modal interactions. */
139
- command: string | null;
145
+ readonly command: string | null;
140
146
  /** Component or modal customId, null for slash and context-menu interactions. */
141
- customId: string | null;
142
- userId: string;
147
+ readonly customId: string | null;
148
+ readonly userId: string;
143
149
  /** Guild the interaction came from, null in DMs. */
144
- guildId: string | null;
150
+ readonly guildId: string | null;
145
151
  /** Channel the interaction came from, null when unavailable. */
146
- channelId: string | null;
147
- interactionId: string;
152
+ readonly channelId: string | null;
153
+ readonly interactionId: string;
148
154
  /** The raw interaction, made JSON-safe at serialization with filterCirculars. */
149
- raw: unknown;
155
+ readonly raw: unknown;
150
156
  }
151
157
  /**
152
158
  * A fault that came from a client event, JSON-safe for a webhook payload. The actor, guild, and channel
153
159
  * are best-effort, derived from the event args. Each is nullable. The raw args carry the full detail.
154
160
  */
155
161
  interface EventFaultSource {
156
- kind: 'event';
157
- eventName: string;
158
- handler: string;
159
- userId: string | null;
162
+ readonly kind: 'event';
163
+ readonly eventName: string;
164
+ readonly handler: string;
165
+ readonly userId: string | null;
160
166
  /** Null outside a guild. */
161
- guildId: string | null;
162
- channelId: string | null;
167
+ readonly guildId: string | null;
168
+ readonly channelId: string | null;
163
169
  /** The raw event args, made JSON-safe at serialization with filterCirculars. */
164
- raw: unknown;
170
+ readonly raw: unknown;
171
+ }
172
+ /**
173
+ * The fields both `responseAttempted` arms carry.
174
+ */
175
+ interface AttemptedWrite {
176
+ readonly routeId: string;
177
+ /** The interaction this write belongs to, for joining against `interactionDispatched`. */
178
+ readonly interactionId: string;
179
+ /** `send` routes to another verb, so it reports the verb that ran. */
180
+ readonly method: WriteMethod;
181
+ readonly durationMs: number;
182
+ }
183
+ /**
184
+ * A write Discord accepted.
185
+ */
186
+ interface ResponseSent extends AttemptedWrite {
187
+ readonly outcome: 'sent';
188
+ /** Null for every ack-only and choice-only verb. */
189
+ readonly messageId: string | null;
190
+ }
191
+ /**
192
+ * A write that threw. A non-Error throw arrives wrapped, carrying the thrown value as its `cause`.
193
+ */
194
+ interface ResponseFailed extends AttemptedWrite {
195
+ readonly outcome: 'failed';
196
+ readonly messageId: null;
197
+ readonly error: Error;
165
198
  }
199
+ /**
200
+ * One write through the reply surface. Check `outcome` to reach `error`.
201
+ */
202
+ type ResponseAttempt = ResponseSent | ResponseFailed;
203
+ /** Whether a write through the reply surface reached Discord. */
204
+ type ResponseOutcome = ResponseAttempt['outcome'];
166
205
  /**
167
206
  * The framework's own subscription keys. A transport adds a key whose payload type references a type
168
207
  * outside this package, through declaration merging on its own module.
@@ -172,75 +211,63 @@ interface EventFaultSource {
172
211
  */
173
212
  interface DefaultSubscriptions {
174
213
  /** Triggered when an unhandled exception (a raw non-Notice throw) occurs. */
175
- unknownException: {
176
- uuid: UUID;
177
- error: Error;
214
+ readonly unknownException: {
215
+ readonly uuid: UUID;
216
+ readonly error: Error;
178
217
  /** Where the throw came from, `slash:ban` for an interaction and `event:name:handler` for an event. */
179
- routeId: string;
180
- guild?: {
181
- id: string;
182
- name: string;
218
+ readonly routeId: string;
219
+ readonly guild?: {
220
+ readonly id: string;
221
+ readonly name: string;
183
222
  } | undefined;
184
- user?: {
185
- id: string;
186
- username: string;
223
+ readonly user?: {
224
+ readonly id: string;
225
+ readonly username: string;
187
226
  } | undefined;
188
- metadata?: unknown;
227
+ readonly metadata?: unknown;
189
228
  };
190
229
  /** Triggered when a reported Notice (`report: true`) is caught. */
191
- handledException: {
192
- denial: Notice;
193
- uuid: UUID;
230
+ readonly handledException: {
231
+ readonly denial: Notice;
232
+ readonly uuid: UUID;
194
233
  /** The same id `interactionDispatched` publishes. An event reads `event:name:handler`. */
195
- routeId: string;
196
- source: FaultSource;
234
+ readonly routeId: string;
235
+ readonly source: FaultSource;
197
236
  };
198
237
  /** Triggered when an interaction dispatch throws past the fault boundary. */
199
- unhandledInteractionError: {
200
- error: Error;
238
+ readonly unhandledInteractionError: {
239
+ readonly error: Error;
201
240
  };
202
241
  /** Triggered once per interaction dispatch, after the handler chain settles. */
203
- interactionDispatched: {
204
- routeId: string;
242
+ readonly interactionDispatched: {
243
+ readonly routeId: string;
205
244
  /** The interaction this dispatch ran, for joining against `responseAttempted`. */
206
- interactionId: string;
207
- kind: `${InteractionRoutes}`;
208
- outcome: DispatchOutcome;
245
+ readonly interactionId: string;
246
+ readonly kind: `${InteractionRoutes}`;
247
+ readonly outcome: DispatchOutcome;
209
248
  /** True when no route matched and the unhandled default ran. */
210
- fallback: boolean;
249
+ readonly fallback: boolean;
211
250
  /** Dispatch entry until the user has a response, replies included. A clock change never affects it. */
212
- durationMs: number;
251
+ readonly durationMs: number;
213
252
  /**
214
253
  * Discord's interaction creation until dispatch entry, read from the snowflake. This covers
215
254
  * network transit plus any time your bot was busy before starting. A rise points at either
216
255
  * Discord or your own backlog. It subtracts a Discord timestamp from the host clock, so a host
217
256
  * running behind Discord reports a negative value.
218
257
  */
219
- queuedMs: number;
258
+ readonly queuedMs: number;
220
259
  };
221
260
  /**
222
261
  * Triggered on every write through the reply surface, several times per interaction. A write that
223
262
  * throws reports here too, with `outcome` `failed`. A write that reaches Discord and then returns an
224
263
  * unexpected shape publishes nothing, since the callback succeeded and carries no message to name.
225
264
  */
226
- responseAttempted: {
227
- routeId: string;
228
- /** The interaction this write belongs to, for joining against `interactionDispatched`. */
229
- interactionId: string;
230
- /** `send` routes to another verb, so it reports the verb that ran. */
231
- method: WriteMethod;
232
- outcome: ResponseOutcome;
233
- durationMs: number;
234
- /** Null for every ack-only or choice-only verb, and for a failed write. */
235
- messageId: string | null;
236
- /** Present when `outcome` is `failed`. */
237
- error?: Error;
238
- };
265
+ readonly responseAttempted: ResponseAttempt;
239
266
  /** Triggered after each command deploy, including the redeploy after a hot reload. */
240
- commandsDeployed: {
241
- global: APIApplicationCommand[];
267
+ readonly commandsDeployed: {
268
+ readonly global: readonly APIApplicationCommand[];
242
269
  /** Keyed by guild id. */
243
- guilds: Record<string, APIApplicationCommand[]>;
270
+ readonly guilds: Readonly<Record<string, readonly APIApplicationCommand[]>>;
244
271
  };
245
272
  }
246
273
  /**
@@ -338,16 +365,16 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
338
365
  private readonly executedOnce;
339
366
  private readonly webhookProbes;
340
367
  constructor(core: CoreBase);
341
- /** @internal the host calls this during startup, keeping the url validation inside `start()` where a failure can still release the singleton guard. */
342
- registerDefaults(): void;
368
+ /** @internal keep this inside start(). a bad webhook url thrown outside it never resets the singleton guard */
369
+ [RegisterDefaults](): void;
343
370
  /** @internal */
344
- register(registration: SubscriberRegistration): void;
371
+ [RegisterSubscriber](registration: SubscriberRegistration): void;
345
372
  /** @internal */
346
- unregister(ctor: StoredSubscriberCtor, keys?: readonly SubscriptionKey[]): void;
373
+ [UnregisterSubscriber](ctor: StoredSubscriberCtor, keys?: readonly SubscriptionKey[]): void;
347
374
  /** @internal */
348
- get registeredCount(): number;
349
- /** @internal Only eagerly registered reporters are probed, which is every server-host one. */
350
- verifyWebhooks(): Promise<void>;
375
+ get [RegisteredCount](): number;
376
+ /** @internal a lazy edge registration never probes. every server-host reporter registers eagerly */
377
+ [VerifyWebhooks](): Promise<void>;
351
378
  private probeWebhook;
352
379
  /**
353
380
  * Publishes an event to its subscribers and native listeners.
@@ -366,6 +393,11 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
366
393
  * @returns Whether the underlying emitter had native listeners for the event
367
394
  */
368
395
  publish<KeyOfSubscribers extends PublishableKey>(event: KeyOfSubscribers, data: AllSubscriptions[KeyOfSubscribers]): boolean;
396
+ /**
397
+ * @deprecated Always throws. Call {@link Bus.publish} to reach subscribers and listeners together.
398
+ * @throws A **SeedcordTypeError** with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
399
+ */
400
+ emit<KeyOfSubscribers extends SubscriptionKey>(event: KeyOfSubscribers, ..._args: SubscriptionTuples[KeyOfSubscribers]): never;
369
401
  /** @internal the framework's own publish path, keyed by a symbol no public entry exports */
370
402
  [PublishDefault]<KeyOfSubscribers extends keyof DefaultSubscriptions>(event: KeyOfSubscribers, data: AllSubscriptions[KeyOfSubscribers]): boolean;
371
403
  private dispatchKey;
@@ -424,5 +456,5 @@ declare enum ShutdownPhase {
424
456
  Logout = 4
425
457
  }
426
458
  //#endregion
427
- export { MiddlewareMetadataKey as A, PublishDefault as C, InteractionMetadataKey as D, GatedMetadataKey as E, WebhookUrlMetadataKey as M, InteractionRouteKeys as O, AckTrace as S, EventMetadataKey as T, reportedWrite as _, StoredSubscriberCtor as a, checkAckLegality as b, Subscriber as c, FaultSource as d, SubscriptionData as f, ReplyTelemetry as g, Notice as h, Bus as i, SubscribeMetadataKey as j, InteractionRoutes as k, DefaultSubscriptions as l, Subscriptions as m, StartupPhase as n, SubscriberRegistration as o, SubscriptionKey as p, CoreBase as r, busLoggerOf as s, ShutdownPhase as t, DispatchOutcome as u, AckState as v, CommandMetadataKey as w, sendTarget as x, ReplyMethod as y };
428
- //# sourceMappingURL=phases-hBxMCD2V.d.mts.map
459
+ export { UnregisterSubscriber as A, SubscribeMetadataKey as B, ReplyMethod as C, RegisterDefaults as D, AckTrace as E, GatedMetadataKey as F, Notice as H, InteractionMetadataKey as I, InteractionRouteKeys as L, PublishDefault as M, CommandMetadataKey as N, RegisterSubscriber as O, EventMetadataKey as P, InteractionRoutes as R, AckState as S, sendTarget as T, WebhookUrlMetadataKey as V, SubscriptionData as _, StoredSubscriberCtor as a, ReplyTelemetry as b, Subscriber as c, DispatchOutcome as d, FaultSource as f, ResponseSent as g, ResponseOutcome as h, Bus as i, VerifyWebhooks as j, RegisteredCount as k, AttemptedWrite as l, ResponseFailed as m, StartupPhase as n, SubscriberRegistration as o, ResponseAttempt as p, CoreBase as r, busLoggerOf as s, ShutdownPhase as t, DefaultSubscriptions as u, SubscriptionKey as v, checkAckLegality as w, reportedWrite as x, Subscriptions as y, MiddlewareMetadataKey as z };
460
+ //# sourceMappingURL=phases-C5Steaa2.d.mts.map
@@ -1,3 +1,3 @@
1
- import { c as PluginOptions, p as PluginLifecycleSpec, r as Plugin } from "./Plugin-CQ9IEj5X.mjs";
1
+ import { c as PluginOptions, p as PluginLifecycleSpec, r as Plugin } from "./Plugin-BW2JH2_h.mjs";
2
2
  import { HmrAware, HmrUpdateEvent } from "@seedcord/types";
3
3
  export { type HmrAware, type HmrUpdateEvent, Plugin, type PluginLifecycleSpec, type PluginOptions };
@@ -1,3 +1,3 @@
1
- import { t as Plugin } from "./Plugin-D9ykXA5g.mjs";
1
+ import { t as Plugin } from "./Plugin-B0SRHzMX.mjs";
2
2
 
3
3
  export { Plugin };
@@ -0,0 +1,14 @@
1
+ //#region src/stops/asError.ts
2
+ /** @internal */
3
+ function asError(caught) {
4
+ return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });
5
+ }
6
+
7
+ //#endregion
8
+ //#region src/subscribers/publishDefault.ts
9
+ /** @internal PublishableKey excludes this key so publish() never emits it externally. */
10
+ const PublishDefault = Symbol("seedcord.publishDefault");
11
+
12
+ //#endregion
13
+ export { asError as n, PublishDefault as t };
14
+ //# sourceMappingURL=publishDefault-D826XXDQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publishDefault-D826XXDQ.mjs","names":[],"sources":["../src/stops/asError.ts","../src/subscribers/publishDefault.ts"],"sourcesContent":["// dropping a non-Error throw would leave the user with no fault card, and every report payload declares an Error\n/** @internal */\nexport function asError(caught: unknown): Error {\n return Error.isError(caught) ? caught : new Error(String(caught), { cause: caught });\n}\n","/** @internal PublishableKey excludes this key so publish() never emits it externally. */\nexport const PublishDefault = Symbol('seedcord.publishDefault');\n"],"mappings":";;AAEA,SAAgB,QAAQ,QAAwB;CAC5C,OAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,IAAI,MAAM,OAAO,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;AACvF;;;;;ACHA,MAAa,iBAAiB,OAAO,yBAAyB"}
@@ -1,6 +1,14 @@
1
+ import { g as CommandMetadataKey, j as isBuilderComponentClass } from "./Bus-yJGE6Fs2.mjs";
1
2
  import { SlashCommandBuilder } from "@discordjs/builders";
2
3
  import { routeLeavesOf } from "@seedcord/utils/internal";
3
4
 
5
+ //#region src/commands/isCommandClass.ts
6
+ /** @internal */
7
+ function isCommandClass(value) {
8
+ return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);
9
+ }
10
+
11
+ //#endregion
4
12
  //#region src/commands/slashRouteLeaves.ts
5
13
  /**
6
14
  * dedupes route keys since the same builder appears once per guild.
@@ -19,5 +27,5 @@ function slashRouteLeaves(commands) {
19
27
  }
20
28
 
21
29
  //#endregion
22
- export { slashRouteLeaves as t };
23
- //# sourceMappingURL=slashRouteLeaves-De1SxqnX.mjs.map
30
+ export { isCommandClass as n, slashRouteLeaves as t };
31
+ //# sourceMappingURL=slashRouteLeaves-BQmbFm-S.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slashRouteLeaves-BQmbFm-S.mjs","names":[],"sources":["../src/commands/isCommandClass.ts","../src/commands/slashRouteLeaves.ts"],"sourcesContent":["import { isBuilderComponentClass } from '#components/Component';\nimport { CommandMetadataKey } from '#src/metadataKeys';\n\nimport type { BuilderComponent } from '#components/Component';\n\n/** @internal */\nexport type CommandCtor = new () => BuilderComponent<'command' | 'context_menu'>;\n\n// the cli's codegen scan reads this too\n/** @internal */\nexport function isCommandClass(value: unknown): value is CommandCtor {\n return isBuilderComponentClass(value) && Reflect.hasMetadata(CommandMetadataKey, value);\n}\n","import { SlashCommandBuilder } from '@discordjs/builders';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\n\nimport type { ContextMenuCommandBuilder } from '@discordjs/builders';\n\n/**\n * dedupes route keys since the same builder appears once per guild.\n * these come from the same {@link routeLeavesOf} that `seedcord codegen` reads, so the boot-time\n * check compares against the generated registry's keys.\n *\n * @internal\n */\nexport function slashRouteLeaves(commands: Iterable<SlashCommandBuilder | ContextMenuCommandBuilder>): Set<string> {\n const leaves = new Set<string>();\n for (const command of commands) {\n if (!(command instanceof SlashCommandBuilder)) continue;\n for (const leaf of routeLeavesOf(command.toJSON())) leaves.add(leaf.route);\n }\n return leaves;\n}\n"],"mappings":";;;;;;AAUA,SAAgB,eAAe,OAAsC;CACjE,OAAO,wBAAwB,KAAK,KAAK,QAAQ,YAAY,oBAAoB,KAAK;AAC1F;;;;;;;;;;;ACAA,SAAgB,iBAAiB,UAAkF;CAC/G,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,WAAW,UAAU;EAC5B,IAAI,EAAE,mBAAmB,sBAAsB;EAC/C,KAAK,MAAM,QAAQ,cAAc,QAAQ,OAAO,CAAC,GAAG,OAAO,IAAI,KAAK,KAAK;CAC7E;CACA,OAAO;AACX"}
@@ -1,9 +1,9 @@
1
- import { D as contextMenuLeaves, E as guardedAccessor, T as clearStore, w as accessorStore } from "./Bus-BvF4VShN.mjs";
1
+ import { D as guardedAccessor, E as clearStore, O as contextMenuLeaves, T as accessorStore } from "./Bus-yJGE6Fs2.mjs";
2
2
  import { SlashCommandBuilder } from "@discordjs/builders";
3
+ import { ApplicationCommandType } from "discord-api-types/v10";
3
4
  import { paint } from "@seedcord/errors";
4
5
  import { Logger } from "@seedcord/logger";
5
6
  import { routeLeavesOf } from "@seedcord/utils/internal";
6
- import { ApplicationCommandType } from "discord-api-types/v10";
7
7
 
8
8
  //#region src/commands/CommandInjector.ts
9
9
  const commandStorage = accessorStore();
@@ -136,4 +136,4 @@ var CommandInjector = class {
136
136
 
137
137
  //#endregion
138
138
  export { Commands as n, ContextMenus as r, CommandInjector as t };
139
- //# sourceMappingURL=subscribers-wnMyNYZd.mjs.map
139
+ //# sourceMappingURL=subscribers-DVa-Pr45.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"subscribers-wnMyNYZd.mjs","names":[],"sources":["../src/commands/CommandInjector.ts"],"sourcesContent":["import { SlashCommandBuilder } from '@discordjs/builders';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { accessorStore, clearStore, guardedAccessor } from '#src/miscellaneous/guarded';\n\nimport type {\n ContextMenuKind,\n MessageContextMenuRegistry,\n UserContextMenuRegistry\n} from '#registries/ContextMenuRegistry';\nimport type { SlashRegistry } from '#registries/SlashRegistry';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { APIApplicationCommand, Snowflake } from 'discord-api-types/v10';\n\nconst commandStorage = accessorStore<CommandInfo>();\nconst userMenuStorage = accessorStore<ContextMenuInfo>();\nconst messageMenuStorage = accessorStore<ContextMenuInfo>();\n\n/** What {@link Commands} resolves to for one slash route. */\nexport interface CommandInfo {\n /** The clickable `</config set:id>` mention. A route whose id did not resolve falls back to `/config set`. */\n readonly mention: string;\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The full route, e.g. `config/set`. */\n readonly route: string;\n /** The route's own description, taken from the subcommand for a nested route. */\n readonly description: string;\n}\n\n/** Each registered slash route mapped to its deployed command. */\nexport type InjectedCommandMap = {\n [K in keyof SlashRegistry]: CommandInfo;\n};\n\n/**\n * The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by\n * {@link CommandInjector} after each deploy, and a read before that throws.\n *\n * @example\n * ```ts\n * import { Commands } from '@seedcord/gateway';\n *\n * await this.reply(`Try ${Commands['config/set'].mention}`);\n *\n * // a help listing\n * const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);\n * ```\n */\nexport const Commands = guardedAccessor('Commands', commandStorage) as InjectedCommandMap;\n\n/** What {@link ContextMenus} resolves to for one context-menu command. */\nexport interface ContextMenuInfo {\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The name shown in Discord's right-click menu. */\n readonly name: string;\n readonly kind: ContextMenuKind;\n}\n\n/** Deployed context-menu commands, split by kind because Discord scopes name uniqueness per command type. */\nexport interface InjectedContextMenuMap {\n readonly user: { [K in keyof UserContextMenuRegistry]: ContextMenuInfo };\n readonly message: { [K in keyof MessageContextMenuRegistry]: ContextMenuInfo };\n}\n\n/**\n * The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read\n * before that throws.\n *\n * @example\n * ```ts\n * import { ContextMenus } from '@seedcord/gateway';\n *\n * const id = ContextMenus.user['View Profile'].id;\n * ```\n */\nexport const ContextMenus = {\n user: guardedAccessor('ContextMenus.user', userMenuStorage),\n message: guardedAccessor('ContextMenus.message', messageMenuStorage)\n} as InjectedContextMenuMap;\n\nconst MULTI_GUILD = Symbol('seedcord:commands:multi-guild');\n\n// Discord scopes name uniqueness per type, and a context-menu name may be all lowercase like a slash name\nfunction commandKey(type: ApplicationCommandType, name: string): string {\n return `${type}:${name}`;\n}\n\nfunction slashBuildersOf(all: readonly CommandBuilder[]): SlashCommandBuilder[] {\n const slash = all.filter((command): command is SlashCommandBuilder => command instanceof SlashCommandBuilder);\n // a guild command in N guilds is the same builder pushed once per guild\n return [...new Set(slash)];\n}\n\ninterface DeployedIds {\n readonly global: Map<string, Snowflake>;\n readonly guild: Map<string, Snowflake | typeof MULTI_GUILD>;\n readonly warned: Set<string>;\n}\n\n/** @internal */\nexport class CommandInjector {\n private readonly logger = new Logger('Commands', { channel: 'bot' });\n\n public inject(deploy: DeployResult, all: readonly CommandBuilder[]): void {\n this.clear();\n\n const ids: DeployedIds = {\n global: this.indexByKey(deploy.global),\n guild: this.indexGuilds(deploy.guilds),\n warned: new Set<string>()\n };\n let clickable = 0;\n\n for (const command of slashBuildersOf(all)) {\n const id = this.resolveId(\n ApplicationCommandType.ChatInput,\n command.name,\n ids,\n 'falling back to plain text (not clickable)'\n );\n for (const leaf of routeLeavesOf(command.toJSON())) {\n commandStorage[leaf.route] = {\n mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),\n ...(id && { id }),\n route: leaf.route,\n description: leaf.description\n };\n if (id) clickable++;\n }\n }\n\n const menus = contextMenuLeaves(all);\n this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);\n this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);\n\n this.logger.utils.summary('Linked commands', {\n clickable,\n plain: Object.keys(commandStorage).length - clickable,\n 'context menus': menus.user.size + menus.message.size\n });\n }\n\n private injectMenus(\n names: ReadonlySet<string>,\n kind: ContextMenuKind,\n storage: Record<string, ContextMenuInfo>,\n ids: DeployedIds\n ): void {\n for (const name of names) {\n const id = this.resolveId(kind, name, ids, 'leaving its id unset');\n storage[name] = { ...(id && { id }), name, kind };\n }\n }\n\n private indexByKey(collection: Map<Snowflake, APIApplicationCommand>): Map<string, Snowflake> {\n const map = new Map<string, Snowflake>();\n for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);\n return map;\n }\n\n private indexGuilds(\n guilds: Map<Snowflake, Map<Snowflake, APIApplicationCommand>>\n ): Map<string, Snowflake | typeof MULTI_GUILD> {\n const map = new Map<string, Snowflake | typeof MULTI_GUILD>();\n for (const collection of guilds.values()) {\n for (const command of collection.values()) {\n const key = commandKey(command.type, command.name);\n const existing = map.get(key);\n // Discord assigns a separate id per guild, so two ids for one name means no single mention\n if (existing === undefined) map.set(key, command.id);\n else if (existing !== command.id) map.set(key, MULTI_GUILD);\n }\n }\n return map;\n }\n\n private resolveId(\n type: ApplicationCommandType,\n name: string,\n ids: DeployedIds,\n consequence: string\n ): Snowflake | undefined {\n const key = commandKey(type, name);\n const global = ids.global.get(key);\n if (global) return global;\n\n const guild = ids.guild.get(key);\n if (guild === undefined) return undefined;\n if (guild === MULTI_GUILD) {\n if (!ids.warned.has(key)) {\n this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);\n ids.warned.add(key);\n }\n return undefined;\n }\n return guild;\n }\n\n private toMention(route: string, id: Snowflake): string {\n return `</${route.replaceAll('/', ' ')}:${id}>`;\n }\n\n private toPlain(route: string): string {\n return `/${route.replaceAll('/', ' ')}`;\n }\n\n private clear(): void {\n for (const store of [commandStorage, userMenuStorage, messageMenuStorage]) clearStore(store);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"subscribers-DVa-Pr45.mjs","names":[],"sources":["../src/commands/CommandInjector.ts"],"sourcesContent":["import { SlashCommandBuilder } from '@discordjs/builders';\nimport { paint } from '@seedcord/errors';\nimport { Logger } from '@seedcord/logger';\nimport { routeLeavesOf } from '@seedcord/utils/internal';\nimport { ApplicationCommandType } from 'discord-api-types/v10';\n\nimport { contextMenuLeaves } from '#src/commands/contextMenuLeaves';\nimport { accessorStore, clearStore, guardedAccessor } from '#src/miscellaneous/guarded';\n\nimport type {\n ContextMenuKind,\n MessageContextMenuRegistry,\n UserContextMenuRegistry\n} from '#registries/ContextMenuRegistry';\nimport type { SlashRegistry } from '#registries/SlashRegistry';\nimport type { CommandBuilder, DeployResult } from '#src/commands/types';\nimport type { APIApplicationCommand, Snowflake } from 'discord-api-types/v10';\n\nconst commandStorage = accessorStore<CommandInfo>();\nconst userMenuStorage = accessorStore<ContextMenuInfo>();\nconst messageMenuStorage = accessorStore<ContextMenuInfo>();\n\n/** What {@link Commands} resolves to for one slash route. */\nexport interface CommandInfo {\n /** The clickable `</config set:id>` mention. A route whose id did not resolve falls back to `/config set`. */\n readonly mention: string;\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The full route, e.g. `config/set`. */\n readonly route: string;\n /** The route's own description, taken from the subcommand for a nested route. */\n readonly description: string;\n}\n\n/** Each registered slash route mapped to its deployed command. */\nexport type InjectedCommandMap = {\n [K in keyof SlashRegistry]: CommandInfo;\n};\n\n/**\n * The bot's deployed slash commands, keyed by {@link SlashRegistry}. Filled by\n * {@link CommandInjector} after each deploy, and a read before that throws.\n *\n * @example\n * ```ts\n * import { Commands } from '@seedcord/gateway';\n *\n * await this.reply(`Try ${Commands['config/set'].mention}`);\n *\n * // a help listing\n * const lines = Object.values(Commands).map((c) => `${c.mention} ${c.description}`);\n * ```\n */\nexport const Commands = guardedAccessor('Commands', commandStorage) as InjectedCommandMap;\n\n/** What {@link ContextMenus} resolves to for one context-menu command. */\nexport interface ContextMenuInfo {\n /** The deployed command id. Absent when the command deployed to two or more guilds. */\n readonly id?: string;\n /** The name shown in Discord's right-click menu. */\n readonly name: string;\n readonly kind: ContextMenuKind;\n}\n\n/** Deployed context-menu commands, split by kind because Discord scopes name uniqueness per command type. */\nexport interface InjectedContextMenuMap {\n readonly user: { [K in keyof UserContextMenuRegistry]: ContextMenuInfo };\n readonly message: { [K in keyof MessageContextMenuRegistry]: ContextMenuInfo };\n}\n\n/**\n * The bot's deployed context-menu commands. Filled by {@link CommandInjector} after each deploy, and a read\n * before that throws.\n *\n * @example\n * ```ts\n * import { ContextMenus } from '@seedcord/gateway';\n *\n * const id = ContextMenus.user['View Profile'].id;\n * ```\n */\nexport const ContextMenus = {\n user: guardedAccessor('ContextMenus.user', userMenuStorage),\n message: guardedAccessor('ContextMenus.message', messageMenuStorage)\n} as InjectedContextMenuMap;\n\nconst MULTI_GUILD = Symbol('seedcord:commands:multi-guild');\n\n// Discord scopes name uniqueness per type, and a context-menu name may be all lowercase like a slash name\nfunction commandKey(type: ApplicationCommandType, name: string): string {\n return `${type}:${name}`;\n}\n\nfunction slashBuildersOf(all: readonly CommandBuilder[]): SlashCommandBuilder[] {\n const slash = all.filter((command): command is SlashCommandBuilder => command instanceof SlashCommandBuilder);\n // a guild command in N guilds is the same builder pushed once per guild\n return [...new Set(slash)];\n}\n\ninterface DeployedIds {\n readonly global: Map<string, Snowflake>;\n readonly guild: Map<string, Snowflake | typeof MULTI_GUILD>;\n readonly warned: Set<string>;\n}\n\n/** @internal */\nexport class CommandInjector {\n private readonly logger = new Logger('Commands', { channel: 'bot' });\n\n public inject(deploy: DeployResult, all: readonly CommandBuilder[]): void {\n this.clear();\n\n const ids: DeployedIds = {\n global: this.indexByKey(deploy.global),\n guild: this.indexGuilds(deploy.guilds),\n warned: new Set<string>()\n };\n let clickable = 0;\n\n for (const command of slashBuildersOf(all)) {\n const id = this.resolveId(\n ApplicationCommandType.ChatInput,\n command.name,\n ids,\n 'falling back to plain text (not clickable)'\n );\n for (const leaf of routeLeavesOf(command.toJSON())) {\n commandStorage[leaf.route] = {\n mention: id ? this.toMention(leaf.route, id) : this.toPlain(leaf.route),\n ...(id && { id }),\n route: leaf.route,\n description: leaf.description\n };\n if (id) clickable++;\n }\n }\n\n const menus = contextMenuLeaves(all);\n this.injectMenus(menus.user, ApplicationCommandType.User, userMenuStorage, ids);\n this.injectMenus(menus.message, ApplicationCommandType.Message, messageMenuStorage, ids);\n\n this.logger.utils.summary('Linked commands', {\n clickable,\n plain: Object.keys(commandStorage).length - clickable,\n 'context menus': menus.user.size + menus.message.size\n });\n }\n\n private injectMenus(\n names: ReadonlySet<string>,\n kind: ContextMenuKind,\n storage: Record<string, ContextMenuInfo>,\n ids: DeployedIds\n ): void {\n for (const name of names) {\n const id = this.resolveId(kind, name, ids, 'leaving its id unset');\n storage[name] = { ...(id && { id }), name, kind };\n }\n }\n\n private indexByKey(collection: Map<Snowflake, APIApplicationCommand>): Map<string, Snowflake> {\n const map = new Map<string, Snowflake>();\n for (const command of collection.values()) map.set(commandKey(command.type, command.name), command.id);\n return map;\n }\n\n private indexGuilds(\n guilds: Map<Snowflake, Map<Snowflake, APIApplicationCommand>>\n ): Map<string, Snowflake | typeof MULTI_GUILD> {\n const map = new Map<string, Snowflake | typeof MULTI_GUILD>();\n for (const collection of guilds.values()) {\n for (const command of collection.values()) {\n const key = commandKey(command.type, command.name);\n const existing = map.get(key);\n // Discord assigns a separate id per guild, so two ids for one name means no single mention\n if (existing === undefined) map.set(key, command.id);\n else if (existing !== command.id) map.set(key, MULTI_GUILD);\n }\n }\n return map;\n }\n\n private resolveId(\n type: ApplicationCommandType,\n name: string,\n ids: DeployedIds,\n consequence: string\n ): Snowflake | undefined {\n const key = commandKey(type, name);\n const global = ids.global.get(key);\n if (global) return global;\n\n const guild = ids.guild.get(key);\n if (guild === undefined) return undefined;\n if (guild === MULTI_GUILD) {\n if (!ids.warned.has(key)) {\n this.logger.warn(`${paint.sky.bold(name)} is deployed to multiple guilds, ${consequence}.`);\n ids.warned.add(key);\n }\n return undefined;\n }\n return guild;\n }\n\n private toMention(route: string, id: Snowflake): string {\n return `</${route.replaceAll('/', ' ')}:${id}>`;\n }\n\n private toPlain(route: string): string {\n return `/${route.replaceAll('/', ' ')}`;\n }\n\n private clear(): void {\n for (const store of [commandStorage, userMenuStorage, messageMenuStorage]) clearStore(store);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@seedcord/core",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.4.1",
5
5
  "engines": {
6
6
  "node": ">=24.11"
7
7
  },
@@ -43,6 +43,12 @@
43
43
  "default": "./dist/internal.index.mjs"
44
44
  }
45
45
  },
46
+ "./node": {
47
+ "import": {
48
+ "types": "./dist/node.index.d.mts",
49
+ "default": "./dist/node.index.mjs"
50
+ }
51
+ },
46
52
  "./hmr": {
47
53
  "import": {
48
54
  "types": "./dist/hmr.index.d.mts",
@@ -71,18 +77,19 @@
71
77
  "@discordjs/rest": "^2.6.3",
72
78
  "discord-api-types": "^0.38.52",
73
79
  "reflect-metadata": "^0.2.2",
74
- "@seedcord/errors": "^0.4.3",
75
- "@seedcord/event-emitter": "^0.1.4",
76
- "@seedcord/logger": "^0.2.1",
77
- "@seedcord/types": "^0.9.1",
78
- "@seedcord/utils": "^0.8.7"
80
+ "@seedcord/custom-id": "^0.1.0",
81
+ "@seedcord/errors": "^0.5.1",
82
+ "@seedcord/event-emitter": "^0.1.5",
83
+ "@seedcord/logger": "^0.2.2",
84
+ "@seedcord/types": "^0.10.1",
85
+ "@seedcord/utils": "^0.8.8"
79
86
  },
80
87
  "devDependencies": {
81
88
  "envapt": "^8.1.1",
82
89
  "type-fest": "^5.8.0",
83
90
  "vite": "^8.2.0",
84
- "@seedcord/eslint-config": "^2.0.0",
85
- "@seedcord/rate-limiter": "^0.1.4",
91
+ "@seedcord/eslint-config": "^2.1.0",
92
+ "@seedcord/rate-limiter": "^0.1.5",
86
93
  "@seedcord/tsconfig": "^2.0.3",
87
94
  "@seedcord/tsdown-config": "^2.0.1",
88
95
  "@seedcord/vitest-config": "^0.0.1"
@@ -94,10 +101,10 @@
94
101
  "scripts": {
95
102
  "build": "tsdown",
96
103
  "clean": "rm -rf dist",
97
- "lint": "eslint 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}' --cache",
98
- "lint:fix": "eslint 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}' --fix --cache",
99
- "fmt": "prettier --write 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,json,md}' --cache",
100
- "fmt:check": "prettier --check 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,json,md}' --cache",
104
+ "lint": "eslint 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}' --cache --no-error-on-unmatched-pattern",
105
+ "lint:fix": "eslint 'src/**/*.{ts,tsx}' 'tests/**/*.{ts,tsx}' --fix --cache --no-error-on-unmatched-pattern",
106
+ "fmt": "prettier --write 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,json,md}' --cache --no-error-on-unmatched-pattern",
107
+ "fmt:check": "prettier --check 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,json,md}' --cache --no-error-on-unmatched-pattern",
101
108
  "tc": "tsgo --noEmit",
102
109
  "test": "vitest run",
103
110
  "test:watch": "vitest dev",